桃猿三结义:结构枚举联合
Posted 跳动的bit
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了桃猿三结义:结构枚举联合相关的知识,希望对你有一定的参考价值。
文章目录
前言
古有刘备、关羽、张飞桃园三结义
现有结构、枚举、联合桃猿三结义
一、结构体
💦 什么是结构体
🔑 官方来说结构体就是一些值的集合,这些值称为成员变量。结构体的每个成员可以是不同类型的变量。说到集合,数组也是集合,但是不同的是数组只能是相同类型元素的集合
💦 结构体类型的声明
------------------------结构体声明样式------------------------
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;
}
📝 小结:
结构体在对齐方式不合适的时候,那么我们可以自己调整默认对齐数
💦 offsetof 宏
⭕ 函数原型和头
⭕ 函数的返回值
⭕ 函数的功能
#include<stdio.h>
#include<stddef.h>
struct S
{
char c1;
int i;
char c2;
};
int main()
{
printf("%d\\n", offsetof(struct S, c1);//0
printf("%d\\n", offsetof(struct S, i);//4
printf("%d\\n", offsetof(struct S, c2);//8
return 0;
}
🧿 百度笔试题:写一个宏,计算结构体中某变量相对于首地址的偏移,并给出说明
(注:在后面会了解并附上链接) [传送门正在维修]
💦 结构体传参
❓❔ 结构体传参可以传值也可以传址,问print1(传值)和print2(传址)哪种方式更好
#include<stdio.h>
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;
}
📝 分析:
从效率的角度来分析:
▶ 结构体传值:假设这个结构体足够大,传值时会拷贝一份一样大的结构体,这时会造成空间和时间上的浪费
▶ 结构体传址:不管这个结构体多大,它都是一个地址,无非就是4/8个字节
从功能的角度来分析(这是相对的):
▶ 结构体传值:假设我们希望在对结构体的内容做一些修改,那么传值就有一定的局限性
▶ 结构体传址:而传址可以有权限去对结构体的内容做一些修改;当然从某方面看,它又是不安全的,如果不希望修改其内容,我们可以加上const来限定
小结:
▶ 结构体传参时,要传结构体的地址
💦 结构体实现位段(位段的填充&可移植性)
❓❔ 什么是位段
🔑 位段的声明和结构是类似的
▶ 位段的成员必须是int、unsigned int 或 signed int (注:经测试位段的成员也能是char类型)
▶ 位段的成员名后面有一个冒号和一个数字
❓❔ 观察分析以下包含位段成员的结构体大小是多大
#include<stdio.h>
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
int main()
{
printf("%d\\n", sizeof(struct A));//8
return 0;
}
📝 分析:
int _a : 2; 代表_a这个变量占2个bit位
int _b : 5; 代表_b这个变量占5个bit位
…
且位段的空间是按照需要4个字节(int)或者1个字节(char)的方式来开辟的
📝 小结:
位段涉及很多不确定的因素,位段是不跨平台的,注重可移植程序应该避免使用位段
🔎 探讨位段里的内存数据是如何开辟的
struct S
{
char a : 3;
char b : 4;
char c : 5;
char d : 4;
};
int main()
{
struct S s = { 0 };
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
return 0;
}
🧷 假设在VS中:当一块空间剩余的空间不够下一个成员使用时,它会浪费掉。且先使用低位的内容再使用高位的内容
📝 小结:
经分析验证:在VS中位段的存储模式正如我们上述的假设(注意仅适用于VS)
⚠ 位段的跨平台问题
▶ int位段被当成有符号数还是无符号数是不确定的
▶ 位段中最大位的数目不能确定(假设是int位段,接着为a分配30个bit的空间 -> int _a : 30,这样写可能有问题,因为在16位平台下,一个int占16bit)
▶ 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义
▶ 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的
📝 小结:
▶ 位段可以很好的节省空间,但是有跨平台的问题
🔎 位段的应用
以上是一个IP数据包的格式:
数据以上的信息都是为了正确的传输信息,如果这些信息在传输时大小不以限制(比如4位版本号给个整型、4位首部长度给个整型…),🧑发了一百条变态给👦,虽然就两字,但是浪费的资源却是庞大的;如果使用位段(比如4位版本号给4位,4位首部长度给4位…),那么效率会大大的提高
二、枚举
💦 什么是枚举
🔑 官方来说:在数学和计算机科学理论中,一个集的枚举是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数。这两种类型经常(但不总是)重叠。
简单来说就是一一列举,把可能的取值一一列举
💦 枚举类型的声明和定义
------------------------枚举的声明样式------------------------
enum tag
{
possible values1,
possible values2,
possible values3
} variable-list;
------------------------解释------------------------
▶ enum是枚举关键字
▶ tag是枚举的标签名,是自定义的
▶ enum tag就是枚举类型
▶ {}里面放的是枚举可能取值,也叫枚举常量
▶ variable-list是变量
enum Day
{
Mon,
Tues,
Web,
Thur,
Fri,
Sat,
Sun
}a,b;//创建全局变量
int main()
{
//创建局部变量
enum Day d = Mon;
return 0;
}
🎗 上面说到枚举是常量,那么打印它们的值
#include<stdio.h>
enum Color
{
RED,
GREEN,
BLUE
};
int main()
{
printf("%d\\n", RED);//0
printf("%d\\n", GREEN);//1
printf("%d\\n", BLUE);//2
return 0;
}
❓❔既然枚举可能取值都有对应的常量值,那能否赋常量值呢
enum Color
{
RED,
GREEN,
BLUE
};
int main()
{
enum Color c = 2;//?
return 0;
}
📝 分析:
2是一个整型,而c是enum Color类型,所以是err。 发现在.c下执行代码并没有err,而.cpp中err
📝 小结:
这里只能说明C++对语法的检查更严格,虽然在C语言中并没有报错,但还是要避免这种写法
❓❔ 枚举常量值能否被修改,为什么常量能被修改
#include<stdio.h>
enum Color
{
RED = 5,
GREEN,
BLUE
};
int main()
{
printf("%d\\n", RED);//5
printf("%d\\n", GREEN);//6
printf("%d\\n", BLUE);//7
return 0;
}
📝 分析:
枚举常量值是可以修改的,且会按照最后修改的值往后递增。在定义枚举时是赋初值(默认是从0开始),当然出了枚举外部去修改时是err
💦 枚举的优点
❓❔ 我们可以使用#define定义常量,为什么还要使用枚举
#define RED 5
#define GREEN 8
#define BLUE 9
//==========================================
enum Color
{
RED = 5,
GREEN = 8,
BLUE //9
};
⚠ 注意:
▶ 增加代码的可读性和可维护性
▶ 和#define定义的标识符比较枚举有类型检查,更加严谨
▶ 防止命名污染(封装)
▶ 便于调试(而#define定义的常量在调试时不能看到标识符)
▶ 使用方便,一次可定义多个常量
➰ 对比
📝 小结:
▶ 代码1:可读性非常差(需要不断的向上翻阅代码来匹配功能)
▶ 代码2:可读性得到了改善(使用枚举)
💦 枚举的使用
enum Color
{
RED = 1
GREEN = 2,
BLUE = 4
};
enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异
三、联合体
💦什么是联合体
🔑 联合也是一种特殊的自定义类型,这种类型定义的变量也包含一系列成员,特征是这些成员公用同一块空间 (所以联合体也叫共用体)
💦 联合类型的声明和定义
------------------------联合的声明样式------------------------
union tag
{
member1;
member2;
}variable-list;
------------------------解释------------------------
▶ union是联合体关键字
▶ tag是联合标签名,是自定义的
▶ union tag就是联合类型
▶ {}里面放的是成员列表
▶ variable-list是变量
#include<stdio.h>
union Un
{
char c;
int i;
};
int main()
{
//定义联合体变量
union Un n;
printf("%d\\n", sizeof(n));//4
return 0;
}
💨 结果:
❓❔ 为什么是4呢,接下来我们就来探讨一下联合体的特点
💦 联合的特点
#include<stdio.h>
union Un
{
char c;
int i;
};
int main()
{
union Un n;
printf("%p\\n", &n);
printf("%p\\n", &n.c);
printf("%p\\n", &n.i);
return 0;
}以上是关于桃猿三结义:结构枚举联合的主要内容,如果未能解决你的问题,请参考以下文章