当前位置: 首页 > news >正文

自定义类型:结构体

目录

一.结构体类型

1.1 结构体的定义

1.2 结构体的声明 

1.3 结构体的特殊声明 

1.4 结构体的自引用 

二.结构体内存对齐

2.1 对齐规则

2.2 练习 

练习1

练习2 

练习3 

练习4 

2.3 修改默认对齐数

三.结构体传参 

四.结构体实现位段 

4.1 位段的定义 

4.2 位段的内存分布 

后记


一.结构体类型

1.1 结构体的定义

结构体的定义使用struct关键字,后跟结构体名和结构体成员列表(成员之间用分号分隔)。结构体成员可以是基本数据类型(如intfloat等),也可以是其他结构体类型,甚至是数组或指针。 

在这个例子中,我们定义了一个名为Student的结构体,它有三个成员:id(整型,用于存储学生的ID)、name(字符数组,用于存储学生的名字)、score(浮点型,用于存储学生的分数)。 

1.2 结构体的声明 

这里声明了两个Student类型的变量stu1stu2。 

1.3 结构体的特殊声明 

在声明结构的时候,可以不完全的声明。 

那么问题来了,在上面代码的基础上,下面的代码合法吗?

 警告:

编译器会把上面的两个声明当成完全不同的两个类型,所以是非法的。

匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使用⼀次。

1.4 结构体的自引用 

在结构中包含⼀个类型为该结构本身的成员是否可以呢?

比如如下代码: 

这个代码正确与否呢?如果它是正确的,那么sizeof(struct Node) 是多少? 

仔细分析,其实是不行的,因为⼀个结构体中再包含⼀个同类型的结构体变量,这样结构体变量的大小就会无穷的大,是不合理的。 

正确的自引用方式: 

 在结构体自引用使用的过程中,夹杂了 typedef 对匿名结构体类型重命名,也容易引入问题,看看 下面的代码,可行吗?

答案是不行的,因为Node是对前面的匿名结构体类型的重命名产生的,但是在匿名结构体内部提前使用Node类型来创建成员变量,这是不行的。 

解决方案如下:定义结构体不要使用匿名结构体了 

写法1:

写法2:

那么结构体的自引用有什么作用呢?

结构体的自引用在编程中,特别是在实现复杂数据结构时,具有非常重要的作用。

比如:构建链表,构建树,图的表示,递归数据结构的表示,实现高级数据结构等

这些内容等我学习了数据结构后再来说明。(本人很菜,还在学习中) 

二.结构体内存对齐

2.1 对齐规则

首先得掌握结构体的对齐规则:

1. 结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处

2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处 对齐数 = 编译器默认的⼀个 对齐数该成员变量大小较小值

- VS 中默认的值为 8

- Linux中 gcc 没有默认对齐数,对齐数就是成员自身的大小。

3. 结构体总大小为最大对齐数(结构体中每个成员变量都有⼀个对齐数,所有对齐数中最大的)的 整数倍

4. 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构 体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。

2.2 练习 

下面我们通过一些题目来练习对于结构体内存对齐规则的掌握

练习1

为什么会是这个结果呢?下面我们通过画图的方式来进行分析:

练习2 

练习3 

 

练习4 

 

由练习3可知,S3结构体所占内存的字节数为16,该代码将S3嵌套在S4中,则S4结构体成员所占的最大字节数为16,根据对齐规则4中:如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。可知,该结构体所占在内存的大小为16的整数倍,即为32

2.3 修改默认对齐数

#pragma 这个预处理指令,可以改变编译器的默认对齐数。 

 

 

结构体在对齐方式不合适的时候,我们可以自己更改默认对齐数。 

三.结构体传参 

 下面我们来看一组代码

struct S
{int data[1000];int num;
};
void print1(struct S ss)
{int i = 0;for (i = 0; i < 3; i++){printf("%d ", ss.data[i]);}printf("%d\n", ss.num);
}
void print2(const struct S* ps)
{int i = 0;for (i = 0; i < 3; i++){printf("%d ", ps->data[i]);}printf("%d\n", ps->num);
}
int main()
{struct S s = { {1,2,3},100 };print1(s);//传值调用print2(&s);//传址调用return 0;
}

让我们来思考一个问题,上面的 print1 和 print2 函数哪个好些?

答案是:首选print2函数。

原因: 函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。 如果传递⼀个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。 

结论: 结构体传参的时候,要传结构体的地址。 

四.结构体实现位段 

4.1 位段的定义 

位段的声明和结构体是类似的,有两个不同:
1.位段的成员必须是int ,unsigned int 或 signed int
2.位段的成员名后边有一个冒号和一个数字 

struct S
{int _a : 2;int _b : 5;int _c : 10;int _d : 30;
};

 S就是⼀个位段类型。 那位段S所占内存的大小是多少?

 

 

 通过验证,我们知道了位段S所占的内存字节数是8

那么8是如何产生的呢,下面我们来研究位段的内存分布

4.2 位段的内存分布 

1. 位段的成员可以是 intunsigned intsigned int 或者是 char 等类型

2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。

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

下面我们来举个例子:

 

这个位段占几个字节呢? 

 解析如下:

 

位段成员冒号后面的数字就是它申请的比特位

1个字节是8个比特位

若是两个或多个位段成员申请的比特位加起来不超过8,那么它们就能共用同一个字节

若是超过8,那么超过8的位段成员继续和下一个位段成员申请的比特位相加,不超过8则申请1个字节共用,超过8则单独申请1个字节

位段成员存储数据时,存储的数字要以二进制的形式存储在位段成员所申请的比特位中,所以应该确保申请的空间能够存储得下要存储的数字。

后记

复课了复课了,冲冲冲!!!

喜欢这篇文章的小伙伴点点赞,点点关注哈,谢谢各位大佬!

共勉!!! 

 


http://www.mrgr.cn/news/23706.html

相关文章:

  • 多个微信是怎么进行管理的?
  • Notepad++ 修改 About
  • llvm使用
  • 蚂蚁数科,独行的170天和未来新征程
  • 第二百二十二节 JPA教程 - JPA合并示例
  • 所有企业都需要的6种市场营销代理报告
  • Visual studio 2022中配置c++版本的opencv
  • 乐观锁悲观锁
  • jina-embeddings 的使用教程,怎么用它做embeddings和rerank的操作呢?
  • 并发编程:AQS(下)
  • 2024年10款成名已久的企业防泄密软件,企业文件加密防泄密必备
  • python简单处理nmap的扫描结果
  • 报名啦|PolarDB数据库创新设计赛(天池杯)等你来战
  • 【C++11 ——— 可变参数模板】
  • Excel怎样计算梯度费用,就拿电费来举例计算
  • HarmonyOS开发之路由跳转
  • 动态代理IP池设计:打造高效网络工具
  • 苹果iOS/ iPadOS18 RC 版、17.7 RC版更新发布
  • 再创辉煌!望繁信科技斩获第十三届中国创新创业大赛四川赛区桂冠
  • Python进阶——使用python操作数据库!