匿名结构体类型、结构体的自引用、结构体的内存对齐以及结构体传参

请添加图片描述

文章目录

  • 🚀前言
  • 🚀结构体
    • ✈️结构体类型的声明
    • ✈️结构体变量的创建与初始化
    • ✈️结构体类型的特殊声明
    • ✈️结构体的自引用
    • ✈️结构体的内存对齐
      • 🚁修改默认对齐数
    • ✈️结构体传参

🚀前言

在C语言中有着各种数据类型,这些类型有配划分为内置类型自定义类型两大类(如下图)。铁子们,今天阿辉要分享的就是自定义类型中的结构体联合体和枚举将在下篇文章分享,至于数组阿辉之前的文章数组篇中已经详细讲到,铁子们感兴趣的话可以点击跳转😘,不多bb直接开始我们今天的学习👊
请添加图片描述

🚀结构体

铁子们是否有这样的疑问——C语言为什么要引入结构体这一自定义类型?
别急,听阿辉一一道来👇

其实结构体数组有一点类似,数组是存储同一种数据类型的集合,而结构体是存储不同类型的集合,比如当你想描述一个学生时,你得有姓名、年龄、学号等等一系列特征
可是我们发现这不是某一种单一的数据类型能够描述的,这时引入结构体这一自定义类型是非常有必要的

对于结构体有何用想必铁子们有了初步的认识,咱们接着往下看👇

✈️结构体类型的声明

声明结构体的语法结构:

struct tag 
{
	member_list; 成员列表,
}variable_list;  变量列表,在结构体声明时就创建的变量
struct tag 这个整体属于类型名,和intchar等等类型名一样

我们来创建一个描述学生的结构体类型:

struct stu
{
	char name[20];名字
	int age;年龄
	int id;学号
	char sex[5];性别
};//注意这里的分号不能丢了

结构体类型的声明同样分为全局声明和局部声明,结构体全局声明以及声明时创建的变量作用域都是整个程序,而结构体的局部声明以及声明时创建的变量的作用域在该大括号内部{}
在这里插入图片描述

✈️结构体变量的创建与初始化

结构体变量有两种创建方式,一种在结构体类型声明时就创建,与结构体类型声明具有相同的作用域;另一种在结构体类型声明后创建,作用域与结构体类型声明无关,咱直接上代码👇

struct stu
{
	char name[20];
	int age;
}s1;//s1属于全局变量

int main()
{
	struct stu s2;//s2局部变量,作用域在main函数内
	return 0;
}

结构体变量初始化:

#include <stdio.h>
struct Stu
{
 char name[20];//名字
 int age;//年龄
 char sex[5];//性别
 char id[20];//学号
};
int main()
{
 //按照结构体成员的顺序初始化
 struct Stu s1 = { "张三", 20, "男", "20230818001" };
 
 //按照指定的顺序初始化
 struct Stu s2 = { .age = 18, .name = "lisi",
  .id = "20230818002", 
 .sex = "⼥};
 return 0;
}

✈️结构体类型的特殊声明

铁子们,结构体还有一种特殊的声明,这种声明把结构体的标签tag给干掉了,这种特殊声明的结构体被称为匿名结构体类型
我们来上一组例子:

struct
{
	int a;
	int b;
}a;

struct
{
	int a;
	int b;
}*p;

int main()
{
	p = &a;
	return 0;
}

上⾯的两个结构在声明的时候省略掉了结构体标签tag
那么问题来了?
p = &a这样写是否合法?

对于匿名结构体类型,上述两个结构体类型看似一样,实则不同,匿名结构体的变量只能在声明时创建且只能有一个变量,上述编译器会把匿名结构体指针变量p与&a当作两个不同的类型

✈️结构体的自引用

结构体的自引用本质是结构体的递归定义,但是这会存在很大问题,如下面这个代码

struct node
{
	int data;
	struct node next;
}

在编译期间,编译器需要知道结构体变量大小为结构体变量分配空间,但是上述这个结构体我们仔细想一下会发现这个结构体无限递归,根本无法确定其大小,为解决上述问题,我们可以通过指针来间接引用结构体,如下:

struct node
{
	int data;
	struct node* next;
}

上述就是正确的结构体自引用,通过结构体自引用我们可以创建具有互相关联关系的数据结构,如链表、树等,在数据结构中结构体尤为重要。

✈️结构体的内存对齐

有了上面对于结构体的理解,铁子们对结构体的基本使用应该不成问题了,接下来咱们来研究一个深入的问题——结构体类型的大小

有的老铁可能会说:不是很简单吗❓直接把所有变量所占字节空间大小全都加起来就完事了

但是真有这么简单吗?我们接着看👇
其实结构体存在内存对齐这一规则:

  • 第一个成员在与结构体变量偏移量为0的地址处
  • 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
    对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值
    VS中默认的值为8,Linux中gcc没有默认对齐数对齐数就是该成员大小
  • 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
  • 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

在VS中char的对齐数就是1int对齐数就是4double对齐数就是8
我们来看看一个是否如此

#include <stdio.h>
struct S1
{
	char c1;
	int i;
	char c2;
};
int main()
{
	printf("%d\n", sizeof(struct S1));
	return 0;
}

在这里插入图片描述

我们可以看到对于两个char和一个int应该是6个字节,但是通过sizeof却打印出了12

这里我用用图为铁子们解释:
请添加图片描述
上图一个方块代表一个字节,对于第一个成员c1就存在偏移量为0的地址处,而对于第二个成员i它是int类型对齐数为4,要存在为4的倍数的偏移量处也就是上图位置出,第三个成员c2char类型对齐数为1存在i成员后面,现在整个大小只有9个字节并非最大对齐数4的整数倍所以还要补3个字节分配给它,上图中蓝色方块代表浪费的内存
知道了结构体内存对齐的计算之后,问题又来了:为什么存在内存对齐❓
有两个原因:

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

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

结构体的内存对齐是一种拿空间换时间的做法

在设计结构体的时候,我们既要满足对齐,又要节省空间,这该如何做呢?
在我们声明结构体时尽量让占用空间小的成员集中在一起,如:

#include <stdio.h>
//例如:
struct S1
{
	char c1;
	int i;
	char c2;
};
struct S2
{
	char c1;
	char c2;
	int i;
};
int main()
{
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));
	return 0;
}

