C语言————结构体

          接下来我们来了解C语言中很重要的内容:结构体。虽然到现在我们可以创建常量,变量,数组,但是存储的都是相同类型的数据,如果我们需要写入不同数据类型的信息怎么办,例如常见的身份证上的信息,有身份证号,有地址,有名字,有照片。又比如一个学生的学习,有学号,姓名,年龄,等等。这样的话,如果我们还是以前那样一个数据创建一个的话,岂不是很麻烦,当我们需要将不同数据类型存储在一起的时候这就引出了 结构体。

       大家可以先看一下下面的图片,大概了解结构体长什么样子:

struct Stu
//struct创建结构体的必要前提
//stu结构体名字
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
};//结尾必须这样写
int main()
{
struct Stu s = { "张三", 20, "男", "20230818001" };//按照结构体成员的顺序初始化
printf("name: %s\n", s.name);
printf("age : %d\n", s.age);
printf("sex : %s\n", s.sex);
printf("id : %s\n", s.id);
return 0;
}

     这样大家大概知道结构体是什么样子了吧。

结构体的创建与初始化

        那接下来我们就正式的开始学习今天的主题吧,首先我们要学习如何创建结构体。但其实结构体的创建是很简单的,首先你需要在你要引用结构体的前面创建(这个肯定都能理解,毕竟要用肯定要有,才能使用)接着就是写出以下的内容:

7a11396ffc7e4893bcad2c978ea60966.png

        我们只需要依照上面的图片一样,先写出结构体的标志struct然后一个{}(注意必须在结尾的括号后添加一个;  表示结构体到这里就结束了),在{}中写入结构体(也就是需要的多种类型)。

       看了上面的内容大家应该知道结构体的创建了吧,接下来我们就学习如何将创建的结构体初始化。

bcab91a8f6cf4c63b2249c31b46ad303.png        这样大家应该了解的差不多了吧,将结构体的第一行抄下来后在后面再写一个名字后就可以结构体赋值了,但是需要注意的是赋值的顺序必须与创建结构体的顺序一样。

结构体可以不完全声明

       学习了上面的知识后,大家应该认为结构体都是这样的了吧,但其实嘞,结构体还有特殊的。大家可以看一下下面的代码,大家认为是否有问题。

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

        其实这些代码是没有问题的,开始我们讲了在struct后写的是结构体的名字,但是,大家知道现在名字可以改的吧。结构体有名字那么肯定也可以改吧。所以在结构体的最后的括号后再的话就可以重新命名。以上是结构体不完全声明。上⾯的两个结构在声明的时候省略掉了结构体标签(tag)。虽然都是没有错误的。匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使⽤⼀次。所以我们写结构体的时候尽量按规定创建结构体,不要炫技。
 

结构体里面包含另外一个结构体

        好了,当我们了解结构体不完全声明后的利害之处后。我们大家想一下结构体可不可以包含另外一个结构体,像数组那样。答案当然是肯定的,在一个结构体中可以包含另外一个结果体,并且可以同时对两个结构体初始化。

cffb150d1a56430c96f5aaad76faffba.png

        这里大家看到了吧,在一个结构体中引入另外一个结构体,首先需要将被包含的结构体写在包含结构体的前面。接着在包含结构体中写入结构体,在写被包含结构体的时候,需要将被包含结构体初始化的名字写出来。不知道大家是否了解了。

      那么接下来的结构体初始化的话,其实与我们平常的结构体初始化是差不多的,我们只需要在要初始化被包含结构体的时候,再写一个{},然后在其中初始化被包含结构体就可以了。

4b0eb6333ad0499580738ce976856482.png

结构体自引用

        好了,我们学习了结构体引用引用结构体,那我们可不可以结构体自己引用自己嘞。这就好比说,自己说自己好帅,自己一会就会捡到100块钱一样。那么结构体可以自引用吗?当然是可以的。

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

      大家觉得这个正确吗?如果正确的话。sizeof(struct Node)结果是什么样的嘞。如果真的去尝试的话,大家会发现,系统会报错的。因为⼀个结构体中再包含⼀个同类型的结构体变量,这样结构体变量的大⼩就会⽆穷的⼤,是不合理的。所以上面的引用是错误的。正确的自引用方法是:

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

       提前学习过的应该知道一个指令typedef(这是一个修改名字的指令)如:

     经过上面的操作后,我们就给int取了一个名字mun了,当然int还是可以继续使用的。那么我们可以将这个指令引入结构体中吗?答案是可以的,虽然可以使用但却很容易出现问题。比如大家可以看一下下面的图片大家思考一下下面的图片是否正确:

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

        大家认为这个代码是正确的吗?大家可以看到我在使用typedef的时候并未对结构体命名,那么这个结构体就是前面说的匿名结构体。Node是对前⾯的匿名结构体类型的重命名产⽣的,但是在匿名结构体内部提前使⽤Node类型来创建成员变量,所以这是不⾏的。

        那么解决犯法是怎么搞呢?其实我们只需要定义结构体不要使⽤匿名结构体了,那具体在结构体使用typedef的正确方法是什么样子的嘞:

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

结构体大小计算

      我们学习到现在已经掌握了结构体的基本使用了,那我们就进一步,来讨论如何计算结构体的大小。计算结构体大小的话,就不得不了解一个知识点:结构体的内存对齐。那么结构体内存对齐规则大致是下面这四条:

      我们直接举一个例子来分析吧,大家看一下下面的结构体,并且可以猜一下下面的结构体大小是多少?

       大家想到了吗,答案是24,大家可能会想啊,不就是1+8+1=10吗。怎么等于24啊。这可不是我瞎说的啊。大家先看一下代码运行下来是什么样子的

       是吧,结果就是24,那为什么会是24呢?结构体的对齐规则说过在结构体中用结构体成员中最大的当对齐数,如果大于了默认的对齐数的话,结构体对齐数就变成了默认数。我们看上面的代码,最大的是double(8)而默认数是也是8.那么这个对齐数不会改变。那么接下来我们来排一下,因为对齐规则说过,堆放都是从0开始,那么char一个字节,那么占第一个,但是对齐数个字节呀,1,2,3,4,5,6,7都不是8的倍数啊,所以double只有在第8个位置安放才符合规则。然后double本身8个字节那么现在就用了16个字节了,但有7个字节是空的,因为对齐规则跳过了。最后还有一个char,17.18.19.20.21.22.23也不是8的倍数,那么char只有在第24个字节的时候储存,那么现在就是24个字节,但是浪费了14个字节。大家可以看一下下面的图片

    这样大家是否了解了一些了,那我们再举一个例子:

        大家可以思考一下上面的结果是多少?因为这个与我们第一个讲的是差不多的只是换了个位置,但是结果却不一样哈。好了,答案是16。为什么嘞,我在给大家讲一下。1. 第一个是char类型,后面每个数据成员存储的起始位置要从该成员大小的整数倍开始算(也就是1的倍数,故char num2 的起始位置是1,double num3的起始位置是2)。\n2.判断当前总内存(10)是否是最大成员内存(8)的整数倍,如果不是需要补齐到满足最大成员内存的最小整数倍(2倍,16)。\n3.故double num3的起始位置修改为8,char num1,char num2,补齐到8个内存,故结构体所占内存为16。这次大家应该就知道了吧。

好了,我们再讲一个特殊一点的,嵌套结构体如何计算例如:

        规则中将嵌套结构体的对齐数,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。也就是说上面的对齐数是8,。如果s3的最大改为4,那么对齐数也会改变。那么这个代码结果是多少嘞?

       那我们分析一下,1.struct S1的内存为16(char b,int c一起补齐8个,共补3个)。\n2.S2中嵌套结构体S1(结构体成员要从其内部最大元素大小的整数倍地址开始存储),当前S1中最大元素大小为8,S2中double也是8,故S2最大对齐数是8的倍数\n3. char num补齐到8,结构体的总内存为(8+16+8=32)。好了那么大家应该知道如何计算结构体大小了吧

 为什么存在内存对齐?

1. 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于,为了访问未对⻬的内存,处理器需要作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数,那么就可以⽤⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两个8字节内存块中。
总体来说:结构体的内存对⻬是拿空间来换取时间的做法。反正呀,我个人认为知道有这个事情就可以了,大佬的事情我们不知道我们也不敢问。

 修改对齐数

#pragma pack,这个预处理指令,可以改变编译器的默认对⻬数。那我们也不废话直接上代码

     在代码的前面#pragma pack(),括号里面就是修改的对齐数。并且我们都知道做事要有始有终,我们既然修改对齐数,那么我们后面肯定要将对齐数改回来呀,所以我们在结构体末尾写上#pragma pack(),那么对齐数就变回去了。

结构体传参

      我们创建了结构体,使用肯定不能像开头那样直接打印一下,我们可以将结构体内容传出去,就是传参嘛。大家可以看一下下面的代码,虽然结构是一样的,但是大家认为哪一种更好一些嘞?

       肯定是选着第二个代码,为什么嘞?因为函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。如果传递⼀个结构体对象的时候,结构体过⼤,参数压栈的的系统开销⽐较⼤,所以会导致性能的下降。

总结:结构体传参的时候,要传结构体的地址。


位段

大家应该很少听过这个词汇吧。

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

上面的A就是⼀个位段类型。,那么位段是定义是什么嘞

我们如何计算位段大小,大家可以看到上面代码后面都跟着一个数字,那个就是给成员分配的比特位,比如说a就分配了5个比特位,b就是5,c是10,d是30。

位段的内存分配

1.位段的成员可以是 int unsigned int signed int 或者是 char 等类型
2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的⽅式来开辟的。
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使⽤位段。

 位段的跨平台问题


1. int位段被当成有符号数还是⽆符号数是不确定的。
2. 位段中最⼤位的数⽬不能确定。(16位机器最⼤16,32位机器最⼤32,写成27,在16位机器会
出问题。
3. 位段中的成员在内存中从左向右分配,还是从右向左分配,标准尚未定义。
4. 当⼀个结构包含两个位段,第⼆个位段成员⽐较⼤,⽆法容纳于第⼀个位段剩余的位时,是舍弃
剩余的位还是利⽤,这是不确定的。
总结:
跟结构相⽐,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。

位段的应⽤


下图是⽹络协议中,
IP数据报的格式,我们可以看到其中很多的属性只需要⼏个bit位就能描述,这⾥使⽤位段,能够实现想要的效果,也节省了空间,这样⽹络传输的数据报⼤⼩也会较⼩⼀些,对⽹络的畅通是有帮助的。

位段使⽤的注意事项


位段的⼏个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。内存中每个字节分配⼀个地址,⼀个字节内部的bit位是没有地址的。
所以不能对位段的成员使⽤&操作符,这样就不能使⽤scanf直接给位段的成员输⼊值,只能是先输⼊放在⼀个变量中,然后赋值给位段的成员。

struct A
{
int _b : 5;
int _c : 10;
int _d : 30;
};
int main()
{
struct A sa = {0};
scanf("%d", &sa._b);//这是错误的
//正确的⽰范
int b = 0;
scanf("%d", &b);
sa._b = b;
return 0;
}

       好了,以上就是鄙人想与大家分享的关于结构体的知识了,肯定还有很多不足甚至错误的地方,希望大家可以在评论区中写出来,以便改正!

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

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

相关文章

小程序框架(概念、工作原理、发展及应用)

引言 移动应用的普及使得用户对于轻量级、即时可用的应用程序需求越来越迫切。在这个背景下,小程序应运而生,成为一种无需下载安装、即点即用的应用形式,为用户提供了更便捷的体验。小程序的快速发展离不开强大的开发支持,而小程…

这一步一步爬的伤痕累累

一、网安专业名词解释 ① CTF CTF(Capture The Flag)中文一般译作夺旗赛,在网络安全领域中指的是网络安全技术人员之间进行技术竞技的一种比赛形式。CTF起源于1996年DEFCON全球黑客大会,以代替之前黑客们通过互相发起真实攻击进…

yolov5v7v8目标检测增加计数功能--免费源码

在yolo系列中,很多网友都反馈过想要在目标检测的图片上,显示计数功能。其实官方已经实现了这个功能,只不过没有把相关的参数写到图片上。所以微智启软件工作室出一篇教程,教大家如何把计数的参数打印到图片上。 一、yolov5目标检测…

【物联网应用案例】智能农业应用案例

随着物联网 (IoT) 的广泛应用,各种互联设备已经深度融入我们的生活,涵盖了健康与健身、家庭自动化、物流运输以及智慧城市和工业物联网等多个领域。因此,将物联网、联网设备和自动化技术应用于农业,是十分符合时代发展需求的&…

求字符串所有整数最小和 - 华为OD统一考试(C卷)

OD统一考试(C卷) 分值: 100分 题解: Java / Python / C 题目描述 1.输入字符串s输出s中包含所有整数的最小和,说明:1字符串s只包含a~z,A~Z,,-, 2.合法的整数包括正整数,一个或者多…

Mycat核心教程--Mycat 监控工具【四】

Mycat核心教程--Mycat 监控工具 九、Mycat 监控工具9.1.Mycat-web 简介9.2.Mycat-web 配置使用9.2.1.ZooKeeper 安装【上面有】9.2.2.Mycat-web 安装9.2.2.1.下载安装包9.2.2.2.安装包拷贝到Linux系统/opt目录下,并解压9.2.2.3.拷贝mycat-web文件夹到/usr/local目录…

QT摄像头采集

主界面为显示框&#xff0c;两个下拉框&#xff0c;一个是所有相机&#xff0c;一个是相机支持的分辨率 系统根据UI界面自动生成的部分不再描述&#xff0c;以下为其他部分源码 widget.h #include <QWidget> #include <QMouseEvent> class QCamera; class QCamer…

express+mysql+vue,从零搭建一个商城管理系统4--mysql数据库链接

提示&#xff1a;学习express&#xff0c;搭建管理系统 文章目录 前言一、创建express_service数据库二、安装mysql三、新建config文件夹四、新建config/db.js五、index.js引入db.js文件六、启动项目预览总结 前言 需求&#xff1a;主要学习express&#xff0c;所以先写service…

“智农”-农业一体化管控平台

大棚可视化|设施农业可视化|农业元宇宙|农业数字孪生|大棚物联网|大棚数字孪生|农业一体化管控平台|智慧农业可视化|智农|农业物联网可视化|农业物联网数字孪生|智慧农业|大棚三维可视化|智慧大棚可视化|智慧大棚|农业智慧园区|数字农业|数字大棚|农业大脑|智慧牧业数字孪生|智…

Presto简介、部署、原理和使用介绍

Presto简介、部署、原理和使用介绍 1. Presto简介 1-1. Presto概念 ​ Presto是由Facebook开发的一款开源的分布式SQL查询引擎&#xff0c;最初于2012年发布&#xff0c;并在2013年成为Apache项目的一部分&#xff1b;Presto 作为现在在企业中流行使用的即席查询框架&#x…

数仓项目6.0(二)数仓

中间的几步意义就在于&#xff0c;缓存中间处理数据样式&#xff0c;避免重复计算浪费算力 分层 ODS&#xff08;Operate Data Store&#xff09; Spark计算过程中&#xff0c;存在shuffle的操作&#xff0c;而shuffle会将计算过程一分为二&#xff0c;前一阶段不执行完&…

word文档自动调节图片大小-宏制作

word文档自动调节图片大小-宏制作 1. 首先创建一个空白word文档 当然也可以是任意word文档&#xff0c;这里方便演示&#xff0c;所以创建一个空白word文档。 2. 在菜单列表找到【视图】- 【宏】 3. 点击宏&#xff0c;录制一个宏 4. 录制宏起个名字&#xff0c;然后确定 5…

如何选择科技公司或者技术团队来开发软件项目呢

最近有客户问我们为什么同样软件项目不同公司报价和工期差异很大&#xff0c;我们给他解释好久才讲清楚&#xff0c;今天整理一下打算写一篇文章来总结一下&#xff0c;有需要开发朋友可以参考&#xff0c;我们下次遇到客户也可以直接转发文章给客户自己看。 我们根据我们自己报…

shardingsphere的IN查询导致数据库CPU打满

背景 上游系统进行并发压测批量查询接口&#xff0c;每次按照50个ID进行IN查询用户信息&#xff08;10个库20张表&#xff09;&#xff0c;在并发量并不大的情况导致数据库CPU达到100%并且重启。 ShardingSphere的版本为4.0.0 分析ShardingSphere的IN查询 通过下面测试发现sh…

C语言第三十二弹---自定义类型:联合和枚举

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 目录 1、联合体 1.1、联合体类型的声明 1.2、联合体的特点 1.3、相同成员的结构体和联合体对比 1.4、联合体大小的计算 1.5、联合的⼀个练习 2、枚举类型 …

【Mysql】Navicat数据库勿删了mysql.infoschema@localhost,导致打不开数据库,如何修改

运行报错如下&#xff1a; 1449 . The user specified as a definer (mysql.infoschemaocalhost) does not exist该方法不需要重启mysql&#xff0c;或者重装&#xff1b;仅需要恢复删除的mysql.infoschemalocalhost用户 一、登录建立用户 mysql -uroot -pxxxxxx密码二、建立…

【Java多线程】面试常考——锁策略、synchronized的锁升级优化过程以及CAS(Compare and swap)

目录 1、锁的策略 1.1、乐观锁和悲观锁 1.2、轻量级锁和重量级锁 1.3、自旋锁和挂起等待锁 1.4、普通互斥锁和读写锁 1.5、公平锁和非公平锁 1.6、可重入锁和不可重入锁 2、synchronized 内部的升级与优化过程 2.1、锁的升级/膨胀 2.1.1、偏向锁阶段 2.1.2、轻量级锁…

[CISCN 2019华东南]Web11

打开题目 看到xff就应该想到抓包 看回显也是127.0.0.1&#xff0c;我们盲猜是不是ssti模板注入 输入{{7*7}}显示49 可以看的出来flag在根目录下 输入{system(‘cat /flag’)} 得到flag 知识点&#xff1a; 漏洞确认 一般情况下输入{$smarty.version}就可以看到返回的smarty…

个人玩航拍,如何申请无人机空域?

我们在《年会不能停》一文中&#xff0c;有分享我们在西岭雪山用无人机拍摄的照片和视频&#xff0c;有兴趣可以去回顾。 春节的时候&#xff0c;趁着回老家一趟&#xff0c;又将无人机带了回去&#xff0c;计划拍一下老家的风景。 原本以为穷乡僻壤的地方可以随便飞&#xf…

从新手到专家:AutoCAD 完全指南

&#x1f482; 个人网站:【 海拥】【神级代码资源网站】【办公神器】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交流的小伙伴&#xff0c;请点击【全栈技术交流群】 引言 AutoCAD是一款广泛用于工程设计和绘图的…