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://www.mzph.cn/news/707251.shtml

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

相关文章

springboot+vue+mysql+easyexcel实现文件导出+导出的excel单元格添加下拉列表

Excel导出 EasyExcel官方文档 官方文档本身写的非常详细,我就是根据官方文档内的写Excel里web中的写实现的导出 后端 对象 需要写一个实体类 其中涉及到一些用到的EasyExcel的注解 ColumnWidth(20) 列宽设为20,自定义的,放在实体类上面是…

Postgresql中触发器的使用

在PostgreSQL中,触发器是一种特殊类型的函数,它会自动在数据库上执行特定操作之前或之后触发。这些操作通常是INSERT、UPDATE或DELETE语句。触发器可以用来执行数据校验、自动更新或维护表之间的关联。 触发器组件 触发器函数:这是实际执行…

binwalk安装记录和burpsuite安装记录

我的虚拟机环境是Ubuntu20.04 python有2.7的和3.8的 [[#binwalk|binwalk]] [[#binwalk#pip|pip]][[#binwalk#安装 sasquatch|安装 sasquatch]][[#binwalk#安装 jefferson|安装 jefferson]][[#binwalk#安装 ubi_reader|安装 ubi_reader]][[#binwalk#安装 yaffshiv|安装 yaffshi…

JavaWeb——005 -- 请求响应 分层解耦(Postman、三层架构、IOC、DI、注解)

目录 一、请求 1、Postman(接口测试工具) 1.1、介绍 ②、安装 2、简单参数 1.1、原始方式 1.2、SpringBoot方法 ③、小结 3、实体参数 3.1、简单实体对象 3.2、复杂实体对象 3.3、小结 4、数组集合参数 ①、数组​编辑 ②、集合 ③、小结…

Alist访问主页显示空白解决方法

文章目录 问题记录问题探索和解决网络方案问题探究脚本内容查看 最终解决教程 问题记录 访问Alist主页显示空白,按F12打开开发人员工具 ,选择控制台,报错如下 index.75e31196.js:20 Uncaught TypeError: Cannot assign to read only property __symbo…

python|闲谈2048小游戏和数组的旋转及翻转和转置

目录 2048 生成数组 n阶方阵 方阵旋转 顺时针旋转 逆时针旋转 mxn矩阵 矩阵旋转 测试代码 测试结果 翻转和转置 2048 《2048》是一款比较流行​的数字游戏​,最早于2014年3月20日发行。原版2048由Gabriele Cirulli首先在GitHub上发布,后被移…

【Day59】代码随想录之动态规划_583两个字符串的删除操作_72编辑距离

文章目录 动态规划理论基础动规五部曲:出现结果不正确: 1. 583两个字符串的删除操作2. 72编辑距离 动态规划理论基础 动规五部曲: 确定dp数组 下标及dp[i] 的含义。递推公式:比如斐波那契数列 dp[i] dp[i-1] dp[i-2]。初始化d…

选择排序的简单介绍

选择排序是一种简单直观的排序算法,其原理如下: 1. 遍历数组,找到最小(或最大)的元素,并将其与数组的第一个元素交换位置。 2. 接着在剩下的元素中找到最小(或最大)的元素&#xff…

Uniapp在IOS系统打包测试流程

大家好我是咕噜美乐蒂,很高兴又和大家见面了!UniApp 是一种基于 Vue.js 的跨平台应用开发框架,可以用于快速构建同时支持多个平台(包括iOS、Android、Web 等)的应用程序。在 iOS 系统上打包和测试 UniApp 应用的流程可…

园区水费收费管理系统

园区水费收费管理系统是专为园区或小区的水费管理而设计的系统,旨在提高水费收费效率、精准监测水费使用情况,简化管理流程,为园区管理方和居民提供便捷、高效的水费管理解决方案。该系统结合了数字化技术和智能化管理手段,通过线…

『NLP学习笔记』图解 GPT-2(可视化 Transformer 语言模型)

图解 GPT-2(可视化 Transformer 语言模型) 文章目录 一. GPT-2和语言模型1.1. 什么是语言模型1.2 Transformer的语言模型1.3 和BERT的不同1.4 Transformer 组件的演变1.4.1 encoder组件1.4.2 decoder组件1.4.3 只有decoder组件的decoder模块1.5 GPT-2内部结构1.6 GPT-2内部结构…

P1090 [NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G python解法

P1090 [NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) nint(input()) arrlist(map(int,input().split())) arr.sort() sumarr[0] total_sum0 #一开始以为单纯排列就行了,然后将之前累计的时间求和 for i…

非同质化权益(NFR):重塑当代商业市场的新范式

每天五分钟讲解一个互联网知识,大家好我是啊浩说互联网 随着区块链技术的日益成熟和数字资产的普及,非同质化权益(Non-Fungible Rights,简称NFR)开始崭露头角,并在当代商业市场中引发了一场深刻的变革。NFR…

pg_rman部署及使用

PG_RMAN部署及使用 PGSQL推出开源备份工具pg_rman,类似于oracle的rman备份策略,实现了全量、增量和归档等多重备份方式,可以很灵活的管理PGSQL数据库的备份,支持在线和基于PITR的备份恢复方式。 1、使用postgres用户部署 [post…

动态规划-状态转移(O(n))

获取生成数组中的最大值 1.题目 2.思路 其实只要看透该题的本质 并不难,应用动态规划,题目就已经给出了状态方程的式子。 首先规定好maxn的大小,防止溢出。定义nums数组。定义一个函数,写入状态方程式子。最后定义一个变量与数…

【数据结构】数组

第一章、为什么数组的下标一般从0开始编号 提到数组,读者肯定不陌生,甚至还会很自信地说,数组很简单。编程语言中一般会有数组这种数据类型。不过,它不仅是编程语言中的一种数据类型,还是基础的数据结构。尽管数组看起…

Openstack云计算架构及前期服务搭建

openstack介绍 Openstack是一个开源的云计算管理平台项目,由几个主要的组件组合起来完成具体工作,支持几乎所有的云环境,项目目标是提供实施简单、可大规模扩展、丰富、标准统一的云计算管理平台 ----百度百科 Openstack是一个云操作系统&a…

STM32开发(屏幕驱动ST7735S-SPI方式)用RT-Thread驱动测试

前言 使用ST7735S屏幕驱动,方便学习LVGL通过结构体的方式来管理相关函数和变量通讯协议和硬件驱动层进行解耦 驱动 配置(用于对接硬件) st7735s_conf.h // // Created by shchl on 2024/2/28. //#ifndef STM32F407V4T6_RTOS_ST7735S_CON…

css常用的选择器介绍

CSS(层叠样式表)选择器是CSS规则的一部分,它用于选择和定位网页上的元素,以便将样式应用到这些元素上。CSS选择器的种类繁多,每种选择器都有其特定的用途、特点和效率。在这篇文章中,我们将讨论一些常用的C…

Spring Boot项目如何快速从零开始打造一个属于自己的RPC框架

一、前言 在平时Spring Boot项目开发过程中,我们进行远程服务调用大都采用@RestController + @RequestMapping相关注解发布接口,使用OpenFeign组件进行微服务之间调用。这套技术架构已经足够完善了,当然没有什么问题,但是作为一个开发者,老是用一套框架天天写代码,不免有…