打印语句
1 |
|
这里使用了%f来输出一个浮点数,但传入的是一个整数 2。在大多数实现中,整数会被隐式转换为浮点数(即 2.000000);但是,由于
printf
的行为可能依赖于具体的编译器和运行时环境,有时可能看到不同的输出(比如由于浮点数表示的精度问题)。字符 ‘2’ 的 ASCII 值是 50
字面量的类型
程序中的数值(字面量)也有类型:默认类型或者指定类型
- 默认类型:2 为 int,0.2 为 double, ‘c’ 为 char
- 指定类型:0.2f 为 float(后缀 f 表示 float)
C 语言是类型严格的语言,字面量也有类型,使用字面量时需要考虑类型;
1 | short s = 2; // 字面量 2 的类型被转换为 short |
变量之间的赋值
大类型赋值给小类型时:可能会发生溢出
- 当数值在小类型范围内 -> 赋值成功
- 当数值超过小类型的范围 -> 发生溢出
小类型可以安全的赋值给大类型
浮点类型赋值给整形,会发生截断(小数部分丢失)
整数赋值给浮点类型,能够完成(会在后面补 0)
signed 与 unsigned
有符号与无符号
- signed int
- 范围:-2147483648 ~ 2147483647
- unsigned int
- 范围:0 ~ 4294967295
扩展
- signed 和 unsigned 可与 char 和 short 组合使用
- signed char,unsigned char
- signed short,unsigned short
- 程序中可能出现的关于 int 的缩写
- signed <-> signed int
- unsigned <-> unsigned int
打印无符号整形时候要使用 %u 而不是%d,%d 会依旧按照有符号进行操作;
类型本身是不会占用内存的,只有变量才会占用内存
sizeof关键字
功能:用于获取类型或者变量所占用的内存大小(字节)
1 | int main() { |
long
- long 在使用不同编译器时,可能占用的内存不同
- long 通常占用 4 个字节,也可能占用 8 个字节
- long long 表示整形,固定占用 8 个字节
- long long 是 long long int 的缩写形式
浮点数
- 浮点类型(float & double)对数据的表示是不准确的
- 整数类型(char,short,int,long)对数据的表示是准确的
- 浮点类型与整数类型在内存中对数据的表示法完全不同
变量的作用域
C 语言中变量的分类
- 局部变量
- 函数内部定义的变量(隶属于当前函数)
- 只能在当前函数中进行使用
- 全局变量
- 全局范围内的函数(不特定隶属于任意一个函数)
- 可以在任意函数中访问使用
同名变量的问题
- 不同函数中的局部变量可以同名(不会产生冲突)
- 全局变量不能同名(会产生冲突)
- 当局部变量和全局变量同名时,优先使用局部变量
全局变量的作用域
全局作用域:可以在程序的各个角落访问并使用(不限于当前文件中)
文件作用域:只能在当前代码文件中访问并使用
全局变量的作用域可能被局部变量覆盖(同名局部变量)
工程开发中,全局变量通常以 g_ 作为前缀命名(工程约定)
不同变量的物理存储区域
- 在现代计算机系统中,物理内存被分为不同区域
- 区域不同,用途不同,不同种类的变量位于不同区域
- 全局数据区域:存放全局变量,静态变量,其中的变量默认初始化为0
- 栈空间:存储函数参数,局部变量
- 堆空间:用于动态创建变量
- 不同变量的生命期
- 全局数据区中的变量
- 程序开始运行时创建,程序结束时被销毁,整个程序运行期合法可用
- 栈空间中的变量
- 进入作用域时创建,离开作用域时销毁(自动销毁)
- 函数中的局部变量在函数调用返回后销毁
- 进入作用域时创建,离开作用域时销毁(自动销毁)
- 作用域和生命周期二者并无本质联系,只是语法层面(作用域)和二进制层面(生命周期)的两个说法
- 全局数据区中的变量
静态变量
- static 是 C 语言中的关键字
- static 修饰的局部变量创建于全局数据区(拥有程序生命周期)
- static 修饰的全局变量只有文件作用域(超出对应文件外无法访问,作用域被缩小了)
- static 局部变量只会初始化一次,作用域与普通变量无异
对于局部变量而言,static 只是改变了它的生命周期,其本身的作用域不变
变量的声明周期
变量的生命周期由变量的存储位置决定
- static 将变量存储于全局数据区,默认初始化为 0
- auto 将变量存储于栈空间,默认初始化为随机值
- register 将变量存储于寄存器,默认初始化随机值(因为寄存器太少了,所以存储不一定成功,故实际中基本不用)
自定义数据类型
typedef
类型命名关键字(typedef)
- C 语言中可以对类型赋予新名字
- 语法 typedef Type NewTypeName;
- 注意:typedef 并没有创建新类型,只是创建了类型别名
应用
- typedef 可在函数中定义”局部类型名“
- typedef 常用于简化类型名(如:unsigned long long)
- typedef 定义类型名,能够以统一方式创建变量(Type var;)
1 |
|
struct
是能够定义不同数据类型变量的集合类型
- struct 结构体变量的本质是变量的集合
- struct 结构体变量中的成员占用独立的内存
- struct 结构体可以用 typedef 赋予新类型名
- 可定义 struct 结构体类型的指针,并指向对应类型的变量
深入 struct 结构体类型
- struct 结构体类型可以先前置声明,在具体定义;
- struct test;
- 前置类型声明只能用于定义指针;
- struct test* p
- 类型完整定义之后才能进行变量定义
- struct 结构体类型可以省略类型名
- 省略类型名时,每次创建变脸必须给出完整结构体定义
1 |
|
- struct 结构体类型可以省略类型名(无名结构体类型)
- 类型名省略时,每次创建变量必须给出完整结构体定义
- 无名结构体类型总是互不相同的类型(互不兼容)
1 |
|
位域
- 现代程序设计中,内存使用的最小单位为字节(约定俗成)
- 在一些特定场合中,可将比特作为最小单位使用内存
- 结构体类型能够指定成员变量占用内存的比特位宽度(位域)
1 |
|
深入位域
- 位域成员必须是整形,默认情况下成员依次排列
- 位域成员占用的位数不能超过类型宽度(错误示例:char c : 9)
- 当存储位不足时,自动启用新存储单元
- char a : 7; char b : 6; 两个加起来超过了一个字节的大小,那么它将会自动启用一个新的字节来存储当前内容
- 可以舍弃当前未使用的位,重新启用存储单元
1 | struct Bits1 { |
unsigned char : 0;
这行代码,其作用主要是作为填充或对齐。当你在结构体中声明一个大小为0的位域时,它实际上不占用任何位(即,它不存在于内存布局中),但它可以影响结构体中后续位域的对齐方式。a占了一个字节的前4位,b正好可以占剩下的后四位,刚刚好一个字节;但
unsigned char : 0;
这行代码会将 a 所在字节的后四位进行占位,b 只能重新分配一个新的字节并占前四位;
unsigned char : 0;
:这定义了一个大小为0的位域。在大多数上下文中,这样的位域实际上不占用任何位(即,它不会为结构体增加任何额外的内存负担),并且它的存在主要是为了可能的对齐效果或作为占位符。然而,需要注意的是,编译器如何处理这种0大小的位域可能依赖于编译器的具体实现,并且可能不会产生预期的效果。unsigned char : 4;
:这定义了一个大小为4位的位域。这意味着这个位域将占用结构体中的4个位,用于存储数据。在这个例子中,由于使用了unsigned char
类型(尽管在定义位域时类型主要影响解释方式,而不是实际占用的内存大小),这4个位可以用来存储从0到15(即0b0000
到0b1111
)的整数值。
联合体 Union
语法
1 | union TypeName { |
union 与 struct 的不同
- union 类型所有成员共享一段内存(所有成员起始地址相同)
- union 类型的大小取决于成员的最大类型
- union 类型的变量只能以第一个成员类型的有效值进行初始化
1 | union UT { |
联合体中的所有成员共享一段内存,即:如果 uu.a = 1,那么 uu.b 也等于 1;
union 类型的大小取决于成员的最大类型,即:uu 这个联合体的大小为 a 这个字段的大小,也就是 4 字节;
union 类型的变量只能以第一个成员类型的有效值进行初始化,即:union UT uu = {0}; 0 是 a 的 int 类型;
1 |
|
int 与 float 在内存中的存储方式不同,所以二者在对数据进行处理后展示的内容也是不同的;
浮点数的保存是不精确的,是会存在误差的;
应用-判断系统大小端
- 小端系统:低位数据存储在低地址内存中
- 大端系统:低位数据存储在高地址内存中
将 1 填入低位,余下补 0,这就是小端系统
将 1 填入高位,余下补 0,这就是大端系统
1 |
|
enum
- enum 是 C 语言中的自定义类型关键字
- enum 能够定义整形常量的集合类型
1 | enum TypeName { |
注意事项
- 第一个枚举常量默认值为 0
- 后续常量的值在前一起常量值的基础上加1
- 可以任意对枚举常量指定整型值(只能指定整型值)
1 | enum Day {MON = 1, Tue, WED, THU, FRI, SAT, SUN}; // 1, 2, 3, ,4 ,5, 6, 7 |
在 C/C++ 中,枚举类型中的每个枚举成员(如
MON
,Tue
, 等等)是一个编译期常量。这些常量并不需要存储在枚举类型中,它们只是用来表示某些具体的数值,通常会被替换为它们的对应值,而不是占用存储空间。这些常量在编译时是已知的,并且在运行时不需要存储在内存中。它们只是简单的整数常量,编译器在需要使用这些值时会直接替换它们。