C语言--结构体详解

C语言--结构体详解

  • 1.结构体产生原因
  • 2.结构体声明
    • 2.1 结构体的声明
    • 2.2 结构体的初始化
    • 2.3结构体自引用
  • 3.结构体内存对齐
    • 3.1 对齐规则
    • 3.2 为什么存在内存对齐
    • 3.3 修改默认对⻬数
  • 4. 结构体传参

1.结构体产生原因

C语言将数据类型分为了两种,一种是内置类型,如:char、short、int、long、float、double等,这些内置类型能够很好的描述单个物体的某一具体特性,但是假设我想描述学⽣,描述⼀本书,这时单⼀的内置类型是不⾏的。描述⼀个学⽣需要名字、年龄、学号、⾝⾼、体重等;描述⼀本书需要作者、出版社、定价等。C语⾔为了解决这个问题,增加了结构体这种⾃定义的数据类型,让程序员可以⾃⼰创造适合的类型。

定义:结构是⼀些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量,如:标量、数组、指针,甚⾄是其他结构体

2.结构体声明

2.1 结构体的声明

结构体一般定义如下:

struct tag//struct是关键字,tag是结构类型名称,自拟,struct tag是结构体变量类型
{member-list;//{}中间是用于描述的变量集合
}variable-list;//variable-list是struct tag的变量声明
//variable-list == struct tag variable-list

例如描述一个学生:

struct Student
{char name[20];//名字int age;//年龄char sex[5];//性别char id[20];//学号
}std; //分号不能省,std == struct Student std

2.2 结构体的初始化

#include <stdio.h>
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);//按照指定的顺序初始化,可随意自行struct Stu s2 = { .age = 18, .name = "lisi", .id = "20230818002", .sex = "⼥}//这种方式初始化可任意调整顺序,不必按照结构体的声明顺序进行printf("name: %s\n", s2.name);printf("age : %d\n", s2.age);printf("sex : %s\n", s2.sex);printf("id : %s\n", s2.id);return 0;
}

特殊结构体声明:在声明结构的时候,可以不完全的声明。

//匿名结构体类型,即不对结构体命名
struct
{int a;char b;float c;
}x;struct
{int a;char b;float c;
}a[20], *p;

如果没有对匿名结构体类型重命名的话,基本上只能使⽤⼀次。

2.3结构体自引用

结构体中可以引用自身,正确引用方式如下:

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

struct Node* next是定义的一个变量为next的结构体变量。
此处不可改为struct Node next,因为⼀个结构体中再包含⼀个同类型的结构体变量,这样结构体变量的⼤⼩就会⽆穷的⼤,是不合理的。
简单来说就是结构体中只能使用结构体指针。

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

typedef 是将 struct Node 重命名为 Node ,即 struct Node == Node ,Node 是struct Node 的重命名为,但是在结构体里面不能直接使用 *Node,因为Node是对前⾯的匿名结构体类型的重命名产⽣的,但是在匿名结构体内部提前使⽤Node类型来创建成员变量,这是不⾏的,因为在使用*Node之前,Node是不存在的,只能在Node出现之后才能使用。

3.结构体内存对齐

3.1 对齐规则

1. 结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处

2. 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。对⻬数 = 编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值。

  • VS 中默认的值为 8
  • Linux中 gcc 没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩

3. 结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的整数倍。

4. 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。

//练习1
struct S1
{char c1;int i;char c2;
};
printf("%d\n", sizeof(struct S1));

在这里插入图片描述
规则 1 :如图假设是一块内存区域,结构体从 “0” 的位置开始往后存放,那么c1的偏移量就是0。

规则2:int 类型的对齐数为4,编译器默认的对⻬数是8,二者取小值就是4,i 的内存就是从偏移量为4的位置开始,中间的 “1,2,3” 会被浪费掉。

char类型的对齐数是1,编译器默认的对⻬数是8,二者取小值就是1,c2 内存就是从偏移量为8的位置开始.

规则3:结构体的大小一定为最⼤对⻬数的整数倍,即为4,中间的“9,10,11”会被浪费掉,所以最后的结果为12.

//练习2
struct S2
{char c1;char c2;int i;
};
printf("%d\n", sizeof(struct S2));

在这里插入图片描述
规则 1 :如图假设是一块内存区域,结构体从 “0” 的位置开始往后存放,那么c1的偏移量就是0。

规则2:char类型的对齐数是1,编译器默认的对⻬数是8,二者取小值就是1,c2 内存就是从偏移量为1的位置开始.

int 类型的对齐数为4,编译器默认的对⻬数是8,二者取小值就是4,i 的内存就是从偏移量为4的位置开始,中间的 “2,3” 会被浪费掉。

规则3:结构体的大小一定为最⼤对⻬数的整数倍,即为4,所以最后的结果为8.

//练习3
struct S3
{double d;char c;int i;
};
printf("%d\n", sizeof(struct S3));

在这里插入图片描述
规则 1 :如图假设是一块内存区域,结构体从 “0” 的位置开始往后存放,那么d的偏移量就是0。

