前言
C 语言基础数据类型只能描述单一数据,而实际开发中经常需要组合数据、状态标记、位级存储、内存复用,这时就需要用到自定义类型。
本文全面讲解 struct 结构体、位段、enum 枚举、union 联合体四大自定义类型,搭配文本版思维导图、可运行代码、底层原理、内存规则、高频笔试题,内容适配课程设计、面试复习、工程开发,建议收藏精读。 前置说明:全文代码兼容 VS / GCC / Dev-C++,可直接复制编译。
一、自定义类型知识体系(文本思维导图)
C语言自定义类型整体框架 ├─ 基础铺垫 │ ├─ 普通内置类型:char/short/int/float/double │ ├─ 自定义类型作用:组合数据、状态枚举、位存储、内存共享 │ └─ 辅助关键字:typedef 类型重命名 ├─ 1. 结构体 struct(核心重点) │ ├─ 基本语法:声明、变量定义、匿名结构体 │ ├─ 初始化与成员访问:. 运算符、-> 指针访问 │ ├─ 结构体内存对齐(面试必考) │ │ ├─ 四大对齐规则 │ │ ├─ offsetof 偏移量计算 │ │ ├─ #pragma pack 修改默认对齐数 │ │ └─ 成员顺序对内存大小的影响 │ ├─ 结构体嵌套、结构体自引用(链表基础) │ ├─ 结构体数组、结构体指针 │ └─ 结构体传参:传值 vs 传地址(效率对比) ├─ 2. 位段(基于结构体,按bit存储) │ ├─ 语法格式:成员 : 位数 │ ├─ 内存分配规则 │ ├─ 适用场景:硬件寄存器、协议报文、标志位 │ ├─ 特性:无法取地址、不能使用scanf直接输入 │ └─ 缺点:分配规则跨平台不统一,可移植性差 ├─ 3. 枚举 enum │ ├─ 语法:枚举常量、默认赋值、手动赋值 │ ├─ 取值规则:常量自动递增 │ ├─ 优势对比 #define:类型安全、支持调试、作用域明确 │ └─ 实战场景:菜单选项、状态码、功能标识 ├─ 4. 联合体 union(共用体) │ ├─ 核心特性:所有成员共享同一块内存 │ ├─ 内存大小:等于最大成员所占字节数 │ ├─ 使用特点:同一时刻仅一个成员有效 │ ├─ 经典实战:联合体判断系统大小端 │ └─ 适用场景:内存复用、类型转换、协议解析 ├─ typedef 类型别名 │ ├─ 基础用法:给已有类型起别名 │ ├─ 结合结构体/枚举/联合体简化书写 │ └─ 易错点:区分 typedef 与 #define ├─ 高频易错 & 面试题汇总 │ ├─ 内存对齐计算、对齐数修改 │ ├─ 结构体自引用写法误区 │ ├─ 位段位数溢出问题 │ ├─ 联合体内存覆盖问题 │ └─ 四大类型使用场景区分 └─ 综合实战 ├─ 学生信息管理(结构体数组) ├─ 简易菜单系统(枚举) ├─ 大小端检测(联合体) └─ 标志位存储(位段)
二、结构体 struct(使用最广泛的自定义类型)
2.1 概念与基本语法
结构体用于将多个不同类型的数据成员封装为一个整体,用来描述一个复杂对象。
2.1.1 结构体声明与变量定义
#include <stdio.h>
// 1. 结构体类型声明
struct Student
{
char name[20];
int age;
float score;
};
// 2. 声明同时定义变量
struct Teacher
{
char name[20];
int id;
} t1, t2;
// 3. 匿名结构体(无类型名,只能当场使用)
struct
{
int x;
int y;
} point;
int main()
{
struct Student s1;
return 0;
}
2.2 初始化与成员访问
• 结构体普通变量:使用 . 访问成员• 结构体指针变量:使用 -> 访问成员
#include <stdio.h>
#include <string.h>
struct Student
{
char name[20];
int age;
float score;
};
int main()
{
// 顺序初始化
struct Student s1 = {"张三", 18, 92.5f};
// 指定成员初始化(C99 推荐)
struct Student s2 = {.age = 19, .name = "李四", .score = 88.0f};
// 变量访问成员
printf("姓名:%s 年龄:%d 分数:%.1f\\n", s1.name, s1.age, s1.score);
// 结构体指针
struct Student* p = &s2;
printf("姓名:%s 年龄:%d\\n", p->name, p->age);
// 字符数组赋值必须使用 strcpy
strcpy(s1.name, "王五");
printf("修改后姓名:%s\\n", s1.name);
return 0;
}
2.3 结构体内存对齐(笔试核心考点)
对齐规则
1. 第一个成员偏移量为 0;
2. 后续成员偏移量 = 自身大小 和 默认对齐数(VS=8,GCC=4)中较小值的整数倍;
3. 结构体总大小 = 内部最大对齐数的整数倍;
4. 嵌套结构体:嵌套结构体整体按照自身最大对齐数对齐。代码验证 + 偏移量计算
#include <stdio.h>
#include <stddef.h> // offsetof 头文件
struct S1
{
char c1;
int a;
char c2;
};
struct S2
{
int a;
char c1;
char c2;
};
int main()
{
printf("S1 总大小:%zu\\n", sizeof(struct S1));
printf("c1 偏移:%zu\\n", offsetof(struct S1, c1));
printf("a 偏移:%zu\\n", offsetof(struct S1, a));
printf("c2 偏移:%zu\\n", offsetof(struct S1, c2));
printf("\\nS2 总大小:%zu\\n", sizeof(struct S2));
return 0;
}
修改默认对齐数
#include <stdio.h>
#pragma pack(4) // 设置对齐数为4
struct S
{
char c;
int a;
};
#pragma pack() // 恢复默认对齐
int main()
{
printf("结构体大小:%zu\\n", sizeof(struct S));
return 0;
}
2.4 结构体自引用(链表基础)
不能包含自身结构体变量,只能包含指向自身的指针。 #include <stdio.h>
struct Node
{
int data;
struct Node* next;
};
int main()
{
struct Node n1 = {10, NULL};
struct Node n2 = {20, &n1};
printf("n2.data = %d\\n", n2.data);
printf("n2.next->data = %d\\n", n2.next->data);
return 0;
}
2.5 结构体传参
优先传地址,传值会完整拷贝结构体,效率低。
#include <stdio.h>
struct Student
{
char name[20];
int age;
};
// 传值(不推荐)
void PrintByVal(struct Student s)
{
printf("姓名:%s 年龄:%d\\n", s.name, s.age);
}
// 传地址(推荐,const 保护数据)
void PrintByAddr(const struct Student* ps)
{
printf("姓名:%s 年龄:%d\\n", ps->name, ps->age);
}
int main()
{
struct Student s = {"小明", 17};
PrintByVal(s);
PrintByAddr(&s);
return 0;
}
三、位段(结构体扩展,按位存储)
位段以比特位(bit) 为单位分配内存,极致节省空间,多用于硬件、协议开发。
• 位段成员类型:int / unsigned int / signed int• 位段没有地址,不能取地址。
#include <stdio.h>
// 位段结构体
struct BitField
{
unsigned int a : 2; // 占 2 bit
unsigned int b : 3; // 占 3 bit
unsigned int c : 1; // 占 1 bit
};
int main()
{
struct BitField bf;
bf.a = 3;
bf.b = 7;
bf.c = 1;
printf("位段结构体大小:%zu 字节\\n", sizeof(struct BitField));
printf("a=%u b=%u c=%u\\n", bf.a, bf.b, bf.c);
return 0;
}
注意:位段内存分配规则跨平台不一致,正式可移植项目谨慎使用。
四、枚举 enum(限定取值范围)
枚举用来列出变量所有合法取值,提升代码可读性与安全性,替代宏定义。
4.1 基础用法
#include <stdio.h>
// 1. 默认从 0 开始递增
enum Week
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
// 2. 手动赋值,后续自动递增
enum Color
{
RED = 5,
GREEN,
BLUE = 10
};
int main()
{
enum Week day = Fri;
enum Color col = BLUE;
printf("Fri = %d\\n", day);
printf("BLUE = %d\\n", col);
return 0;
}
4.2 实战:菜单状态枚举
#include <stdio.h>
enum Menu
{
EXIT,
ADD,
SUB,
MUL,
DIV
};
int main()
{
int choose = 0;
while (1)
{
printf("\\n0-退出 1-加法 2-减法 3-乘法 4-除法\\n");
scanf("%d", &choose);
switch (choose)
{
case EXIT:
printf("程序退出\\n");
return 0;
case ADD:
printf("执行加法\\n");
break;
case SUB:
printf("执行减法\\n");
break;
case MUL:
printf("执行乘法\\n");
break;
case DIV:
printf("执行除法\\n");
break;
default:
printf("输入错误\\n");
break;
}
}
return 0;
}
五、联合体 union(共用内存空间)
联合体所有成员共享同一块内存,同一时刻只有一个成员有效;联合体大小等于最大成员的字节数。
5.1 基础使用
#include <stdio.h>
union Data
{
char ch;
int num;
float f;
};
int main()
{
union Data d;
printf("联合体大小:%zu\\n", sizeof(union Data));
d.num = 100;
printf("num=%d ch=%d f=%f\\n", d.num, d.ch, d.f);
d.ch = 'A';
printf("num=%d ch=%c\\n", d.num, d.ch);
return 0;
}
5.2 经典实战:联合体判断大小端
#include <stdio.h>
union Endian
{
int n;
char c;
};
int main()
{
union Endian e;
e.n = 1;
if (e.c == 1)
{
printf("当前系统:小端字节序\\n");
}
else
{
printf("当前系统:大端字节序\\n");
}
return 0;
}
六、typedef 类型重命名
为已有类型起别名,简化复杂类型书写。
#include <stdio.h>
// 结构体重命名
typedef struct Student
{
char name[20];
int age;
} Stu;
// 枚举重命名
typedef enum Color
{
RED,
GREEN,
BLUE
} Col;
int main()
{
Stu s = {"小华", 16};
Col c = GREEN;
printf("姓名:%s 颜色值:%d\\n", s.name, c);
return 0;
}
七、四大自定义类型总结与区分
1. 结构体 struct:成员独立占用内存,用于描述复合对象,内存对齐是重点。
2. 位段:基于结构体,按比特位存储,省内存,多用于硬件/协议,跨平台差。
3. 枚举 enum:限定变量合法取值,替代宏,类型安全、可读性强。
4. 联合体 union:成员共享内存,节省空间,常用于大小端判断、数据解析。
5. typedef:类型别名,简化代码,提升可维护性。
自定义类型是 C 语言从入门到进阶的分水岭,也是结构体、链表、文件、项目开发的基础,务必吃透内存规则与使用场景。



