目录
一、结构体详解
1、结构体类型的声明
1.1.结构体是什么?
1.2.结构体声明
1.3.特殊的结构体声明(匿名结构体)
2、结构体的自引用
3、结构体变量的定义和初始化
4、结构体的内存对齐
内存对齐的规则是什么呢?
列1:
列2:
列3:
列4.内嵌结构体:
宏offsetof
#pragram pack()
为什么会出现内存对齐?
5、结构体传参
6、结构体实现位段(填充&移植)
1.什么是位段?
2.位段的内存分配
3.位段跨平台的具体问题
4.位段的运用
二、枚举详解
1)、枚举类型的定义
2)、枚举的优点
3)、枚举的使用
三、联合体详解
1)、联合体类型的定义
2)、联合体类型的特点
3)、联合体的大小计算
结构体是用来定义一个复杂对象,其关键字:struct
如果我们需要来描述一本书:书名、价格、序号
struct Book
{
char name[20];// 书名
int price;// 价格
int list;// 序号
};//这就是我们声明的一个书的类型
同时我们可以在声明时定义一个变量
struct Book
{
char name[20];// 书名
int price;// 价格
int list;// 序号
}bk1;// 我们定义了一个结构体变量 变量名是bk1
如果我们在声明时没有给这个类型附加类型名,那么这就是一个匿名结构体。
注意:如果我们定义的是一个匿名结构体,那么我们只能在声明时定义变量,以后就不能在使用这个匿名结构体去定义变量了。
struct
{
char a;
int b;
short c;
}x;// 这就是一个匿名结构体,注意匿名结构体只能被使用一次
为了区分一下特殊情况看看如下代码:
struct
{
char a;
int b;
short c;
}x;
struct sct
{
char a;
int b;
short c;
}*p;定义了一个结构体指针
p = &x;//正确吗??可以这样用吗??
答案是不正确的,这两个结构体虽然成员的类型是一样的,但是编译器会将其区分为两个结构体类型,而两给个不同的类型的指针是不能赋值的。
定义:自引用就是在结构体的内部,有一个成员类型是这个结构体的指针。
struct Node
{
int val;
struct Node* next;
};// 这个结构体里面有一个自己的指针
那么可不可以将其成员写成自己这个结构体类型呢?不行,编译器不知道到底应该分配多少空间给这个结构体。
struct Node
{
int val;
struct Node next;
};// 这样是不行的,结构体里面有结构体,然后这个结构体里面又有结构体………………这样
//编译器就不知道到底该为这个结构体分配多少空间了。
技巧:我们定义一个结构体变量时,总是会反复的写重复的关键字,我们可以typedef这个类型,方便我们以后定义变量时,编写迅速
typede fstruct Node
{
int val;
struct Node* next;
}Node;// 将这个结构体类型名重定义为 Node
那么我们在自引用时可不可以直接写重定义的类型名呢?
typedef struct Node
{
int val;
Node* next;
}Node;// 这样时不行的,这时会有一个经典的问题:是先有鸡,还是现有蛋。
// 这里也是,是先有这个结构体,还是先重定义。
在声明时定义&在普通定义
struct Book
{
char name[20];// 书名
int price;// 价格
int list;// 序号
}bk1;
struct Book bk2;
初始化
struct Book
{
char name[20];// 书名
int price;// 价格
int list;// 序号
}bk1;
struct Book p = { "mane", 75, 1 };
嵌套初始化
struct Book
{
char name[20];// 书名
int price;// 价格
int list;// 序号
}bk1 = { "name", 75, 2};
struct Book
{
char name[20];// 书名
int price;// 价格
struct Book p;
int list;// 序号
}bk1;
bk1 = { "name", 75, {"n", 60, 1}, 2 };
每一种类型都会有自己的大小,比如int占4个字节,char占1一个字节,结构体类型也是,但是结构体的成员的不固定的,其大小也是不固定的,并且有自己的一套大小计算方法(内存对齐)。
// vs结构体默认对齐数是8
struct S
{
char ch;// 8 -> 1
int i;// 8 -> 4
char c;// 8 -> 1
};// 内存:12字节
1、第一个成员在与结构体偏移量为0的地址处。
2、其他成员要对齐到这个成员的对齐数的整数倍偏移量地址处。
(成员的对齐数:默认对齐数 与 这个成员的大小 相比较的较小值)
(首先:我们需要知道默认对齐数。(vs编译器中是8))
3、结构体的总大小为 成员最大对其数的整数倍
4、如果嵌套了结构体成员p,那么这个嵌套结构体成员p的对齐数就等于这个结构体p的成员的最大对齐数。
// vs结构体默认对齐数是8
struct S
{
char ch;// 8 -> 1
int i;// 8 -> 4
char c;// 8 -> 1
};// 内存:12字节
图解1:
struct S1
{
int i;// 8 -> 4
char ch;// 8 -> 1
char c;// 8 -> 1
};// 内存:8字节
图解2:
struct S2
{
char ch;// 8 -> 1
int i;// 8 -> 4
double b;// 8 -> 8
};// 内存:16字节
图解3:
struct S2
{
char ch;// 8 -> 1
int i;// 8 -> 4
double b;// 8 -> 8
};// 内存:16字节
struct S3
{
char ch;// 8 -> 1
struct S2;// 8 -> 8
double b;// 8 -> 8
};// 内存:32字节
图解4:
如果我们不确定自己所求的成员偏移量,我们可以利用offsetof这个宏,将一个结构体的成员偏移量打印出来:
其参数是这个结构体类型,其成员名
struct S3
{
char ch;// 8 -> 1
struct S2 s;// 8 -> 8
double b;// 8 -> 8
}c;// 内存:32字节
int main()
{
printf("%u\n",offsetof(struct S3, s));
return 0;
}
vs中默认对其数是8,如果我们需要可以将其修改为我们所需要的对齐数
struct S2
{
char ch;// 8 -> 1
int i;// 8 -> 4
double b;// 8 -> 8
};// 内存:16字节
#pragma pack(1) // 将默认对齐数修改为1
struct S3
{
char ch;// 8 -> 1
struct S2 s;// 8 -> 1
double b;// 8 -> 1
}c;// 内存:1 + 16 + 8 = 25字节
1、平台(移植)原因:并不是所有的硬件都可以在任何地址上存放内容的,一些硬件只能在某一些地址处取地址,否则硬件就会报出错误
2、性能原因:在访问内存的时候,由于处理器是32位的,一次处理32个位,如果内存不对齐,就有可能一个数据需要在内存中去取两次的情况
总结:拿空间换时间
1、值传递
直接将这个结构体变量实参传给函数
这样做是不好的,调用函数时会发生压栈的操作,而值传递一个结构体,这个结构体占用内存较大,就会过多的耗损空间和时间,增大系统开销,所以我们尽量采用指针传递。
2、指针传递
将这个结构体的指针传给函数,不管什么指针,只要是指针就只会占用四个字节
位段和结构体相识有些许的不同
i.位段的成员只能是 int, unsigned int, char
ii.位段成员后面有一个冒号(:)
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
i.位段的成员是int /unsigned int/char
ii.开辟的空间是按照需要以四字节或者一字节来开辟的
iii.位段空间分配会涉及到很多内容,所以位段是不跨平台的
struct A
{
int _a : 2;// 2 个bit位
int _b : 5;// 5 个bit位
int _c : 10;// 10 个bit位
int _d : 30;// 30 个bit位
};// 8字节
成员冒号后面的值表示这个成员占用多少bit位,最大不得超过这个成员的最大值,这里不能超过32,在vs环境下,当当前开辟的空间剩余的bit不能容纳下一个成员时,会开辟一个新的4字节空间。
i.int 被当作有符号还是无符号是不确定的
ii.位段中最大位数不能确定。(16位机器下int十六位,32位机器下int三十二位)
iii.位段成员是由左到右还是从右到左分配没有规定
iiii.当结构不能容纳下一个成员时,是舍弃当前剩余bit位,还是不舍弃,不确定
比如网络ip地址就是运用的位段
枚举就是一一列举
关键字:enum
enum Day//星期
{
Mon,// 0
Tues,// 1
Wed,// 2
Thur,// 3
Fri,// 4
Sat,// 5
Sun //8
};// 这个成员的值实际上就是 由0开始依次递增的值
enum Sex//性别
{
MALE,
FEMALE,
SECRET
};
enum Color//颜色
{
RED,
GREEN,
BLUE
};
1. 增加代码的可读性和可维护性
2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
3. 防止了命名污染(封装) 4. 便于调试
5. 使用方便,一次可以定义多个常量
enum Day//星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
int main()
{
enum Day d = Wed;
return 0;
}
我们可以将枚举成员设置初始值
enum Day//星期
{
Mon = 5,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
但我们不可以对这些成员再赋值
enum Day//星期
{
Mon = 5,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
Wed = 4;//这是错误的
联合体成员公用一块空间
关键字:union
//联合类型的声明
union Un
{
char c;
int i;
};
//联合变量的定义
union Un un;
//计算连个变量的大小
printf("%d\n", sizeof(un));// 4
联合体成员共用一块空间
union Un
{
int i;
char c;
};
union Un un;
// 下面输出的结果是一样的吗?
printf("%d\n", &(un.i));
printf("%d\n", &(un.c));
//下面输出的结果是什么?
un.i = 0x11223344;
un.c = 0x55;
printf("%x\n", un.i);
前面两行表示,成员共用一块内存
第三行表示,成员之间会相互影响
所以联合体一般一次只使用一个成员
1.联合体的大小至少是最大成员的大小
2.当最大成员大小不是最大对齐数大小的整数倍时,就要对齐到最大对齐数的整数倍
union Un1
{
char c[5];// 8 -> 1
int i;// 8 -> 4
};// 8 字节
union Un2
{
short c[7];// 8 -> 2
int i;// 8 -> 4
};// 16字节
//下面输出的结果是什么?
printf("%d\n", sizeof(union Un1));
printf("%d\n", sizeof(union Un2));