编程之路,从0开始:结构体详解

目录

前言

正文

1、结构体引入

2、结构体的声明

3、typedef

4、结构体的匿名声明

5、结构的自引用

(1)链表

(2)自引用

6、结构体内存对齐

(1)对齐规则

(2)题目

(3)为什么存在内存对齐?

(4)默认对齐数

7、结构体实现位段

(1)什么是位段

(2)位段的跨平台问题

(3)位段的应用

总结


 

前言

        Hello大家好!很高兴我们又见面啦!给生活添点passion,开始今天的编程之路!

        今天我们来学习C语言中非常重要的一部分:结构体。


正文

1、结构体引入

        首先,什么是结构体?结构体是我们的自定义类型之一。我们在之前简单提到过结构体,这里我们再复习一下。我们在描述一个物品时,可能需要从多个角度进行描述,比方说描述一个人,可能由年龄,名字,性别,身高等等多个方面来描述。那么这时我们就可以声明一个结构体,用来存放描述这个学生的各种信息,然后再把值赋予这个结构体里的各种类型信息。

2、结构体的声明

        现在我们声明一个结构体:

struct stu 
{char name[20];char sex[10];int age;
};

        其中,stu是我们的标签名。

        现在我们给结构体中放上东西:

#include "stdio.h"
struct stu 
{char name[20];char sex[10];int age;
}s3;//全局变量
struct stu s4;//全局变量
int main()
{struct stu s1 = { "张三","男",20 };//创建局部变量struct stu s2 = { .name = "李四",.sex = "男",.age = 19 };printf("%s\n", s1.name);printf("%s\n", s1.sex);printf("%d\n", s1.age);printf("%s\n", s2.name);printf("%s\n", s2.sex);printf("%d\n", s2.age);return 0;
}

        结构体声明就相当于规定这个自定义类型里面包含什么数据,就比方说我们的int,他里面放的就是整型,这是C语言规定的。结构体也是一样,只不过是我们得通过声明自己规定里面有什么。

        这里我们怎么理解呢?我们可以把声明想象成一个学生的个人档案,里面有很多很多表格。这个表格填什么呢?这就需要声明来确定填入哪些数据。然后我们把这个档案起个名字,比方说是张三的档案,然后我们把档案交给张三,让他去填他的个人信息。那这个给档案起名的过程放到结构体中就是给结构体起个名字。

        现在我们在上面的代码介绍了三种给档案起名字的方法:其中有两个是定义全局变量,有一个是局部变量。

        填入数据的方式我们也介绍了两种,如以上代码所示。

        打印方法如以上代码所示,输出结果:

b72fb1b939c54cc09dd2fe90c6592a1f.png

3、typedef

        那现在我说你这太麻烦了,放东西的时候得写struct,再写个标签名,再写个名字,可读性太差是不是?

        这时候就用到我们的关键字typedef了:typedef可以用于为基本数据类型、结构体、联合体、枚举等复杂数据类型创建新的名称。

        当然了,联合体和枚举我们以后会讲,这里我们先用他给结构体重命名:

typedef struct stu
{char name[20];char sex[10];int age;
}student;//全局变量
struct stu s4;//全局变量
int main()
{student s1 = { "张三","男",20 };//创建局部变量student s2 = { .name = "李四",.sex = "男",.age = 19 };printf("%s\n", s1.name);printf("%s\n", s1.sex);printf("%d\n", s1.age);printf("%s\n", s2.name);printf("%s\n", s2.sex);printf("%d\n", s2.age);return 0;
}

 

        现在原代码所有的struct stu都可以用student来代替。当然了typedef还有可以以其他形式给别的数据类型起别名,这里由于我们主要是来讲结构体我们只介绍怎么给结构体起别名,这也是最主要的用法。 

4、结构体的匿名声明

        什么是匿名声明呢?就是我们在声明的时候不给他标签名。

#include "stdio.h"
#include<stdlib.h>struct 
{char name[20];char sex[10];int age;
}s1;//全局变量
int main()
{strcpy(s1.name,"张三");strcpy(s1.sex, "男");s1.age = 20;printf("%s\n", s1.name);printf("%s\n", s1.sex);printf("%d\n", s1.age);return 0;
}

        我们发现尽管不给他标签名,也可以正常使用。只是全局变量在没标签名在赋值有些不同寻常。

        但是这样有一个非常严重的问题,就是在我们的结构体不重命名的情况下只能使用一次,意思就是说我们只能定义s1这一个结构体变量!

