结构体
1. 结构体类型
struct stu {
char name[20];
char tele[12];
char sex[10];
int age;
} s4, s5, s6;
这是声明一个结构体类型,stu是结构体标签,name,tele,sex,age;是成员变量,s4,s5,s6是成员列表,表示在声明结构体时创建出来的变量。
结构体变量初始化
//创建结构体-结构体变量初始化
struct stu s1 = { "zhangshan","111222333","nan",22 };
struct stu s2;
printf("%s,%s,%s,%d\n", s1.name, s1.tele, s1.sex, s1.age);
匿名结构体:
struct {
int a;
char c;
float b;
}va;
匿名结构体只能通过名va来创建,声明中的va也是全局变量。
结构体的自引用:
struct ListNode {
int data;
struct ListNode* next;
};
2. 结构体大小计算
结构体对齐原则:
- 第一个成员在结构体变量偏移量为0的地址处
- 其他成员变量对齐到某个数字(对齐数)的整数倍的地址处
- 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
- 对齐数=编译器默认的一个对齐数与该成员大小的较小值。vs中默认的值为8
struct d1 {
char a;
char b;
int c;
};
struct d2 {
char a;
int b;
char c;
};
int main()
{
struct d1 s3 = { 0 };
struct d2 s4 = { 0 };
printf("%d\n", sizeof(s3));
printf("%d\n", sizeof(s4));
return 0;
}
为什么两个结构体类型的大小不一样?
解析:
d1结构体类型:假设s3的地址为0,那么第一个成员变量a的地址也是0,其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处,首先对齐数是8和1中的较小值1,因此b的对齐数就是1,而每一个地址都是1的整数倍,因此b的地址就是1,对于c的对齐数就是8和4中较小的4,所以c的地址必须是4的倍数,因此要跳过4个:0123,再加上int型的4个字节。最后内存中存储的是:01_ _4567。结构体的大小是最大对齐数的整数倍,也就是4的整数倍,所以最后存储的是:01_ _4567。8的字节
d2结构体类型:假设s4的地址为0,那么第一个成员变量a的地址也是0,其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处,对于b的对齐数就是8和4中较小的4,所以b的地址必须是4的倍数,因此要跳过4个:0123,在加上int型的4个字节。在4处开始存储,因此b存储完就是0_ _ _4567。而c的对齐数就是1,地址为8。并且结构体的总大小要是4的倍数,所以最后存储的是:01_ _45678_ _ _。12的字节
结构体嵌套:
struct d3 {
double a;
char b;
int c;
};
struct d4 {
char a;
struct d3 b;
double c;
};
int main()
{
struct d3 s5 = { 0 };
struct d4 s6 = { 0 };
printf("%d\n", sizeof(s5));
printf("%d\n", sizeof(s6));
return 0;
}
解析:
可以求出来s5的大小为16,最大对其数为8。所以对于s6来说:a的地址是0,b是结构体类型,他自己的最大对齐数是8(不包括默认值8),而不是他的大小16,所以要对齐到8处,所以b的地址是在8处开始,0_ _ _ _ _ _ _891011121314151617181920212223,c的对齐数是8,在24处开始,0_ _ _ _ _ _ _8910111213141516171819202122232425262728293031
最大对齐数就是8,总大小是8的整数倍,所以一共32个字节。
重点:为什么有结构体对齐原则:
- 不是所有的硬件平台都可以访问任意地址上的任意数据,某些平台只能在某些地址处取某些特定类型的数据,否则抛出异常
- 数据结构尤其是栈应尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问只需要一次。
- 如果内存连续:char a;int b; 地址为01234—,对于32为的电脑,有32根地址线和32根数据线,每次可以访问4个字节,所以要读取b的话,需要从0开始读取123,再从4开始读取到4,才能读取完整的b,所以是两次。如果内存对齐:0—1234,只需一次就可以读取b。
- 结构体的内存对齐是拿空间来换取时间
结构体中类型的顺序也影响内存的大小,既要满足对齐,也要节省空间。同一类型的数据尽量挨在一起。
修改默认对齐数:
#pragma pack(4)--修改为4,中间放要修改的结构体类型声明
#pragma pack()--取消设置
查看结构体中的元素类型的偏移量:
offsetof(),是一个宏,头文件:#include <stddef.h>
size_t offsetof(structName, memberName)
printf("%d\n", offsetof(struct d1, a));
printf("%d\n", offsetof(struct d1, b));
printf("%d\n", offsetof(struct d1, c));
位端
位端也是一种结构体类型,但和结构体有点区别,位端内的成员变量一般都是一个类型。并且成员变量后面都跟一个变量所占的比特位数。
struct s {
int _a : 2;//_a占2个比特位
int _b : 5;//_b占5个比特位
int _c : 10;//_c占10个比特位
int _d : 30;//_d占30个比特位
};
int main()
{
struct s s1;
printf("%d\n", sizeof(s1));
}
解析:
s类型实际上占47个比特位,按理来说6个字节就可以。
实际上,在电脑中整型一次开辟4个字节,放入_a,_b,_c,还剩15个字节。不够放_d的,直接舍弃掉。这时再开辟4个字节,放入_d,还剩2个字节,也舍弃掉。这样最终就是8个字节。
位端就是为了节省空间,不用位端要使用16个字节。位端不适合跨平台使用。
内存中存储原理:
struct c {
char _a : 3;
char _b : 4;
char _c : 5;
char _d : 6;
};
int main()
{
struct c c1 = { 0 };
c1._a = 10;
c1._b = 20;
c1._c = 3;
c1._d = 4;
}
解析:
- 先开辟一个字节8个比特位:0000 0000;a是10,二进制是1010,但是a只有3个比特位,所以只能放进去10。0000 0010。
- b是20,二进制是10100,但是b有4个比特位,只能存储0100,并且在a后面。0010 0010;还剩一个字节舍弃。
- c是3,二进制是011,c有5个比特位,不全的补0,00011。此时再开辟8个比特位,放入后为:0000 0011
- d是4,二进制为100,d有6个比特位,000100。此时再开辟8个比特位,放入后为:0000 0100
- 所以最后存储的是:0010 0010;0000 0011;0000 0100----2 2 0 3 0 4
枚举类型
//枚举类型
enum Sex {
//枚举的可能取值
MALE,
FEMALE,
SECRET
};
enum he {
//枚举的可能取值
MALE1 = 2,
FEMALE1 = 3,
SECRET1 = 5
};
int main()
{
//枚举类型使用
enum Sex s = MALE;
printf("%d,%d,%d\n", MALE, FEMALE, SECRET);
printf("%d,%d,%d\n", MALE1, FEMALE1, SECRET1);
return 0;
}
解析:
定义枚举变量的值不能取其他值,已经规定了可能取得值,只能等于声明中的值。枚举类型内部已经定义了变量的值,第一个可能取值是0,以此类推。但是在类型声明的时候可以定义值。
联合体-共用体
在C语言中,变量的定义是分配存储空间的过程。一般的,每个变量都具有其独有的存储空间,那么可不可以在同一个内存空间中存储不同的数据类型呢?
答案是可以的,使用联合体就可以达到这样的目的。在C语言中定义联合体的关键字是union。
union Un {
int i;
char a[5];
};
int main()
{
union Un u;
printf("%d\n", sizeof(u));
printf("%p\n", &u);
printf("%p\n", &(u.a));
printf("%p\n", &(u.i));
}
可以看出来u的地址和u中a的地址以及u中i的地址全是一样的。那为什么它的大小是8字节?
a和i共用一块空间,联合体中的a和i不能同时使用,一时只能使用一个,联合体的大小至少是最大成员的大小,当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。所以要大于5,并且对齐到8。
使用联合体检测内存大小端存储
什么是大小端存储?
int a = 0x11223344
低地址------------------高地址
…[][][][11][22][33][44][][][][][][]…大端字节序存储模式
…[][][][44][33][22][11][][][][][][]…小端字节序存储模式
不使用联合体判断:
int a = 1;
if (1 == *(char*)&a)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
解析:
取a的地址变为char*类型,里面的四个字节只可以取第一个了,然后解引用,如果是1就是小端
使用联合体判断:
int system_check()
{
union un {
char a;
int b;
}u;
u.b = 1;
return u.a;
}
int main()
{
int ret = system_check();
if (1 == ret)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
}
解析:
a和b共用4个字节,a占第一个字节,所以令b的值等于1,如果小端那么a占用的字节存储为1(途中绿色的情况),反之为0(图中紫色的情况)。