C语言的结构体类型

news2024/9/20 6:28:44

在我们使用C语言进行编写代码时,常常会使用已经给定的类型来创建变量,比如int型,char型,double型等,而当我们想创建一些较为复杂的东西时,单单用一个类型变量是没办法做到的,比如我们想创建一个学生,学生拥有的属性繁多,有年龄,有姓名,有性别,有成绩等等等等...那么此时我们应该如何创建一个学生类型变量呢?这就涉及到我们今天要学习的新知识,结构体类型

一、结构体类型的声明

当我们要统计很多同类型数据的时候,我们可以使用数组而通常定义复杂的类需要多种不同的类型数据结构体就是用来存放这些不同类数据的

struct tag
{
	member - list;
}variable - list;

这段代码中,tag代表的意思是要创建结构体的名字member-list代表结构体中的各种成员变量variable-list可以不写,写的时候可以代表创建结构体类型时顺带创建的结构体变量

比如此时我们想定义一个学生变量:

struct Stu
{
	int age;//年龄
	char name[20];//名字
	double score;//成绩
	char sex[5];//性别
};//分号一定不能忘记

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

① 结构体变量的定义

对于结构体变量的定义,我们可以像上面提到的,在创建结构体类型时直接创建出结构体变量

struct Stu
{
	int age;//年龄
	char name[20];//名字
	double score;//成绩
	char sex[5];//性别
}s1;//分号一定不能忘记

这样就成功定义了结构体变量s1,当然除了这种方法,还可以在声明结构体之后再定义

struct Stu
{
	int age;//年龄
	char name[20];//名字
	double score;//成绩
	char sex[5];//性别
};
int main()
{
	struct Stu s1;
	return 0;
}

② 结构体变量的初始化

而对于结构体变量的初始化,也可以根据上面两种定义方式,分成两种初始化的方法:

在创建结构体类型时直接创建出结构体变量的情况下结构体变量初始化

struct Stu
{
	int age;//年龄
	char name[20];//名字
	double score;//成绩
	char sex[5];//性别
}s1 = { 18,"zhangsan",99.5,"男" };
int main()
{
	printf("%d %s %.1f %s\n", s1.age, s1.name, s1.score, s1.sex);
	return 0;
}

在声明结构体之后再定义的情况下对结构体变量初始化

struct Stu
{
	int age;//年龄
	char name[20];//名字
	double score;//成绩
	char sex[5];//性别
};
int main()
{
	Stu s1 = { 18,"zhangsan",99.5,"男" };
	//struct Stu s1 = { 18,"zhangsan",99.5,"男" };
	//两种方法都是可以的
	printf("%d %s %.1f %s\n", s1.age, s1.name, s1.score, s1.sex);
	return 0;
}

(注:如果定义的结构体名和变量名不冲突,那么在定义结构体变量时,可以省略掉struct关键字。)

③ 结构的特殊声明

在声明结构的时候,可以不完全的声明,也就是说在声明结构体时并不为此结构体命名,此声明被称为匿名结构体类型

struct
{
	int age;
	char name[20];
	double score;
	char sex[5];
}s1;
struct
{
	int age;
	char name[20];
	double score;
	char sex[5];
}*sp;
int main()
{
	sp = &s1;
	return 0;
}

那么如果我们分别声明两个成员变量类型相同的匿名结构体,一个定义s1结构体变量,另一个定义指针*sp,那么再对s1取地址,&s1与sp会是相等的嘛答案是,此行为是非法的

编译器会把上面的两个声明当成完全不同的两个类型,所以是非法的。 匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使用一次

④ 结构的自引用

结构体能够存储各种成员变量,那我们是否能在结构中包含一个类型为该结构本身的成员呢?

正常来说,在结构中包含整形变量就是int a,字符型变量就是char a,那同样的,我们要包含同类型结构变量,是写成struct ...吗?例如:

struct Node
{
    int date;
    struct Node next;
}

这样看似是代表了该结构本身成员date,但实际上操作起来,比如我们此时计算sizeof(struct Node)会发生"套娃"的情况一直循环的进入Node结构体,而每一次进入都多了一个占四字节的date,最后变成无穷大,所以这样写是错误的。

