#结构体
结构体是一种自定义类型,其内部可以含多种数据类型。
例如:
struct x //结构体标签x可以省略
{
int num[10];
char name[5];
int age;
}stu,*p; /*结构体名称stu可以省略,p是
结构体指针,最后的分号不可以省
略。*/
结构体支持嵌套,即结构体中成员仍是结构体。
###结构体成员的访问:
1,用 结构体名称 + . + 成员名称,比如 :stu.name。
2,用 结构体指针 + -> + 成员名称,比如 :p->name。
###需要注意的几点:
1 . 结构体内不能包含类型为该结构本身的成员。
比如:
struct Node
{
int data;
struct Node next;
};
这样会导致Node定义时无法确定结构体的大小,修改的方法如下
struct Node
{
int data;
struct Node* next;
};
定义成指针就可以了,大小确定。
- 结构体可以被初始化,但不能被整体赋值,这一点与数组很像。
错误写法:
struct Node
{
int data;
char ch[10];
};
struct Node x;
x={3,"abc"};
正确写法:
struct Node
{
int data;
struct Node* next;
};
struct Node x={3,"abc"};
3.结构体嵌套初始化时要用大括号套着大括号。
比如:
struct x
{
int a;
int b;
};
struct Node
{
char ch[10];
struct x num;
struct Node* next;
};
struct Node y={"abcde",{3,4},NULL};
#结构体内存对齐
结构体内存对齐是结构体知识的重点和难点,想理解内存对齐是怎么回事要先明白几个概念和几条原则。
1,为什么要内存对齐?
并非所有的硬件平台都能够在任何地址读取任意的数据,部分平台在某些特定的地址只能读取对应类型的数据,举个例子:比如 x平台 在0x00000001出只能读取char型的数据,但你在该地址出定义的数据的数据类型为int,就会出现报错。
所以结构体内每个成员在存储的时候都要内存对齐,为的就是保证代码的平台移植性。
还有,为了读取未对齐的内存,处理器要进行两次内存访问,而对于已经对齐的内存只用进行一次内存访问。
总的来说,内存对齐就是一种用空间换时间的做法。为了节省空间,我们通常将占用内存小的成员集中在一起
2,内存对齐原则 (重点)
(1)第一个成员在与结构体变量偏移量为0的地址处。但这并不是第一个成员不用对齐。
(2). 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的值为8 Linux中的默认值为4
(3). 结构体总大小为最大对齐数(每个成员变量除了第一个成员都有一个对齐数)的最小整数倍
(4). 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
以在vs环境下举几个例子:(默认对齐数为8)
struct S1
{
char c1;
int i;
char c2;
};
printf("%d\n", sizeof(struct S1));
s1的第一个成员c1存在距离起始偏移量为0的地址处,占1个字节
第二个成员i自身大小为4字节,比默认对齐数8小,故i的对齐数为4。结合第二条规定,i必须存在4的整数倍地址处,故存在起始偏移量为4的地址处
同理第三个成员c2存在距起始偏移量为8的地址处。
s1最大对齐数为4,现整体长度为9,4比9大的最小倍数是12,故12为结构体s1
的大小。
struct S2
{
double d;
char c;
int i;
};
printf("%d\n", sizeof(struct S2));
如图: 16刚好能被8整除,故结构体s2的大小为16字节。
struct S3
{
double d;
char c;
int i;
};
printf("%d\n", sizeof(struct S3));
struct S4
{
char c1;
struct S3 s3;
double d;
};
printf("%d\n", sizeof(struct S4));
结构体s3的大小为16字节。 在s4中,s3的对齐数取齐内部最大对齐数8,所以s4大小为
1+7+16+8=32字节。
#位段
位段的声明和结构是类似的,有两个不同:
1.位段的成员必须是 int、 unsigned int 或signed int 。
2.位段的成员名后边有⼀个冒号和⼀个数字。
3. 位段的空间上是按照需要以4个字节(int )或者1个字节(char )的⽅式来开辟的。
4. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使⽤位段。
struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
}
#枚举
顾名思义,枚举就是一一列举。
比如:
enum Day <-枚举名称
{
Mon,
Tues,
Wed,
Thur, <-枚举变量,默认初值为0,依次递增1。
Fri,
Sat,
Sun
}; <-分号不能少
枚举的优点:
- 增加代码的可读性和可维护性
- 很#define定义的标识符⽐较枚举有类型检查,更加严谨。
- 防⽌了命名污染(封装)
- 便于调试
在定义枚举变量时,不能直接令其等于某个值,而应该令枚举变量的值等于原先定义的某个枚举变量。
enum Color//颜⾊
{
RED=1,
GREEN=2,
BLUE=4
};
enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
clr = 5;
#联合
联合的概念非常好理解,即在联合体内的所有成员共享同一块内存空间,联合体的大小至少是最大成员的大小。但当最大成员的大小不是联合体最大对齐数的整数倍时,联合体的大小就要扩充至对齐数的整数倍。
⽐如:
union Un1
{
char c[5];
int i;
};
union Un2
{
short c[7];
int i;
};
Un1最大成员大小为5字节,但最大对齐数为4,故要扩充至8字节。