【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;采用完全独立于编程语言的文本格式来存储和表示…

spring基于XML的声明式事务控制

在Spring框架中&#xff0c;基于XML的声明式事务控制是一种通过配置来管理事务的方式&#xff0c;而无需在代码中显式编写事务逻辑。以下是如何在Spring中使用XML来配置声明式事务控制的步骤&#xff1a; 添加必要的依赖 确保你的项目中包含了Spring框架的核心包和Spring事务…

赶紧收藏!2024 年最常见 100道 Java 基础面试题(四十八)

上一篇地址&#xff1a;赶紧收藏&#xff01;2024 年最常见 100道 Java 基础面试题&#xff08;四十七&#xff09;-CSDN博客 九十五、spring支持几种bean的作用域&#xff1f; Spring框架支持多种Bean的作用域&#xff0c;每种作用域都定义了Spring容器如何管理和创建Bean的…

论文:论面向服务的架构设计

题目 在面向服务的架构&#xff08;Service-Oriented Architecture&#xff0c;SOA&#xff09;中&#xff0c;服务的概念有了延伸&#xff0c;泛指系 统对外提供的功能集。例如&#xff0c;在一个大型企业内部&#xff0c;可能存在进销存、人事档案和财务等多个系统&#xff…

Java程序运行流程

Java运行流程 Java源代码 — 编译工具 —> Java字节码文件 — 运行工具 —> Java程序运行 Java程序的基本单位 类 Java程序执行入口 main方法

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

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

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

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

上海市计算机学会竞赛平台2020年8月月赛丙组促销骰子

题目描述 小爱的商店正在促销。顾客在付款的时候&#xff0c;有机会掷一次骰子&#xff0c;如果掷出 66&#xff0c;可以获得优惠&#xff0c;并且可以继续掷骰子&#xff0c;直到出现不是 66 的情况&#xff0c;或掷三次为止。获奖规则如下&#xff1a; 如果没有 66&#xf…

【C#】某AGV调度系统源码笔记(十二)

AGV调度服务的核心类库 小车事件参数 两个公共属性&#xff1a;车辆信息、事件描述。一个构造函数传入两个参数赋值给属性。 核心数据类 初始化及存储调度系统中所有的档案信息。 多个静态公共属性存储不同类型的系统数据&#xff1a;是否全局服务、数据库操作对象、系统参数集…

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

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

P2234 [HNOI2002] 营业额统计

题目描述 Tiger 最近被公司升任为营业部经理&#xff0c;他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况。 Tiger 拿出了公司的账本&#xff0c;账本上记录了公司成立以来每天的营业额。分析营业情况是一项相当复杂的工作。由于节假日&#xff0c;大…

对话机器人技术解说

一、RAG介绍 如何不通过微调模型来提高LLM性能&#xff0c;检索增强生成&#xff08;RAG&#xff09;是未来的发展方向。 Embedding&#xff1a;将文档的句子或单词块转换为数字向量。就向量之间的距离而言&#xff0c;彼此相似的句子应该很近&#xff0c;而不同的句子应该离…

react-native 渲染引擎经历了什么

React Native 的渲染引擎经历了多个迭代&#xff0c;不断优化和改进。以下是一些较为显著的迭代&#xff1a; 原生组件封装&#xff1a;最初的 React Native 版本是通过 JavaScript 渲染 UI&#xff0c;并通过桥接层将 UI 转化为原生组件。随着发展&#xff0c;React Native 开…

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

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

美易官方:收盘美股收涨纳指创历史新高,市场关注CPI通胀数据

​收盘之际&#xff0c;美股市场呈现出一派欣欣向荣的景象&#xff0c;各大指数纷纷收红&#xff0c;尤其是纳斯达克指数更是创下了历史新高&#xff0c;市场气氛热烈而积极。这一日的交易过程中&#xff0c;投资者们信心满满&#xff0c;积极寻觅着能够带来丰厚回报的投资机会…

上层建筑(理解)

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