在这里插入图片描述
struct s2就要比struct s1的空间小4个字节

🚁修改默认对齐数

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

#pragma pack(1)//设置默认对齐数为1
struct S1
{
	char c1;
	int i;
	char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
	//输出的结果是什么?
	printf("%d\n", sizeof(struct S1));
	return 0;
}

输出为6当默认对齐数为1时也就不存在对齐了,直接把所有变量所占字节空间大小全都加起来就完事了

✈️结构体传参

与其他类型变量传参一样,同样可以传址和传值

#include <stdio.h>
struct S
{
	int data[1000];
	int num;
};
struct S s = { {1,2,3,4}, 1000 };
//结构体传参
void print1(struct S s)
{
	printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
	printf("%d\n", ps->num);
}
int main()
{
	print1(s); //传结构体
	print2(&s); //传地址
	return 0;
}

上述代码都可以帮我们打印,但是传址调用更好

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

SO结构体在传参时要传递结构体地址


感谢老铁能看到这,到这里结构体的分享就到此为止了,如果觉得阿辉写得不错的话,记得给个赞呗,你们的支持是我创作的最大动力🌹
请添加图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第九节HarmonyOS 常用基础组件3-TextInput

一、TextInput描述 TextInput组件用于输入单行文本&#xff0c;响应输入事件。TextInput的使用也非常广泛&#xff0c;例如应用登录账号密码、发送消息等。和Text组件一样&#xff0c;TextInput组件也支持文本样式设置&#xff0c;下面的示例代码实现了一个简单的输入框&#x…

JavaScript WebAPI(三)(详解)

这次介绍一下webAPI中的一些知识&#xff1a; 回调函数 回调函数是指 如果将函数A做为参数传递给函数B时&#xff0c;我们称函数A为回调函数 例如&#xff1a; // 立即执行函数中传递的函数是一个回调函数 (function(){ console.log("我是回调函数") })(); // …

<Linux>(极简关键、省时省力)《Linux操作系统原理分析之linux存储管理(1)》(17)

《Linux操作系统原理分析之linux存储管理&#xff08;1&#xff09;》&#xff08;17&#xff09; 6 Linux 存储管理6.1 80x86 的分段机制6.1.1 80x86 的虚拟存储空间6.1.2 段描述符表6.1.3 逻辑地址向线形地址的转换 6 Linux 存储管理 6.1 80x86 的分段机制 Linux 最早在 in…

Docker篇之利用docker搭建ftp服务器可实现多用户上传

一、前言 场景&#xff1a;公司需要搭建FTP服务器&#xff0c;供内网之前可以互相传递数据&#xff0c;安全稳定&#xff0c;需要满足开通多个账号&#xff0c;每个用户上传的文件有自己对应的文件目录。 这里建议&#xff1a;用户目录Disk尽量大一点&#xff0c;避免因为空间不…

三十六、seata的部署和集成

seata的部署和集成 一、部署Seata的tc-server 1.下载 首先我们要下载seata-server包&#xff0c;地址在http&#x1f615;/seata.io/zh-cn/blog/download.html 当然&#xff0c;资料也准备好了&#xff1a; 2.解压 在非中文目录解压缩这个zip包&#xff0c;其目录结构如下…

走向未来能源之巅:可控核聚变的探索与挑战

走向未来能源之巅:可控核聚变的探索与挑战 引言 随着人类文明的进步和科技的发展,对能源的需求与日俱增。传统的化石燃料能源面临着枯竭和环境问题的双重压力,因此,寻找一种清洁、可持续、高效的能源成为了全球科学家的共同使命。在这个过程中,可控核聚变作为一种具有巨…

synchronized和volatile的区别是什么?

