【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源码...

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型
摘要 拍照搜题系统采用“三层管道(多模态 OCR → 语义检索 → 答案渲染)、两级检索(倒排 BM25 向量 HNSW)并以大语言模型兜底”的整体框架: 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后,分别用…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...

基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...

MODBUS TCP转CANopen 技术赋能高效协同作业
在现代工业自动化领域,MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步,这两种通讯协议也正在被逐步融合,形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...

c#开发AI模型对话
AI模型 前面已经介绍了一般AI模型本地部署,直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型,但是目前国内可能使用不多,至少实践例子很少看见。开发训练模型就不介绍了&am…...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...