比如以下代码是无法实现的:

#include "stdio.h"
#include<stdlib.h>struct 
{char name[20];char sex[10];int age;
}s1;//全局变量
int main()
{struct  s2 = { "sss","ddd",10 }; //报错,无法实现return 0;
}

        其次,这种匿名结构体不能实现如下代码:

#include "stdio.h"
#include<stdlib.h>struct 
{char name[20];char sex[10];int age;
} * s1;//全局变量struct {char name[20];char sex[10];int age;} p;int main()
{  s1 = &p;//报错  return 0;
}

        如果你这么想了,首先我得夸你聪明。我们把结构体声明为一个指针对象,在放入另一个结构体的地址不就好了吗(两个结构体对应的数据类型必须相同),但是如上代码无法打印,为什么?

我们看一看他打印的的错误信息是:

710e9f41e7e049f3ac774400231a5c28.png

        也就是说,我们系统仍然认为这两个结构体的类型是不同的!

        这里其实我们可以想一下,是不是struct这个东西根本就不能作为指针呢?那么我们是不是可以给这个结构体里的每一个元素,都对应的指向另一个结构体中的每一个元素,然后再打印呢?

        我把这个问题留给各位思考。

        实际上,匿名指针有什么优势呢?什么优势也没有!反而会带来很多的问题!所以我们尽量不要使用匿名指针。其实一些细节问题,也完全没必要扣的很细。

5、结构的自引用

        在结构中包含一个类型为该结构本身的成员是否可以呢?

(1)链表

        首先我们来介绍一下链表。

3f1230b5d6434403b478619d2be4a72e.png

        我们把一个字符串放到一个char类型数组里,我们打印的时候只需要把这个数组的首地址放到printf里,他就能顺着打印出来。这是因为我们的每个字母所占的内存块都是连着的!

        那么如果内存块没连着,该怎么办呢?这也很容易想到,我们只需要把每块内存手动连接起来不就好了吗?(如图下面的那种情况)。

        这时,每个块块我们叫他一个节点,那么我们怎么通过节点与节点之间进行连接呢?

        我在这里也不卖关子了,直接给大家写出来:

struct node
{char a;struct node* next;
};

        我们在填入数据的时候,把下一个节点的地址填入,那么我们访问的时候是不是就可以通过访问第一个节点来访问第二个节点了呢?

注:链表这部分在以后我们还会详细讲解。

(2)自引用

        像这种在结构中包含一个类型为该结构本身的成员就叫自引用。

        但需要注意的是,自引用无法通过匿名结构体实现!

6、结构体内存对齐

        这一部分可以说是很有意思哈~

        我们先想想,int 型占4个字节,char占一个字节,double占八个字节,那结构体占几个字节呢?我们声明的时候也没说它占几个字节啊?实际上,系统会自己计算它占几个字节,系统计算的规则就是我们的内存对齐规则。

(1)对齐规则

        内存对齐规则

  1. 结构体内的第一个变量的地址偏移量为0:结构体的第一个成员总是从偏移量为0的地址开始存储。

  2. 结构体内的其他变量的起始地址:其他变量的起始地址应为对齐数整数倍的地址处。对齐数等于编辑器默认对齐数(VS默认为8)与该变量大小的较小值。

  3. 结构体整体对齐:结构体的总大小应为结构体中最大对齐数的整数倍。

  4. 嵌套结构体的对齐:如果结构体中包含嵌套的结构体,则子结构体的最大数据类型作为子结构体的内存对齐标准。

        我们通过小题来理解一下

(2)题目

#include "stdio.h"
#include<stdlib.h>
struct s1
{char a;int i;char c;
};int main()
{printf("%d", sizeof(struct s1));return 0;
}

        我们在不知道内存对齐之前可能以为他是占1+2+1=5个字节,但实际上,它占12个字节。为啥呢?首先根据对齐规则,我们把第一个char类型放在最开始。

        然后就是int型,但是int型根据对齐规则应该在对齐数的整数倍处开始,也就是第4个字节的位置开始存放。

        接着存放char型,占一个字节,理论来说9个字节就够了啊?但是根据对齐规则来说,结构体总大小是结构体中最大对齐数的整数倍,当然了,这个数应该比9大,在这里应该是12,他是最大对齐数4的倍数。