而其实我们想做到的就是找到同类型成员变量,那其实我们直接使用指针去指向下一个变量就好了~所以其实正确的方法很简单,只需要我们取next地址,像这样写就好了:

struct Node
{
    int date;
    struct Node* next;
}

而后我们再来了解一下typedef的用法,它的作用很多,主要作用于:

定义一种类型的别名,而不只是简单的宏替换。可以用作同时声明指针型的多个对象。

意思就是说,我们正常来定义整形变量,就是int num,而我们可以通过typedef来创建一个独特的新词来代表int,比如此时我们输入,typedef int Int;那么此时我们再写入int a和INT a,其实是一样的。(需要注意的是后面那句话!!!可以用作同时声明指针型的多个对象)

多说无益,我们来看一道例题:

#define INT_PTR int*
typedef int* int_ptr;
INT_PTR i, j;
int_ptr x, y;

在上面代码中,i,j,x,y四个常量的数组类型哪一个是int数据类型

答案为j是int型,i,x,y这个常量为int*类型,为什么?

在#define这个宏,在预处理阶段将会转化为int* i , j;而typedef不会出现这种情况,新定义的名称所代表的类型是什么,对应创造的变量就是什么。

② 声明struct新对象时可以使用。

我们来看这段代码是否可行呢?

 typedef struct
 {
     int data;
     Node* next;
 }Node;

答案是不行的,因为Node是对前面的匿名结构体类型的重命名产生的,但是在匿名结构体内部提前使用Node类型来创建成员变量,这是不行的。解决方案:

 typedef struct Node
 {
     int data;
     struct Node* next;
 }Node;

三、结构体的内存对齐

结构体内存对齐非常重要!!!故需要讲解的内容也较多!!!所以我单独分出一篇文章来对结构体的内存对齐进行透彻深入的讲解,需要的小伙伴们请进入这个博客进行进一步学习~

⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇

(C语言结构体内存对齐-CSDN博客)

⬆⬆​​​​​​​⬆​​​​​​​⬆​​​​​​​⬆​​​​​​​⬆​​​​​​​⬆​​​​​​​⬆​​​​​​​⬆​​​​​​​⬆​​​​​​​⬆​​​​​​​⬆​​​​​​​⬆​​​​​​​⬆​​​​​​​⬆​​​​​​​⬆​​​​​​​⬆​​​​​​​⬆​​​​​​​⬆​​​​​​​⬆​​​​​​​⬆​​​​​​​⬆​​​​​​​⬆​​​​​​​⬆​​​​​​​⬆​​​​​​​⬆​​​​​​​⬆​​​​​​​⬆​​​​​​​⬆​​​​​​​⬆​​​​​​​⬆

① 结构体内存对齐的规则

结构体中存放的数据各种各样,它们的存储是否能做到在内存中紧密排列呢?又或者说,结构体的内存应该怎样去计算呢?让我们来举个例子:

struct Stu1 {
	int age;
	char name;
	int id;
	char sex;
};
struct Stu2 {
	char sex;
	char name;
	int age;
	int id;
};
int main()
{
	int num1 = sizeof(struct Stu1);
	int num2 = sizeof(struct Stu2);
	printf("num1长度为:%d\nnum2长度为:%d\n", num1, num2);
	return 0;
}

如果按照之前计算整型数组和字符数组大小的常规思路来判断,这两个结构体的大小应该是相等的,一个int型变量占4个字节,一个char型变量占1个字节,那么Stu1有两个int型变量和两个char型变量,大小理应为4+4+1+1=10,同样的Stu2也应为10。那让我们将代码运行一下看看是不是这样的:欸?奇怪了,不仅两个结构体的大小不为10,甚至两个结构体的大小都不相等,这是怎么一回事呢?其实结构体Stu1在内存中的存址形式是这样的:

为什么会是这样的存储形式呢?这就是结构体内存对齐所造成的。

结构体内存对齐的规则:

① 结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处。

② 其他成员变量要对齐一个叫"对齐数"的数字的整倍数的地址。

(对齐数的概念)