synchronized和volatile是Java中的两个关键词&#xff0c;分别用于实现线程同步和线程间的可见性。 synchronized用于实现线程之间的互斥同步&#xff0c;即同一时刻只能有一个线程访问被synchronized修饰的代码块或方法&#xff0c;其他线程需要等待。synchronized确保了线程…

大学里学编程,为什么这么难?

在大学学习计算机专业&#xff0c;为何很多同学觉得编程学得不顺心呢&#xff1f;许多同学会有这种感觉&#xff0c;在上大学里的计算机专业课程时&#xff0c;听得头都大了&#xff0c;但是真正要写代码&#xff0c;却不知道从哪里开始&#xff0c;或是觉得&#xff0c;大学里…

【golang】为什么使用goland终端修改不了Go语言的配置环境?

问题 最近在做项目时&#xff0c;需要使用golang的交叉编译&#xff0c;在windows系统上打包一个可以在linux系统上运行的golang程序的二进制文件。 这就需要暂时修改一下golang的配置环境&#xff1a; set GOARCH amd64 set GOOS linux但是修改的时候发现在goland终端输入…

基于51单片机的交通灯_可调时间_夜间+紧急模式

51单片机交通灯 1 讲解视频&#xff1a;2 功能要求3 仿真图&#xff1a;4 原理图PCB5 实物图6 程序设计&#xff1a;7 设计报告8 资料清单&#xff08;提供资料清单所有文件&#xff09;&#xff1a;设计资料下载链接&#xff1a; 51单片机简易交通灯_可调时间_夜间紧急 仿真代…

【数据结构】环形队列

环形队列 1. 定义 环形队列就是将队列在逻辑上看作环形结构、物理上仍是数组形式存储的一种数据结构。 其实现主要分为两种情况&#xff1a; 浪费空间法记录空间法 2. 实现 实现要考虑的是成员变量 2.1 记录空间法 使用used标识当前存储了多少元素&#xff0c;如果为空&a…

JDK17的安装与配置

JDK17的安装与配置 下载地址安装步骤配置环境变量验证安装是否成功 下载地址 此jdk17安装的系统是win10系统 https://www.oracle.com/java/technologies/downloads/ 这里选择JDK17进行下载 下载完成之后&#xff0c;显示如下图&#xff1a; 安装步骤 自定义的安装路径&…

clickhouse -- clickhouse解析复杂JSON数组

举例 - 查数据 select _id,doctorId,patientId,diagnosisList from patient_disease final where diagnosisList is not null limit 3;- 解析数组 SELECT _id,doctorId,patientId,visitParamExtractRaw(diagnosisList,diagnosisName) FROM patient_disease final where _id …

接口测试工具:Jmeter详解

安装 使用JMeter的前提需要安装JDK&#xff0c;需要JDK1.7以上版本 目前在用的是JMeter5.2版本&#xff0c;大家可自行下载解压使用 运行 进入解压路径如E: \apache-jmeter-5.2\bin&#xff0c;双击jmeter.bat启动运行 启动后默认为英文版本&#xff0c;可通过Options – C…

BUUCTF-MISC-你竟然赶我走

下载题目并打开 jpg图片文件格式 010工具分析一波下滑底部在16进制字符串哪里发现了flag得到flag&#xff1a;flag{stego_is_s0_bor1ing} 本题意义&#xff1a; 对杂项图片隐写有了入门了解&#xff0c;对010图片分析工具有了一定的认识&#xff0c;为图片隐写题目的基础夯实有…

盘点40个Android游戏Game源码安卓爱好者不容错过

盘点40个Android游戏Game源码安卓爱好者不容错过 学习知识费力气&#xff0c;收集整理更不易。 知识付费甚欢喜&#xff0c;为咱码农谋福利。 下载链接&#xff1a;https://pan.baidu.com/s/193LoWrXM1ZLLCA7mhfZpiA?pwd8888 提取码&#xff1a;8888 项目名称 24点游戏-…

vue 解决响应大数据表格渲染崩溃问题

如果可以实现记得点赞分享&#xff0c;谢谢老铁&#xff5e; 1.场景描述 发起请求获取上万条数据&#xff0c;进行表格渲染&#xff0c;使浏览器卡顿&#xff0c;导致网页崩溃。 2.分析原因 1.大量数据加载&#xff0c;过多操作Dom&#xff0c;消耗性能。 2.表格中包含其他…

【halcon】裁剪

前言 目前我遇到的裁剪相关的函数都是以clip打头的函数。一共4个&#xff1a; clip_end_points_contours_xldclip_contours_xldclip_regionclip_region_rel 前面两个是对轮廓的裁剪。 后面是对区域的裁剪。 裁剪轮廓的两端 clip_end_points_contours_xld 用于实现裁剪XLD…

Hdoop学习笔记(HDP)-Part.10 创建集群

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

mfc项目设置软件版本

//上面设置的版本通过下面的代码可以获取到 TSTRING CVersion::GetSoftVersion() {TSTRING strVer _T("");TCHAR szPath[MAX_PATH] _T("");memset(szPath, 0, sizeof(szPath));::GetModuleFileName(NULL, szPath, sizeof(szPath));//得到本程序的目录UIN…