文章目录
前言
古有刘备、关羽、张飞桃园三结义
现有结构、枚举、联合桃猿三结义
一、结构体
💦 什么是结构体
🔑 官方来说结构体就是一些值的集合,这些值称为成员变量。结构体的每个成员可以是不同类型的变量。说到集合,数组也是集合,但是不同的是数组只能是相同类型元素的集合
💦 结构体类型的声明
------------------------结构体声明样式------------------------
struct tag
{
member1;
member2;
} variable-list;
------------------------解释------------------------
▶ struct是结构体关键字
▶ tag是结构体的标签名,是自定义的
▶ struct tag就是结构体类型
▶ {}里面放的是成员列表
▶ variable-list是变量
struct Book { char name[20]; int price; char id[]; }b4, b5, b6;//2、创建结构体变量 int main() { //1、创建结构体变量 struct Book b1; struct Book b2; struct Book b3; return 0; }
📝 分析:
这里定义了一个结构体类型struct Book,再使用类型创建变量 (两种方法创建结构体变量):
相同的是:
▶ 它们的类型是相同的,都是struct Book
不同的是:
▶ 在main函数内创建的变量b1,b2,b3是局部变量
▶ 在main函数外创建的变量b4,b5,b6是全局变量
------------------------特殊的结构体声明样式------------------------
struct
{
char a;
int b;
double c;
} s;
------------------------解释------------------------
▶ 这个结构体没有标签名tag
▶ 在声明结构体的时候,可以不完全声明
▶ 这种类型的结构体叫做匿名结构体
▶ 使用匿名结构体直接创建变量s
struct { char a; int b; double c; } s; struct { char a; int b; double c; } *ps;//使用匿名结构体类型创建一个变量,这个变量是一个指针 int main() { ps = &s;//? return 0; }
❓❔ 两个相同的匿名结构体类型去创建变量 s 和 *ps ,问这里的 ps = &s; 是合法的吗
📐 验证:
📝分析:
▶ 在编译器看来,虽然结构体的成员是一样的,但是它会认为 s 和 *ps 是两个不同的类型,所以是非法的
▶ 所以可以试想一下,使用匿名结构体去创建变量时,只能用一次
💦 结构体的自引用
❓❔ 结构体成员包含该结构体创建的结构体变量(非指针)
struct N { int a; struct N n;//? }; int main() { struct N n; return 0; }
📝 分析:
假设这种写法是可行的,那么使用struct N去创建一个变量n,请问n的大小是多大?细想一下,你搁这卡bug呢? 这不是无限套娃吗?😵😵
📐 验证:
语法都不支持
✔ 但是结构体成员可以包含其它结构体创建的结构体变量(嵌套结构体)
struct U { int b; int c; }; struct N { int a; struct U n;//? }; int main() { struct N n; return 0; }
🧿 拓展
❓❔ 什么是数据结构
数据结构指的是数据在内存中存储结构
🧷 举例:如果要存储1 2 3 4 5
🔎 这里主要了解链表:
💦 结构体变量的定义和初始化
🎗 创建局部/全局结构体变量并初始化
struct Book { char name[20]; int price; char id[]; }b4, b5, b6; int main() { //使用局部变量初始化 struct Book b1 = { "CSDN", 38, "202306030033" }; //使用全局变量初始化 struct Book b4 = { "C语言", 40, "2451176292" }; return 0; }
🎗 嵌套结构体的初始化和成员访问
struct Stu { char name[20]; int age; }; struct Book { char name[20]; int price; char id[20]; struct Stu s; }; int main() { //初始化 struct Book b = { "C语言结构体", 40, "133927471", { "小明", 20 } }; //访问成员 //使用. printf("%s %d %s\n%s %d\n", b.name, b.price, b.id, b.s.name, b.s.age); //使用-> struct Book* ps = &b;//定义一个结构体类型的指针指向b的地址 printf("%s %d %s\n%s %d\n", ps -> name, ps -> price, ps -> id, ps -> s.name, ps -> s.age); return 0; }
💦 结构体内存对齐
❓❔ 一个结构体占多大字节呢,是不是直接把一个结构体里每个成员的类型大小加起来呢
#include<stdio.h> struct S { int i; char c; }; int main() { struct S s = { 0 }; printf("%d\n", sizeof(s)); return 0; }
❓❔ 假设结构体的大小 = 结构体里每个成员大小之和,那么这里的结果是5个字节
📐 验证:
✔ 说明对于结构体是如何计算大小有它自己的规则
⚠ 这个规则就是结构体内存对齐
▶ 第1个成员在与结构体变量偏移量为0的地址处
▶ 其它成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数 = 编译器默认的一个对齐数与该成员大小的较小值。
VS中默认对齐数是8; Linux没有默认对齐数,它是按照自身大小来对齐的
▶ 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
▶ 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
⚠ 为什么会存在内存对齐
注:大部分参考资料是这样说的(没有官方具体的说法):
▶ 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
▶ 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问
▶ 总体来说:结构体的内存对齐就是拿空间换取时间的做法
人话:
▶ 假设某些硬件平台规定只能在4的倍数地址处去访问数据,那么未来在储存时就必须存储到指 定的位置上,这样才能被访问,所以在存储数据时最好能内存对齐
▶ 对于内存对齐:b的访问只需要一次;对于非内存对齐b的访问需要二次
❓❔ 在设计结构体时,如何做到既要满足对齐,又要节省空间
#include<stdio.h> struct S1 { char c1; int i; char c2; }; struct S2 { char c1; char c2; int i; }; int main() { struct S1 s1 = { 0 }; struct S2 s2 = { 0 }; printf("%d\n", sizeof(s1));//12 printf("%d\n", sizeof(s2));//8 return 0; }
📝 小结:
s1和s2的类型成员一模一样,但是s1和s2的大小不同。发现让占用空间小的成员尽量集中在一起有助于节省空间
❓❔ 前面有说到VS的默认对齐数是8,能不能自己调整呢
#include<stdio.h> //默认对齐数是8 struct S1 { char c1;//0 //1-3 int i;//4-7 char c2;//8 //9-11 }; //修改默认对齐数为2 #pragma pack(2)//始 struct S2 { char c1;//0 //1 int i;//2-5 char c2;//6 //7 }; #pragma pack() int main() { printf("%d\n", sizeof(struct S1));//12 printf("%d\n", sizeof(struct S2));//8 return 0; }
📝 小结:
结构体在对齐方式不合适的时候,那么我们可以自己调整默认对齐数