* 在不同的编译器中,默认的对齐数也有所不同,Visual Studio Code的编译器的对齐数是8,Linux的编译器的对齐数是4。
* 在结构体存址时,对齐数取(编译器默认的对齐数)和(该成员变量大小)中较小的数。
③ 结构体所占内存大小等于最大对齐数(结构体中每个成员变量都有一个对齐数,各成员变量中最大的对齐数)的整数倍。

④ 如果结构体有嵌套,那么嵌套的结构体存储在自己成员的最大对齐数的整数倍地址处,嵌套结构体大小为所有成员(包括嵌套的结构体的成员)的最大对齐数的整数倍。

② 结构体内存对齐运算

比如此时我们创建一个结构体变量:

struct Stu1 {
	char name;
	int id;
	char sex;
};

用图片来表示:我们发现结构体的大小一下子从1变成了8,并且其中出现了三个浪费的空间,那么造成这种情况的原因是什么呢?让我们来一起计算一下它现在的对齐数当我们只存放一个char型变量name时,对齐数为1,所以此时内存大小仅仅为1。但当我们存入int型变量id时,对齐数变成4,所以结果的内存大小必须被4整除,故浪费三个空间,使内存大小变成8。最后也浪费三个空间,与上面是同理的。

四、结构体实现位段

① 位段的定义与初始化

既然讲了结构体,就不得不提到结构体的位段了。那么,什么是结构体的位段呢有些数据在存储时并不需要占用一个完整的字节,只需要占用一个或几个二进制位即可。为了充分利用好内存空间,就出现了位段~

位段的声明:

位段的成员必须是int,unsigned int,或signed int,在C99中位段成员的类型也可以选择其他类型。

段位的成员名后面有一个冒号和一个数字。

 后面的数字用来限定成员变量占用的位数。

让我们来用代码来进行一下举例

struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};

这就是位段的初始化,四个成员变量是int型,但使用位段将它们的内存大小进行了定义。而这种内存的定义应该从何体现呢?我们对着这段代码进行一些增加

struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};
int main()
{
	struct A arr = { 1,15,511,536870911 };
	printf("%d\n%d\n%d\n%d\n", arr._a, arr._b, arr._c, arr._d);
	return 0;
}

此时我们定义的四个成员变量,所占用的内存分别为2bit,5bit,10bit,30bit,相应的,因为int型变量是有正负之分的,所以还需要有一个符号位,那么此时真正储存大小的bit位就分别是1,4,9,29。而此时我输入的1,15,511,536870911恰好就是除符号位以外其他位全部为1的情况~所以此情况是能够正常输出的:现在我们将这四个数全部都加一,就会变成:这是因为此时所有位进1,使符号位从0变成了1,所以数字都变成了负数。而符号位一般都是最后一位,这也就证明初始化位段,冒号后的数字是bit个数~

那么位段A所占的内存大小又是多少呢?

struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};
int main()
{
	struct A arr = {};
	printf("%d\n",sizeof(arr));
	return 0;
}

答案是:8。那么这是为什么呢?接下来让我们学习一下位段的内存分配。

② 位段的内存分配

Ⅰ 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型

Ⅱ 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。

Ⅲ 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

让我们看一段代码,结构体中存放了四个char型变量,并且都通过位段改变了内存大小,那么这个结构体的大小是多少呢?

struct S
{
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
};
int main()
{
	struct S s = {};
	s.a = 10;
	s.b = 12;
	s.c = 3;
	s.d = 4;
	printf("%d", sizeof(s));
	return 0;
}

答案是3。那么接下来让我们通过这张图来深入的了解一下结构体内存分配这就是结构体S的大小为3的原因啦~

接下来再让我们对数据s.a,s.b,s.c,s.d四个成员变量所占用的地址进行一下分析最后得到的地址是:62 03 04 xx。结果是否是这样呢?让我们证实一下~没错~我们的答案是正确的使用位段能够有效的节省大量不必要的空间,而需要注意的是:地址的存储是从右往左使用的,并且当一个字节中剩余空间不足时,会浪费一段空间,在新的空间中存储,所以使用位段时也需要精心计算,否则或许反而会使占用内存变多!

然而,位段的好处如此多,也就相应的证明它也有坏处!!!

