首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >收集飞花令碎片——【C语言】自定义类型(结构体、联合体、枚举)

收集飞花令碎片——【C语言】自定义类型(结构体、联合体、枚举)

作者头像
枫亭湖区
发布2025-11-13 09:16:32
发布2025-11-13 09:16:32
10
举报

🌊🌊点击进入C语言专栏🌊🌊 🌊🌊如何收集自己的代码仓库🌊🌊

学习应明为何而学、学了何用 在开始今天的课程之前,我们先谈谈为什么需要自定义类型 结构体(Struct) 是一种自定义的数据类型,允许将多个不同类型的数据组合在一起形成一个新的复合数据类型 结构体(Struct) 主要作用于封装和组织,简化复杂数据的管理,提高代码的可读性和维护性,为面向对象编程打下基础

(一)结构体

1)结构体的定义

代码语言:javascript
复制
// 基本语法
struct 结构体标签 {
    数据类型 成员1;
    数据类型 成员2;
    // ...
};

// 示例
struct Student {
    int id;
    char name[50];
    float score;
    int age;
};

2)结构体变量的声明和初始化

2.1)结构体的三种声明格式
代码语言:javascript
复制
// 方法1: 先定义结构体类型,再声明变量
struct Student {
    int id;
    char name[50];
    float score;
};
struct Student stu1, stu2;

// 方法2: 定义结构体类型的同时声明变量
struct Student {
    int id;
    char name[50];
    float score;
} stu1, stu2;

// 方法3: 匿名结构体
struct {
    int id;
    char name[50];
} temp_stu;

2.2)结构体的初始化
代码语言:javascript
复制
#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;
}

3)结构体传参

下面详细讲解C语言结构体传参的各种方式,包括值传递、地址传递、返回结构体

代码语言:javascript
复制
	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;
}

上面的print1 和 print2 函数哪个好些? 肯定首选print2

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

4)结构体的自引用

🧩 一、什么是结构体自引用? 结构体里面有一个成员,它的类型又是这个结构体本身。 但要注意! 结构体不能直接包含自己,只能包含指向自己的指针。

在这里插入图片描述
在这里插入图片描述

这里 next 的类型是 struct Node, 但我们现在正在定义 struct Node 本身。 它还没定义完!编译器就被问: “请告诉我struct Node的大小是多少。” 编译器就懵了: “我还在定义它呢,我也不知道多大啊!” 😵 于是编译器报错。

  • ✅ 指针为什么就没问题?
代码语言:javascript
复制
struct Node {
    int data;
    struct Node *next;
};

