【C语言】自定义类型:结构体

news2024/9/30 9:20:40

前言

今天这篇文章,我们来学习自定义类型中的结构体类型

之前我们就初步了解过结构体类型,知道他是用来描述复杂类型的

像之前的short、int、long之类的称为C语言的内置类型
而如结构体、枚举、联合类型称为自定义类型

初识结构体

在正式学习前,我们先来回忆一下之前的知识

定义

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

结构体类型的声明

struct tag { //struct是关键字,tag是自定义的标签
	member - list //成员列表
}variable - list;//变量列表

例子

struct Stu { 
	char name[20];
	int age;
};//;不可以省略

结构体变量的创建

方式一

struct Stu { 
	char name[20];
	int age;
}s3, s4, s5;

struct Stu s6;

int main()
{
	struct Stu s1;
	struct Stu s2;

	return 0;
}

注意

s3, s4, s5,s6都是全局结构体变量

方式二:使用typedef简化

使用typedef将node重新命名为node

typedef struct node
{
	char arr[20];
	struct node* next;
}node;

创建变量的方式就有两种了

int main()
{
	struct node s1;
	node s2;

	return 0;
}

特殊的创建方式

匿名结构体类型(不建议这么使用)

struct
{
	int a;
	char b;
	float c;
}x;
struct
{
	int a;
	char b;
	float c;
}a[20], * p;

这种创建方式,只能创建全局变量,不能创建局部变量,因为这个结构体没有标签(名字)

注意1

当尝试使用p = &a进行赋值时,程序会报错
因为二者的类型不同,即他们虽然都隐去了标签,但编译器认为他们是属于不同的类型

注意2

当使用匿名结构体类型时,是无法使用typedef进行简化的

结构的自引用

引用自己的变量(错误)

不可以在结构体的成员列表处创建自己的结构体变量,如下

struct node
{
	char arr[20];
	struct node n;
};

这跟函数递归一点关系都没有,这么写是错误的

引用指针(正确)

正确的方式是引用一个地址,方便找到下一个元素存储在哪里
(就是链表的实现方式)

struct node
{
	char arr[20];
	struct node* next;
};

结构体变量的定义和初始化

示例

struct S
{
	char c;
	int a;
	double d;
	char arr[20];
};

int main()
{
	struct S s = { 'c', 100, 3.14,"hello world"};

	printf("%c %d %lf %s\n", s.c, s.a, s.d, s.arr);

	return 0;
}

结构体嵌套初始化

结构体嵌套(结构体包含结构体的)初始化方式如下
要使用{}

struct T
{
	int age;
	double weight;
};

struct S
{
	char c;
	int a;
	double d;
	char arr[20];
	struct T st;
};

int main()
{
	struct S s = { 'c', 100, 3.14,"hello world", {20, 80.0} };
	printf("%d\n", s.st.age);
	
	return 0;
}

结构体内存对齐

计算结构体内存大小

引入

下面代码的结果是什么

struct S1
{
	char c1;
	int a;
	char c2;

};


struct S2
{
	int a;
	char c1;
	char c2;

};


int main()
{
	struct S1 s1 = { 0 };
	struct S2 s2 = { 0 };

	printf("%d\n", sizeof(s1));

	printf("%d\n", sizeof(s2));

	return 0;
}

运行结果
在这里插入图片描述
这是为什么?下面我们就来讲解它

对齐规则

代码就是上面“引入”中的代码

1

第一个成员在与结构体变量偏移量为0的地址处。

意思就是:结构体的第一个成员就存储在结构体变量所处的地址处
偏移量为0,就是重合

2

其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的值为8
gcc中无明确默认值

先解释对齐数
以a作为例子,a为整型,大小是4,小于8,所以对齐数就是4

再解释整数倍
对齐数是4,那a这个变量就应该存储在4的倍数的地址处,
意思就是:从4的倍数的地址处开始存储,前面的就空着,不存储

那么根据前两条规则,结构体S1的大小是9,输出结果是12,那这是为什么呢

3

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

以结构体变量s1为例,最大对齐数是4,所以就再浪费三个字节的空间,将大小扩展到12个字节

用前三条规则也可以得出s2的大小是8个字节

4

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

在“例题2”处,会做详细解释

例题1

下面结构体变量的大小是多少

struct S3
{
 double d;
 char c;
 int i;
};

根据前三条规则可以轻易得出:
8+1+3+4 == 16

大小就是16个字节

例题2

下面结构体的大小是多少

struct S4
{
 char c1;
 struct S3 s3;
 double d;
};

这里就需要用到第四条规则了:结构体嵌套