0d89fa63a2de4327a3800136f7633fb1.png

图片理解:

e2ff35ee353e44f0a4f663de81786d0c.png

        那么我们空白的地方就浪费掉了!我们想想,如果把两个char类型的数据放在前,int类型数据放在后面,是不是能少浪费一些内存块呢?

代码:

#include "stdio.h"
#include<stdlib.h>
struct s1
{char a;char c;int i;
};int main()
{printf("%d", sizeof(struct s1));return 0;
}

输出:

dfe65d9f017546cc80ab877328da27df.png

图片理解:

810b2baa7ef04e37951baf102d2989e9.png

下面我们来看这道题:

#include "stdio.h"
#include<stdlib.h>
struct s2
{double i;char z;int w;
};
struct s1
{char a;//1struct s2 s;//16double i;//8
};int main()
{printf("%d", sizeof(struct s1));return 0;
}

        先分析一下,s2占用16个字节,然后再看s1,a占一个字节,由于struct s2 s的字节数为16大于默认对齐数8,所以在这struct s2 s的对齐数为8,他从第八个字节开始放入。接着再放8个字节的double类型。输出结果为32:

a7d4444b54f04d16a55eaaa941372e99.png

图片理解:

e9785a5eb929439c99b9f5952cec68b5.png

(3)为什么存在内存对齐?

        比方说现在一个处理器每次读取四个字节,倘若没有内存对齐,那下图这种情况读取的时候就要复杂一些

a7ba6dc9c68841f5bedccbcf0341838d.png

        我们第一次读取四个字节,只能读char和四分之三个int类型,第二次读取才能读完int类型。也就是说当我们只读int时需要读取两次!这就无疑给系统增加了计算时间和计算复杂度。这就是内存对齐存在的原因。

(4)默认对齐数

        我们可以通过#pragma修改默认对齐数:

#include "stdio.h"
#pragma pack(1)
struct s2
{double i;char z;int w;
};
struct s1
{char a;//1struct s2 s;//16double i;//8
};int main()
{printf("%d", sizeof(struct s1));return 0;
}

7、结构体实现位段

(1)什么是位段

       其实就是修改结构体成员所占内存大小。

       但需要注意的是,位段的成员必须是int, unsigned int ,signed int。

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

#include "stdio.h"
struct A
{int a : 2;int b : 5;int c : 10;int d : 30;
};
int main()
{struct A a = { 0 };a.a = 10;a.b = 20;a.c = 5;a.d = 6;printf("%d", sizeof(struct A));return 0;
}

        我们乍一看,2+5+10+30=47,那不应该是六个字节吗?这是怎么回事?

        但实际上,位段的空间上是按照需要以4个字节( int )或者1个字节( char )的⽅式来开辟的,也就是说,4个字节32bit,存不下47个,那么就需要64个bit,也就是8字节才能存下。换句话说,字节数一定是4的整数倍,因为我们这里a b c d都是int类型的。

        那我们具体到底是怎么开辟的呢?到底是怎么放入数据的呢?别急,我们一点点来看

69643bf867ae442999fc24abc8e26f1f.png

 

       现在我把上面代码开辟的内存拿出来了(我把后面四个字节省略了),首先开辟的原则是从右往左开辟,我们依次开辟了2 5 10个比特。剩下的30比特由于装不下了,所以放在了后面的四个字节里。

       现在我们存入数据,第一个位置存入10,10的二进制补码是1010,现在问题来了,你第一个位置就两位啊,这怎么存?就两位那就只能存两位了,我们存入的是后边的10。同理,剩下的数据也是如此存入。

       也就是说,如果我们开辟空间不当的话,数据有可能会出现错误!所以说位段使用的时候还得多加小心啊!

(2)位段的跨平台问题

       位段固然可以节省一些空间,比如在结构中,由于对齐规则,我们不可能让a,b,c这三个空间放到一个int中挨着存放数据。

       但是位段其实存在一些问题:

1、int位段被当成有符号数还是无符号数是不确定的

2、位段中最大位的数目不能确定。

3、位段中的成员在内存中从左向右分配还是从右向左分配,标准尚未定义。当然在vs2022中是从右向左分配。

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

也就是说,位段的使用可以节省空间,但是有跨平台的问题出现。有可能我们换个编译器就达不到我们想要的结果了。

