C语言进阶——自定义类型:结构体

news2025/6/4 0:58:42

🌇个人主页:_麦麦_

📚今日名言:生活不可能像你想象的那么好,也不会像你想象的那么糟。——莫泊桑《羊脂球》

目录

一、前言

二、正文

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

●结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍

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

#include<stdio.h>
//结构体内存对齐代码1
struct s1
{
	double d;	//对齐数8
	char c;		//对齐数1
	int i;		//对齐数4
};



//结构体内存对齐代码2
struct s2
{
	char c1;		//对齐数1
	struct s1 s1;	//对齐数8
	double d;		//对齐数8
};

int main()
{
	printf("%d", sizeof(struct s1));	//16
	printf("%d", sizeof(struct s2));	//32
	return 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语言中提供了一个宏来计算结构体成员的偏移量?(无需掌握,只是证明我们上述计算的思路无误)

#include<stddef.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.h>

struct 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博客-领域博主
        大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下期见!

 

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

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

相关文章

若依框架---数据转树状层级

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是小童&#xff0c;Java开发工程师&#xff0c;CSDN博客博主&#xff0c;Java领域新星创作者 &#x1f4d5;系列专栏&#xff1a;前端、Java、Java中间件大全、微信小程序、微信支付、若依框架、Spring全家桶 &#x1f4…

Node=>Express路由 学习2

1.概念 Express路由指的是客户端的请求与服务器处理函数之间的映射关系 Express路由由三部分组成 请求类型 请求URL地址 处理函数 app.METHOD ( PATH , HANDLER )根据定义的先后顺序进行匹配 请求类型和请求的URl同时匹配成功才会调用相应的处理函数 简单用法 2.模块化路由 为了…

《里奥哈酒友记》 | 里奥哈的历史—品鉴瑞格尔侯爵葡萄酒

2022年末&#xff0c;里奥哈大使组合怪怪和思羽完成了里奥哈线上活动两大“壮举”&#xff0c;10期《里奥哈酒友记》系列视频和40集《美酒之乡——里奥哈》有声专辑&#xff0c;吸引了许多葡萄酒资深爱好者的目光&#xff0c;也成功地让更多的人了解到里奥哈。由里奥哈推广大使…

粘包拆包问题

什么是粘包/拆包 TCP是个“流”式的协议&#xff0c;会根据TCP缓冲区的实际情况进行包的划分。在业务层面认为一个完整的包&#xff0c;可能会被TCP拆分成多个小包进行发送&#xff0c;也可能把多个小的包封装成一个大的数据包进行发送 粘包拆包问题解决方法 由于TCP协议底层…

电脑分盘怎么分?分盘详细教程来了,图文教学

电脑作为小伙伴日常生活使用的工具&#xff0c;很多事情都需要使用电脑来进行处理。虽然小伙伴使用电脑比较多&#xff0c;但是还是有不少的小伙伴不知道电脑分盘怎么分&#xff1f;其实电脑分盘很简单&#xff0c;下面小编就以图文教学的方式&#xff0c;详细的向小伙伴介绍电…

Python:迷宫

题目描述 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。 X 星球的一处迷宫游乐场建在某个小山坡上。它是由 1010 相互连通的小房间组成的。 房间的地板上写着一个很大的字母。我们假设玩家是面朝上坡的方向站立&#xff0…

【Linux系统编程】01:命令行解析

命令行解析 1.getopt调用介绍 头文件&#xff1a;<unistd.h> 原型&#xff1a;int getopt(int argc, char * const argv[], const char *optstring); argc、argv&#xff1a;由main函数的参数直接传递而来optstring&#xff1a;一个包含所有合法可选字符组成的字符串 …

超高精度PID调节器的特殊功能(3)——变送输出(转发)功能及其应用

摘要&#xff1a;变送输出是高级PID控制器的一项重要扩展功能&#xff0c;可用于多区控制、串级控制、比值控制和差值控制以及数据采集及记录。为展示变送输出功能的强大作用&#xff0c;本文主要针对超高精度VPC 2021系列PID控制器&#xff0c;介绍了变送输出的具体功能、参数…

【FPGA】Verilog:组合电路 | 3—8译码器 | 编码器 | 74LS148

前言&#xff1a;本章内容主要是演示Vivado下利用Verilog语言进行电路设计、仿真、综合和下载 示例&#xff1a;编码/译码器的应用 ​ 功能特性&#xff1a; 采用 Xilinx Artix-7 XC7A35T芯片 配置方式&#xff1a;USB-JTAG/SPI Flash 高达100MHz 的内部时钟速度 存储器&…

部门新来了个软件测试工程师,看着一副毛头小子的样儿,哪想到是新一代卷王...

内卷&#xff0c;是现在热度非常高的一个词汇&#xff0c;随着热度不断攀升&#xff0c;隐隐到了“万物皆可卷”的程度。 在程序员职场上&#xff0c;什么样的人最让人反感呢? 是技术不好的人吗?并不是。技术不好的同事&#xff0c;我们可以帮他。 是技术太强的人吗?也不…

【精彩点评】正确理解区块链能源消耗的内涵以及对绿色区块链的探索

发表时间&#xff1a;2022年4月13日 信息来源&#xff1a;bsvblockchain.org 为了理解区块链技术的工作原理并确定如何更好地对其加以利用&#xff0c;就区块链技术提出疑问是不可避免的。也许你正在被区块链的能源效率这个问题所困扰。 经常有人说&#xff0c;一些区块链网络…

Spring MVC 之获取参数(对象、JSON格式数据、URL地址参数、文件、Cookie)

文章目录1. 获取单个参数2. 获取多个参数3. 获取对象4. 后端参数重命名 RequestParam5. 接收 JSON 格式的数据 RequestBody6. 从 URL 地址中获取参数 PathVariable7. 上传文件 RequestPart8. 获取Cookie (CookieValue)/Session/header8.1 获取 Request 和 Response 对象8.2 获取…

【GD32F427开发板试用】SysTick定时器的ms中断,us计时

本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动&#xff0c;更多开发板试用活动请关注极术社区网站。作者&#xff1a;远小刘 1、系统滴答定时器 参考资料《Cortex M3与M4权威指南.pdf》中的9.5 The SysTick timer小节&#xff0c;里面详细讲解了 1、为什么要用…

一分钟教会使用ChatGPT,保姆级ChatGPT教程

最近ChatGPT大火&#xff01;微软退出首款ChatGPT搜索引擎&#xff0c;阿里等国内巨头也纷纷爆出自家产品&#xff0c;一夜之间&#xff0c;全球最大的科技公司仿佛都回到了自己年轻时的样子&#xff01; 然而&#xff0c;ChatGPT这么火&#xff0c;这么好玩的东西&#xff0c…

Knowledgeowl VS Baklib:你需要知道的一切!

KnowledgeOwl 和 Baklib都是知识库软件的示例。您可以使用每个平台为用户创建自己的知识库和托管文档。每个平台都允许您从同一帐户创建多个知识库&#xff0c;并且可以为用户提供内部和外部知识库。什么是知识库&#xff1f;知识库是一个集中的信息存储库&#xff0c;旨在为无…

Koa2-项目中的基本应用

文章目录安装配置koa2配置nodemon,热更新我们的项目中间件什么是中间件&#x1f47b;洋葱模型路由中间件连接数据库 - mysql后端允许跨域处理请求getpostputdelete后续会继续更新安装配置koa2 &#x1f47b;安装 koa2 npm i koa2 -s&#x1f47b;在package.json 配置,当然是在…

用LCR测试仪测试无线充电系统中的线圈

宽阻抗范围用来表征电感和质量因数– 高精度 DCR 测量– 制造环节快速测量– 大量夹具可供选择智能终端上不断增加新功能&#xff0c;电池寿命成为用户最头痛的问题之一。相比便携式电源和电缆供电而言&#xff0c;无线充电技术因其方便性和多功能性获得了很大的关注&#xff0…

MySQL —— 表的增删查改(一)

目录 表的增删查改&#xff08;一&#xff09; 一、create 1. 单行数据全列插入 2. 多行数据指定列插入 3. 插入否则更新 4. 替换 二、Retrieve 1. select列 1. 全列查询 2. 指定列查询 3. 查询字段为表达式 4. 为查询结果指定别名 5. 结果去重 2. where条件 1…

辞旧岁立新年 | 展望前端工程师的2023

「InfoQ 年度技术盘点与展望」是由InfoQ主办的年度技术盘点与展望项目&#xff0c;是 InfoQ 全年最重要的内容选题之一。 近日&#xff0c;字节跳动云原生 PaaS 资深前端工程师黄健受邀参与&#xff0c;分享主题为 《展望前端研发工程师的 2023》 。本文整理自黄健于直播中分享…

中国区注册OpenAI账号,试用ChatGPT指南

OpenAI最近推出ChatGPT&#xff0c;但国内&#xff08;包括香港&#xff09;并不支持OpenAI账号注册&#xff0c;多数会提示&#xff1a; OpenAI’s services are not available in your country. 前期准备 科学上网&#xff0c;最好是美国IP&#xff08;可以购买v屁n&…