③ 位段的跨平台问题

①. int 位段被当成有符号数还是无符号数是不确定的。

②. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会 出问题)

③. 位段中的成员在内存中从左向右分配,还是从右向左分配,标准尚未定义。

④. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

总结:

跟结构相比,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。

那么关于结构体的相关知识,这次就为大家分享到这里啦~值得关注的是,结构体属于自定义类型中的一种,而自定义类型还有联合与枚举,而联合与枚举的相关知识就要留到下一篇文章给大家讲解啦~还请大家多多期待,如果这篇文章有讲得不细致的地方,或者有出现错误的地方,还请大家多多指出~我也会虚心学习的!那么我们下一篇再见啦~

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

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

相关文章

图文讲解HarmonyOS应用发布流程

HarmonyOS应用的开发和发布过程可以分为以下几个步骤:证书生成、应用开发、应用签名和发布。 1. 证书生成: 在开始开发HarmonyOS应用之前,首先需要生成一个开发者证书。开发者证书用于标识应用的开发者身份并确保应用的安全性。可以通过Har…

R语言统计分析——功效分析3(相关、线性模型)

参考资料:R语言实战【第2版】 1、相关性 pwr.r.test()函数可以对相关性分析进行功效分析。格式如下: pwr.r.test(n, r, sig.level, power, alternative) 其中,n是观测数目,r是效应值(通过线性相关系数衡量&#xff0…

LoRA: Low-Rank Adaptation Abstract

LoRA: Low-Rank Adaptation Abstract LoRA 论文的摘要介绍了一种用于减少大规模预训练模型微调过程中可训练参数数量和内存需求的方法,例如拥有1750亿参数的GPT-3。LoRA 通过冻结模型权重并引入可训练的低秩分解矩阵,减少了10,000倍的可训练参数&#xf…

HashMap常用方法及底层原理

目录 一、什么是HashMap二、HashMap的链表与红黑树1、数据结构2、链表转为红黑树3、红黑树退化为链表 三、存储(put)操作四、读取(get)操作五、扩容(resize)操作六、HashMap的线程安全与顺序1、线程安全2、…

云计算实训49——k8s环镜搭建(续2)

一、Metrics 部署 在新版的 Kubernetes 中系统资源的采集均使⽤ Metrics-server,可 以通过 Metrics 采集节点和 Pod 的内存、磁盘、CPU和⽹络的使⽤ 率。 (1)复制证书到所有 node 节点 将 master 节点的 front-proxy-ca.crt 复制到所有 No…

Linux进阶命令-top

作者介绍:简历上没有一个精通的运维工程师。希望大家多多关注作者,下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 经过上一章Linux日志的讲解,我们对Linux系统自带的日志服务已经有了一些了解。我们接下来将讲解一些进阶命令&am…

【计算机网络】初识网络

初识网络 初识网络网络的发展局域网广域网 网络基础IP地址端口号协议五元组协议分层OSI 七层模型TCP/IP五层模型封装和分用"客户段-服务器"结构 初识网络 网络的发展 在过去网络还没有出现的时候, 我们的计算机大部分都是独自运行的, 比如以前那些老游戏, 都是只能…

Chainlit集成Langchain并使用通义千问实现文生图网页应用

前言 本文教程如何使用通义千问的大模型服务平台的接口,实现图片生成的网页应用,主要用到的技术服务有,chainlit 、 langchain、 flux。合利用了大模型的工具选择调用能力。实现聊天对话生成图片的网页应用。 阿里云 大模型服务平台百炼 API…

1.SpringCloud与SpringCloud Alibaba

SpringCloud与SpringCloud Alibaba主要讲解的内容: 备注:黑色部分是springcloud社区原版,红色的是SpringCloud Alibaba 服务注册与发现 Consul Alibaba Nacos 服务调用和负载均衡 LoadBalancer OpenFeign 分布式事务 Alibaba Seata 服务熔…

批量插入insert到SQLServer数据库,BigDecimal精度丢失解决办法,不动代码,从驱动层面解决

概述 相信很多人都遇到过,使用sql server数据库,批量插入数据时,BigDecimal类型出现丢失精度的问题,网上也有很多人给出过解决方案,但一般都要修改应用代码,不推荐。 丢失精度的本质是官方的驱动有BUG造成…

机器学习特征-学习篇

一、特征概念 1. 什么是特征 特征是事物可供识别的特殊的征象或标志 在机器学习中,特征是用来描述样本的属性或观测值的变量。它们可以是任何类型的数据,包括数字、文本、图像、音频等。 作用: 特征是训练和评估机器学习模型的基础。好的特…

[基于 Vue CLI 5 + Vue 3 + Ant Design Vue 4 搭建项目] 09 集成 Ant Design Vue

我们要将 Ant Design Vue 集成到项目中 1.首先进入到我们的项目 2.然后使用下面的命令 npm i --save ant-design-vue解释一下这个命令: npm:npm 命令 i:install 的简写 –save:将其保存到 pagckage.json ant-design-vue&am…

PHP随时随地预订民宿酒店预订系统小程序源码

随时随地预订,民宿酒店预订系统让旅行更自由! 🌍 说走就走的旅行,从预订开始 旅行,总是让人心生向往,但繁琐的预订流程却常常让人望而却步。不过,现在有了“随时随地预订民宿酒店预订系统”&am…

centos7安装MySQL5.7.44

下载压缩文件 命令: #放到在/usr/local目录下 cd /usr/local #上传命令选择安装包 rz #解压缩包 tar -zxvf mysql-5.7.44-linux-glibc2.12-x86_64.tar.gz #给包重命名为mysql mv mysql-5.7.44-linux-glibc2.12-x86_64 mysql #查看mysql目录下有什么东西 [rootlocal…

【Python 数据分析学习】Pandas基础与应用(1)

题目 1 Pandas 简介1.1 主要特征1.2 Pandas 安装 2 Pandas中的数据结构2.1 Series 数据结构和操作2.1.1 Series的数据结构2.1.2 Seres的操作 2.2 DataFrame 数据结构和操作2.2.1 DataFrame 数据结构2.2.2 Dataframe 操作2.2.3 DateFrame 的特殊操作 2.3 Series 和 DataFrame 的…

JMeter 入门之远程启动,服务模式,多机联测,负载均衡测试

本文主要介绍 JMeter 远程启动及使用多节点完成大并发测试(负载均衡测试),主打一个压力山大,借用 黑神话:悟空 的技能来描述就是远程开大,释放猴子猴孙技能。 搜了一些 jmeter 的案例或教程,讲的…

Windows10 如何设置电脑ip

1、首先打开控制面板 或者使用WinR 输入control 找到网络和Internet 点击网络和共享中心 点击更改适配器设置 找到你要需要设置的网络,右键 如果你的网口特别多,不确定是哪一个,拔插一下看看哪个以太网的标志是断开状态就可以了 点击属性…

★ C++基础篇 ★ string类的实现

Ciallo&#xff5e;(∠・ω< )⌒☆ ~ 今天&#xff0c;我将继续和大家一起学习C基础篇第五章下篇----string类的模拟实现 ~ 上篇&#xff1a;★ C基础篇 ★ string类-CSDN博客 C基础篇专栏&#xff1a;★ C基础篇 ★_椎名澄嵐的博客-CSDN博客 目录 一 基础结构 二 迭代器 …

即插即用篇 | YOLOv8 引入组装式Transformer模块AssembleFormer | arXiv 2024

本改进已同步到YOLO-Magic框架! 摘要—早期检测和准确诊断可以预测恶性疾病转化的风险,从而增加有效治疗的可能性。轻微的症状和小范围的感染区域是一种不祥的警告,是疾病早期诊断的重中之重。深度学习算法,如卷积神经网络(CNNs),已被用于分割自然或医学对象,显示出有希…

JVM源码解析

一、java虚拟机概述 1. java程序的跨平台性 之前的话&#xff0c;通过Linux或者Windows开发&#xff0c;当需要跨平台时&#xff0c;程序不能运行。java出现后&#xff0c;产生了jvm&#xff0c;针对不同的操作系统&#xff0c;产生了不同的java虚拟机。 在Java虚拟机中执行…