【C语言】结构体与共用体深入解析
在C语言中,结构体(struct
)和共用体(union
)都是用来存储不同类型数据的复合数据类型,它们在程序设计中具有重要的作用。
推荐阅读:操作符详细解说,让你的编程技能更上一层楼
1. 结构体的定义与使用
1.1 结构体的基本概念
结构体(struct
)是C语言中的一种用户自定义的数据类型,它允许用户将不同类型的数据组合成一个单一的复合数据类型。结构体中的每个数据元素称为“成员”或“字段”。每个成员可以是不同类型的数据。
结构体的定义方式如下:
struct StructName {type member1;type member2;type member3;// ...
};
注意:不要忘了最后的==;==
举例:下面结构体 包含num,gender,name三个成员,成员的数据类型分别是int,char,char 数组
struct StructName {int num;char gender;char name[20];
};
1.2 结构体的定义与初始化
定义一个结构体之后,我们可以定义该结构体类型的变量并对其进行初始化。结构体变量的声明方式如下:
struct StructName varName;
结构体的初始化可以通过两种方式进行:静态初始化和动态初始化。
1.2.1 静态初始化
静态初始化是在定义结构体变量时直接赋值。
struct Person {char name[50];int age;
};struct Person person1 = {"John", 25};
1.2.2 动态初始化
动态初始化是通过用户输入或运行时计算来初始化结构体的成员。
struct Person person2;
strcpy(person2.name, "Alice");
person2.age = 30;
1.3 访问结构体成员
结构体成员通过点操作符(.
)来访问。
struct Person person1 = {"John", 25};
printf("Name: %s, Age: %d\n", person1.name, person1.age);
1.4 结构体的数组
结构体的数组允许我们创建多个结构体对象。声明结构体数组的方式与普通数组类似。
struct Person people[3] = {{"John", 25}, {"Alice", 30}, {"Bob", 22}};
访问结构体数组元素时,需要使用数组索引和点操作符:
printf("Name: %s, Age: %d\n", people[0].name, people[0].age);
1.5 结构体作为函数参数
结构体可以作为函数的参数传递。可以通过值传递或者引用传递(指针传递)传递结构体。
1.5.1 结构体值传递
在函数中对结构体进行值传递时,函数接收到结构体的副本,对副本的修改不会影响原结构体。
void printPerson(struct Person p) {printf("Name: %s, Age: %d\n", p.name, p.age);
}struct Person person1 = {"John", 25};
printPerson(person1);
1.5.2 结构体指针传递
如果希望在函数内修改结构体的内容,可以通过传递结构体指针来引用原结构体。
void updatePerson(struct Person *p) {p->age = 28; // 使用箭头操作符访问结构体指针成员
}struct Person person1 = {"John", 25};
updatePerson(&person1);
printf("Updated Age: %d\n", person1.age);
1.6 结构体内存对齐与填充
结构体在内存中的存储方式受到内存对齐的影响。为了提高处理器的效率,结构体的成员通常会根据其类型进行内存对齐。这意味着有时结构体成员之间可能会有空洞,称为“填充”。
可以使用#pragma pack
指令或__attribute__((packed))
来调整结构体的内存对齐方式。
#pragma pack(push, 1)
struct Example {char a;int b;
};
#pragma pack(pop)
对齐的原则
结构体内存对齐与填充(Padding)是C语言中涉及数据结构存储方式的重要概念。它直接影响到程序的内存使用效率和性能,尤其在处理多平台或者低层次的系统编程时,需要对这一点有深入的理解。
1. 内存对齐的概念
内存对齐指的是将数据类型按一定的规则存储到内存中的方式。由于现代计算机处理器的存取效率,通常要求数据类型按一定的边界对齐存储。也就是说,不同类型的数据应该存放在特定的内存地址上,这样能够提高处理器访问内存的速度。
2. 内存对齐的原理
在C语言中,每个数据类型都有自己的“对齐要求”。对齐要求是指某个数据类型的变量在内存中应存储在某个特定的地址上,这个地址通常是该数据类型大小的倍数。
例如:
char
类型的数据通常可以存储在任意地址(1 字节对齐)。int
类型的变量通常要求存储在4字节对齐的地址上(即地址必须是4的倍数)。double
类型通常要求存储在8字节对齐的地址上(即地址必须是8的倍数)。
不同编译器可能会有不同的默认对齐方式,但是常见的规则是:
char
类型:1字节对齐。short
类型:2字节对齐。int
类型:4字节对齐。double
类型:8字节对齐。
3. 填充(Padding)
填充是指为了满足对齐要求,在结构体成员之间或结构体末尾插入空闲字节,以确保每个成员的数据按照其对齐要求存储。
举个例子,考虑一个结构体包含 char
和 int
类型的成员:
struct Example {char a; // 1 字节int b; // 4 字节
};
假设系统对 int
类型要求4字节对齐,那么在 char a
后面会有 3 个字节的填充,以确保 b
在 4 字节对齐的位置开始存储。这是因为 b
需要在内存中存储在地址是4的倍数的位置。
因此,结构体的内存布局可能如下:
| char a | padding | padding | padding | int b |
这个结构体的总大小将会是 8 字节,而不是 5 字节。这样,b
的起始地址就符合 4 字节对齐的要求。
4. 内存对齐与填充的规则
-
结构体成员对齐:
每个成员都必须存储在一个地址上,这个地址必须是该成员类型对齐要求的倍数。 -
结构体总对齐:
结构体的对齐要求是结构体中最大对齐要求成员的对齐要求。例如,如果结构体中有char
和double
成员,那么结构体的对齐要求就是double
的对齐要求,通常是 8 字节。 -
结构体大小:
结构体的大小是根据最大对齐要求来计算的。结构体的大小通常是结构体总内存的最小倍数,这个倍数是结构体内最大成员对齐的倍数。
5. 示例:结构体内存对齐与填充
让我们来看一个例子,假设在一个系统中,int
类型要求4字节对齐,char
类型要求1字节对齐:
#include <stdio.h>struct Example {char a; // 1 字节int b; // 4 字节char c; // 1 字节
};
这个结构体中的 a
需要 1 字节,而 b
需要 4 字节的对齐。由于 b
必须从 4 字节对齐的位置开始,因此 a
后面会有 3 个字节的填充,接着 b
存储。然后 c
占用 1 字节,由于 b
的对齐要求,结构体的总大小将根据最大对齐需求(通常为 4 字节)填充。
因此,结构体在内存中的布局如下:
| char a | padding | padding | padding | int b | char c | padding |
结构体总大小为 8 字节。可以通过 sizeof
操作符来查看结构体的实际大小:
printf("Size of struct Example: %zu\n", sizeof(struct Example)); // 输出:8
6. 编译器对齐设置
在一些情况下,编译器允许通过指令来设置对齐方式。例如,GCC 和 Clang 提供了 #pragma pack
指令,可以控制结构体的对齐方式。可以通过设置对齐大小来减小结构体的内存占用。
例如,在GCC中,使用 #pragma pack(1)
可以强制按 1 字节对齐,这样就不会有任何填充字节:
#pragma pack(1)
struct Example {char a; // 1 字节int b; // 4 字节
};
#pragma pack() // 恢复默认对齐
这样,结构体将没有填充字节,内存布局将是:
| char a | int b |
举例说明
2. 共用体的定义与使用
2.1 共用体的基本概念
共用体(union
)是一种特殊的数据结构,它与结构体类似,但与结构体不同的是,共用体的所有成员共享相同的内存空间。即同一时刻,共用体只能存储一个成员的值。这使得共用体能够节省内存空间。
共用体的定义格式如下:
union UnionName {type member1;type member2;type member3;// ...
};
2.2 共用体的定义与初始化
定义一个共用体并初始化时,通常初始化其中的第一个成员。
union Data {int i;float f;char c;
};union Data data1;
data1.i = 10;
data1.f = 3.14; // 此时 data1.i 的值会被覆盖
2.3 访问共用体成员
由于共用体的成员共享相同的内存位置,因此只能访问最后存储的成员。当一个成员被赋值时,其他成员的值将被覆盖。
union Data data1;
data1.i = 10;
printf("Integer: %d\n", data1.i);data1.f = 3.14;
printf("Float: %.2f\n", data1.f); // 访问 float 类型成员
2.4 共用体的应用场景
共用体主要用于节省内存,特别是在需要存储不同类型数据,但在任何时刻只需要其中之一的场合。常见的应用场景包括:
- 多种类型的数据共享同一内存空间。
- 处理不同类型数据的协议解析。
2.5 共用体与结构体的区别
- 内存分配:结构体中的每个成员都有自己独立的内存空间,而共用体的所有成员共享同一块内存空间。
- 用途:结构体适用于需要存储多个不同类型数据的场合,而共用体适用于需要存储不同类型数据,但在同一时刻只需要其中一个的场合。
3. 结构体与共用体与指针的结合
3.1 结构体指针
结构体指针是指向结构体类型变量的指针。通过结构体指针,可以访问结构体的成员。结构体指针通常与malloc
动态内存分配结合使用。
3.1.1 声明与初始化
struct Person {char name[50];int age;
};struct Person *ptr;
ptr = (struct Person *)malloc(sizeof(struct Person)); // 动态分配内存strcpy(ptr->name, "John");
ptr->age = 25;
3.1.2 访问结构体成员
通过结构体指针访问成员时,使用箭头操作符(->
)。
printf("Name: %s, Age: %d\n", ptr->name, ptr->age);
3.2 共用体指针
与结构体指针类似,我们也可以创建共用体指针,通过指针来访问共用体的成员。
3.2.1 声明与初始化
union Data {int i;float f;char c;
};union Data *ptr;
ptr = (union Data *)malloc(sizeof(union Data));ptr->i = 10;
3.2.2 访问共用体成员
与结构体指针类似,共用体指针也使用箭头操作符(->
)来访问成员。
printf("Integer: %d\n", ptr->i);
3.3 结构体与共用体混合使用
结构体和共用体也可以混合使用,以满足更复杂的需求。例如,我们可以在结构体中包含一个共用体,或者在共用体中使用结构体。
struct Person {char name[50];int age;
};union Data {struct Person p;int i;
};union Data data;
data.p.age = 30;
strcpy(data.p.name, "Alice");printf("Name: %s, Age: %d\n", data.p.name, data.p.age);
4.结论
结构体和共用体是C语言中非常强大的数据结构。结构体允许你将不同类型的数据组织在一起,而共用体通过共享内存来节省空间。在实际开发中,理解这两者的使用场景和优缺点,并掌握它们与指针的结合,是编写高效和内存优化代码的关键。
通过本篇文章的学习,希望你能够全面理解结构体与共用体的定义、使用方式及其在指针方面的应用,从而更好地应对C语言编程中的复杂问题。
相关文章:

