一、结构体(struct)
1.声明
声明一个学生的结构体如下:
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}; //分号不能丢
注:结构体在声明时可省略标识符'Stu',但这种声明方法只能在声明时创建对应的变量,而且若存在另一个成员变量与其完全相同的匿名结构体,编译器会把这两个声明当成完全不同的类型,因此不建议这样声明。
- 结构体的成员变量可以是其他结构体类型,但不能是自身结构体变量
- 结构体的成员变量可以是自身结构体指针
2.结构体变量的定义和初始化
结构体变量可以在声明的同时定义,也可在主函数内部需要时定义。区别在于前者为全局变量,后者为局部变量。
定义变量的同时赋初值即可进行初始化。
struct Point
{
int x;
int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
//初始化:定义变量的同时赋初值。
struct Stu //类型声明
{
char name[15];//名字
int age; //年龄
};
struct Stu s = {"zhangsan", 20};//初始化
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化
3.结构体内存对齐
(1)对齐规则
1>每个成员变量相对于结构体首地址的偏移量是对齐数的整数倍
- 对齐数:编译器默认对齐数(VS为8,gcc无默认值)和成员大小(若为数组,是每个元素的大小;若为结构体,是该结构体中最大的对齐数)中的较小值
2>整个结构体的大小是各个成员中最大对齐数的整数倍
(2)对齐原因
①平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特 定类型的数据,否则抛出硬件异常
②性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问
总体来说: 结构体的内存对齐是拿空间来换取时间的做法
(3)举例
struct s1 //对齐数:
{
double a; //8
char b[5]; //1
int c; //4
};
struct s2
{
char x; //1
struct s1 y; //8(s1中最大的对齐数为8)
float z; //4
};
int main()
{
printf("%d %d\n", sizeof(struct s1), sizeof(struct s2));
return 0;
}
输出结果为:24 40
解释:对于s1,a的对齐数是8,因为a是第一个成员变量,可以直接存放在首地址处(偏移量为对齐数的0倍)并开辟8个字节存放a, b的对齐数为1,8是1的整数倍,故直接开辟1*5=5个字节存放b,c的对齐数为4,但目前开辟的8+5=13个字节不是4的整数倍,故先开辟3个字节(13+3=16是4的整数倍),再开辟4个字节存放c,现在共开辟了20个字节,但并不是最大对齐数8的整数倍,故再开辟4个字节(20+4=24是8的整数倍)。因此,s1的大小为24个字节。
对于s2,x的对齐数为1,直接开辟1个字节存放x,y的对齐数为8,先开辟7个字节(1+7=8是8的整数倍),再开辟24个字节(s1的大小)存放y,z的对齐数为4,8+24=32是4的整数倍,直接开辟4个字节存放z,现在共开辟了36个字节,不是最大对齐数8的整数倍,继续开辟4个字节(36+4=40是8的整数倍)。因此,s2的大小为40个字节。
(4)修改默认对齐数
使用预处理指令#pragma pack()可以修改编译器的默认对齐数
例如,将上面代码结构体的默认对齐数改为4,其大小分别变为了20 28
#pragma pack(4)//修改默认对齐数为4
struct s1 //对齐数:
{
double a; //8->4
char b[5]; //1
int c; //4
};
struct s2
{
char x; //1
struct s1 y; //8(s1中最大的对齐数为8)->4
float z; //4
};
#pragma pack()//恢复默认对齐数8
int main()
{
printf("%zd %zd\n", sizeof(struct s1), sizeof(struct s2));
return 0;
}
4.结构体传参
在结构体传参时,最好传结构体的地址。原因:
函数传参的时候,参数是需要压栈的,会有时间和空间上的系统开销。 如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降
二、枚举(enum)
1.枚举类型的定义
enum Day//星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
enum Sex//性别
{
MALE,
FEMALE,
SECRET
};
enum Color//颜色
{
RED,
GREEN,
BLUE
};
以上定义的 enum Day , enum Sex , enum Color 都是枚举类型。 {}中的内容是枚举类型的可能取值,也叫 枚举常量
枚举常量都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值。
例如:enum Color //颜色
{
RED=1,
GREEN=2,
BLUE=4
};
2.枚举的优点
我们可以使用 #define 定义常量,为什么非要使用枚举?
枚举的优点:
1. 增加代码的可读性和可维护性
2. 和#define定义的标识符相比枚举有类型检查,更加严谨。
3. 防止了命名污染(封装)
4. 便于调试
5. 使用方便,一次可以定义多个常量
三、联合(union)
1.联合类型的定义
//联合类型的声明
union Un
{
char c;
int i;
};
//联合变量的定义
union Un un;
2.联合的特点
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小
union Un
{
int i;
char c;
};
union Un un;
int main()
{
printf("%p\n", &(un.i));
printf("%p\n", &(un.c));
un.i = 0x11223344;
un.c = 0x55;
printf("%x\n", un.i);
}
输出为:
因为联合的成员共用一块内存,因此i和c的地址是相同的;i占4个字节,因为小端存储,第一个字节存放的是44,c只占一个字节,将原来44改为了55,所以输出为11223355
3.联合大小的计算
- 联合的大小至少是最大成员的大小。
- 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍
例如:
union Un1
{
char c[5];
int i;
};
union Un2
{
short c[7];
int i;
};
Un1的大小为8(=5+3) ,Un2的大小为16(=14+2)