首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >自定义类型:结构体+枚举类型+联合体+(内存对齐原则)

自定义类型:结构体+枚举类型+联合体+(内存对齐原则)

作者头像
青衫哥
发布2023-03-31 09:03:37
发布2023-03-31 09:03:37
53200
代码可运行
举报
文章被收录于专栏:C++打怪之路C++打怪之路
运行总次数:0
代码可运行

结构体

定义

结构是一些值的集合,这些值成为成员变量。结构的每个成员可以是不同类型的变量。

结构的声明

🎄🎄格式:

struct  结构类型 { 成员变量; };

举例:

代码语言:javascript
代码运行次数:0
运行
复制
struct Stu
{
 char name[20];//名字
 int age;//年龄
 char sex[5];//性别
 char id[20];//学号
};//分号不能丢

当然,也可以不完全声明,也就是匿名结构,不给出结构的名字。

问题:

代码语言:javascript
代码运行次数:0
运行
复制
​
//匿名结构体类型
struct
{
 int a;
 char b;
 float c;
}x;
struct
{
 int a;
 char b;
 float c; 
}a[20], *p;

​

当两个结构都省略掉类型(标签)的时候,那么  p=&x 这个用法就是不对的。

因为编译器会把它们当做完全不同的两个类型,所以是非法的。

结构的自引用:

举例:

代码语言:javascript
代码运行次数:0
运行
复制
struct Node
{
    int data;
    struct Node* next;
}

记住是 类型+*,不能忽略掉*

问题:

代码语言:javascript
代码运行次数:0
运行
复制
typedef struct
{
 int data;

 Node* next; 
}Node;

这样是可以的吗,答案是不行的,应该写成这样

代码语言:javascript
代码运行次数:0
运行
复制
typedef struct Node
{
 int data;
 struct Node* next; 
}Node;

这里还没typedef定义完成就已经开始使用了,很明显是错误的。

结构体的定义

1,

 定义一个结构体时 逐个给定值 2, 在初始化的同时给定值。

3,

在定义结构体的时候在末尾顺便加上一个个体名.

🚀🚀结构体内存对齐(重点)

 光看文字很难理解,我们加上例题分析:

例题1:

🎄🎄 第一个成员变量从地址0处开始,vs中默认的对齐数是8,char类型大小是1,选择其中较小值作为对齐数,所以是1。而int类型的i的对齐数是4(类型大小或者默认对齐数的较小值),要对齐到对齐数的整数倍开始,也就是从4开始存储了,也就是最后存储完对齐的地址是8,char同理算得对齐数是1,所以对齐到地址9地方。然后结构体总大小要是最大对齐数的倍数,也就是要是4的倍数,所以自动增到12为止。

 如图,最后对齐的大小就是12个字节了。

例题2:

 例题2算得16. 🎄🎄第一个double对齐数算得是8,所以占用内存0~7总共八个字节,第二个char类型对齐数是1,占用第八个字节, 最后一个int从12开始占用12-15四个字节,因为对齐原则,最终要对齐到最大的对齐数8的倍数,所以是16个字节。

例题3:

由这句话可以知道s3的对齐数是8,对齐大小是16个字节。  🎄🎄c1对齐第一个字节,然后s3对齐到8开始占用,占用8~23这16个字节,d的对齐数是8,从24开始占用8个字节大小。最终占用0~31这32个字节。

🛸🛸内存对齐这么复杂,为什么要有这东西呢?难道是为了给自己添加麻烦吗? 当然不是!

 首先,每个硬件平台不一定都可以随意访问任意地址上的数据,有些只能访问指定的地址处的数据,所以为了能够获取到需要的东西,设置了这个规则。

 其次,内存的获取是一次四个字节,像图中第二种方式,那么获取一个int型就要获取两次,而第一种只需要一次,在性能上大大改善了。 总的来说:结构体内存的对其就是拿空间换取时间的做法。

修改默认对齐参数

我们要用到 #pragma 这个预处理指令

