🔥博客主页🔥:【 坊钰_CSDN博客 】
欢迎各位点赞👍评论✍收藏⭐
目录
1. 结构体
1.1 结构体定义
1.2 结构体的声明
1.3 结构体变量的定义和初始化
1.4 结构体的特殊声明->匿名声明
1.5 结构体的自应用
2. 结构体内存对齐
2.1 结构体内存对齐规则
2.1.1 偏移量
2.2 为什么存在内存对齐
2.3 自定义修改对齐数
3. 结构体传参
4. 联合体
4.1 联合体类型的声明
4.2 联合体的特点
4.3 相同成员变量结构体和联合体比较
4.4 联合体大小的计算
4.5 联合体判断大小端
5. 枚举
5.1 枚举类型的声明
5.2 枚举的优点
5.3 枚举的使用
6. 小结
1. 结构体
1.1 结构体定义
结构是⼀些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量,如:
标量、数组、指针,甚⾄是其他结构体
1.2 结构体的声明
struct tag
{
member-list;
}variable-list;
例:描述一个学生
struct Stu
{
char name[20]; //名字
int age; //年龄
char sex[5]; //性别
char id[20]; //学号
};
1.3 结构体变量的定义和初始化
//代码1:变量的定义
struct Point
{
int x;
int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
//代码2:初始化。
struct Point p3 = {10, 20};
struct Stu //类型声明
{
char name[15];//名字
int age; //年龄
};
struct Stu s1 = {"zhangsan", 20};//初始化
struct Stu s2 = {.age=20, .name="lisi"};//指定顺序初始化
//代码3
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化
1.4 结构体的特殊声明->匿名声明
在声明结构的时候,可以不完全的声明
//匿名结构体类型
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20], *p;
两个结构在声明的时候省略掉了结构体标签(tag);
但是要注意:
对于上面两个,下面代码是不合法的
p = &x
因为:
- 编译器会把上⾯的两个声明当成完全不同的两个类型,所以是⾮法的
- 匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使⽤⼀次
1.5 结构体的自应用
在结构中包含⼀个类型为该结构本⾝的成员是否可以呢?
例:
struct Node
{
int data;
struct Node next;
};
仔细分析,其实是不⾏的,因为⼀个结构体中再包含⼀个同类型的结构体变量,这样结构体变量的大小【sizeof(struct Node)】就会⽆穷的⼤,是不合理的;
正确的方式:
struct Node
{
int data;
struct Node* next;
};
应采用结构体指针的方法来自应用;
2. 结构体内存对齐
我们已经掌握了结构体的基本使用了
现在我们深⼊讨论⼀个问题:计算结构体的大小
这也是⼀个特别热门的考点:【 结构体内存对齐】
2.1 结构体内存对齐规则
计算时我们要掌握规则
2.1.1 偏移量
练习:
//练习1
struct S1
{
char c1;
int i;
char c2;
};
printf("%d\n", sizeof(struct S1));
//练习2
struct S2
{
char c1;
char c2;
int i;
};
printf("%d\n", sizeof(struct S2));
//练习3
struct S3
{
double d;
char c;
int i;
};
printf("%d\n", sizeof(struct S3));
//练习4-结构体嵌套问题
struct S4
{
char c1;
struct S3 s3;
double d;
};
printf("%d\n", sizeof(struct S4));
结果:
//练习1
----> 12字节
//练习2
----> 8字节
//练习3
----> 16字节
//练习4
----> 32字节
2.2 为什么存在内存对齐
1. 平台不同情况:不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定 类型的数据,否则抛出硬件异常
2. 电脑性能:数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于,为了访问未对⻬的内存,处理器需要 作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地 址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数,那么就可以 ⽤⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两 个8字节内存块中
总归一点:用空间来换取时间
那如何做到节省空间?我们可以这样写:
让占用空间小的成员尽量集中在⼀起
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
S1 和 S2 类型的成员⼀模⼀样,但是 S1 和 S2 所占空间的大小有了⼀些区别
2.3 自定义修改对齐数
【#pragma】 这个预处理指令,可以改变编译器的默认对⻬数
#include <stdio.h>
#pragma pack(1)//设置默认对⻬数为1
struct S
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的对⻬数,还原为默认
结果:
6字节
结构体在对齐方式不合适的时候,我们可以自己更改默认对齐数
3. 结构体传参
struct S
{
int data[1000];
int num;
};
struct S s = {{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
print1(s); //传结构体
print2(&s); //传地址
return 0;
}
上述函数print2更好,因为:
- 函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销
- 如果传递⼀个结构体对象的时候,结构体过⼤,参数压栈的的系统开销⽐较⼤,所以会导致性能的下降
总结:结构体传参的时候,要传结构体的地址
4. 联合体
4.1 联合体类型的声明
- 像结构体⼀样,联合体也是由⼀个或者多个成员构成,这些成员可以不同的类型
- 但是编译器只为最⼤的成员分配⾜够的内存空间
- 联合体的特点是所有成员共⽤同⼀块内存空间
- 所以联合体也叫:【共⽤体】
- 给联合体其中⼀个成员赋值,其他成员的值也跟着变化
#include <stdio.h>
//联合类型的声明
union Un
{
char c;
int i;
};
int main()
{
//联合变量的定义
union Un un = {0};
//计算连个变量的⼤⼩
printf("%d\n", sizeof(un));
return 0;
}
结果:
4字节
为什么?
4.2 联合体的特点
联合的成员是共⽤同⼀块内存空间的,这样⼀个联合变量的大小,⾄少是最⼤成员的大小(因为联合⾄少得有能⼒保存最⼤的那个成员)
#include <stdio.h>
//联合类型的声明
union Un
{
char c;
int i;
};
int main()
{
//联合变量的定义
union Un un = {0};
// 下⾯输出的结果是⼀样的吗?
printf("%p\n", &(un.i));
printf("%p\n", &(un.c));
printf("%p\n", &un);
return 0;
}
结果:
三个地址⼀模⼀样,就说明了联合体共用一个空间;
4.3 相同成员变量结构体和联合体比较
//代码1
struct S
{
char c;
int i;
};
struct S s = {0};
//代码2
union Un
{
char c;
int i;
};
union Un un = {0};
它们在内存中储存:
4.4 联合体大小的计算
- 联合的大小⾄少是最⼤成员的大小
- 当最⼤成员大小不是最⼤对齐数的整数倍的时候,就要对齐到最⼤对齐数的整数倍
计算下面两个联合体大小?
//代码1
union Un1
{
char c[5];
int i;
};
//代码2
union Un2
{
short c[7];
int i;
};
结果:
//代码1
----> 8字节
//代码2
----> 16字节
4.5 联合体判断大小端
int check_sys()
{
union
{
int i;
char c;
}un;
un.i = 1;
return un.c;//返回1是⼩端,返回0是⼤端
}
5. 枚举
5.1 枚举类型的声明
枚举顾名思义就是⼀⼀列举
把可能的取值⼀⼀列举
⽐如我们现实⽣活中:
- 三月有多少天------可一一列举
- 一个星期有多少天------可一一列举
- ....
enum Day//星期
{
Mon,
Tues, //记住是逗号
Wed,
Thur,
Fri,
Sat,
Sun
};
以上定义的 【enum Day 】是枚举类型
{..} 中的内容是枚举类型的可能取值,也叫枚举常量
这些可能取值都是有值的,默认从0开始,依次递增1,当然在声明枚举类型的时候也可以赋初值
enum Day//星期
{
Mon=2,
Tues=4, //记住是逗号
Wed=6,
Thur,
Fri,
Sat,
Sun
};
5.2 枚举的优点
- 增加代码的可读性和可维护性
- 和#define定义的标识符⽐较枚举有类型检查,更加严谨
- 便于调试,预处理阶段会删除 #define 定义的符号
- 使⽤⽅便,⼀次可以定义多个常量
- 枚举常量是遵循作⽤域规则的,枚举声明在函数内,只能在函数内使⽤
5.3 枚举的使用
光的三原色:红 绿 蓝
enum Color//颜⾊
{
RED=1,
GREEN=2,
BLUE=4
};
enum Color clr = GREEN;//使⽤枚举常量给枚举变量赋值
6. 小结
以上就是关于自定义类型的内容了,具体还需宝子们去实践,如果觉得该博客对你有用的话,希望一键三连,点个关注不迷路,谢谢支持!