s3根据上一题可知,字节大小是16,
存储开始位置:嵌套结构体(此处就是s3)自己的最大对齐数的整数倍处
对于s3来说,最大的就是double的8个字节,也就是从8的整数倍处开始存储

并且结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

所以,变量s4大小就是:
1+7+16+8 == 32

内存对齐存在的原因(了解即可)

用空间换时间

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

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

2. 性能原因:

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

比如:32位的机器,一次可以读取4个字节的数据,如果不对齐,那就可能分两次读取,如果对齐,读取一次就够了

改进

所以为了节省空间和满足对齐条件
我们在创建结构体变量的时候,应该像“引入”中的变量s2一样:让占用空间小的成员尽量集中在一起

修改默认对齐数

使用预处理指令
#pragma

设置默认对齐位为4

#pragma pack(4)

取消设置的默认对齐位

#pragma pack()

结构体传参

注意:
结构体传参,分为两种:传值调用、传址调用
不修改变量成员可以使用传值调用和传址调用,建议使用传址调用,因为传过去的是地址,字节大小是固定的
要修改成员(如,初始化)就不能使用传值调用,只能传址调用

传值调用

struct S
{
	int data[100];
	int num;
};

void print1(struct S tmp)
{
	printf("%d\n", tmp.num);
}

int main()
{
	struct S s = { {1,2,3}, 100 };
	print1(s);
	print2(&s);

	return 0;
}

传址调用

如果担心ps指向的对象被修改时,只需要用const修饰即可

struct S
{
	int data[100];
	int num;
};

void print2(const struct S* ps)
{
	printf("%d\n", ps->num);
}

int main()
{
	struct S s = { {1,2,3}, 100 };
	print1(s);
	print2(&s);

	return 0;
}

原因(网上搜的)

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

结语

结构体的初步介绍就到这里了,希望对你有帮助
下一篇文章我们会学习位段,我们下次见~

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

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

相关文章

Postgres入门:三种免费且简单的方法

大家好,开发者们!今年大约有9万人参与了Stack Overflow的调查。令人印象深刻的是,Postgres被评为第一数据库。此外,DB Engines还将PostgreSQL列为全球增长最快的数据库之一。这对我们意味着什么呢?很明显,我…

数学思维导图怎么画?别错过这几简单绘制方法

数学思维导图怎么画?数学思维导图可以帮助我们更好地组织和理解各种数学概念。不仅是学生和教师可以受益,数学思维导图也可以在研究和工作中发挥作用。这种工具可以帮助你清晰地表示各种数学概念和关系,并将它们可视化,以便更容易…

LeetCode.双指针(三)

例题一 一、题目 两数之和 II - 输入有序数组 给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] &#xf…

思维导图怎么制作?了解一下这几种制作方法

思维导图怎么制作?思维导图是一种非常有效的组织思考和表达想法的工具。它可以帮助人们更好地理解和记忆信息,并且可以通过可视化的方式帮助人们更好地理解复杂的关系和概念。制作思维导图有多种方法,例如手绘、使用电子表格或专业的思维导图…

《软件方法》强化自测题-分析(4)

DDD领域驱动设计批评文集 通过做强化自测题加入“软件方法建模师”群 《软件方法》各章合集 按照业务建模、需求、分析、设计工作流考察,答案不直接给出,可访问自测链接或扫二维码自测,做到全对才能知道答案。 知识点见《软件方法》&…

不同企业如何选择合适的CRM系统?

市场上的CRM系统千差万别,如何选到适合的CRM系统?很多企业凭借感觉盲目选型,结果上线后发现CRM系统功能不符合需求。这就好比买衣服,不试穿就买回家,结果发现尺码不合适,还不能退换。下面说说企业如何进行C…

一起学SF框架系列7.4-spring-AOP-AOP代理创建

