【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)

填充是指为了满足对齐要求,在结构体成员之间或结构体末尾插入空闲字节,以确保每个成员的数据按照其对齐要求存储。

举个例子,考虑一个结构体包含 charint 类型的成员:

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. 内存对齐与填充的规则
  1. 结构体成员对齐:
    每个成员都必须存储在一个地址上,这个地址必须是该成员类型对齐要求的倍数。

  2. 结构体总对齐:
    结构体的对齐要求是结构体中最大对齐要求成员的对齐要求。例如,如果结构体中有 chardouble 成员,那么结构体的对齐要求就是 double 的对齐要求,通常是 8 字节。

  3. 结构体大小:
    结构体的大小是根据最大对齐要求来计算的。结构体的大小通常是结构体总内存的最小倍数,这个倍数是结构体内最大成员对齐的倍数。

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语言编程中的复杂问题。

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

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

相关文章

LLM架构与优化:从理论到实践的关键技术

标题&#xff1a;“LLM架构与优化&#xff1a;从理论到实践的关键技术” 文章信息摘要&#xff1a; 文章探讨了大型语言模型&#xff08;LLM&#xff09;开发与应用中的关键技术&#xff0c;包括Transformer架构、注意力机制、采样技术、Tokenization等基础理论&#xff0c;以…

思维练习题

目录 第一章 假设法1.题目1. 如何问问题2. 他们的职业是分别什么3. 谁做对了4. 鞋子的颜色 2.答案 空闲时间写一些思维题来锻炼下思维逻辑&#xff08;题目均收集自网上&#xff0c;分析推理为自己所写&#xff09;。 第一章 假设法 一个真实的假设往往可以让事实呈现眼前&…

【C++高并发服务器WebServer】-10:网络编程基础概述

本文目录 一、MAC地址二、IP地址三、子网掩码四、TCP/IP四层模型五、协议六、socket七、字节序 一、MAC地址 网卡是一块被设计用来允许计算机在计算机网络上进行通讯的计算机硬件&#xff0c;又称为网络适配器或网络接口卡NIC。其拥有 MAC 地址&#xff0c;属于 OSI模型的第2层…

为何SAP S4系统中要设置MRP区域?MD04中可否同时显示工厂级、库存地点级的数据?

【SAP系统PP模块研究】 一、物料主数据的MRP区域设置 SAP ECC系统中想要指定不影响MRP运算的库存地点,是针对库存地点设置MRP标识,路径为:SPRO->生产->物料需求计划->计划->定义每一个工厂的存储地点MRP,如下图所示: 另外,在给物料主数据MMSC扩充库存地点时…

C++ list 容器用法

C list 容器用法 C 标准库提供了丰富的功能&#xff0c;其中 <list> 是一个非常重要的容器类&#xff0c;用于存储元素集合&#xff0c;支持双向迭代器。<list> 是 C 标准模板库&#xff08;STL&#xff09;中的一个序列容器&#xff0c;它允许在容器的任意位置快速…

C++ | 红黑树

前言 本篇博客讲解c中数据结构红黑树&#xff0c;看这篇博客之前请先去看&#xff1a; C | AVL树_c avl树能有重复节点吗-CSDN博客 &#x1f493; 个人主页&#xff1a;普通young man-CSDN博客 ⏩ 文章专栏&#xff1a;C_普通young man的博客-CSDN博客 ⏩ 本人giee: 普通小青…

C语言从入门到进阶

视频&#xff1a;https://www.bilibili.com/video/BV1Vm4y1r7jY?spm_id_from333.788.player.switch&vd_sourcec988f28ad9af37435316731758625407&p23 //枚举常量 enum Sex{MALE,FEMALE,SECRET };printf("%d\n", MALE);//0 printf("%d\n", FEMALE…

WebSocket 详解:全双工通信的实现与应用

目录 一、什么是 WebSocket&#xff1f;&#xff08;简介&#xff09; 二、为什么需要 WebSocket&#xff1f; 三、HTTP 与 WebSocket 的区别 WebSocket 的劣势 WebSocket 的常见应用场景 WebSocket 握手过程 WebSocket 事件处理和生命周期 一、什么是 WebSocket&#xf…

一文讲解Java中Object类常用的方法

在Java中&#xff0c;经常提到一个词“万物皆对象”&#xff0c;其中的“万物”指的是Java中的所有类&#xff0c;而这些类都是Object类的子类&#xff1b; Object主要提供了11个方法&#xff0c;大致可以分为六类&#xff1a; 对象比较&#xff1a; public native int has…

