1.结构体声明
1.1结构体类型
这里我们定义一个结构体:
struct Stu { char name[20]; int age; };
struct Stu就是结构体类型
1.1.1匿名结构体类型
//匿名结构体类型 struct { int a; char b; float c; }s; struct { int a; char b; float c; }*ps;
这里的struct在声明的时候省略掉了结构体标签,这里的s,*ps是结构体变量名,像这样的省略标签项的结构体类型就是匿名结构体
这里有一个疑问,如果令 ps=&s 可以吗?答案是不行的,它们虽然成员相同,不过并不是同一个结构体,存放在不同的内存空间,编译器会把上面的两个声明当成完全不同的两个类型,是非法的
1.1.2 typedef定义结构体类型
代码运用:
typedef struct Stu { int a; char b; float c; }Node; int main() { Node n={1,'w',5.24}; printf("%d %c %lf", n.a, n.b, n.c);//1 w 5.240000 return 0; }
当结构体类型比较复杂时就可以用typedef将结构体类型重定义,如上面的代码中将struct Stu重定义为Node后再使用
1.2结构体自引用
在结构中可以包含一个类型为该结构本身的成员,如:
struct Stu { char name[20]; int age; struct Stu* date;//OK //struct Stu date;//err //Stu date;//err };
注意:结构体自引用只能使用传址调用,传值调用(sizeof(struct Stu)无法得知结构体大小)或者是省略结构体关键字都是错误的写法
1.3结构体内存对齐
结构体内存对齐主要用于帮助我们计算结构体的大小。
结构体的对齐规则:
1.结构体的第一个成员直接对齐到相对于结构体变量起始位置为0的地址处
2.从第二个成员开始,要对齐到某个数字(对齐数)的整数倍的偏移处
对齐数:结构体成员自身大小和编译器默认对齐数的较小值
VS中默认对齐数为8
Linux环境默认不设对齐数(对齐数是结构体成员的自身大小)
3.结构体的总大小必须是最大对齐数的整数倍。
每个结构体成员都有一个对齐数,其中最大的对齐数就是最大对齐数
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
1.3.1结构体大小计算实例
例题一
struct S1 { char c1; int i; char c2; }; int main() { printf("%d\n", sizeof(struct S1)); return 0; } //输出12
题解:
例题二
struct S2 { char c1; char c2; int i; }; int main() { printf("%d\n", sizeof(struct S2)); return 0; } //输出8
题解:
例题三
struct S1 { char c1; int i; char c2; }; struct S2 { char c3; struct S1 s; double d; }; int main() { printf("%d\n", sizeof(struct S4)); return 0; } //输出32
题解:
1.3.1为什么存在内存对齐
- 平台原因(移植原因):
- 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
- 性能原因:
- 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
- 总体来说:结构体的内存对齐是拿空间来换取时间的做法。
- 在设计结构体的时候,我们既要满足对齐,又要节省空间,就要做到,让占用空间小的成员尽量集中在一起。如上述的例题一和例题二。
1.4修改默认对齐数
可以使用#prangma预处理指令改变我们的默认对齐数。结构在对齐方式不合适的时候,我们可以自己更改默认对齐数。
代码实例:
#include <stdio.h> #pragma pack(1)//设置默认对齐数为8 struct S1 { char c1; int i; char c2; }; #pragma pack()//取消设置的默认对齐数,还原为默认 struct S2 { char c1; int i; char c2; }; int main() { printf("%d\n", sizeof(struct S1));//输出6 printf("%d\n", sizeof(struct S2));//输出12 return 0; }
2.位段
2.1什么是位段?
位段的声明和结构是类似的,有两个不同:
1.位段的成员必须是 int、unsigned int 或signed int 。
2.位段的成员名后边有一个冒号和一个数字。
3.位段-这里的位是二进制位
代码实例:
struct Stu { int a : 2; int b : 5; int c : 10; int d : 30; };
2.2位段的内存分配
- 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
- 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
- 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
- 代码实例:
struct S { char a : 3; char b : 4; char c : 5; char d : 4; }; int main() { struct S s = { 0 }; s.a = 10; s.b = 12; s.c = 3; s.d = 4; printf("%d\n", sizeof(struct S));//输出3 return 0; }
图解:
2.3位段的跨平台问题
int 位段被当成有符号数还是无符号数是不确定的。
位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的
总结:跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。
好了今天的内容就到这里了,对友友们有帮助的话不妨来个三连加关注支持一下,后期后持续更新C语言干货!