深圳龙岗网站建设,代理推广,Wordpress深入,网站建设基础教学设计#x1f307;个人主页#xff1a;_麦麦_ #x1f4da;今日名言#xff1a;生活不可能像你想象的那么好#xff0c;也不会像你想象的那么糟。——莫泊桑《羊脂球》 目录 一、前言
二、正文
1结构体
1.1结构体的基础知识
1.2结构的声明
1.3特殊的声明
1.4结构体变量的… 个人主页_麦麦_ 今日名言生活不可能像你想象的那么好也不会像你想象的那么糟。——莫泊桑《羊脂球》 目录 一、前言
二、正文
1结构体
1.1结构体的基础知识
1.2结构的声明
1.3特殊的声明
1.4结构体变量的定义和初始化
1.5结构的自引用 1.6结构体内存对齐
1.7修改默认对齐数
1.8结构体传参
2.位段
2.1什么是位段 2.2位段的内存分配 2.3位段的跨平台问题
2.4位段的应用
三、结语 一、前言 好久不见今天为小伙伴们带来C语言中有关结构体的详细知识干货满满图文并茂一定要看到底哦 二、正文
1结构体
1.1结构体的基础知识 结构是一些值的集合这些值称为成员变量结构的每个成员可以是不同类型的变量 1.2结构的声明 struct tag { member—list; } ; 注分号不能丢 //结构的声明演示描述一个学生
struct Stu
{char name[20]; //名字char sex[5]; //性别int age; //年龄char id[20]; //学号
}; 1.3特殊的声明 在声明结构的时候可以不完全声明【匿名结构体类型】由于匿名所以声明后就得在后面直接创建变量。 //匿名结构体类型
struct
{int a;char b;float c;
}x; //创建变量x 注哪怕两个匿名结构体中的内容完全一样也会被编译器当成两个完全不同的类型
//匿名结构体类型1
struct
{int a;char b;float c;
}x; //匿名结构体类型2
struct
{int a;char b;float c;
}*p;//非法操作
p x; 1.4结构体变量的定义和初始化 结构体变量的定义共分为三类 ①全局变量定义 ②局部变量定义 ③在结构的声明的同时定义变量 //结构体变量的三种定义方法
struct Stu
{char name[20]; //名字char sex[5]; //性别int age; //年龄char id[20]; //学号
}Stu1; //声明结构的同时定义变量struct Stu Stu2; //全局变量定义int main()
{struct Stu Stu3; //局部变量定义return 0;
} 结构体变量的初始化即定义变量的同时赋初值。不过结构体的初始化也分为正常的初始化和嵌套初始化。 //结构体变量的初始化
struct Peo
{char name[20]; //名字char sex[5]; //性别int age; //年龄
};struct Peo Peo1 { 陈书婷,女,35}; //结构体正常初始化struct Node
{char movie[20];struct Peo p;
}Peo2 { 狂飙,{高志强,男,40 }}; //结构体嵌套初始化struct Node Peo3 { 狂飙,{高启盛,男,28}}; //结构体嵌套初始化注结构体的初始化其实可以更加灵活——乱序按照自己想法来初始化。
struct Peo
{char name[20]; //名字char sex[5]; //性别int age; //年龄
};struct Peo Peo4 { .sex 男,.name 安欣,.age 30 }; 1.5结构的自引用 采取指针的形式 在结构中包含一个类型为该结构本身的成员 //自引用1错误示范
struct Node
{int date;struct Node next;
};//自引用2正确示范
struct Node
{int date;struct Node* next;
}; 1.6结构体内存对齐 在小伙伴们掌握了结构体的基本使用之后接下来让我们深入讨论一个问题如何计算结构体的大小为了解决这个问题我们就必须掌握结构体的对齐规则。 ● 第一个成员在与结构体变量偏移量为0的地址处
●其他成员变量要对齐到某个数字对齐数的整数倍的地址处 对齐数编译器默认的一个对齐数与该成员大小的较小值 ★VS中默认的值为8
●结构体总大小为最大对齐数每个成员变量都有一个对齐数的整数倍
●如果嵌套了结构体的情况嵌套的结构体对齐到自己的最大对齐数的整数倍处结构体的整体大小就是所有最大对齐数含嵌套结构体的对齐数的整数倍
#includestdio.h
//结构体内存对齐代码1
struct s1
{double d; //对齐数8char c; //对齐数1int i; //对齐数4
};//结构体内存对齐代码2
struct s2
{char c1; //对齐数1struct s1 s1; //对齐数8double d; //对齐数8
};int main()
{printf(%d, sizeof(struct s1)); //16printf(%d, sizeof(struct s2)); //32return 0;
} 结构体内存对齐代码1首先我们将d成员放入内存中由于它是第一个成员且对齐数为8所以从偏移量为0的地址一直放到偏移量为7的地址。其次是c成员它的对齐数为1因此可以放在偏移量为8的地址处最后是i成员它的对齐数是4但是偏移量为9的地址并不是对齐数4的倍数所以我们只好跳到偏移量为12的地址处直至偏移量为15的地址处才放下i成员。在将所有的成员放入后就是计算结构体s1的大小由于15并不是最大对齐数8的倍数所以结构体s1的大小为16个字节。 结构体内存对齐代码2首先我们将c1成员放入内存中由于它是第一个成员且对齐数为1所以放入偏移量为0的地址处。其次是s1成员该嵌套结构体内的最大对齐数为8因此跳到偏移量为8的地址处开始存放double类型成员直至偏移量为15的地址处才存放完毕。接着是char类型成员对齐数为1存放在偏移量为16的地址处。继而是整型成员对齐数为4从偏移量为20的地址存放直至偏移量为23的地址处存放完毕。最后是d成员对齐数为8从偏移量为24的地址存放直至偏移量为31的地址处存放结束。在将所有的成员放入后就是计算结构体s2的大小由于31并不是最大对齐数8的倍数所以结构体s2的大小为32个字节 最后总结一下计算结构体大小的步骤 ①计算出所有成员的对齐数并得出结构体的最大对齐数 ②根据每个成员的对齐数依次存放每个成员 ③所有成员存放完毕后依据结构体的最大对齐数得出结构体大小 那么如何证明我们对结构体成员的偏移量计算是否正确呢?C语言中提供了一个宏来计算结构体成员的偏移量无需掌握只是证明我们上述计算的思路无误
#includestddef.h
int main()
{printf(%d\n, offsetof(struct s1,d));printf(%d\n, offsetof(struct s1,c));printf(%d\n, offsetof(struct s1,i));printf(%d\n, offsetof(struct s2, c1));printf(%d\n, offsetof(struct s2, s1.d));printf(%d\n, offsetof(struct s2, s1.c));printf(%d\n, offsetof(struct s2, s1.i));printf(%d\n, offsetof(struct s2, d));return 0;
} 在了解完结构体的内存可能有的小伙伴会发出如下的疑问为什么会存在内存对齐呢这到底有什么用呢 大部分的参考资料都是如是说的 1.平台原因移植原因 不是所有的硬件平台都能任意访问任意地址上的任意数据的某些硬件平台只能在某些地址处取某些 特定类型的数据否则就会抛出硬件异常 2.性能原因 数据结构尤其是栈应该尽可能地在自然边界对齐。 原因在于为了访问未对齐的内存处理器需要作两次内存访问而对齐的内存访问仅需要一次访问。 总的来说结构体的内存对齐是拿空间来换取时间的做法 那么在设计结构体的时候我们既要满足对齐又要节省空间该如何做呢 让占用空间小的成员尽量集中在一起 1.7修改默认对齐数 那么默认对齐数可以修改吗答案是肯定的。在C语言中存在#pragma这个预处理指令通过这个我们就可以改变默认对齐数了。 //修改默认对齐数
#include stdio.h#pragma pack(2)
struct s1
{char c1;int i;char c2
};注如果将默认对齐数设置为1则不存在对齐效果。在平常的使用中小伙伴们一定要根据实际需求修改默认对齐数 1.8结构体传参 在之前的指针学习中我们了解到了传值调用和传址调用这两个概念那么在结构体传参时依旧存在以上两种方式那么那种方式是最优选择呢 //结构体传参
#include stdio.hstruct s
{int date[1000];int num;
};struct s s1 { {1,2,3,4},666 };void print1(struct s s1)
{printf(%d,s1.num);
}void print2(struct s* s1)
{printf(%d, s1-num);
}
int main()
{print1(s1); //传结构体print2(s1); //传地址
} 其实在结构体传参这一步中传结构体的地址是一个更好的选择。因为函数传参的时候参数是需要压栈的会有时间和空间上的系统开销。如果传递一个结构体对象的时候结构体过大参数压栈的系统开销比较大所以会导致性能的下降。 2.位段 在结构体讲完之后就要向小伙伴们介绍结构体实现位段的能力 2.1什么是位段 位段的声明和结构是类似的却能更加的节省空间但是有两个地方存在差异 ①位段的成员必须是int、unsigned int 或 signed int ②位段的成员名后边有一个冒号和数字【表示占几个二进制位】 #include stdio.h
struct A
{int _a : 2; //a只占2个二进制位int _b : 5; //b只占5个二进制位int _c : 10; //c只占10个二进制位int _d : 30; //d只占30个进制位
};int main()
{printf(%d\n, sizeof(struct A)); //打印为8个字节,是不是更节省空间了呢return 0;
} 2.2位段的内存分配 1.位段的成员可以是int 、unsigned int、signed int、或者是char属于整形家族类型 2.位段的空间上是按照需要以4个字节int或者1个字节char的方式来开辟的 3.位段设计很多不确定因素位段是不跨平台的注意可移植的程序应该避免位段 4.位段是不存在内存对齐的 注在不同的编译器中同一位段的大小也是不确定的接下来我们以VS2019的环境下解释上述位段的大小为何为8个字节。 在理解完位段在VS下的内存开辟那么内存是如何使用的呢在VS的环境下内存开辟后是从右向左使用的且为小端存储依旧以上面的代码为例 2.3位段的跨平台问题 ①int位段被当成有符号数还是无符号数是不确定的 ②位段中最大位的数目不能确定16位机器最大16,32位机器最大32写成27在16位机器会出现问题 ③位段中的成员在内存中从左向右分配还是从右向左分配标准尚未定义 ④当一个结构包含两个位段第二个位段成员比较大无法容纳与第一个位段剩余的位时是舍弃剩余的位还是利用这是不确定的 总结跟结构相比位段可以达到同样的效果但是可以很好地节省空间但是有跨平台的问题存在 2.4位段的应用 大家都知道彼此之间相互的交流看似简单其实文字交流的背后是大量的网络数据而位段的使用恰好可以对数据进行压缩减少网络的压力和负担。形象的来说我们可以把网络想象成高速公路如果上面全是未经压缩的数据也就都是大卡车的话就会十分拥挤。而如果采取位段的方式对没有必要使用的空间进行压缩就可以将大卡车变成小轿车从而缓解交通压力。 三、结语 关于结构体的讲解就已经全部结束了下期我们会继续分享自定义类型的其他成员 关注我 _麦麦_分享更多干货_麦麦_的博客_CSDN博客-领域博主 大家的「关注❤️ 点赞 收藏⭐」就是我创作的最大动力谢谢大家的支持我们下期见