1. 结构体
1.1 结构体基本概念
结构体是一些值的集合,这些值称为成员变量,结构的每个成员可以是不同类型的变量。
1.2 结构体的声明
struct tag //自定义结构体名
{
member-list; //成员列表
}variable-list; //变量列表
1.3 特殊的结构体声明
声明结构体类型时,可以不完全声明。
#include <stdio.h>
struct //匿名结构体类型
{
char a;
int b;
float f;
}x;
struct
{
char a;
int b;
float f;
}*p;
int main()
{
p = &x;
return 0;
}
上面代码定义了两个一样的匿名结构体类型,分别声明了两个变量x和*p,但是运行main函数时,却会出现以下问题:
虽然两个结构体成员一样,但是编译器任然认为这是两个结构体,一般情况下匿名结构体只使用一次。当希望一个结构体只使用一次时,就可以使用匿名结构体。
1.4 结构体的自引用
在结构体中包含一个类型为该结构本身的成员。
//代码1
struct Node
{
int a;
struct Node next;
};
//代码2
struct Node
{
int a;
struct Node* next;
};
代码1运行,但是sizeof(struct Node)计算结构体大小时,struct Node中又包含了struct Node,会一直套娃,系统也不知道要怎么才能算出大小。代码2的话,把自引用换成指针,通过指针指向的地址找引用的结构体。
1.5 结构体变量的定义和初始化
struct Stu
{
char name[20];
int age;
char sex[5];
}S1; //全局变量
int main()
{
struct Stu S2 = { "zhangsan", 20, "nan" }; //局部变量
return 0;
}
结构体变量可以分为两种,一种是定义结构体完毕后就定义变量,这种属于全局变量;一种是在需要的地方定义结构体变量,这种是局部变量。初始化在一对大括号 { } 里面。
1.6 结构体内存对齐
结构体对齐规则:
- 第一个成员在与结构体变量偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数 = 编译器默认的一个对齐数与该成员大小的较小值。
- 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整倍数。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自身的最大对齐数的整倍数处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
- VS中默认的值为8
- Linux中没有默认对齐数,对齐数就是成员自身的大小
struct tag1
{
char c1;
int a;
char c2;
};
struct tag2
{
char c1;
char c2;
int a;
};
struct tag3
{
char c1;
struct tag1 S;
int a;
};
int main()
{
printf("%zd\n", sizeof(struct tag1));
printf("%zd\n", sizeof(struct tag2));
printf("%zd\n", sizeof(struct tag3));
return 0;
}
当上面代码运行打印tag1和tag2所占字节大小时,我们首先算其中成员大小,两个char类型,一个int类型,加起来应该是6字节。但实际上运行打印出来的却是:
这是因为对齐数的规则在里面处理了数据的存放。
tag1把c1放进去,再放a时,a的本身是4,对齐数是8,比较之后a的对齐数是4,存储4根据规则需要4的整倍数,1、2、3都不是,但是空间被浪费了,到4时把a按照自身大小存储后,再把c2存储。但是此时tag1总字节为9,结构体总大小是最大对齐数整倍数,最大对齐数是4,9不是4的整倍数,再往下浪费3个字节空间到12时停止。
tag2存放c1,c2后找到4,其中浪费2个字节,再把a放进去,总大小为8,是最大对齐数的整倍数,停止。
设计结构体时尽量把小字节的成员放在一起,可以有效的节省空间,避免浪费。
当tag3嵌套了结构体时,先把自身c1放进去,然后看嵌套的结构体最大对齐数是多少,按照最大对齐数整数倍开始存放,存放规则与tag1相同,存放完再存放自身a。存放完毕后看总大小是否为最大对齐数整数倍,嵌套的结构体总体大小虽然占12个字节,但是最大对齐数是4,按最大对齐数来算。此时总大小20字节,是最大对齐数的倍数。
1.7 修改默认对齐数
#pragma pack(1) //修改默认对齐数为1
struct tag1
{
char c1;
int a;
char c2;
};
#pragma pack() //还原为默认对齐数
struct tag2
{
char c1;
int a;
char c2;
};
int main()
{
printf("%zd\n", sizeof(struct tag1));
printf("%zd\n", sizeof(struct tag2));
return 0;
}
默认对齐数可以通过#pragma pack()来进行修改。
2. 枚举
枚举就是把可能的值一 一列举,比如性别:男,女,季节:春,夏,秋,冬。
2.1 枚举类型的定义
enum Color
{
RED,
GREEN = 2,
BLUE
};
enum Sex
{
MALE,
FEMALE
};
int main()
{
printf("%d\n", RED);
printf("%d\n", GREEN);
printf("%d\n", BLUE);
return 0;
}
枚举类型通过enum关键字创建, { } 中可能的取值都叫做枚举常量,其中的常量都是有值的,默认从0开始,依次往下加1,也可以指定值的大小,接下来的值会从被指定的值的基础上开始加1。
2.2 枚举的用法
enum Color
{
RED,
GREEN,
BLUE
};
int main()
{
enum Color C = GREEN;
C = 5;
return 0;
}
枚举类型变量只能使用枚举常量赋值才不会出现类型差异。赋值后改变变量的值不会影响枚举常量的值。
3. 联合(共用体)
3.1 联合类型的定义
联合是一种特殊的自定义类型
这种类型也包含了一系列成员,但是这些成员共用同一块空间(联合也叫共用体)
union Un
{
int a;
char b;
};
int main()
{
union Un un;
printf("%zd\n", sizeof(un));
printf("%p\n", &un.a);
printf("%p\n", &un.b);
return 0;
}
从代码运行打印的信息可以看到两个变量地址是一样的。
3.2 联合的特点
联合的成员共用同一块空间,那么以一个联合的总体大小,至少也是最大成员的大小(最少要保证能够容纳最大的成员)。
3.3 联合大小的计算
- 联合的大小最少是最大成员的大小
- 联合最大成员不是最大对齐数的整数倍时,要对齐到最大整数倍
union Un
{
int a;
char b[5];
};
int main()
{
union Un un;
printf("%zd\n", sizeof(un));
return 0;
}