51.和的逆运算问题|Marscode AI刷题

1.题目 问题描述 n 个整数两两相加可以得到 n(n - 1) / 2 个和。我们的目标是&#xff1a;根据这些和找出原来的 n 个整数。 按非降序排序返回这 n 个数&#xff0c;如果无解&#xff0c;输出 "Impossible"。 测试样例 样例1&#xff1a; 输入&#xff1a;n 3, …

SpringAI 搭建智能体(一):让模型执行客户端操作

在现代 AI 应用中&#xff0c;仅仅通过模型生成文本并不能满足复杂场景的需求。许多情况下&#xff0c;模型需要调用客户端的工具或函数来完成某些特定任务&#xff0c;例如查询数据库、访问第三方 API、执行系统命令等。Spring AI 提供了工具调用功能&#xff0c;使开发者可以…

Zemax 中的屋脊棱镜建模

光学棱镜是一种透明的光学元件&#xff0c;其表面平坦且抛光&#xff0c;可以折射光线。最常见的棱镜类型是三棱镜&#xff0c;它有两个三角形底座和三个矩形或略呈梯形的表面。棱镜通常由玻璃或丙烯酸等材料制成。当光线以一定角度进入棱镜时&#xff0c;它会在穿过棱镜时发生…

目标跟踪之sort算法(3)

这里写目录标题 1 流程1 预处理2 跟踪 2 代码 参考&#xff1a;sort代码 https://github.com/abewley/sort 1 流程 1 预处理 1.1 获取离线检测数据。1.2 实例化跟踪器。2 跟踪 2.1 轨迹处理。根据上一帧的轨迹预测当前帧的轨迹&#xff0c;剔除到当前轨迹中为空的轨迹得到当前…

solidity高阶 -- 线性继承

Solidity是一种面向合约的高级编程语言&#xff0c;用于编写智能合约。在Solidity中&#xff0c;多线继承是一个强大的特性&#xff0c;允许合约从多个父合约继承属性和方法。本文将详细介绍Solidity中的多线继承&#xff0c;并通过不同的实例展示其使用方法和注意事项。 在Sol…

神经网络|(七)概率论基础知识-贝叶斯公式

【1】引言 前序我们已经了解了一些基础知识。 古典概型&#xff1a;有限个元素参与抽样&#xff0c;每个元素被抽样的概率相等。 条件概率&#xff1a;在某条件已经达成的前提下&#xff0c;新事件发生的概率。实际计算的时候&#xff0c;应注意区分&#xff0c;如果是计算综…

双目立体校正和Q矩阵

立体校正 对两个摄像机的图像平面重投影&#xff0c;使二者位于同一平面&#xff0c;而且左右图像的行对准。 Bouguet 该算法需要用到双目标定后外参(R&#xff0c;T) 从上图中可以看出&#xff0c;该算法主要分为两步&#xff1a; 使成像平面共面 这个办法很直观&#xff…

软件开发中的密码学(国密算法)

1.软件行业中的加解密 在软件行业中&#xff0c;加解密技术广泛应用于数据保护、通信安全、身份验证等多个领域。加密&#xff08;Encryption&#xff09;是将明文数据转换为密文的过程&#xff0c;而解密&#xff08;Decryption&#xff09;则是将密文恢复为明文的过程。以下…

C语言练习(29)

13个人围成一圈&#xff0c;从第1个人开始顺序报号1、2、3。凡报到“3”者退出圈子&#xff0c;找出最后留在圈子中的人原来的序号。本题要求用链表实现。 #include <stdio.h> #include <stdlib.h>// 定义链表节点结构体 typedef struct Node {int num;struct Nod…

postgres基准测试工具pgbench如何使用自定义的表结构和自定义sql

使用 pgbench 进行 PostgreSQL 性能测试时&#xff0c;可以自定义表结构和测试脚本来更好地模拟实际使用场景。以下是一个示例&#xff0c;说明如何自定义表结构和测试脚本。 自定义表结构 创建自定义表结构的 SQL 脚本。例如&#xff0c;创建一个名为 custom_schema.sql 的文…

5.2 软件需求分析

文章目录 需求分析的意义软件需求的组成需求分析的5个方面需求分析方法 需求分析的意义 需求分析解决软件“做什么”的问题。由于开发人员比较熟悉计算机而不熟悉领域业务&#xff0c;用户比较熟悉领域业务而不熟悉计算机&#xff0c;双方需要通过交流&#xff0c;制定出完整、…