C 语言自定义类型学习笔记:结构体、联合体与枚举
最近系统学习了 C 语言自定义类型三大核心:结构体、联合体、枚举,这是 C 语言从基础语法走向实际开发的关键知识点。
第一部分:结构体(struct)- 最常用的自定义类型
结构体是把不同类型的数据打包在一起,用来描述一个完整的对象(学生、商品、节点等),是 C 语言数据封装的基础。
1. 结构体的 3 种声明方式 + typedef 重命名
声明时末尾分号绝对不能丢,这是我最开始反复踩的坑!
cpp
#include <stdio.h>
// 1. 标准声明(最常用)
struct Student {
// 成员变量(不同类型任意组合)
char name[20]; // 姓名
int age; // 年龄
float score; // 成绩
};
// 2. 声明时直接定义变量
struct Teacher {
char name[20];
int id;
} t1, t2; // 直接定义全局变量t1、t2
// 3. typedef重命名(简化写法,后续不用写struct)
typedef struct Book {
char bookName[30];
float price;
} Book; // 以后直接用Book代替struct Book
2. 结构体的 3 种初始化方式
推荐指定成员初始化:
cpp
int main() {
// 方式1:按成员顺序初始化
struct Student s1 = {"张三", 18, 95.5};
// 方式2:指定成员初始化(C99标准,最灵活)
struct Student s2 = {.score=88.0, .name="李四", .age=20};
// 方式3:typedef重命名后初始化
Book b1 = {"《C语言从入门到精通》", 59.9};
// 打印验证
printf("s1:%s %d %.1f\n", s1.name, s1.age, s1.score);
printf("s2:%s %d %.1f\n", s2.name, s2.age, s2.score);
printf("书籍:%s %.1f\n", b1.bookName, b1.price);
return 0;
}
3. 匿名结构体(慎用!)
省略结构体名,只能用一次,编译器会把相同成员的匿名结构体当成不同类型!
cpp
// 匿名结构体
struct {
int a;
char b;
} x; // 只能定义变量x,无法重复使用
// 错误:两个匿名结构体是不同类型,不能赋值
// struct {int a; char b;} y = x;
4. 结构体自引用(链表核心!)
错误:直接包含自身变量 → 内存无限嵌套;正确: 包含自身指针 → 指针大小固定(4/8 字节)
cpp
// 正确的结构体自引用(单链表节点)
typedef struct Node {
int data; // 数据域
struct Node* next; // 指针域:指向下一个节点
} Node;
// 实战:创建3个节点的简单链表
int main() {
Node n1 = {10, NULL};
Node n2 = {20, NULL};
Node n3 = {30, NULL};
// 建立连接
n1.next = &n2;
n2.next = &n3;
// 遍历链表
Node* p = &n1;
while (p != NULL) {
printf("%d ", p->data);
p = p->next;
}
return 0;
}
// 输出:10 20 30
5. 核心难点:结构体内存对齐
5.1对齐规则(VS 默认对齐数 8)
- 第一个成员 → 偏移量 0
- 后续成员 → 对齐到
min(默认对齐数, 成员大小)的整数倍 - 总大小 → 最大对齐数的整数倍
- 嵌套结构体 → 对齐到自身最大对齐数
5.2代码示例:计算结构体大小
cpp
// 示例1
struct S1 {
char c1; // 1字节
int i; // 4字节
char c2; // 1字节
};
// 大小计算:1(对齐到4)+4+1(对齐到4) = 12字节
// 示例2:优化成员顺序(小成员集中)
struct S2 {
char c1;
char c2;
int i;
};
// 大小计算:1+1(对齐到4)+4 = 8字节
// 示例3:嵌套结构体
struct S3 {
char c;
struct S2 s2;
double d;
};
// 大小:1 + 7(填充) + 8 + 8 = 24字节
// 测试代码
int main() {
printf("S1大小:%d\n", sizeof(struct S1)); // 12
printf("S2大小:%d\n", sizeof(struct S2)); // 8
printf("S3大小:%d\n", sizeof(struct S3)); // 24
return 0;
}
5.3修改默认对齐数:
cpp
#pragma pack(1) // 设置默认对齐数为1(无内存填充)
struct S4 {
char c1;
int i;
};
#pragma pack() // 恢复默认对齐数
// sizeof(S4) = 5
6. 结构体数组 + 结构体指针
cpp
// 结构体数组:存储多个学生
struct Student arr[3] = {
{"小明", 17, 90.0},
{"小红", 18, 92.5},
{"小刚", 19, 85.0}
};
// 结构体指针访问成员:两种方式
int main() {
struct Student s = {"小王", 20, 88.0};
struct Student* p = &s;
// 方式1:(*指针).成员
printf("%s ", (*p).name);
// 方式2:指针->成员(最常用)
printf("%d %.1f\n", p->age, p->score);
return 0;
}
7. 结构体传参(必传指针!)
传值会拷贝整个结构体,效率极低;传指针仅 4/8 字节,更高效:
cpp
struct Student {
char name[20];
int age;
};
//不推荐:传值(拷贝大结构体,开销大)
void print1(struct Student s) {
printf("%s %d\n", s.name, s.age);
}
//推荐:传指针(加const防止修改)
void print2(const struct Student* s) {
printf("%s %d\n", s->name, s->age);
}
int main() {
struct Student s = {"小李", 21};
print1(s);
print2(&s);
return 0;
}
8. 结构体进阶:位段(节省内存)
位段 = 结构体的特殊形式,按 bit 分配内存 ,用于网络协议、硬件编程。规则:成员必须是整型,: 后数字表示占用 bit 数
cpp
// 位段声明
struct BitSeg {
int a : 2; // a占2bit
int b : 5; // b占5bit
int c : 10; // c占10bit
int d : 30; // d占30bit
};
int main() {
struct BitSeg bs;
bs.a = 3; // 2bit最大存3
bs.b = 20; // 5bit最大存31
printf("位段大小:%d\n", sizeof(bs)); // 8字节(VS分配规则)
return 0;
}
第二部分:联合体(union)
联合体也叫共用体 ,所有成员共用同一块内存,大小 = 最大成员大小(对齐后)。
1. 核心特性:内存共享(赋值互相覆盖)
cpp
#include <stdio.h>
// 联合体声明
union Data {
int i;
char c;
float f;
};
int main() {
union Data data;
data.i = 10;
printf("i = %d\n", data.i); // 10
data.c = 'A'; // 覆盖低字节
printf("c = %c\n", data.c); // A
printf("i = %d\n", data.i); // 65(内存被覆盖)
return 0;
}
2. 联合体大小计算(对齐规则)
- 至少 = 最大成员大小
- 必须是最大对齐数的整数倍
cpp
// 示例1
union Un1 {
char c[5]; // 5字节,对齐数1
int i; // 4字节,对齐数4
};
// 大小:8(5对齐到4的整数倍)
// 示例2
union Un2 {
short s[7]; // 14字节,对齐数2
int i; // 4字节,对齐数4
};
// 大小:16(14对齐到4的整数倍)
int main() {
printf("Un1:%d\n", sizeof(union Un1)); // 8
printf("Un2:%d\n", sizeof(union Un2)); // 16
return 0;
}
3. 经典实战:判断机器大小端
小端 :低字节存低地址;大端:高字节存低地址
cpp
// 联合体判断大小端(极简代码)
int check_sys() {
union {
int i;
char c;
} un;
un.i = 1;
// 返回1=小端,0=大端
return un.c;
}
int main() {
if (check_sys()) {
printf("小端存储\n");
} else {
printf("大端存储\n");
}
return 0;
}
4. 多类型数据存储(节省内存)
cpp
// 礼品兑换:同一时间只存一种礼品属性
typedef struct Gift {
int type; // 0=图书,1=杯子,2=衣服
// 联合体封装特殊属性
union {
struct { char author[20]; } book;
struct { char color[10]; } cup;
struct { int size; } clothes;
} attr;
} Gift;
int main() {
Gift g;
g.type = 0;
g.attr.book.author = "张三";
return 0;
}
第三部分:枚举(enum)- 优雅定义常量
枚举 = 把有限的取值一一列举 ,替代#define,代码更规范、有类型检查。
1. 枚举声明 + 赋值规则
默认从 0 开始,依次 + 1;可手动赋值,后续自动递增
cpp
// 1. 默认赋值:RED=0, GREEN=1, BLUE=2
enum Color {
RED,
GREEN,
BLUE
};
// 2. 手动赋值:MON=1, TUE=2, WED=3...
enum Week {
MON=1,
TUE,
WED,
THU,
FRI,
SAT,
SUN
};
2. 枚举完整使用示例
cpp
#include <stdio.h>
enum Week {MON=1,TUE,WED,THU,FRI,SAT,SUN};
int main() {
enum Week today = WED;
printf("今天是星期%d\n", today); // 3
// 枚举做分支判断(代码可读性拉满)
if (today == SAT || today == SUN) {
printf("周末休息!\n");
} else {
printf("工作日\n");
}
return 0;
}
3. 枚举 VS #define(为什么用枚举?)
cpp
// #define 无类型、无作用域、调试不显示
#define RED 1
#define GREEN 2
#define BLUE 3
// 枚举:有类型、有作用域、可调试、一次定义多个
enum Color {cRED=1, cGREEN, cBLUE};
// 枚举传参(类型安全,#define做不到)
void printColor(enum Color c) {
printf("%d\n", c);
}
第四部分:综合实战(整合所有知识点)
4.1 简易学生信息管理系统
cpp
#include <stdio.h>
// 结构体封装学生
typedef struct Stu {
char name[20];
int age;
float score;
} Stu;
// 枚举定义菜单
enum Menu {EXIT, ADD, SHOW};
// 显示菜单
void menu() {
printf("1.添加学生 2.显示学生 0.退出\n");
}
// 添加学生(传指针)
void addStu(Stu* s, int i) {
printf("输入第%d个学生信息:\n", i+1);
scanf("%s %d %f", s->name, &s->age, &s->score);
}
// 显示学生
void showStu(Stu* s, int n) {
for (int i=0; i<n; i++) {
printf("%s %d %.1f\n", s[i].name, s[i].age, s[i].score);
}
}
int main() {
Stu arr[50];
int n = 0;
int choice;
while (1) {
menu();
scanf("%d", &choice);
switch (choice) {
case ADD:
addStu(&arr[n], n);
n++;
break;
case SHOW:
showStu(arr, n);
break;
case EXIT:
return 0;
default:
printf("输入错误!\n");
}
}
}
我的学习总结
- 结构体 :封装不同数据,重点掌握内存对齐、传指针、自引用;位段用于节省内存。
- 联合体 :成员共享内存,大小 = 最大成员(对齐后),大小端判断是必考用法。
- 枚举 :定义有限常量,比
#define更安全、可读性更高,适合分支判断。 - 开发原则:结构体传参必传指针;成员顺序优化节省内存;优先用枚举代替宏常量。