AOP的BeanDefinition加载后,Spring提供了自动代理机制,让容器自动根据目标bean生成AOP代理bean,本文讲述具体如何实现。 基本机制 Spring的启动过程中,在bean实例化前后、初始化前后均提供了外部介入处理机制(详见“…

三、SQLServer 数据库安装集

一、Docker 安装 Docker下安装SqlServer2019Docker 安装 SQLServer 1. 创建容器 前置准备 # 1. 创建主机映射目录 mkdir -p /root/sqlserver # 2. 修改主机映射目录权限 chown -R 10001:0 /root/sqlserver创建容器 # 1、拉取镜像。 #sudo docker pull mcr.microsoft.com/mssql/…

超越函数界限:探索JavaScript函数的无限可能

🎬 岸边的风:个人主页 🔥 个人专栏 :《 VUE 》 《 javaScript 》 ⛺️ 生活的理想,就是为了理想的生活 ! 目录 📚 前言 📘 1. 函数的基本概念 📟 1.1 函数的定义和调用 📟 1.2 …

用加持了大模型的 Byzer-Notebook 做数据分析是什么体验

Byzer-Notebook 是专门为 SQL 而研发的一款 Web Notebook。他的第一公民是 SQL,而 Jupyter 则是是以 Python 为第一公民的。 随着 Byzer 引擎对大模型能力的支持日渐完善, Byzer-Notebook 也在不自觉中变得更加强大。我和小伙伴在聊天的过程中才发现他已…

TCP定制协议,序列化和反序列化

目录 前言 1.理解协议 2.网络版本计算器 2.1设计思路 2.2接口设计 2.3代码实现: 2.4编译测试 总结 前言 在之前的文章中,我们说TCP是面向字节流的,但是可能对于面向字节流这个概念,其实并不理解的,今天我们要介…

QT:绘图事件QPainter

绘图事件QPainter 绘图事件&#xff08;需要重写的函数&#xff09;&#xff1a;paintEvent 声明一个画家对象 QPainter painter(this) 指定绘图设备 画线&#xff0c;画圆&#xff0c;画矩形&#xff0c;画文字 可设置画笔&#xff0c;画刷#include <QPainter> ...... …

剑指 Offer 48. 最长不含重复字符的子字符串(C++实现)

剑指 Offer 48. 最长不含重复字符的子字符串https://leetcode.cn/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof/ dp 注意&#xff1a;缩小 不含重复字符子串 时的写法 dp_1 min(i - charToIndex[s[i]], dp_0 1); int lengthOfLongestSubstring(string s…

Autosar MCAL-S32K324 CAN-FD配置及使用

文章目录 前言配置MCAL CANCAN Controller配置CAN FD波特率配置Ram block关于MailBox 代码中使用CAN FD报文发送和接收CAN FD报文接收CAN FD报文发送 总结 前言 在之前的文章中&#xff0c;介绍了标准CAN的MCAL配置&#xff0c;在此基础上&#xff0c;扩展为CAN-FD就会容易很多…

6.RocketMQ之消费索引文件ConsumeQueue

功能&#xff1a;作为CommitLog文件的索引文件。 本文着重分析为consumequeue/topic/queueId目录下的索引文件。 1.ConsumeQueueStore public class ConsumeQueueStore {protected final ConcurrentMap<String>, ConcurrentMap<Integer>, ConsumeQueueInterface…

NetSuite OIDC、SAML SSO 演示

NetSuite的SSO的策略近些年处于演进过程&#xff0c;所以原来的Inbound SSO和Outbound SSO已经退出历史舞台。前者已经废止&#xff0c;后者在24年底废止。目前的SSO策略是&#xff1a; 第三方的身份认证服务商NetSuite as OIDC Provider 前者的含义是&#xff0c;把认证服务…

数据结构 - 基本概念和术语

基础概念之间的关系大致如下&#xff1a; 一、数据、数据元素、数据项和数据对象 数据 > 数据对象 > 数据元素 > 数据项 类比数据库&#xff0c;这四个概念代表的含义如下所示&#xff1a; 数据&#xff1a;整个数据库的所有数据数据对象&#xff1a;这个数据库的…

Shell脚本五:函数和数组

文章目录 1.函数1.1Shell函数的概念1.2函数的好处1.2函数的组成1.3函数的结构1.4查看函数列表1.5删除函数1.6函数的返回值1.6.1使用原则1.6.2示例 1.7函数的作用范围1.8函数递归1.8.1示例 2.数组2.1什么是数组2.2数组的作用2.3数组名和索引2.4定义数组的方式2.5普通数组和关联数…

深入理解分布式架构,构建高效可靠系统的关键

深入探讨分布式架构的核心概念、优势、挑战以及构建过程中的关键考虑因素。 引言什么是分布式架构&#xff1f;分布式架构的重要性 分布式系统的核心概念节点和通信数据分区与复制一致性与一致性模型负载均衡与容错性 常见的分布式架构模式客户端-服务器架构微服务架构事件驱动…

对Lua的理解

在redis和nginx中都潜入了Lua环境用于快速上手开发。但如何理解Lua以及Lua与宿主环境的交互是需要掌握的。 首先是Lua本身&#xff0c;打开5.1的lua版本开始编译后最后生成一个lua的可执行文件&#xff0c;这其实就是一个包含了Lua虚拟机的终端.。所以其实在不管redis也好nginx…