规则2:char类型的对齐数是1,编译器默认的对⻬数是8,二者取小值就是1,c 内存就是从偏移量为8的位置开始.

int 类型的对齐数为4,编译器默认的对⻬数是8,二者取小值就是4,i 的内存就是从偏移量为12的位置开始,中间的 “9,10,11” 会被浪费掉。

规则3:结构体的大小一定为最⼤对⻬数的整数倍,即为8,所以最后的结果为16.

//练习4-结构体嵌套问题
struct S4
{char c1;struct S3 s3;double d;
};
printf("%d\n", sizeof(struct S4));

在这里插入图片描述
规则 1 :如图假设是一块内存区域,结构体从 “0” 的位置开始往后存放,那么c的偏移量就是0。

规则2、4:由上面可知结构体s3的大小为16,结构体s3⾃⼰成员中最⼤对⻬数是8,编译器默认的对⻬数是8,二者取小值就是8,s3 内存就是从偏移量为8的位置开始,中间部分会被浪费掉。

double 类型的对齐数为8,编译器默认的对⻬数是8,二者取小值就8,d 的内存就是从偏移量为24的位置开始.

规则3:结构体的大小一定为最⼤对⻬数的整数倍,即为8,所以最后的结果为32.

3.2 为什么存在内存对齐

1. 平台原因 (移植原因):

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

2. 性能原因:

数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于,为了访问未对⻬的内存,处理器需要作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数,那么就可以⽤⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两个8字节内存块中。

总体来说:结构体的内存对⻬是拿空间来换取时间的做法。

让占⽤空间⼩的成员尽量集中在⼀起

3.3 修改默认对⻬数

#pragma pack() 这个预处理指令,可以改变编译器的默认对⻬数,括号中填要更改的对齐数。

#include <stdio.h>
#pragma pack(1)//设置默认对⻬数为1
struct S
{char c1;int i;char c2;
};
#pragma pack()//取消设置的对⻬数,还原为默认
int main()
{//输出的结果是什么?printf("%d\n", sizeof(struct S));return 0;
}

结构体在对⻬⽅式不合适的时候,我们可以⾃⼰更改默认对⻬数。

4. 结构体传参

结构体传参一般有两种方式,即传值传参和传址传参

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* ps)
{printf("%d\n", ps->num);
}
int main()
{print1(s); //传结构体print2(&s); //传地址return 0;
}

结构体一般使用的是传址传参,因为函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。如果传递⼀个结构体对象的时候,结构体过⼤,参数压栈的的系统开销⽐较⼤,所以会导致性能的下降。

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

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

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

相关文章

防火安全球阀,到2027年市场增长至68亿美元

防火安全球阀是一种在火灾、爆炸等危险环境下仍能正常使用的阀门。它被广泛用于石化、化工、船舶、电力等领域&#xff0c;以保障生产和人员安全。下面我们将从全球市场和中国市场两个方面对其发展趋势进行分析。全球市场分析&#xff1a; 从全球市场的角度来看&#xff0c;防火…

软件测试|Linux基础教程:ln命令与软链接和硬链接

简介 在Linux系统中&#xff0c;ln命令是一个非常有用的工具&#xff0c;用于创建链接&#xff08;link&#xff09;&#xff0c;将一个文件或目录链接到另一个位置。链接允许一个文件或目录可以同时存在于多个位置&#xff0c;而不会占用额外的磁盘空间。ln命令支持创建硬链接…

UI5与后端的文件交互(四)

文章目录 前言一、后端开发1. 新建管理模板表格2. 新建Function&#xff0c;动态创建文档 二、修改UI5项目1.Table里添加下载证明列2. 实现onClickDown事件 三、测试四、附 前言 这系列文章详细记录在Fiori应用中如何在前端和后端之间使用文件进行交互。 这篇的主要内容有&…

Unity 打包AB 场景烘培信息丢失

场景打包成 AB 资源的时候&#xff0c;Unity 不会打包一些自带相关的资源 解决办法&#xff1a;在 Project settings > Graphics下设置&#xff08;Automatic 修改成 Custom&#xff09;

selenium对于页面改变的定位元素处理办法

在学习selenimu中&#xff0c;总是发现元素定位不到&#xff0c;想了各种办法&#xff0c;最后总结大致有两个原因。 1.等待时间不够&#xff0c;页面还没有完全渲染就进行操作&#xff0c;使用time模块进行等待。 2.换了页面后&#xff0c;发现定位不到元素&#xff0c;因为…

HTML5和JS实现明媚月色效果

HTML5和JS实现明媚月色效果 先给出效果图&#xff1a; 源码如下&#xff1a; <!DOCTYPE html> <html> <head><title>明媚月光效果</title><style>body {margin: 0;overflow: hidden;background-color: #000; /* 添加一个深色背景以便看到…

安全与认证Week3

目录 Key Management 密钥管理 密钥交换、证书 密钥的类别 密钥管理方面 密钥分发问题 密钥分发方案 混合密钥分发 公钥分发 公钥证书 X.509 理解X.509 X.509证书包含 X.509使用过程 X.509身份验证服务 X.509版本3 取消 由X.509引申关于CA 用户认证、身份管理…