这里` next 是一个 指针,指针的大小是固定的: 👉 无论struct Node内容有多复杂,指针的大小编译器都知道。

在这里插入图片描述
在这里插入图片描述

所以编译器就能顺利计算出整个结构体的大小。


5)结构体作为函数返回值

首先来看一个简单的例子,如何直接将结构体作为函数返回值:

代码语言:javascript
复制
#include <stdio.h>

// 定义一个结构体类型 Point,表示二维平面上的点
struct Point {
    int x;  // x坐标
    int y;  // y坐标
};

// 函数:创建并返回一个Point结构体
// 参数:x - 点的x坐标,y - 点的y坐标
// 返回值:包含指定坐标的Point结构体
struct Point createPoint(int x, int y) {
    struct Point p;  // 在函数内部定义一个Point结构体变量
    p.x = x;         // 设置结构体的x成员值
    p.y = y;         // 设置结构体的y成员值
    return p;        // 返回这个结构体(返回的是结构体的副本)
}

int main() {
    // 调用createPoint函数创建结构体实例
    struct Point p1 = createPoint(10, 20);
    
    // 打印结构体成员的值
    printf("Point: (%d, %d)\n", p1.x, p1.y);
    
    return 0;
}

解释:

  • createPoint函数创建并返回一个struct Point类型的变量。
  • 结构体Point的成员xy被赋值,并作为返回值返回。
  • main函数中,p1 得到返回的结构体,直接打印其成员。

节省运行效率 如果需要返回一个结构体的指针,应该返回一个 动态分配的结构体(malloc 或者静态变量)。

代码语言:javascript
复制
struct Point* createPoint() {
    struct Point* p = malloc(sizeof(struct Point));  // 动态分配内存
    p->x = 10;
    p->y = 20;
    return p;  // 返回指向动态分配内存的指针
}

💡 返回的指针指向的是堆区内存,而堆区内存不会随着函数的结束而销毁;也可以避免局部变量的生命周期问题


6)结构体内存对齐

内存对齐是指计算机内存中数据的存储方式,目的是优化内存访问速度。特别是在 C 语言中,结构体内存对齐是一个至关重要的概念。结构体中每个成员变量会根据其数据类型的大小自动进行对齐,以便更高效地存储和访问数据。

6.1)结构体内存对齐规则

我们先看一段示例代码,再来讲规则

代码语言:javascript
复制
//练习1
struct S1 {
    char c1;  // 1 字节
    int i;    // 4 字节
    char c2;  // 1 字节
};

printf("%d\n", sizeof(struct S1));

观察它的内存格式

成员

类型

大小(字节)

偏移地址

占用内存区间

填充(字节)

c1

char

1

0

[0]

3

i

int

4

4

[4, 7]

0

c2

char

1

8

[8]

3

尾部填充

-

-

-

-

3

  • 偏移计算: c1 从 0 开始,占 1 字节。 i(4 字节)需对齐到 4 的倍数,因此在 c1 后填充 3 字节,i 从偏移 4 开始。 c2 从 8 开始,占 1 字节。
  • 尾部填充: 结构体总大小需为其最大成员对齐值的整数倍(此处为4), 因此在 c2 后填充3字节, 使总大小为12字节

有了上面的例子,我们再来讲解一下结构体对齐规则: 1. 结构体的第1个成员对齐到和结构体变量起始位置偏移量为0的地址处 2. 从第2个成员变量开始,都要对齐到某个对齐数的整数倍的地址处 对齐数 = 编译器默认的一个对齐数与该成员变量大小的较小值

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

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

这样一看,是不是上面例子更加浅显易懂了?

6.2)代码练习
代码语言:javascript
复制
struct S3
{
	double d;
	char c;
	int i;
};
printf("%d\n", sizeof(struct S3));
在这里插入图片描述
在这里插入图片描述

→ 最终结构体大小:16 字节


代码语言:javascript
复制
//结构体嵌套问题
struct S4
{
	char c1;
	struct S3 s3;
	double d;
};
printf("%d\n", sizeof(struct S4));
在这里插入图片描述
在这里插入图片描述

→ 最终结构体大小:32 字节


6.3)修改默认对齐数

#pragma 这个预处理指令,可以改变编译器的默认对齐数

代码语言:javascript
复制
#include <stdio.h>

// 指示编译器按 1 字节对齐(即不再进行自动填充对齐)
// 结构体中的每个成员紧密排列,节省空间但访问速度可能变慢
#pragma pack(1)

// 定义结构体 S
struct S
{
    char c1;  // 占 1 字节,偏移量 0
    int i;    // 占 4 字节,按 pack(1) 不再对齐,紧跟在 c1 后,占偏移量 1~4
    char c2;  // 占 1 字节,偏移量 5
};
// 当前结构体占 6 字节:c1(1) + i(4) + c2(1)

#pragma pack()  // 恢复默认对齐方式(通常是 4 或 8)

int main()
{
    // 输出结构体 S 的大小
    printf("%d\n", sizeof(struct S));  // 输出 6
    return 0;
}

成员

字节范围

说明

c1

0

char,占 1 字节

i

1–4

int,占 4 字节(紧接在 c1 后,因为 #pragma pack(1) 取消对齐)

c2

5

char,占 1 字节


6.4)为什么要内存对齐
  • 硬件要求:某些CPU只能从对齐的地址读取数据
  • 性能优化:对齐的数据访问速度更快
  • 平台兼容:不同硬件平台有不同的对齐要求

7)结构体实现位段

7.1)位段是什么?

位段(Bit-field) 是 C 语言结构体中一种特殊成员,用来在一个整型变量的二进制位级上划分存储空间 它允许你精确控制某个字段占用多少个二进制位,从而节省内存


7.2)位段的规则
  1. 位段的成员必须是intunsigned intsigned int,在 C99 中位段成员的类型也可以选择其他整型家族类型,比如:char
  2. 位段的成员名后边有一个冒号和一个数字
代码语言:javascript
复制
struct A
{
		int _a:2;
		int _b:5;
		int _c:10;
		int _d:30;
};

那么A所占的内存是多少?

🧠 位段的关键规则(以 GCC / MSVC 常见实现为准) 位段的类型是 int → 每个存储单元是 4 字节(32 位)。 同类型的位段会共享同一个 32 位单元,直到放不下为止。 如果剩余位数不足,新的位段会从下一个32位单元重新开始。 结构体整体大小要满足 最大对齐数(这里是4字节) 的倍数。

在这里插入图片描述
在这里插入图片描述

→ 最终结果:sizeof(struct A) = 8


7.3)位段的特点
在这里插入图片描述
在这里插入图片描述

(二)联合体

联合体的所有成员共享同一内存,联合体的大小等于其最大成员大小(通常再加上对齐填充)

代码语言:javascript
复制
#include <stdio.h>

// 联合类型的声明
union Un 
{
    char c;   // 字符类型成员,占用1字节
    int i;    // 整型成员,占用4字节
};

int main() 
{
    // 联合变量的定义,并初始化为0
    union Un un = {0};
    
    // 计算联合变量的大小
    printf("联合体的大小: %d\n", sizeof(un));
    
    return 0;
}

所以联合体也叫:共用体

给联合体其中一个成员赋值,其他成员的值也跟着变化。

代码语言:javascript
复制
#include <stdio.h>

// 定义一个联合体,包含一个整数和一个字符
union Data {
    int number;
    char letter;
};

int main() {
    union Data data;
    
    printf("=== 联合体成员值变化演示 ===\n\n");
    
    // 1. 先给number赋值
    data.number = 100;
    printf("1. 给number赋值100后:\n");
    printf("   data.number = %d\n", data.number);
    printf("   data.letter = %c (ASCII: %d)\n", data.letter, data.letter);
    printf("   (letter的值被number影响了)\n\n");
    
    // 2. 再给letter赋值
    data.letter = 'A';
    printf("2. 给letter赋值'A'后:\n");
    printf("   data.letter = %c\n", data.letter);
    printf("   data.number = %d\n", data.number);
    printf("   (number的值被letter改变了!)\n\n");
    
    // 3. 再次给number赋值
    data.number = 65; // 65是'A'的ASCII码
    printf("3. 给number赋值65后:\n");
    printf("   data.number = %d\n", data.number);
    printf("   data.letter = %c\n", data.letter);
    printf("   (letter又变成了'A')\n");
    
    return 0;
}
在这里插入图片描述
在这里插入图片描述

2.1)联合体的特点

联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码1输出的三个地址一模一样,代码2的输出,我们发现将i的第4个字节的内容修改为55 我们仔细分析就可以画出,un的内存布局图

在这里插入图片描述
在这里插入图片描述

(三)枚举

枚举提供了类型安全的常量定义方式,提高了代码的可读性和可维护性。选择合适的声明格式取决于具体的使用场景和代码组织需求。

1)基本声明格式

  • 格式1:仅声明枚举类型
代码语言:javascript
复制
enum 枚举类型名
{
    枚举常量1,
    枚举常量2,
    枚举常量3,
    // ...
};
  • 格式2:声明类型的同时定义变量
代码语言:javascript
复制
enum 枚举类型名
{
    枚举常量列表
} 变量名1, 变量名2, ...;
  • 格式3:匿名枚举(不指定类型名)
代码语言:javascript
复制
enum
{
    枚举常量列表
} 变量名1, 变量名2, ...;
  • 格式4:使用typedef简化
代码语言:javascript
复制
typedef enum 枚举类型名
{
    枚举常量列表
} 类型别名;

2)枚举类型的使用

代码语言:javascript
复制
enum Color  // 颜色
{
    RED = 1,
    GREEN = 2,
    BLUE = 4
};

enum Color clr = GREEN;  // 使用枚举常量给枚举变量赋值

如果你觉得这篇文章对你有帮助,麻烦给个三连吧

我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=7yhfn16q1d2

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-11-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • (一)结构体
    • 1)结构体的定义
    • 2)结构体变量的声明和初始化
      • 2.1)结构体的三种声明格式
      • 2.2)结构体的初始化
    • 3)结构体传参
    • 4)结构体的自引用
    • 5)结构体作为函数返回值
    • 6)结构体内存对齐
      • 6.1)结构体内存对齐规则
      • 6.2)代码练习
      • 6.3)修改默认对齐数
      • 6.4)为什么要内存对齐
    • 7)结构体实现位段
      • 7.1)位段是什么?
      • 7.2)位段的规则
      • 7.3)位段的特点
  • (二)联合体
    • 2.1)联合体的特点
  • (三)枚举
    • 1)基本声明格式
    • 2)枚举类型的使用
  • 如果你觉得这篇文章对你有帮助,麻烦给个三连吧
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档