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

news2024/12/28 20:05:16

请添加图片描述

文章目录

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

🚀前言

在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结构体在传参时要传递结构体地址


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

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

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

相关文章

服务注册发现 配置中心 springcloud alibaba nacos

文章目录 0100 系统环境0200 nacos安装0201 下载0202 安装 0300 工程说明0301 结构说明0302 运行效果 0400 代码说明0401 服务提供者&#xff08;Provider Service&#xff09;0402 服务消费者&#xff08;Consumer Service&#xff09;服务提供者SDK&#xff08;Provider Serv…

应用商店优化之利用季节性提高应用曝光

在应用商店中利用季节性优化&#xff0c;是提高应用和游戏的曝光度以及应用商店转化率的最有效策略之一。 1、季节性的影响。 用户消费来自许多不同渠道的新闻和信息&#xff0c;这会影响他们做出决策的方式。比如在节日期间&#xff0c;当用户寻找购物类型的应用时&#xff0…

springboot数据格式验证——自定义日期格式验证及list验证

我们在工作中经常需要对日期格式进行定义&#xff0c;如果客户端传来的日期字符串不符合要求&#xff0c;那么根本无法保存&#xff0c;但是已有的注解并没有日期格式的验证&#xff0c;那我们就自己实现一个 一、自定义日期格式验证的注解DateFormat import javax.validatio…

Linux--初识和基本的指令(3)

目录 1.前言 1.指令 1.1 cat指令 1.2 echo指令 1.3 more 指令 1.4 less指令 1.5 什么时候使用less和more 1.6 head指令 1.7 tail指令 1.8 wc指令 1.9 与时间相关的指令 1.9.1 date指令 1.9.2 cal指令 1.10 16.find指令&#xff1a;&#xff08;灰常重要&#x…

熬夜会秃头——beta冲刺Day7

这个作业属于哪个课程2301-计算机学院-软件工程社区-CSDN社区云这个作业要求在哪里团队作业—beta冲刺事后诸葛亮-CSDN社区这个作业的目标记录beta冲刺Day7团队名称熬夜会秃头团队置顶集合随笔链接熬夜会秃头——Beta冲刺置顶随笔-CSDN社区 一、团队成员会议总结 1、成员工作…

蓝桥杯算法心得——小郑躲太阳(思维推导)

大家好&#xff0c;我是晴天学长&#xff0c;一道与平时的题型截然不同的题型&#xff0c;需要的小伙伴可以关注支持一下哦&#xff01;后续会继续更新的。&#x1f4aa;&#x1f4aa;&#x1f4aa; 1) .小郑躲太阳 问题描述 小郑一觉醒来发现起晚啦!现在需要从家里飞速前往公司…

服装行业中小企业零售数字化转型的工作目标和主要实施路径|徐礼昭

目标1&#xff1a;实现“人、货、场”的在线化和经营数字化 实施路径&#xff1a;中小企业可以选择商派的微信小程序商城系统&#xff0c;结合导购助手小程序&#xff0c;实现业务在线化&#xff0c;导购在线化&#xff0c;通过微信公众号、企微社群和视频号&#xff0c;开展私…

synchronized和volatile的区别是什么?

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

在Android上搭建一个NDK项目

首先New Project&#xff0c;选择Native C&#xff0c;点击Next。 填入项目名称和包名&#xff0c;点击Next。 这里我们选择Cmake默认的C版本。 创建好的项目目录&#xff0c;里面比我们正常的Android项目多了一个cpp目录 打开MainActivity。里面定义了一个jni方法stringFromJN…

SOCKET、TCP、HTTP之间的区别与联系

SOCKET、TCP、HTTP之间的区别与联系 一、 Socket 1、什么是socket2、为什么需要socket3、建立socket连接 二、HTTP(基于TCP) 1、HTTP的概念2、HTTP连接的特点 连接请求&#xff1a;一次连接连接请求&#xff1a;短连接(socket是长连接) 三、TCP/IP协议簇 四、HTTP、Socket…

Linux中shell的运行原理

在Linux中&#xff0c;每次输入命令时&#xff0c;前面都会出现一串字母&#xff0c;我们称之为命令行提示符 实际上&#xff0c;命令行提示符是一种外壳程序 外壳程序的概念&#xff1a; 前面我们提到过&#xff0c;在Linux中&#xff0c;一切皆文件&#xff0c;所谓的命令就…

linux设置权限_setfacl_getfacl

3.2 设置权限ACL&#xff08;access control list&#xff09; 假设&#xff1a;/data所有者与所属组均为root&#xff0c;在不改变所有者的前提下&#xff0c;要求用户tom对该目录有完全访问权限&#xff08;rwx&#xff09;。只能考虑&#xff1a; 方法一&#xff1a;给/dat…

Spring AOP记录接口访问日志

Spring AOP记录接口访问日志 介绍应用范围组成通知&#xff08;Advice&#xff09;连接点&#xff08;JoinPoint&#xff09;切点&#xff08;Pointcut&#xff09;切面&#xff08;Aspect&#xff09;引入&#xff08;Introduction&#xff09;织入&#xff08;Weaving&#x…

003、应用程序框架-UIAbility

之——UIAbility 目录 之——UIAbility 杂谈 正文 1.UIAbility 2.基本使用 2.1 创建Ability工程 2.2 添加基础功能 2.3 新建页面 2.4 页面间的跳转 3.生命周期 总结 杂谈 UIAbility&#xff0c;其中的页面创建、页面间的跳转、数据传递、生命周期。 正文 1.UIAbil…

BUUCTF-WEB-刷题记录(2)

[网鼎杯 2018]Fakebook 注册一个账户&#xff0c;进去之后查看源代码&#xff0c;感觉存在注入点 是数字型注入&#xff0c;payload&#xff1a; 1%20and(false) 1%20and(true)判断列数 1 order by 5改为4的时候则页面正常 判断显示位&#xff0c;可以看见第二列存在数据回…

C语言每日一题(43)旋转链表

力扣 61 旋转链表 题目描述 给你一个链表的头节点 head &#xff0c;旋转链表&#xff0c;将链表每个节点向右移动 k 个位置。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], k 2 输出&#xff1a;[4,5,1,2,3]示例 2&#xff1a; 输入&#xff1a;head [0,1,2], …

Matlab 自适应选择搜索半径

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 通常我们计算某一点邻域的信息使用的都是固定搜索半径,然而,这取决于局部特征规模以及不包括代表性的几何信息。但如果存在三维噪声,固定的半径对最终的结果往往有着很大的影响。因此,要找到一个唯一的半径在所…

【网络安全技术】实体认证技术Kerberos

一、什么是Kerberos Kerberos解决的是客户端与服务器通信场景中&#xff0c;确保客户端服务器双方的身份可信&#xff0c;并提供对称密钥的分发来加密传输。是一个应用层的协议。 二、一个简单的模型 1.看这个基础的模型&#xff0c;客户端要和服务器通信&#xff0c;他先将自…

(使用vite搭建vue3项目(vite + vue3 + vue router + pinia + element plus))

使用vite搭建vue3项目&#xff08;vite vue3 vue router pinia element plus&#xff09; 初始化项目安装依赖&#xff0c;运行项目初始配置 初始化项目 1.需要在创建项目的位置cmd目录下执行 2. npm init vitelatest 回车 npm init vitelatest3.填上自己的项目名称 回车…

Rocket-核心编程模型

RocketMQ的消息模型 深入理解RocketMQ的消息模型 RocketMQ客户端基本流程 RocketMQ基于Maven提供了客户端的核心依赖&#xff1a; <dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-client</artifactId><version&…