外延炉及其相关的小知识

外延炉是一种用于生产半导体材料的设备&#xff0c;其工作原理是在高温高压环境下将半导体材料沉积在衬底上。 硅外延生长&#xff0c;是在具有一定晶向的硅单晶衬底上&#xff0c;生长一层具有和衬底相同晶向的电阻率且厚度不同的晶格结构完整性好的晶体。 外延生长的特点&am…

Pycharm恢复默认设置

window 系统 找到下方目录-->删除. 再重新打开Pycharm C:\Users\Administrator\.PyCharm2023.3 你的不一定和我名称一样 只要是.PyCharm*因为版本不同后缀可能不一样 mac 系统 请根据需要删除下方目录 # Configuration rm -rf ~/Library/Preferences/PyCharm* # Caches …

LVGL的List控件的触摸按键和实体按键的处理

在LVGL的List控件使用过程中&#xff0c;虽然通过触摸按键选择item&#xff0c;但是有些场景需要实体按键选取item&#xff0c;但是LVGL 的V8.3中没有像Emwin那样有函数选择list item的函数。LVGL中List引入了Group的概念&#xff0c;把列表项都添加到同一个group中。然后通过更…

基于机器视觉的车牌检测-边缘检测因子的选择

车牌检测概述 车牌识别在检测报警、汽车出入登记、交通违法违章以及移动电子警察方面应用广泛。车牌识别过程为&#xff1a;首先通过摄像头获取包含车牌的彩色图像&#xff1b;然后进行车牌边缘检测&#xff0c;先粗略定位到车牌位置&#xff0c;再精细定位&#xff1b;最后根…

不同像平面坐标系下的Brown畸变系数互转

不同像平面坐标系下Brown畸变系数转换 记 u , v u,v u,v为像素为单位的坐标&#xff0c;f为焦距&#xff0c;单位也是像素。 记 x , y x,y x,y为理想坐标。本文推导两种情况下的Brown畸变系数转换关系&#xff1a; 相同坐标系定义、不同的坐标单位&#xff08;像素坐标与归一…

云消息队列 Kafka 版生态谈第一期:无代码转储能力介绍

作者&#xff1a;娜米 云消息队列 Kafka 版为什么需要做无代码转储 云消息队列 Kafka 版本身是一个分布式流处理平台&#xff0c;具有高吞吐量、低延迟和可扩展性等特性。它被广泛应用于实时数据处理和流式数据传输的场景。然而&#xff0c;为了将云消息队列 Kafka 版与其他数…

Gin 项目引入热加载

Gin 项目引入热加载 文章目录 Gin 项目引入热加载一、什么是热加载二、Air2.1 介绍2.2 特性特性&#xff1a;2.3 相关文档2.4 安装推荐使用 install.sh使用 go install 2.5 配置环境变量2.6 使用 三、Fresh3.1 介绍3.2 相关文档3.3 安装与使用 四、bee4.1 介绍4.2 相关文档4.3 …

K8S--持久卷(PersistentVolume)的用法

原文网址&#xff1a;K8S--持久卷(PersistentVolume)的用法-CSDN博客 简介 本文介绍K8S的持久卷(PersistentVolume)的用法。 目标&#xff1a;用持久卷的方式将主机的磁盘与容器磁盘映射&#xff0c;安装nginx并运行。 --------------------------------------------------…

高通开发系列 - toolchain交叉编译器编译kernel以及生成boot镜像

By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅! 你的喜欢就是我写作的动力! 返回:专栏总目录 目录 背景概述分析过程generate_defconfig.sh脚本环境准备合并其他几个配置文件开始编译生成dtb镜像

Python笔记07-异常、模块和包

文章目录 异常及捕获方法python模块python包安装第三方包 异常及捕获方法 当检测到一个错误时&#xff0c;Python解释器就无法继续执行了&#xff0c;反而出现了一些错误的提示&#xff0c;这就是所谓的“异常”, 也就是我们常说的BUG 例如&#xff1a;以r方式打开一个不存在的…

ssm基于Vue的戏剧推广网站论文

摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统戏剧推广信息管理难度大&#xff0c;容错率低&#xff0c…

TypeScript接口、对象

目录 1、TypeScript 接口 1.1、实例 1.2、联合类型和接口 1.3、接口和数组 1.4、接口和继承 1.5、单继承实例 1.6、多继承实例 2、TypeScript 对象 2.2、对象实例 2.3、TypeScript类型模板 2.4、鸭子类型&#xff08;Duck typing&#xff09; 1、TypeScript 接口 接口…

提升图像分割精度:学习UNet++算法

文章目录 一、UNet 算法简介1.1 什么是 UNet 算法1.2 UNet 的优缺点1.3 UNet 在图像分割领域的应用 二、准备工作2.1 Python 环境配置2.2 相关库的安装 三、数据处理3.1 数据的获取与预处理3.2 数据的可视化与分析 四、网络结构4.1 UNet 的网络结构4.2 UNet 各层的作用 五、训练…