结构体(C语言进阶)(一)

news/2025/3/15 5:15:34/

目录

前言

1、结构体声明

1.1 结构体基本概念

1.2 结构体声明

1.3 特殊的结构体声明

1.3.1 匿名结构体声明

1.4 结构体自引用

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

1.6 结构体内存对齐

1.7 修改默认对齐数

1.8 结构体传参

总结


前言

        C语言除了有其内置类型,还有自定义类型。因为在实际运用中,我们可能会遇到一类数据,它的内容是由多种数据组成的,例如:一个学生的个人信息,它就包含了姓名、年龄、学号等等。这些数据并不是统一的类型,因此不能用C语言本身的数组去定义,那如果我们需要将其放在一个变量中时,我们就可以运用到结构体了。结构体的出现就是用来处理这样的内容数据不统一的变量,这给了编程者很大的操作空间,使得编程更加灵活自由。

        接下来我们就详细介绍一下结构体吧。

1、结构体声明

1.1 结构体基本概念

        结构体是一些值得集合,这些值称为成员变量。结构体的每个成员可以是不同类型的变量。类比数组,数组的成员变量的类型必须相同,而结构体没有要求。

1.2 结构体声明

        举个例子:

        

        这是一个“学生”结构体的定义,它的类型为struct stu,struct是结构体声明的前缀,stu是自己定义的类型名,因为是包含“学生”信息的结构体,因此我们用来stu。定义好类型名之后,我们用一个大括号阔住接下来的内容,并且在大括号后面加上“;”。然后我们来看大括号里面的内容,上面这个例子我大括号里面放了了四个变量,分别用来存放:姓名;年龄;性别;学号。如果你想定义其他的也是可以的,内容是可以自己灵活改变的。这样,我们就算是定义好了一个结构体类型,它可以用来表示学生的基本信息。

        注意,上面仅只是建立了一个类型,还没有正真建立结构体变量。相当于我们只是建立了一个类似于:int;float;double这样的东西,后面还需要用具体的变量去承载。类比int,我们定义int类型的变量,还得想一个变量名,比如“p”,那我们定义方式就是:int p=0;那这里p就是一个整型的变量。同理结构体也一样,上面的操作只是相当于写了一个“int”,还需要想一个变量名,假如“p”,定义时像这样:struct stu p={ 0 };这样,才算定义出了一个结构体变量p。

        例如:

        这是两种声明方法,可以在结构体建立时就声明变量p1,也可先定义类型,后再声明p2。这两种方法的区别是,前者定了全局变量,后者定义的是局部变量。

1.3 特殊的结构体声明

1.3.1 匿名结构体声明

        例如:

        声明结构体类型时,没有名字。这种类型的结构体只能用一次,而且是马上建立马上用:

        像这样,定义一个s1的结构体之后,就再也无法使用这个匿名结构体。

        匿名结构体特点:对于不同的匿名结构体,即使其内容一模一样,计算机还是会认为他们不是一个类型,如果用两个内容相同的匿名结构体,一个定义变量,一个定义指针,当指针指向变量时,计算机会报错。

1.4 结构体自引用

        结构体内部包含自己。看一个例子:

        对于这个结构体,第一个数据是一个整型,第二个数据定义的居然是指向自己的指针类型,这样的话,我们去访问这个结构体时,可以在它内部发现指向自己的指针,这就是结构体的自引用。

        想一个问题:我们能不能在结构体里面直接定义一个本身类型的结构体变量呢?我们为什么要定义指针呢?

        原因:因为如果我们在一个结构体里面直接定义它本身类型的变量,那我们将无法预估这个结构体的大小,因为每次计算这个结构体第二个参数的大小时,又会进入到一个结构体里面,而那个结构体的第二个参数还是结构体,是一个死循环。所以我们在结构体自引用时,只定义自身类型 的指针,就能避开这个问题。

        为什么要这么用?

        这个用法很重要,有了这样一个功能,才能实现链表建立。是数据结构很重要的内容。

        注意:结构体的自定义是无法通过匿名结构体实现的。

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

        这个在前面其实已经讲到一部分,下面看几种定义的例子:

        p1和p2定义时未初始化,p3定义时进行了初始化。这三种方式都定义了结构体变量,p3在定义时进行了初始化。

1.6 结构体内存对齐

        先看一个例子,思考如下结构体占用几个字节:

#include <stdio.h>
struct S1
{char c1;int c2;char c3;
};
int main()
{printf("%d", sizeof(struct S1));return 0;
}

        答案:

        答案是12。那么这是为什么呢?为什么不是6?这就涉及到我们揭晓来要讲的:结构体内存对齐。

        内存中如何开辟结构体空间?

        首先了解结构体的对其规则:

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

2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

 对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
(VS中默认的值为8)

3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍
处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数
倍。

        就拿上面的结构体举例,讲解其在内存中的存放情况:

