结构体
首先,我们来讲一下,什么是数组?什么是结构体?
简单的说就是:
数组:具有相同类型元素的集合。
结构体:具有不同类型元素的集合。
结构体的声明
struct tag {
number-list;
}variable-list;
匿名结构体
struct {
number-list;
}variable-list;
注意:结构体成员变量完全一样,依旧是两种类型。即:
一种结构体一种类型
。
结构体的自引用
struct Node{
int data;
struct Node* next;
};
我们可以看到,这里在结构体里放一个自己的时候,放的是自己的指针。
我们可以简单的理解为,在定义一个结构体的时候,如果内部还有一个自己,那么就会永远的求不出大小,深度无限大。
注意:结构体的自引用,当内部用自己的时候,
必须
用指针。
结构体变量的初始化
struct stu{
char name[SIZE];
int age;
};
struct stu S = {"xiaowang", 18};//初始化为name:xiaowng,age:18
嵌套初始化:
如果,结构体内部还有其他结构体变量,那么只需要在初始化的时候,在
{}
内部使用{}
即可,即为嵌套初始化。
结构体内存对齐
首先上例子:
struct s1{
char c1;
int c2;
char c2;
};
它的大小是多少呢?即sizeof(struct s1)
是多少呢?
如果按照我们的最主观计算,算出来来的应该是1 + 4 + 1 = 6
,但是结果不是6
而是12
.
那么为什么是12
呢?
答案就是:通常情况下,存储器系统是按照字节(Byte)编址,但CPU访问时,通常按照字(Word)读取,因此地址就有字节地址和字地址的区别。这里就不简述什么是字地址了,我们这里先可以理解为,CPU读取内存时候,一次性读入4个字节(32Bit)。可以看下图来理解理解:
在存储的时候,计算机采用内存对其的方式来存储:
什么是内存对齐?
是以牺牲空间的方式来换取时间的方式。
为什么要内存对齐?
因为硬件限制,不进行内存对齐可能会导致CPU寻址效率降低。
怎么内存对齐?
①第一个成员在与结构体变量偏移量为
0
的地址处。
②其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐:起始偏移量能整除对齐数即为对齐。
对齐数:编译器默认的一个对齐数与该成员大小的较小值。任何成员变量都是自身对齐的,数组的对齐数是其内部元素类型的大小。(Microsoft Visual Studio的默认对齐数 =8
)
③结构体总大小为最大对齐数(每个成员变量都有一个对齐数,最大对齐数为所有成员各自对齐数中的最大值)的整数倍。
④如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(包含嵌套结构体的对齐数)的整数倍。
刚刚提到了默认对齐数,那么如何修改默认对齐数呢?
#pragma pack(number)//设置默认对齐数为number
···
#pragma pack()//取消设置默认对齐数,还原为默认(Microsoft Visual Studio默认为8)
结构体传参:
①函数传参的时候,会发生值拷贝,且拷贝的数据会压栈,会有时间和空间上的系统开销。
②如果传递的是一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,会导致性能下降。
所以,在结构体传参的时候,要传结构体的地址(结构体指针)。
说完了这些概念,我们在反过来看我们的那个例子,它的大小为什么是12
呢?
struct s1{
char c1;
int c2;
char c2;
};
char c1;//对齐数为1,起始偏移量为0,对齐,+1
int c2;//对齐数为4,起始偏移量为1,不对齐,所以为了对齐,我们需要+3=4就对齐了,再加上它的大小+4
char c2;//对齐数为1,其实偏移量为8,对齐,+1
//现在,我们算出来的大小为9,但是结构体的整体大小必须是其最大对齐数(这里是4)的整数倍。所以我们在+3 = 12
位段
位段和结构体是非常类似的:
①位段的成员必须是
int
、unsigned int
或signed int
。
②位段的成员变量名后变有一个:
和一个数字
。
例:试计算下面的类型的大小:sizeof(struct A)
struct A{
int a:2;//只使用2bit
int b:5;//只使用5bit
int c:10;//只使用10bit
int d:30;//只使用30bit
};
位段的内存分配
①位段的成员可以是
int
、unsigned int
、signed int
或者是char
(属于整形家族)类型。
②位段的空间大小是按照需要以4个字节(int
)或者1个字节(char
)的方式来开辟的。
③位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
所以,我们简单的看出a,b,c变量占用4Byte,d变量单独使用4Byte,sizeof(struct A) = 8
位段的跨平台问题
①int位段被当成有符号数还是无符号数是不确定的。
②位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出现问题)
③位段中的成员在内存中从左往右分配,还是从右往左分配标准尚未定义。
④当一个结构体包含两个位段,第二个位段成员比较大,无法容纳第一个位段剩余的位时,是舍弃剩余的位还是利用,这个是不确定的。
位段的应用
位段可以用于tcp中的报文。
枚举
所谓枚举,就是一 一列举。
枚举类型的定义
enum day1{//星期一,二,三,四,五,六,日
Mon,
Tues,
Wed,
Thue,
Fri,
Sat,
Sun
};
这个就是一个枚举类型(enum day
),{}
中的内容是枚举类型的可能取值,也叫枚举常量
。
他们的取值,是有默认的数字的,即默认从0
开始,我们也可以给他赋初值,举例:
enum day2{//星期一,二,三,四,五,六,日
Mon = 1,
Tues,
Wed,
Thue,
Fri,
Sat,
Sun
};
对于枚举类型enum day1
其数据分别为:0,1,2,3,4,5,6
;对于enum day2
其数据分别为:1,2,3,4,5,6,7
;我们这样就可以清楚的知道赋初值是什么效果了。
枚举的优点
①增加代码的可读性和可维护性
②和#define
定义的标识符比较枚举有类型检查,更加严谨
③防止了命名污染(封装)
④便于调试
⑤使用方便,一次可以定义多个常量
枚举的大小
sizeof(enum name) = 4
枚举类型的大小其实就是一个int
的大小,就是4个字节。
联合(共用体)
联合类型的定义
联合是一种特殊的自定义类型,这种类型定义的变量也包含一系列的成员,特征是这些成员公用一块空间。
union un{
char a;
int b;
};
联合的大小
①联合的大小至少是最大成员的大小。
②当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
我们上面这个联合体的大小为:sizeof(union un) = 4
,因为其最大成员的大小为4
,且是最大对齐数(4
)的倍数。
union un1{
char c[5];//大小为5
int i;//最大对齐数为4
};
union un2{
short c[7];//大小为14
int i;//最大对齐数为4
};
其中,sizeof(union un1) = 8
,sizeof(union un2) = 16
。
联合的特点
联合的成员是共用一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)。
联合体中的成员变量都是按照内存中的地址靠近低的地址开始的,如果左边是高地址右边是低地址,我们可以说他们都是右对齐的。