(3)位段的应用

        1、处理位操作‌:位段使得对位的操作更加方便和高效。例如,在操作系统和TCP/IP协议中,位段可以用来处理各种标志位和状态位,使得代码更加简洁和高效‌。

        2、定义复杂的数据结构‌:位段可以用来定义复杂的数据结构,特别是那些需要精确控制存储空间的数据结构。例如,在嵌入式系统中,常常需要精确控制硬件寄存器的每一位,位段提供了这种能力‌。

        3、优化存储‌:在需要存储大量标志或状态信息时,使用位段可以大大减少所需的存储空间。例如,在处理大量开关状态时,每个开关只需要一位,使用位段可以极大地节省空间‌。


总结

       大家也发现了这一篇很长,这是因为结构体这一部分很重要。他和指针一样,对于今后的学习有很大的影响,所以希望大家能够掌握。

       好了,今天的内容就分享到这。制作不易,希望各位老铁三连一波支持一下,我会持续更新c/c++/算法相关知识!期待我们的下次见面。

 

 

 

 

 

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

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

相关文章

Flink监控checkpoint

Flink的web界面提供了一个选项卡来监控作业的检查点。这些统计信息在任务终止后也可用。有四个选项卡可以显示关于检查点的信息:概述(Overview)、历史(History)、摘要(Summary)和配置(Configuration)。下面依次来看这几个选项。 Overview Tab Overview选项卡列出了以…

04-转录组下游分析-标准化、聚类、差异分析

准备工作 1.数据标准化 标准化前需要进行数据预处理 过滤低表达的基因&#xff0c;并检查是否有异常样本 以下是常见的几种过滤方式&#xff08;过滤的标准都可以自己调整&#xff09; 1&#xff1a;在至少在75%的样本中都表达的基因&#xff08;表达是指在某个样本中count值…

常见网络厂商设备默认用户名/密码大全

常见网络厂商的默认用户名/密码 01 思科 (Cisco) 设备类型&#xff1a;路由器、交换机、防火墙、无线控制器 默认用户名&#xff1a;cisco 默认密码&#xff1a;cisco 设备类型&#xff1a;网管型交换机 默认用户名&#xff1a;admin 默认密码&#xff1a;admin 02 华…

Spring 与 Spring MVC 与 Spring Boot三者之间的区别与联系

一.什么是Spring&#xff1f;它解决了什么问题&#xff1f; 1.1什么是Spring&#xff1f; Spring&#xff0c;一般指代的是Spring Framework 它是一个开源的应用程序框架&#xff0c;提供了一个简易的开发方式&#xff0c;通过这种开发方式&#xff0c;将避免那些可能致使代码…

【热门主题】000055 网络安全:构筑数字时代的坚固防线

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 【热…

鸿蒙中位置权限和相机权限

1.module.json5中添加相关权限和string.json中配置信息 2. import { hilog } from kit.PerformanceAnalysisKit; import { TAG } from ohos/hypium/src/main/Constant; import { bundleManager, common } from kit.AbilityKit; import { abilityAccessCtrl } from kit.Ability…

2024.6使用 UMLS 集成的基于 CNN 的文本索引增强医学图像检索

Enhancing Medical Image Retrieval with UMLS-Integrated CNN-Based Text Indexing 问题 医疗图像检索中&#xff0c;图像与相关文本的一致性问题&#xff0c;如患者有病症但影像可能无明显异常&#xff0c;影响图像检索系统准确性。传统的基于文本的医学图像检索&#xff0…

H.264/H.265播放器EasyPlayer.js网页直播/点播播放器关于播放的时候就有声音

EasyPlayer.js H5播放器&#xff0c;是一款能够同时支持HTTP、HTTP-FLV、HLS&#xff08;m3u8&#xff09;、WS、WEBRTC、FMP4视频直播与视频点播等多种协议&#xff0c;支持H.264、H.265、AAC、G711A、Mp3等多种音视频编码格式&#xff0c;支持MSE、WASM、WebCodec等多种解码方…

Redis 的代理类注入失败,连不上 redis

在测试 redis 是否成功连接时&#xff0c;发现 bean 没有被创建成功&#xff0c;导致报错 根据报错提示&#xff0c;需要我们添加依赖&#xff1a; <dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId>&l…

Prometheus结合K8s(一)搭建