struct S1
{char c1;int c2;char c3;
};

        只有vs有默认对齐数,其他平台是没有默认对齐数的,其他平台对齐数就是数据本身的大小。

        为什么存在内存对齐?

        1、平台原因

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

        2、性能原因:

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

        总的来说:

        结构体的内存对齐是拿空间换取时间的做法。

在我们大致了解对齐规则后,我们需要了解,结构体定义的时候,位置的不同会影响其空间的大小。让占用空间小的数据尽量放在一起,这样可以减少结构体空间的浪费。

1.7 修改默认对齐数

        在vs中默认对齐数是8,但是它可以被修改。

        使用#pragma

        看例子:

#include <stdio.h>
#pragma pack(4)
struct S
{int i;double d;
};
#pragma pack()int main()
{printf("%d\n", sizeof(struct S));return 0;
}

        用#pragma pack(4)将默认对齐数改成了4,结果从16变成了12。

        后面的#pragma pack()是用来恢复默认对齐数。

1.8 结构体传参

        直接看例子:

#include <stdio.h>
struct S
{int data[1000];int num;
};
struct S s = { {1,2,3,4},1000 };
void print1(struct S s)
{printf("%d\n", s.num);
}
void print2(struct S* p)
{for(int i=0;i<10;i++)printf("%d\n", p->data[i]);
}
int main()
{print1(s);print2(&s);return 0;
}

        结构体传参有两种方式,第一种print1是直接将值传送过去(传值调用);第二种print2是传地址(传址调用)。我们认为第二种更好,因为直接传值的话,参数是需要压栈的,会有时间和空间的系统开销,如果传值一个过大的结构体,会导致系统的性能下降。

总结

        本篇详细讲解了结构体的定义,希望对你有所帮助。


http://www.ppmy.cn/news/1373704.html

相关文章

记录一下go的包管理

如何降低版本 假设go版本go 1.16运行项目&#xff0c; 查看运行报错&#xff0c;根据报错信息&#xff0c;一条一条解决 go run main.go错误: /home/server1/go/pkg/mod/google.golang.org/grpcv1.58.2/internal/buffer/unbounded.go:92:34: undefined: any类似这样的报错提醒…

【软考】单元测试

目录 1. 概念2. 测试内容2.1 说明2.2 模块接口2.3 局部数据结构2.4 重要的执行路径 3. 测试过程2.1 说明2.2 单元测试环境图2.3 驱动模块2.4 桩模块 4. 模块接口测试与局部数据结构测试的区别 1. 概念 1.单元测试也称为模块测试&#xff0c;在模块编写完成且无编译错误后就可以…

three.js如何实现简易3D机房?(四)点击事件+呼吸灯效果

接上一篇&#xff1a; three.js如何实现简易3D机房&#xff1f;&#xff08;三&#xff09;显示信息弹框/标签&#xff1a;http://t.csdnimg.cn/5W2wA 目录 八、点击事件 1.实现效果 2.获取相交点 3.呼吸灯效果 4.添加点击事件 5.问题解决 八、点击事件 1.实现效果 2.…

linux大版本之间的区别和特性

1. 描述Linux内核版本和发行版之间的关系。 Linux内核版本指的是Linux操作系统核心的特定版本&#xff0c;而Linux发行版则是基于该内核并集成了其他软件和工具的完整操作系统。具体分析如下&#xff1a; Linux内核版本&#xff1a;是操作系统的核心部分&#xff0c;它提供了…

STM32CubeMX软件界面花屏,混乱的解决方案。

添加系统环境变量:J2D_D3D 值:false 即可解决。

如何优化ElasticSearch搜索性能?

优化 Elasticsearch(ES)的查询性能涉及多个方面,从查询本身到集群配置和硬件资源。以下是些关键的优化策略: 集群和硬件优化 负载均衡: 确保查询负载在集群中均衡分配,硬件资源: 根据需要増加 CPU、内存或改善 Ⅳ/O 性能(例如使用 SSD)配置 JVM: 优化 JVM 设置,如堆大小,以提…

MOGDB/openGauss数据库gs dump备份脚本及备份清理

MOGDB/openGauss 数据库 gs_dump 备份脚本及备份清理 需要对 MOGDB/openGauss 进行每天逻辑备份。如下脚本分享给大家。 一、备份脚本 1.脚本 c.sh (可以改名字)# database dump shell # you should change the GAUSSHOME GAUSSPORT GAUSSDATA DUMP_USER DUMP_PASSWORD #!/bi…

使用Navicat连接阿里云服务器上的MySQL数据库

打开navicat&#xff0c;连接如下&#xff1a; 服务器的默认密码是 root 连接时出现 使用 ls 查找 /etc/my.cnf ls /etc/my.cnf 用vi打开my.cnf&#xff1a; vi /etc/my.cnf看看是否有绑定本地回环地址的配置&#xff0c;如果有&#xff0c;注释掉下面这段文字&#xff1a;…