【C语言】结构体与共用体深入解析
在C语言中,结构体(struct)和共用体(union)都是用来存储不同类型数据的复合数据类型,它们在程序设计中具有重要的作用。 推荐阅读:操作符详细解说,让你的编程技能更上一层楼 1. 结构体…...

es6.7.1分词器ik插件安装-和head插件连接es特殊配置
es6.7.1分词器ik插件安装-和head插件连接es特殊配置 如果对运维课程感兴趣,可以在b站上、A站或csdn上搜索我的账号: 运维实战课程,可以关注我,学习更多免费的运维实战技术视频 1.查看es6.7.1和es-head安装位置和es插件路径 [ro…...

java求职学习day18
常用的设计原则和设计模式 1 常用的设计原则(记住) 1.1 软件开发的流程 需求分析文档、概要设计文档、详细设计文档、编码和测试、安装和调试、维护和升级 1.2 常用的设计原则 (1)开闭原则(Open Close Principle…...

单链表专题(上)
链表的定义与创建 线性表: 1. 物理结构上不一定是线性的 2. 逻辑结构上一定是线性的 链表是一种物理存储结构上非连续,非顺序的存储结构 链表也是线性表的一种,但是在物理结构上不是连续的 链表是由一个一个的节点组成,需要数…...

【stm32学习】STM32F103相关特性
| 名称 | 缩写 | 频率 | 外部连接 | 功能 | 用途 | 特性 | |--------------------|------|----------------|---------------|------------|--------------|----------------| | 外部高速晶体振荡器 | HSE | 4~16MHz …...

PostGIS笔记:PostgreSQL中表、键和索引的基础操作
创建、查看与删除表 在数据库中创建一个表,使用如下代码: create table streets (id serial not null primary key, name varchar(50));这里的表名是streets,id是主键所以非空,采用serial数据类型,这个数据类型会自动…...

蓝桥杯python语言基础(3)——循环结构
一、for语句 理解range函数 range(start, stop, step) start: 序列开始的数字(默认为0)。stop: 序列结束的数字(不包含stop)。step: 步长(默认为1)。 练习 输出在 l 和 r 之间的所有偶数: pri…...

微服务网关鉴权之sa-token
目录 前言 项目描述 使用技术 项目结构 要点 实现 前期准备 依赖准备 统一依赖版本 模块依赖 配置文件准备 登录准备 网关配置token解析拦截器 网关集成sa-token 配置sa-token接口鉴权 配置satoken权限、角色获取 通用模块配置用户拦截器 api模块配置feign…...

23【进制的理解】
很多人可能听过计算机的最底层是2进制执行,但是原理并不知道,我们今天先不讨论那么复杂的问题,先讨论什么是进制 1910,10并不是1个字符,而是2个字符,也就是说在10进制里面没有“10”这个字符,1…...

jemalloc 5.3.0的tsd模块的源码分析
一、背景 在主流的内存库里,jemalloc作为android 5.0-android 10.0的默认分配器肯定占用了非常重要的一席之地。jemalloc的低版本和高版本之间的差异特别大,低版本的诸多网上整理的总结,无论是在概念上和还是在结构体命名上在新版本中很多都…...

【Convex Optimization Stanford】Lec3 Function
【Convex Optimization Stanford】Lec3 Function 前言凸函数的定义对凸函数在一条线上的限制增值扩充? 一阶条件二阶条件一些一阶/二阶条件的例子象集和sublevel set关于函数凸性的扩展(Jesen Inequality)保持函数凸性的操作非负加权和 & 仿射函数的…...

深入 Rollup:从入门到精通(三)Rollup CLI命令行实战
准备阶段:初始化项目 初始化项目,这里使用的是pnpm,也可以使用yarn或者npm # npm npm init -y # yarn yarn init -y # pnpm pnpm init安装rollup # npm npm install rollup -D # yarn yarn add rollup -D # pnpm pnpm install rollup -D在…...

wangEditor富文本编辑器,Laravel上传图片配置和使用
文章目录 前言步骤1. 构造好前端模版2. 搭建后端存储3. 调试 前言 由于最近写项目需要使用富文本编辑器,使用的是VUE3.0版本所以很多不兼容,实际测试以后推荐使用wangEditor 步骤 构造好前端模版搭建后端存储调试 1. 构造好前端模版 安装模版 模版安…...

chrome源码剖析—进程通信
Chrome 浏览器采用多进程架构(multi-process architecture),这种架构使得每个浏览器标签、扩展、插件、GPU 渲染等都在独立的进程中运行。为了确保不同进程之间的高效通信,Chrome 使用 进程间通信(IPC, Inter-Process …...

JJJ:linux时间子系统相关术语
文章目录 墙上时间内核管理的各种时间无时钟滴答模式(tickless mode 或 no-tick mode)简要介绍具体实现动态时钟滴答 Dynamic Ticks完全无时钟滴答(Full Tickless) nohz sleep单触发模式 oneshot mode 墙上时间 真实世界的真实时…...

0 基础学运维:解锁 K8s 云计算运维工程师成长密码
前言:作为一个过来人,我曾站在技术的门槛之外,连电脑运行内存和内存空间都傻傻分不清,完完全全的零基础。但如今,我已成长为一名资深的k8s云计算运维工程师。回顾这段历程,我深知踏上这条技术之路的艰辛与不…...

大一计算机的自学总结:位运算的应用及位图
前言 不仅异或运算有很多骚操作,位运算本身也有很多骚操作。(尤其后几个题,太逆天了) 一、2 的幂 class Solution { public:bool isPowerOfTwo(int n) {return n>0&&n(n&-n);} }; 根据二进制表示数的原理&#…...

计算机毕业设计Django+Tensorflow音乐推荐系统 机器学习 深度学习 音乐可视化 音乐爬虫 知识图谱 混合神经网络推荐算法 大数据毕设
温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…...

AI 图片涌入百度图库
在这个信息爆炸的时代,我们习惯了通过搜索引擎来获取各种想要的信息和图片。然而,现在打开搜索引擎看到的却是许多真假难辨的信息——AI图片,这部分数据正以惊人的速度涌入百度图库,让小编不禁想问:未来打开百度图库不…...

可爱狗狗的404动画页面HTML源码
源码介绍 可爱狗狗的404动画页面HTML源码,源码由HTMLCSSJS组成,记事本打开源码文件可以进行内容文字之类的修改,双击html文件可以本地运行效果 效果预览 源码获取 可爱狗狗的404动画页面HTML源码...

【微服务与分布式实践】探索 Dubbo
核心组件 服务注册与发现原理 服务提供者启动时,会将其服务信息(如服务名、版本、所在节点的网络地址等)注册到注册中心。服务消费者则可以从注册中心发现可用的服务提供者列表,并与之通信。注册中心会存储服务的信息,…...

OpenCSG月度更新2025.1
1月的OpenCSG取得了一些亮眼的成绩 在2025年1月,OpenCSG在产品和社区方面继续取得了显著进展。产品方面,推出了AutoHub浏览器自动化助手,帮助用户提升浏览体验;CSGHub企业版功能全面升级,现已开放试用申请,…...

C++封装红黑树实现mymap和myset和模拟实现详解
文章目录 map和set的封装map和set的底层 map和set的模拟实现insertiterator实现的思路operatoroperator- -operator[ ] map和set的封装 介绍map和set的底层实现 map和set的底层 一份模版实例化出key的rb_tree和pair<k,v>的rb_tree rb_tree的Key和Value不是我们之前传统意…...

二次封装的方法
二次封装 我们开发中经常需要封装一些第三方组件,那么父组件应该怎么传值,怎么调用封装好的组件原有的属性、插槽、方法,一个个调用虽然可行,但十分麻烦,我们一起来看更简便的方法。 二次封装组件,属性怎…...

消息队列篇--通信协议篇--网络通信模型(OSI7层参考模型,TCP/IP分层模型)
一、OSI参考模型(Open Systems Interconnection Model) OSI参考模型是一个用于描述和标准化网络通信功能的七层框架。它由国际标准化组织(ISO)提出,旨在为不同的网络设备和协议提供一个通用的语言和结构,以…...

Python实现U盘数据自动拷贝
功能:当电脑上有U盘插入时,自动复制U盘内的所有内容 主要特点: 1、使用PyQt5创建图形界面,但默认隐藏 2、通过CtrlAltU组合键可以显示/隐藏界面 3、自动添加到Windows启动项 4、监控USB设备插入 5、按修改时间排序复制文件 6、静…...

汇编的使用总结
一、汇编的组成 1、汇编指令(指令集) 数据处理指令: 数据搬移指令 数据移位指令 位运算指令 算术运算指令 比较指令 跳转指令 内存读写指令 状态寄存器传送指令 异常产生指令等 2、伪指令 不是汇编指令,但是可以起到指令的作用,伪…...

DeepSeek理解概率的能力
问题: 下一个问题是概率问题。乘车时有一个人带刀子的概率是百分之一,两个人同时带刀子的概率是万分之一。有人认为如果他乘车时带上刀子,那么还有其他人带刀子的概率就是万分之一,他乘车就会安全得多。他的想法对吗?…...

AI 浪潮席卷中国年,开启科技新春新纪元
在这博主提前祝大家蛇年快乐呀!!! 随着人工智能(AI)技术的飞速发展,其影响力已经渗透到社会生活的方方面面。在中国传统节日 —— 春节期间,AI 技术也展现出了巨大的潜力,为中国年带…...

AI时代的网络安全:传统技术的落寞与新机遇
AI时代的网络安全:传统技术的落寞与新机遇 在AI技术飞速发展的浪潮中,网络安全领域正经历着前所未有的变革。一方面,传统网络安全技术在面对新型攻击手段时逐渐显露出局限性;另一方面,AI为网络安全带来了新的机遇&…...