公司之前K8s集群没有监控&#xff0c;top查看机器cpu使用率很高&#xff0c;为了监控pod的cpu和内存&#xff0c;集群外的mysql资源&#xff0c;初步搭建了Prometheus监控系统 提前准备镜像 docker.io/grafana/grafana 10.4.4 docker.io/prom/prometheus v2.47.2 docker.io/…

Vscode/Code-server无网环境安装通义灵码

Date: 2024-11-18 参考材料&#xff1a;https://help.aliyun.com/zh/lingma/user-guide/individual-edition-login-tongyi-lingma?spma2c4g.11186623.0.i0 1. 首先在vscode/code-server插件市场中安装通义插件&#xff0c;这步就不细说了。如果服务器没网&#xff0c;会问你要…

【划分型DP-约束划分个数】力扣813. 最大平均值和的分组

给定数组 nums 和一个整数 k 。我们将给定的数组 nums 分成 最多 k 个非空子数组&#xff0c;且数组内部是连续的 。 分数 由每个子数组内的平均值的总和构成。 注意我们必须使用 nums 数组中的每一个数进行分组&#xff0c;并且分数不一定需要是整数。 返回我们所能得到的最…

IDEA:2023版远程服务器debug

很简单&#xff0c;但是很多文档没有写清楚&#xff0c;wocao 一、首先新建一个远程jvm 二、配置 三、把上面的参数复制出来 -agentlib:jdwptransportdt_socket,servery,suspendn,address5005 四、然后把这串代码放到服务器中 /www/server/java/jdk1.8.0_371/bin/java -agentl…

centos安装jenkins

本机使用虚拟机centos 7.9.2009 安装gitlab&#xff0c;本机的虚拟机ip地址是 192.168.60.151&#xff0c; 步骤记录如下 1、下载jenkins&#xff0c;安装jenkins之前需要安装jdk jdk和jenkins的版本对应关系参考&#xff1a;Redhat Jenkins Packages Index of /redhat-stable…

蜀道山CTF<最高的山最长的河>出题记录

出这道题的最开始感觉就是,因为现在逆向的形式好多,我最开始学习的时候,经常因为很多工具,或者手段完全不知道,就很懵逼,很多师傅都出了各种类型的,我就想着给以前的"自己"出一道正常exe,慢慢调的题,为了不那么简单,我就选择了C(究极混淆,可能比rust好点),让大家无聊…

中伟视界:AI智能分析算法如何针对非煤矿山的特定需求,提供定制化的安全生产解决方案

非煤矿山智能化改造&#xff0c;除了政策文件&#xff0c;上级监管单位需要安装的AI智能分析算法功能之外的&#xff0c;矿方真正关心的&#xff0c;能解决矿方安全生产隐患的AI智能分析算法功能有哪些呢&#xff1f; 经过与矿方的现场交流沟通&#xff0c;收集第一现场人员对安…

如何生成python项目需要的最小requirements.txt文件?

今天咱们来聊聊 Python 项目中如何生成一个“最小的” requirements.txt 文件。我们都知道&#xff0c;当我们开发一个 Python 项目的时候&#xff0c;很多时候都会在一个虚拟环境中进行&#xff0c;这样一来&#xff0c;就能避免不同项目之间的依赖冲突。 可有时候&#xff0c…

每日论文22-24ESSERC一种54.6-65.1GHz多路径同步16振荡器

《A 54.6-65.1 GHz Multi-Path-Synchronized 16-Core Oscillator Achieving −131.4 dBc/Hz PN and 195.8 dBc/Hz FoMT at 10 MHz Offset in 65nm CMOS》24欧洲固态 本文是在60GHz 16核VCO的工作&#xff0c;主要亮点在于每一组中四个VCO之间的三路同步拓扑结构&#xff0c;有…

web——upload-labs——第十一关——黑名单验证,双写绕过

还是查看源码&#xff0c; $file_name str_ireplace($deny_ext,"", $file_name); 该语句的作用是&#xff1a;从 $file_name 中去除所有出现在 $deny_ext 数组中的元素&#xff0c;替换为空字符串&#xff08;即删除这些元素&#xff09;。str_ireplace() 在处理时…

网络安全之国际主流网络安全架构模型

目前&#xff0c;国际主流的网络安全架构模型主要有&#xff1a; ● 信息技术咨询公司Gartner的ASA&#xff08;Adaptive Security Architecture自适应安全架构&#xff09; ● 美国政府资助的非营利研究机构MITRE的ATT&CK&#xff08;Adversarial Tactics Techniques &…