目录
1.结构体类型
1.1声明
1.2结构的自引用
1.3结构体变量的定义和初始化
1.4结构体内存对齐
1.4.1结构体的对齐规则
1.4.2编译器的默认对齐数修改
1.5结构体传参
1.6结构体实现位段(位段的填充&&可移植性)
1.6.1位段的定义
1.6.2位段的内存分配
1.6.3位段的跨平台问题
1.6.4位段的应用->ip数据报
2.枚举
2.1枚举类型的定义
2.2枚举的优点
3.联合(共用体)
3.1联合类型的定义
3.2联合的特点
3.3联合大小的计算
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量
struct tag //struct是结构体关键字。tag是结构体名,自取
{
member-list; //成员变量
}variable-list; //该结构体类型的变量(可省),分号不能丢
//若结构体放在函数之外,则表示全局声明,后面跟的变量也是全局变量
//第二种写法->匿名结构体类型
struct //结构体名字被省略
{
member-list; //成员变量
}variable-list; //该结构体类型的变量(不可省),分号不能丢
//第二种只能在声明的同时创建变量,只能用一次
//且声明两次相同的结构体类型创建的变量,编译器仍然判断这两个变量为不同类型
//第三种->结构体名的重定义
typedef struct tag //struct是结构体关键字。tag是结构体名,自取
{
member-list; //成员变量
}Node; //此时Node就是重命名后的名称
//以后定义该结构体类型的变量可以这样写->Node n1;
//类似的,下面实现对指针类型的结构体变量命名
typedef struct tag //struct是结构体关键字。tag是结构体名,自取
{
member-list; //成员变量
}* Linklist;
举例:
struct Node{
int date;
struct Node* next;
//不能在结构体类型内定义自身结构体变量,否则sizeof()无法计算大小
};
举例:
//方式1
struct Node(
int num1;
int num2;
)p1 = {1,2};
//方式2
struct Node(
int num1;
int num2;
);
struct Str(
char str[20];
int num2;
struct Node* node;
);
int mian(){
struct Node s1 = {1,2};
struct Str s2 = {"zhangsan",23,{1,2}};
return 0;
}
初始化时使用“{}”包含成员变量,可不完全初始化
代码1:
struct S1
{
char c1; //1
int i; //4
char c2; //1
};
图解:
代码2:
struct S2
{
char c1; //1
char c2; //1
int i; //4
};
图解:
代码3:
struct S3
{
char c1;
struct S2 s2;
double d;
};
图解:
存在内存对齐的原因:
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总体来说:
结构体的内存对齐是拿空间来换取时间的做法。
由S1和S2可得到结论:让占用空间小的成员尽量集中在一起,可以既满足对齐,又节省空间
需要#pragma预处理指令
举例:
#include
#pragma pack(8) //设置默认对齐数为8
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack() //取消设置的默认对齐数,还原为默认,之前设置的只对S1有影响
#pragma pack(1) //设置默认对齐数为8
struct S2
{
char c1;
int i;
char c2;
};
#pragma pack() //取消设置的默认对齐数,还原为默认,之前设置的默认对齐数1只对S2有影响
结构体传参包括值传参和地址传参
举例:
struct S
{
int data[1000];
int num;
};
struct S s1 = {{0},1000};
void Print1(strcut S s)
{
printf("%d\n",s.num);
}
void Print2(struct S* s)
{
printf("%d\n",s->num);
}
int main(){
//方式1,值传递,参数过大时压栈并赋值临时变量既浪费空间,也浪费时间,性能下降
Print1(s1);
//方式二,地址传递,传递时只会传递一个地址,压栈时系统开销相对不大,所以传参时首选地址传递
Print2(&s1);
return 0;
}
相对节省空间的操作
位段的声明和结构体是类似的,有两个不同:
举例:
struct A //A就是一个位段类型
{
int a:2; //只给a分配两个bit
int b:5; //b分配五个bit
int c:10; //10个bit
int d:30; //30个bit
};
在VS2019编译器x86环境下中,位段A的大小为八个字节,64个bit
举例:
struct A
{
char a:3; //给a分配3个bit
char b:4; //b分配4个bit
char c:5; //5个bit
char d:4; //4个bit
};
struct A s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
分析(VS2019平台):
通过查看内存显示证明:
总结:跟结构相比,位段可以达到相同的效果,而且可以很好的节省空间,但是有跨平台的问题存在
将有限较少的可能的全部取值一一列举
举例:
enum Day //星期
{
Mon, //注意都是逗号
Tues,
Wed,
Thur,
Fri,
Sat,
Sun //最后一个元素不用加逗号
};
//本质上还是一种类型,只有定义出变量后才会分配空间
//经笔者验证,这种写法需要将定义放在需要它的文件内
int main(){
enum Day d = Fri; //创建enum Day变量并进行赋值时,赋值只能选择以上七个选项
return 0;
}
例如:
enum Day //星期
{
Mon = 1, //表示此时从1开始递增
Tues, //其他取值也可任意赋值,后续的值都是从前面最后一次赋值依次递增
Wed,
Thur,
Fri,
Sat,
Sun
};
int main(){
enum Day d = Fri; //此时Fri = 5
return 0;
}
比如代码运行在菜单界面后用户做出选择,代码编写时用枚举中的可能取值代替数字,可以让程序员逻辑更清晰
#define定义的标识符无符号,枚举定义出来的就是枚举类型,只能用枚举类型中的可能取值进行赋值,而不能用数字等等(C语言或许语法要求不严格,.c不报错,C++严格,.cpp会报错)
但枚举类型可以在调试中看到
联合也是一种特殊的自定义类型
这种类型定义的变量也包含一系列的成员,特征是这些成员共用同一块空间(所以联合也叫共用体)
举例:
#include
//联合类型的声明
union Un
{
char c;
int i;
};
//两个成员不能同时用
int main() {
//联合类型的定义
union Un un;
//计算联合变量的大小
printf("%d\n", sizeof(un));
printf("%p\n", &un);
printf("%p\n", &un.c);
printf("%p\n", &un.i);
return 0;
}
运行结果:
原因:
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小
确认当前计算机大小端存储方法2:
//联合实现
int check_sys() {
union {
int i;
char ch;
}nn;
nn.i = 1;
return nn.ch;
}
int main() {
int ret = 0;
ret = check_sys();
if (ret) {
printf("小端\n");
}
else {
printf("大端\n");
}
return 0;
}
举例:
union Un {
char str[5]; //5
int i; //4
};
int main() {
union Un un;
printf("%d", sizeof(un));
return 0;
}
运行结果:
原因: