【C语言】自定义类型之---结构体超详解(结构体的定义使用、指针结构体,内存对齐,......代码详解)


目录

前言:

一:结构体

1.1:什么是结构体?

1.2:结构体类型的声明

1.3:结构体变量的定义

1.4:结构体的内存对齐

1.5:结构体传参

二:位段

2.1:位段是什么?

2.2:位段的内存分配

2.3:位段在vs编译器上内存的分配和使用


前言:

        今天分享的内容是C语言中自定义类型之一的结构体。在C语言中我们知道有很多种数据类型,如 int ,char,float 等,但是我们处于社会中,那么社会中的东西能用数据来表示吗?比如,一本书能用 int 或者 char 类型所表示吗?答案是不能的。因为一本书既包含有书名,还包含有作者名,单价和出版社等信息,那么此时单纯的 int ,char 等数据类型就行不通了,这时就得根据自己的需要来自定义一种结构体来描述这本书,其中以书名,单价,出版社等表示结构体的成员列表。可见自定义结构体给我们很大的遐想空间,表述万事万物。接下来我们一起看看结构体有哪些独特的魅力。

一:结构体

1.1:什么是结构体?

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

1.2:结构体类型的声明

声明:

struct tag       // struct(关键字),tag是自定义事物的名称
{mem_list;    // 成员列表,可以一个/多个
}variable_list;  // 变量列表

1.2.1:普通声明

例如描述一个学生:

//结构体定义一个学生
struct Student
{char name[20];	// 学生姓名char sid[20];	// 学生学号char sex[5];    // 学生性别int age;		// 学生年龄
}stu1,stu2;    //注意有分号,此时创建了两个struct Student类型的变量stu1和stu2.
// stu1 和 stu2 是全局变量int main()
{struct Student stu3,stu4;    //创建了两个struct Student类型的全局变量。reutrn 0;
}

此处的 stu1和stu2是全局变量,是在声明结构体的时候顺带创建的,当然也可以不顺带创建。

//结构体定义一个学生
struct Student
{char name[20];	// 学生姓名char sid[20];	// 学生学号char sex[5];    // 学生性别int age;		// 学生年龄
};

1.2.2:特殊声明

在声明结构体的时候,可以不完全的声明,被称为匿名结构体类型。

例如:

// 匿名结构体
struct 
{char name[20];int age;
}x;    //注意:在创建匿名结构体变量的时候,只能在这里创建(x)int main()
{return 0;
}

 但是这种结构在声明的时候已经省略了结构体标签(tag),这种结构体变量只能在定义的时候创建,并且这种结构体类型只能在声明的时候用一次(只能声明一次)。

为什么只能用一次?

// 结构体1
struct 
{int a;char b;double c;
}x;// 结构体2
struct
{int a;char b;double c;
}*p;int main()
{p = &x;return 0;
}

发现运行报错:

发现 结构体指针变量p与&结构体x的类型不匹配。 

我们知道:

int a = 10;
int* p = &a;

 所以 结构体1和结构体2的类型是不同的(即类型不匹配),故此这种匿名结构体在程序中只能使用一次。

1.2.3:结构体的自应用

在结构中包含一个类型为该结构本身的成员是否可以呢?就如同数据结构之中的链表一样:

struct Node
{int data;struct Node next;
};

 不过这样的结构体形式是否正确呢?答案是错误,这是因为在这一结构体中包含有一个结构体,这样的结构体在实现的时候会一直进行下去,永远没有尽头,也就是说它只有进没有出结构体的条件。那该怎样实现结构体的自引用呢?

正确形式:

struct Node
{int data;            // 数据域struct Node* next;   // 指针域,声明一个同类型的指针
};

就是说:将一个结构体通过其内的结构体指针与另一个同类型的结构体相连接起来,之后继续连,连,连。即将多个同类型的结构体通过内部的结构体指针(next)相连接起来就是结构体的自引用。 

1.2.4:结构体的重命名

重命名的关键字:typedef

 我们发现结构体的类型写起来实在是太长了:

struct Student;
struct Node;
...

将它们重新命一下名,方便写些。

typedef struct Student
{char name[20];int age;
}Stu;    //此处的 Stu 不是全局变量了,而是此结构体被重命名之后的新名字int main()
{struct Student stu1;Stu stu2;return 0;
}

其中,变量 stu1 和变量 stu2 的类型是相同的。 

1.3:结构体变量的定义与初始化

1.3.1:声明类型的同时定义变量

struct point
{int x;int y;int z;
}p1;    // 在声明类型的同时定义结构体变量

1.3.2:先声明类型,再定义变量

// 先定义一个结构体
struct point
{int x;int y;int z;
};// 再定义一个结构体变量(全局变量)
struct point p2;    int main()
{struct point p3;  // 局部变量return 0;
}

1.3.3:结构体变量的初始化 

struct point
{int x;int y;int z;
}p1 = {1,2,3};    //对p1初始化struct point p2 = {2,3,4};   //对p2初始化 int main()
{struct point p3 = {3,4,5};     //对p3初始化return 0;
}

1.3.4:结构体嵌套结构体的定义与初始化

struct point
{int x;int y;int z;
};struct stu
{char name[20];int age;struct point a;	//结构体嵌套一个struct point类型的结构体
};int main()
{struct stu s1 = { "张三",13,{1,2,3} };    // 赋值初始化struct stu s2 = { "李四",20,{2,3,4} };printf("%-20s\t%d\t%d\t%d\t%d\n", s1.name, s1.age, s1.a.x, s1.a.y, s1.a.z);printf("%-20s\t%d\t%d\t%d\t%d\n", s2.name, s2.age, s2.a.x, s2.a.y, s2.a.z);return 0;
}

代码运行结果:

1.4:(*)结构体的内存对齐(*)

        当我们掌握了关于结构体的使用情况后,接下来我们来深思考虑一下结构体的大小有多大呢?

1.4.1:分析结构体内存大小(内存对齐)

先看一下下面两种结构体代码:

struct Stu1
{int a;char b;char c;
};struct Stu2
{char b;int a;char c;
};int main()
{printf("sizeof(struct Stu1) = %d\n", sizeof(struct Stu1));printf("sizeof(struct Stu2) = %d\n", sizeof(struct Stu2));return 0;
}//运行结果:
sizeof(struct Stu1) = 8
sizeof(struct Stu2) = 12

 结果显示 struct Stu1类型的结构体大小是8个字节,而显示 struct Stu2类型的结构体大小是8个字节。为什么它们结构体内部的类型变量都相同,只是排序不同它们的大小就不同呢?这就涉及到结构体内部的内存对齐规则了。

结构体的对齐规则:

  1. 第一个成员在与结构体偏移量为0的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
  3. 对齐数=编译器默认的一个对齐数与该成员大小的较小值。
  4. VS编译器上的对齐数是8,gcc编译器和Linux编译器上没有默认对齐数,这两种编译器上对齐数就是其成员自身的大小。
  5. 结构体大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
  6. 若有嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

该如何计算结构体的大小呢?

结构体struct Stu1 内的成员变量一共占用了6个字节,因为结构体的内存对齐规则,需要2个最大对齐数才能将其存下。所以, 结构体struct Stu1的总大小为8个字节。

同理:结构体 struct Stu2 需要3个最大对齐数才能将其存下。所以, 结构体 struct Stu2 的总大小为12个字节。

1.4.2:为什么存在内存对齐

1. 平台原因(移植原因):

        不是所有的硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因:

        数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

结构体的内存对齐是拿空间来换取时间的做法。

设计一个优秀的结构体

让占用空间小的成员尽量集中在一起 。

1.4.3:修改默认对齐数

#pragma    // 预处理指令--用来改变默认对齐数

#include<stdio.h>#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() //取消设置的默认对齐数,还原为默认int main()
{printf("%d\n", sizeof(struct s1));printf("%d\n", sizeof(struct s2));return 0;
}// 运行结果:
12
6

1.5:结构体传参

#include<stdio.h>
struct Stu
{char name[20];int age;
};void Print1(struct Stu s)        // 传整个结构体
{printf("%s\n", s.name);
}void Print2(struct Stu* ps)    // 传结构体的指针
{printf("%s\n", ps->name);
}void test2()
{struct Stu s = { "小明",12 };Print1(s);Print2(&s);
}

共有两种结构体传参的方法。

但是哪种方法更好一些呢?

首选Print2函数。因为:

1. 函数传参的时候,参数是需要压栈的,会有时间和空间上的系统开销。

2. 如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,故会导致性能的下降。

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

二:位段

2.1:位段是什么?

首先:位段的声明和结构体是类似的,但有两个不同:

1. 位段的成员类型必须是 int,unsigned int,signed int。

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

例如:

#include<stdio.h>
struct A
{int a : 2;int b : 5;int c : 10;int d : 30;
};//其中 A 就是一个位段类型。int main()
{printf("%d\n", sizeof(struct A));return 0;
}//运行结果:8

 A 就是一个位段类型,但是为什么运行结果是8呢?位段A内部有4个int类型的数据,不应该是4*4=16个字节吗,这就需要了解了解位段的内存分配问题来。

2.2:位段的内存分配

        首先我们应知道 ”位段“ 中的 ”位“ 是二进制位,所以,int a:2 表示给 a 分配 2个二进制位(bit位),同理:int b:5 表示给 b 分配 5个二进制位(bit位),int c:10 表示给 c 分配 10个二进制位(bit位),int d:30 表示给 d 分配 30个二进制位(bit位)。

位段的内存分配规则:

1. 位段的成员可以是 int,unsigned int,signed int,或者是 char(属于整形家族)类型

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

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

以 struct A 位段分析:

 因为位段的空间是按照以4个字节( int )或者 1个字节(char)的方式来开辟的。此时当4个字节不够的时候,需要再申请4个字节来存储,直到存储完毕。

看下列代码:

#include<stdio.h>
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;
}

内存分析:

我们可以知道此位段得到大小是3个字节(一次增加一个字节)。

 vs编译器中一个字节内部是按照从右往左的顺序,即从二进制低位向高位使用的。

 位段的跨平台问题:

1. int 位段被当成有符号数还是无符号数是不确定的。
2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

结论:

跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。 


总结:    

        今天,我们从结构体的声明与定义开始,学习了另外一种特殊声明(匿名结构体),又了解了结构体变量的定义与初始化是怎样描述的,接着学习了本章最最重要的结构体的内存分配对其问题,最后介绍了一种特殊结构体(位段),优点是比结构体更节省空间,但是也不要忘了它的局限性。

        以上的内容若是对大家起到帮助的话,大家可以动动小手点赞,评论+收藏。大家的支持就是我进步路上的最大动力!


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/12670.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024年【金属非金属矿山(露天矿山)安全管理人员】模拟考试题库及金属非金属矿山(露天矿山)安全管理人员作业模拟考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 金属非金属矿山&#xff08;露天矿山&#xff09;安全管理人员模拟考试题库参考答案及金属非金属矿山&#xff08;露天矿山&#xff09;安全管理人员考试试题解析是安全生产模拟考试一点通题库老师及金属非金属矿山&a…

Google I/O 2024 干货全解读:Gemini AI 横空出世,智能未来触手可及!

Google I/O 2024 干货全解读&#xff1a;Gemini AI 横空出世&#xff0c;智能未来触手可及&#xff01; 博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》…

面试前端随笔20240510

最近公司招聘前端开发人员有幸参与帮听&#xff0c;总结了三个有关vue的面试问题和答案&#xff0c;现在分享一下。 1.Vue2数据监听无法监听数组为啥&#xff1f;有啥解决方案&#xff1f;vue3中是如何处理这个问题&#xff1f; vue2的官方说明了defineProperty的一些限制&…

Oracle SQL Developer 脚本输出中文显示乱码

问题描述 在测试Oracle Select AI&#xff08;自然语言查询数据库&#xff09;时&#xff0c;发现Run Statement中文显示正常&#xff1a; 而Run Script中文显示乱码&#xff1a; 问题解决 进入菜单Tools>Preferences...>Environment&#xff0c; 修改SQL Developer…

JavaScript-JSON对象

JSON格式 JSON&#xff08;JavaScript Object Notation, JS对象简谱&#xff09;是一种轻量级的数据交换格式。它基于ECMAScript&#xff08;European Computer Manufacturers Association, 欧洲计算机协会的一个子集&#xff0c;采用完全独立于编程语言的文本格式来存储和表示…

盘点那些年我们一起玩过的网络安全工具

一、反恶意代码软件 1.Malwarebytes 这是一个检测和删除恶意的软件&#xff0c;包括蠕虫&#xff0c;木马&#xff0c;后门&#xff0c;流氓&#xff0c;拨号器&#xff0c;间谍软件等等。快如闪电的扫描速度&#xff0c;具有隔离功能&#xff0c;并让您方便的恢复。包含额外…

项目中使用Elasticsearch的API相关介绍

项目中使用Elasticsearch的API相关介绍 0、域映射类型 text&#xff1a;会分词&#xff0c;不支持聚合对当前搜索关键词&#xff0c;先自身分词&#xff0c;分成多个词&#xff0c;然后去一个一个的词去利用倒排索引去查询es索引库一般应用在搜索关键字匹配的字段的类型。 商…

Beego 使用教程 6:Web 输入处理

beego 是一个用于Go编程语言的开源、高性能的 web 框架 beego 被用于在Go语言中企业应用程序的快速开发&#xff0c;包括RESTful API、web应用程序和后端服务。它的灵感来源于Tornado&#xff0c; Sinatra 和 Flask beego 官网&#xff1a;http://beego.gocn.vip/ 上面的 bee…

Spring的监听器使用(实用,直接拿去修改可用)

一&#xff0c;前言 这里我们以ApplicationListener为例&#xff0c;简单说明一下监听器如何使用。 本人基本只输出实用&#xff0c;即用的代码&#xff0c;希望能帮助到各位&#xff0c;如果想研究底层逻辑&#xff0c;大家可自行根据代码去类源码查看。 监听器的使用主要分…

上层建筑(理解)

上层建筑(Superstructure)是指建立在一定经济基础上的社会意识形态以及与之相适应的政治法律制度和设施等的总和。它包括阶级关系&#xff08;基础关系&#xff09;、维护这种关系的国家机器、社会意识形态以及相应政治法律制度、组织和设施等。 上层建筑与经济基础对立统一。建…

相机模型,坐标变换,畸变

小孔成像模型 墨子就记录了小孔成像是倒立的。这从几何光学的角度是很好理解的&#xff1a;光沿直线传播&#xff0c;上方和下方的光线交叉&#xff0c;导致在成像平面位置互换。 小孔的大小有什么影响&#xff1f; 小孔越大&#xff0c;进光量变大了&#xff0c;但是成像平…

第二步 完善MBR

文章目录 前言一、什么是MBR&#xff1f;二、我们需要什么样的MBR&#xff1f;三、设计我们的MBR&#xff01;1、打印“1 MBR”2、加载次引导程序——loader 四、实践检验&#xff01; 查看系列文章点这里&#xff1a; 操作系统真象还原 前言 在上一篇文章 第一步 从启动BIOS开…

社交电商的三大模式,新零售招商模式策划

链动21奖励模式&#xff0c;七人拼团模式拆解&#xff0c;分享购模式解析 坐标&#xff1a;厦门&#xff0c;我是易创客肖琳 深耕社交新零售行业10年&#xff0c;主要提供新零售系统工具及顶层商业模式设计、全案策划运营陪跑等。 随着数字时代的到来&#xff0c;“互联网”概…

PyCharm2023 社区版安装 +中文语言包+配置教程+Python环境搭建

一、Python 安装 我们在安装Pycharm之前&#xff0c;首先要先安装Python环境也就是安装Python解释器 因为PyCharm是一个用于编写和调试Python代码的开发工具&#xff0c;而Python解释器是用于解释执行Python代码PyCharm需要依赖Python解释器来执行Python代码&#xff0c;因此…

R语言贝叶斯方法在生态环境领域中的应用

贝叶斯统计已经被广泛应用到物理学、生态学、心理学、计算机、哲学等各个学术领域&#xff0c;其火爆程度已经跨越了学术圈&#xff0c;如促使其自成统计江湖一派的贝叶斯定理在热播美剧《The Big Bang Theory》中都要秀一把。贝叶斯统计学即贝叶斯学派是一门基本思想与传统基于…

R语言基础--文件读写

From生物技能树&#xff08;R第五节&#xff09; 文章目录 一、文件读写1.注意用project管理工作目录2、文件读取1、读取.txt文件2、读取.csv文件注意&#xff1a;数据框不允许重复的行名 3.数据框的导出4.读取文件的其他方式(用于读取/导出文件的R包)--经验1.base2.readr3.dat…

图搜索算法-最小生成树问题-克鲁斯卡尔算法(kruskal)

相关文章&#xff1a; 数据结构–图的概念 图搜索算法 - 深度优先搜索法&#xff08;DFS&#xff09; 图搜索算法 - 广度优先搜索法&#xff08;BFS&#xff09; 图搜索算法 - 拓扑排序 图搜索算法-最短路径算法-戴克斯特拉算法 图搜索算法-最短路径算法-贝尔曼-福特算法 最小生…

【Redis】数据类型

Redis数据类型&#xff08;5 3 1&#xff09; 五种基本数据类型 String字符串 特点 二进制安全&#xff0c;可以包含任何数据&#xff0c;如数字&#xff0c;字符串&#xff0c;jpg图片或者序列化的对象 应用场景 缓存&#xff1a; redis作为缓存层&#xff0c;mysql做持…

【ORACLE战报】2024.4月最新OCP考试喜报.

课程介绍 DBA数据库管理必备认证&#xff1a;ORACLE OCP 19C 教材下载 ORACLE OCP 19C 官方电子教材 ORACLE OCP 12C官方电子教材 题库下载 ORACLE 19C题库 &#xff08;083384题、082362题&#xff09;-2024答案修正版.rar 所有的收获都是默默耕耘的成果 2024.4月【最新考试成…

Chromium 调试指南2024 Windows11篇-条件断点、函数断点(十一)

1. 前言 在调试过程中&#xff0c;步进代码和条件断点/函数断点是非常有用的工具和技术&#xff0c;它们可以帮助开发者更加精确地定位和解决问题。本文将介绍步进代码的常用工具以及条件断点/函数断点的设置方法&#xff0c;帮助开发者更加高效地进行调试工作。 2. 步进代码…