目录
结构体
结构体是一些值的集合,这些值是它的成员。它们可以是不同类型的变量。
结构体的声明
注意:它的分号不能丢了
//栗子:这里定义一个学生 struct Stu { int age; char name[20]; char sex[5]; };
特殊的声明
在声明结构体的时候,还有一种特殊的声明,就是匿名结构体类型:
//栗子: struct { int a; char b; float c; }s1; struct { int a; char b; float c; }*p;
上面两个结构体省略了结构体的名字,这时有一个问题就是:
p = &s1在编译器上是编译不过去的。原因就是编译器把他们两个当成了不同的结构体类型。
结构体的自引用
在结构体中包含一个类型为该结构本身的成员是可以的:
struct Node { int a; struct Node *next; };
结构体的定义和初始化
声明类型的时候可以定义变量,也可以在声明完后定义。初始化可以在定义完后初始化,也可以定义的同时初始化。
struct point { int a; int b; }s1; //声明的同时定义 struct point s2; //先声明再定义结构体变量 //初始化:定义的同时初始化 struct point s3 = { 1,2 }; struct Stu { int age; char name[20]; }; struct Stu s = { 20,"zhangsan" }; struct Node { int date; struct Stu s1; struct Node* next; }n1 = {1, {19, "lishi"}, NULL}; //结构体嵌套初始化 struct Node n2 = { 2, {25,"wangwu"}, NULL };//结构体嵌套初始化
结构体内存对齐
对于结构体内存对齐有以下几点规则:
第一个成员放在与结构体变量偏移量为0的位位置
其他变量要对齐到对齐数的整数倍处的地址处
对齐数是编译器默认的一个对齐数与该成员大小的较小值(vs默认值为8,gcc,Linux没有默认对齐数,对齐数是成员自身大小)
结构体总大小必须是最大对齐数的倍数
如果是嵌套了结构体的情况,嵌套的结构体对齐到自己最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数的整数倍(含嵌套结构体的对齐数)
练习
//练习一 struct s1 { char c1; int i; char c2; }; int main() { printf("%d\n", sizeof(struct s1)); }
c1为首成员,对齐偏移量为0的位置,占1个字节。i为4个字节小于8,对齐数为4,从&c1+3的位置开始存放i。c2为1个字节小于8,对齐数为1,接在i后面存放。又因为它们加起来为9不是最大对齐数的倍数,所以要提升为12.
//练习二 struct s2 { char c1; char c2; int i; }; int main() { printf("%d\n", sizeof(struct s2)); }
//练习3 struct S3 { double d; char c; int i; }; int main() { printf("%d\n", sizeof(struct S3)); }
//练习4 struct S4 { char c1; struct S3 s3; double d; }; int main() { printf("%d\n", sizeof(struct S4)); }
内存对齐的原因
平台原因:
不是所有的硬件平台都能访问任意地址上的数据。某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
性能原因:
数据结构应该尽量的在自然边界上对齐。原因就是为了访问未对齐的内存,处理器需要进行两次内存访问。而对齐的内存只需要访问一次。
总得来说就是内存对齐是用来拿空间换时间的做法。
所以在设计结构体的时候我们要尽量的满足对齐,又节约空间。做法就是让空间小的成员尽量在一起:
//错误的做法 struct S1 { char c1; int i; char c2; }; //正确的做法 struct S2 { char c1; char c2; int i; };
修改默认对齐数
修改对齐数我们需要用到一个预处理指令:#pragma,它可以改变我们的默认对齐数:
#pragma pack(8) struct S1 { char c1; int i; char c2; }; #pragma pack() #pragma pack(1) struct S2 { char c1; int i; char c2; }; #pragma pack() int main() { printf("%d\n", sizeof(struct S1)); printf("%d\n", sizeof(struct S2)); return 0; }
在我们遇到对齐方式不合适的时候,我们就可以用pragma来改变默认对齐数。
结构体传参
结构体传参有两种方法:一种是直接传参,一种是地址传参。一般来说是地址传参比较好。
因为:函数传参需要压栈,会有时间和空间的开销。如果传递的结构体对象过大,那么参数压栈的开销就会比较大,所以会导致性能的下降。
struct Stu { char name[20]; char sex[4]; int age; }; struct Stu s = { "zhangsan","nan", 20 }; //结构体传参 void print_f(struct Stu s) { printf("%s\n", s.name); } //结构体地址传参 void print_t(struct Stu* s) { printf("%d\n", s->age); } int main() { print_f(s);//传结构体 print_t(&s);//传地址 return 0; }
位段
位段的含义
段位和结构体是类似的,但是有两点不同:
段位的成员必须是int,unsigned int, signed int
段位的成员名后面有一个冒号和一个数字
举个栗子:
struct S { int a : 2; int b : 5; int c : 10; int d : 30; };
位段的内存分配
段位的成员是int unsigned int signed int 或者是char类型
段位的空间是按照需要以4个字节或者1个字节的方式开辟的。
段位涉及很多不确定的因素,段位是不跨平台的,注重可移植的程序需要避免使用段位。
栗子:
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; return 0; }
位段的跨平台问题
int 位段被当成有符号数还是无符号数是不确定的。
位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机
器会出问题。
位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是
舍弃剩余的位还是利用,这是不确定的
位段的应用
它可以极大的减少数据的空间,是数据可以高速传递
枚举
枚举就是可能的情况一一列举。比如:生活中的星期,月份,性别都可以一一列举
枚举类型的定义
enum day { mon, tues, wed, thur, fri, sat, sun, }; enum color { red, green, blue };
以上定义的day和color都是枚举类型,{}中的内容是枚举类型的可能取值,叫枚举常量。这些值都是有默认值的,默认从0开始,每次增加1,在定义的时候也是可以赋值的:
enum day { mon = 1, sun = 7, sat = 6 };
枚举的优点
增加代码的可读性和可维护性
和#define的标识符比较枚举有类型检查,更加严谨
防止了命名污染(封装)
便于调试
使用方便,一次可定义多个常量
枚举的使用
int main() { enum color str = red; //只有拿枚举类型给枚举常量赋值,才不会出现类型的差异 str = 6; return 0; }
联合体
联合体的定义
联合体是一种特殊的自定义类型。这种类型的变量包括了许多的成员,他的特点是这些成员共同使用一块空间。
举个栗子:
union un { char c; int i; }; //联合变量的定义 union un u;
联合的特点
联合体的成员公用一块空间,这样的联合体大小至少是最大成员的大小。
通过下面代码可以发现,它们公用一块空间,且还会改变对方的值
union un { int i; char c; }; int main() { union un u; printf("%p\n", &(u.i)); printf("%p\n", &(u.c)); u.i = 0x11223344; u.c = 0x55; printf("%x\n", u.i); return 0; }
联合体大小的计算
联合的大小至少是最大成员的大小
当最大成员的大小不是最大对齐数的倍数的时候,就要对齐到最大对齐数的整数倍
栗子:
union un1 { char c[5]; int i; }; union un2 { short c[7]; int i; }; int main() { printf("%d\n", sizeof(union un1)); printf("%d\n", sizeof(union un2)); return 0; }