修改默认对齐数
在之前我们就见过#pragma这个预处理指令,这里我们可以以使用#pragma来修改默认对齐数
#pragma pack(8)//设置默认对齐数为8 struct S1 { char c1; int i; char c2; }; #pragma pack()//取消设置的默认对齐数,还原为默认 #pragma pack(1)//设置默认对齐数为1 struct S2 { char c1; int i; char c2; }; #pragma pack()//取消设置的默认对齐数,还原为默认
总结:
结构在对齐方式不合适的时候,我们可以自己更改默认对齐数。
结构体传参
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; }
上面print1和print2函数哪个更好一些?
答案是:首选print2函数。
原因:
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。
总结:
结构体传参的时候,要穿结构体的地址。
😜位段😜
什么是位段
位段的声明和结构是类似的,有两个不同:
1.位段的成员必须是 int、unsigned int 或signed int 。
2.位段的成员名后边有一个冒号和一个数字。
比如:
//位段:位->二进制位 struct A { int _a : 2; int _b : 5; int _c : 10; int _d : 30; }; int main() { printf("%d\n", sizeof(struct A)); return 0; }
明明是四个int类型的变量为什么只占了四个字节的空间呢?
要想知道这个问题,我们就要先了解每个变量":"后面的数字代表的意思:它代表的是这个变量所占的二进制位数,比如2就代表_a占了两个比特位,5就代表_b占了五个比特位。那他内存中又是怎么样分配的呢?不要着急我们慢慢分析来看。
位段的内存分配
1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
我们来举个例子观察一下内存是如何开辟的>
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; }
位段的跨平台问题
1. int 位段被当成有符号数还是无符号数是不确定的。
2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
总结:
跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。
位段的应用
网络底层封装就是使用的位段。
🌝枚举🌝
枚举顾名思义就是——列举。
把可能的的取值——列举。
比如月份、一周的星期、性别:男,女,保密、等等。
枚举类型的定义
enum Sex//性别 { MALE, FEMALE, SECRET }; enum Day//星期 { Mon, Tues, Wed, Thur, Fri, Sat, Sun };
以上定义的 enum Day , enum Sex 都是枚举类型。
{}中的内容是枚举类型的可能取值,也叫 枚举常量 。
这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值。
例如:
enum Sex//性别 { MALE=1, FEMALE=2, SECRET=4 };
枚举的优点
为什么要使用枚举呢?
我们可以使用 #define 定义常量,为什么非要使用枚举?
枚举的优点:
1. 增加代码的可读性和可维护性
2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
3. 防止了命名污染(封装)
4. 便于调试
5. 使用方便,一次可以定义多个常量
枚举的使用
enum Color//颜色 { RED=1, GREEN=2, BLUE=4 }; enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异
🌝联合体(共用体)🌝
联合类型的定义
联合也是一种特殊的自定义类型
这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。
比如:
//联合类型的声明 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; }
画图理解一下内存中如何存储>
联合的特点
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)。
面试题:
判断当前机器的大小端。
这道题在我们之前的文章中有讲过,我们先用老方法来实现一下>
int main() { int a = 1;//0x 00 00 00 01 //低------------------------>高 //01 00 00 00小端 //00 00 00 01大端 if (*(char*)&a == 1) printf("小端\n"); else printf("大端\n"); return 0; }
接下来我们使用联合体来解决一下这个问题>
int main() { int a = 1;//0x 00 00 00 01 //低------------------------>高 //01 00 00 00小端 //00 00 00 01大端 union un { int i; char c; }un; un.i = 1; if (un.c == 1) printf("小端\n"); else printf("大端\n"); return 0; }
运行结果>
联合大小的计算
- 联合的大小至少是最大成员的大小。
- 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
比如:
union Un1 { char c[5]; int i; }; union Un2 { short c[7]; int i; }; int main() { printf("%d\n", sizeof(union Un1)); printf("%d\n", sizeof(union Un2)); return 0; }
🍀 小结🍀
今天我们学习了结构体、位段、枚举、联合体,相信大家看完有一定的收获。种一棵树的最好时间是十年前,其次是现在! 把握好当下,合理利用时间努力奋斗,相信大家一定会实现自己的目标!加油!创作不易,辛苦各位小伙伴们动动小手,三连一波💕💕~~~,本文中也有不足之处,欢迎各位随时私信点评指正!