如图,#pragma pack(8)开始, #pragma pack()结束,这中间的句子中,就是修改了默认对齐数的。其中第一个括号中的数字就是默认对齐数。修改的数字应该为2的次方数,例如:1,2,4,8……

位段

什么是位段?以一段代码为例:

代码语言:javascript
代码运行次数:0
运行
复制
struct A {
 int _a:2;
 int _b:5;
 int _c:10;
 int _d:30;
};

其中A就是一个位段类型。

位段与结构体是类似的,但有两个不同:

1.位段的成员必须是 int、unsigned int 或signed int 。

2.位段的成员名后边有一个冒号和一个数字。

🛸🛸位段的内存分配

1、位段的成员可以是 int unsigned、int、signed int 或者是 char(整形家族)类型。

2、位段的空间上是按照需要以四个字节或者一个字节(char)的方式来开辟的。

3、位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

例子:

代码语言:javascript
代码运行次数:0
运行
复制
//一个例子
struct S {
 char a:3;
 char b:4;
 char c:5;
 char d:4;
};
struct S s = {0};
s.a = 10; 
s.b = 12;
s.c = 3;
s.d = 4;

冒号后面的数字是规定使用的比特位大小。

那么空间是如何开辟的呢?

我们可以知道,10转化为二进制的代码是1010,可是 只能用3个比特位,所以取出010来存储,12二进制是1100,取用四个比特位,全部放进去,而存储的时候是四个。存储时,四个二进制为是一个十六进制位。按照这样计算,得到的数字是6、2、3、4。所以地址中的存储是,620304,也验证了我们的猜想,而且我们也可以知道,位段在一个字节中浪费的位置,下一个变量如果不够的话不会继续使用,而是开辟新的字节并在其中存储。

枚举类型

顾名思义,就是一一列举

枚举类型的定义

代码语言:javascript
代码运行次数:0
运行
复制
enum Day//星期
{
 Mon,
 Tues,
 Wed,
 Thur,
 Fri,
 Sat,
 Sun
};

其中定义的Day就是一个枚举类型,其中一一列举了一个星期七天。{}中的内容是枚举类型的可能取值,也叫枚举常量 。这些可能取值都是有默认值的,默认从0开始,逐个+1。当然也可以在最开始给定初值,但是在外面就不能改变了,因为这是一个常量。

🛸🛸枚举的好处

明明可以用 #define 代替,为什么还要用枚举呢?

1、增加代码的可读性和可维护性。

2、和 #define 定义的标识符相比,枚举有类型检查,更加的严谨。

3、为了防止命名污染(两个变量命名相同)。因为枚举类型是封装在一个类型中,是局部变量。

4、便于调试。#define是不可调试的,在预处理阶段就已经改变。

5、便于使用,一次可以定义多个常量。

联合(共用体)

联合也是一种特殊的自定义类型 这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。

代码语言:javascript
代码运行次数:0
运行
复制
//联合类型的声明
union Un
{
 char c;
 int i;
};
//联合变量的定义
union Un un;
//计算连个变量的大小
printf("%d\n", sizeof(un));

其中的c和i这两个变量是共用同一块内存的。un的大小为4。当然,不可能就是最大成员的大小,那样也过于简单了。

其中的变量同时只能用一个,因为会互相改变值.

依靠这个原理,我们又有了一种测试大小端的方法:

🎄🎄内存计算原则:        >=最大的变量大小

                                        是最大对齐数的倍数         

比如:        

 其中short s[7]的大小是14,对齐数是4,所以最后大小是对齐到16。

这里可以将数组看作是七个short类型的变量。

非常感谢各位的观看!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-07-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 结构体
    • 定义
    • 结构的声明
    • 结构的自引用:
    • 结构体的定义
    • 🚀🚀结构体内存对齐(重点)
      • 修改默认对齐参数
  • 位段
    • 🛸🛸位段的内存分配
  • 枚举类型
    • 枚举类型的定义
    • 🛸🛸枚举的好处
  • 联合(共用体)
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档