第09章 构造类型
9.1 结构体 (struct)
9.1.1 结构体的定义
结构体: 将不同类型的数据组合在一起的复合数据类型。
语法:
struct 结构体名
{
数据类型 成员1;
数据类型 成员2;
// ...
};
示例:
#include <stdio.h>
struct Student
{
int id;
char name[50];
int age;
float score;
};
int main(void)
{
struct Student stu; // 定义结构体变量
stu.id = 1001;
stu.age = 20;
stu.score = 95.5;
printf("ID: %d, 年龄: %d, 成绩: %.1f\n",
stu.id, stu.age, stu.score);
return 0;
}
9.1.2 结构体的初始化
方式1: 定义时初始化
struct Student stu = {1001, "张三", 20, 95.5};
方式2: 指定成员初始化(C99)
struct Student stu = {
.id = 1001,
.name = "张三",
.age = 20,
.score = 95.5
};
方式3: 部分初始化
struct Student stu = {1001, "张三"}; // age和score默认为0
完整示例:
#include <stdio.h>
#include <string.h>
struct Student
{
int id;
char name[50];
int age;
float score;
};
int main(void)
{
// 方式1
struct Student stu1 = {1001, "张三", 20, 95.5};
// 方式2
struct Student stu2 = {
.id = 1002,
.name = "李四",
.age = 21,
.score = 88.0
};
// 方式3
struct Student stu3;
stu3.id = 1003;
strcpy(stu3.name, "王五");
stu3.age = 19;
stu3.score = 92.5;
printf("学生1: ID=%d, 姓名=%s, 年龄=%d, 成绩=%.1f\n",
stu1.id, stu1.name, stu1.age, stu1.score);
printf("学生2: ID=%d, 姓名=%s, 年龄=%d, 成绩=%.1f\n",
stu2.id, stu2.name, stu2.age, stu2.score);
return 0;
}
9.1.3 typedef定义类型别名
语法:
typedef struct 结构体名
{
// 成员
} 类型别名;
示例:
#include <stdio.h>
typedef struct
{
int x;
int y;
} Point; // Point是类型别名
int main(void)
{
Point p1 = {10, 20}; // 无需写struct
Point p2 = {.x = 30, .y = 40};
printf("p1: (%d, %d)\n", p1.x, p1.y);
printf("p2: (%d, %d)\n", p2.x, p2.y);
return 0;
}
9.1.4 结构体数组
#include <stdio.h>
#include <string.h>
typedef struct
{
int id;
char name[50];
float score;
} Student;
int main(void)
{
Student students[3] = {
{1001, "张三", 95.5},
{1002, "李四", 88.0},
{1003, "王五", 92.5}
};
for (int i = 0; i < 3; i++)
{
printf("学生%d: ID=%d, 姓名=%s, 成绩=%.1f\n",
i+1, students[i].id, students[i].name, students[i].score);
}
return 0;
}
9.1.5 结构体指针
#include <stdio.h>
typedef struct
{
int x;
int y;
} Point;
int main(void)
{
Point p = {10, 20};
Point *ptr = &p;
// 方式1: 通过指针访问成员
printf("x = %d, y = %d\n", (*ptr).x, (*ptr).y);
// 方式2: 使用箭头运算符(推荐)
printf("x = %d, y = %d\n", ptr->x, ptr->y);
// 修改成员
ptr->x = 30;
ptr->y = 40;
printf("修改后: x = %d, y = %d\n", p.x, p.y);
return 0;
}
⚠️ 注意:
(*ptr).x等价于ptr->x- 箭头运算符
->更常用
9.1.6 结构体嵌套
#include <stdio.h>
typedef struct
{
int year;
int month;
int day;
} Date;
typedef struct
{
int id;
char name[50];
Date birthday; // 嵌套结构体
} Person;
int main(void)
{
Person p = {
.id = 1001,
.name = "张三",
.birthday = {1990, 5, 20}
};
printf("姓名: %s\n", p.name);
printf("生日: %d-%02d-%02d\n",
p.birthday.year, p.birthday.month, p.birthday.day);
return 0;
}
9.1.7 结构体作为函数参数
传值:
#include <stdio.h>
typedef struct
{
int x;
int y;
} Point;
void printPoint(Point p) // 传值,拷贝整个结构体
{
printf("Point: (%d, %d)\n", p.x, p.y);
}
int main(void)
{
Point p = {10, 20};
printPoint(p);
return 0;
}
传指针(推荐):
#include <stdio.h>
typedef struct
{
int x;
int y;
} Point;
void printPoint(const Point *p) // 传指针,高效
{
printf("Point: (%d, %d)\n", p->x, p->y);
}
void movePoint(Point *p, int dx, int dy)
{
p->x += dx;
p->y += dy;
}
int main(void)
{
Point p = {10, 20};
printPoint(&p);
movePoint(&p, 5, 10);
printPoint(&p);
return 0;
}
9.1.8 结构体赋值
#include <stdio.h>
typedef struct
{
int x;
int y;
} Point;
int main(void)
{
Point p1 = {10, 20};
Point p2;
p2 = p1; // 结构体可以直接赋值(拷贝所有成员)
printf("p1: (%d, %d)\n", p1.x, p1.y);
printf("p2: (%d, %d)\n", p2.x, p2.y);
p2.x = 30; // 修改p2不影响p1
printf("修改p2后:\n");
printf("p1: (%d, %d)\n", p1.x, p1.y);
printf("p2: (%d, %d)\n", p2.x, p2.y);
return 0;
}
9.2 内存对齐
9.2.1 什么是内存对齐
内存对齐: 编译器为了提高访问效率,会在结构体成员之间插入填充字节。
#include <stdio.h>
struct Test1
{
char c; // 1字节
int i; // 4字节
short s; // 2字节
};
int main(void)
{
printf("sizeof(char): %lu\n", sizeof(char));
printf("sizeof(int): %lu\n", sizeof(int));
printf("sizeof(short): %lu\n", sizeof(short));
printf("sizeof(Test1): %lu\n", sizeof(struct Test1));
// 期望: 1 + 4 + 2 = 7
// 实际: 12 (因为内存对齐)
return 0;
}
9.2.2 对齐规则
- 结构体第一个成员偏移量为0
- 每个成员的偏移量是其大小的整数倍
- 结构体总大小是最大成员大小的整数倍
示例分析:
struct Test1
{
char c; // 偏移0,占1字节
// 填充3字节(对齐到int)
int i; // 偏移4,占4字节
short s; // 偏移8,占2字节
// 填充2字节(总大小对齐到int的4字节)
};
// 总大小: 12字节
struct Test2
{
char c; // 偏移0,占1字节
short s; // 偏移2,占2字节(填充1字节)
int i; // 偏移4,占4字节
};
// 总大小: 8字节
9.2.3 优化结构体大小
原则: 将相同或相近大小的成员放在一起
#include <stdio.h>
// 未优化
struct Unoptimized
{
char c1; // 1字节
int i; // 4字节
char c2; // 1字节
short s; // 2字节
}; // 总大小: 12字节
// 优化后
struct Optimized
{
int i; // 4字节
short s; // 2字节
char c1; // 1字节
char c2; // 1字节
}; // 总大小: 8字节
int main(void)
{
printf("Unoptimized: %lu 字节\n", sizeof(struct Unoptimized));
printf("Optimized: %lu 字节\n", sizeof(struct Optimized));
return 0;
}
9.2.4 指定对齐
使用#pragma pack:
#include <stdio.h>
#pragma pack(1) // 1字节对齐
struct Packed
{
char c;
int i;
short s;
};
#pragma pack() // 恢复默认对齐
int main(void)
{
printf("sizeof(Packed): %lu\n", sizeof(struct Packed)); // 7字节
return 0;
}
⚠️ 注意: 紧凑对齐可能降低访问效率,慎用!
9.3 共用体 (union)
9.3.1 共用体的定义
共用体: 所有成员共享同一块内存,同一时间只能存储一个成员。
语法:
union 共用体名
{
数据类型 成员1;
数据类型 成员2;
// ...
};
示例:
#include <stdio.h>
union Data
{
int i;
float f;
char str[20];
};
int main(void)
{
union Data data;
printf("union大小: %lu\n", sizeof(data)); // 20字节(最大成员)
data.i = 10;
printf("data.i = %d\n", data.i);
data.f = 3.14; // 覆盖了i的值
printf("data.f = %.2f\n", data.f);
printf("data.i = %d (已被覆盖)\n", data.i); // 输出垃圾值
return 0;
}
9.3.2 共用体的应用
应用1: 类型转换
#include <stdio.h>
union FloatInt
{
float f;
unsigned int i;
};
int main(void)
{
union FloatInt fi;
fi.f = 3.14;
printf("float值: %f\n", fi.f);
printf("对应的整数表示: 0x%08X\n", fi.i);
return 0;
}
应用2: 变体类型
#include <stdio.h>
typedef enum
{
TYPE_INT,
TYPE_FLOAT,
TYPE_STRING
} DataType;
typedef struct
{
DataType type;
union
{
int i;
float f;
char str[50];
} value;
} Variant;
void printVariant(Variant v)
{
switch (v.type)
{
case TYPE_INT:
printf("整数: %d\n", v.value.i);
break;
case TYPE_FLOAT:
printf("浮点数: %.2f\n", v.value.f);
break;
case TYPE_STRING:
printf("字符串: %s\n", v.value.str);
break;
}
}
int main(void)
{
Variant v1 = {TYPE_INT, .value.i = 100};
Variant v2 = {TYPE_FLOAT, .value.f = 3.14};
Variant v3 = {TYPE_STRING, .value.str = "Hello"};
printVariant(v1);
printVariant(v2);
printVariant(v3);
return 0;
}
9.3.3 结构体 vs 共用体
| 特性 | struct | union |
|---|---|---|
| 内存 | 所有成员独立存储 | 所有成员共享内存 |
| 大小 | 所有成员大小之和(+对齐) | 最大成员的大小 |
| 访问 | 可同时访问所有成员 | 同时只能使用一个成员 |
| 用途 | 组合不同类型数据 | 节省内存,类型转换 |
9.4 枚举 (enum)
9.4.1 枚举的定义
枚举: 定义一组命名的整数常量。
语法:
enum 枚举名
{
枚举常量1,
枚举常量2,
// ...
};
示例:
#include <stdio.h>
enum Weekday
{
MONDAY, // 0
TUESDAY, // 1
WEDNESDAY, // 2
THURSDAY, // 3
FRIDAY, // 4
SATURDAY, // 5
SUNDAY // 6
};
int main(void)
{
enum Weekday today = FRIDAY;
printf("今天是星期: %d\n", today);
if (today == FRIDAY)
{
printf("明天是周末!\n");
}
return 0;
}
9.4.2 指定枚举值
#include <stdio.h>
enum Color
{
RED = 1,
GREEN = 2,
BLUE = 4,
YELLOW = 8
};
enum Season
{
SPRING = 1,
SUMMER, // 2
AUTUMN = 10,
WINTER // 11
};
int main(void)
{
enum Color c = RED;
printf("RED = %d\n", c);
enum Season s = SUMMER;
printf("SUMMER = %d\n", s);
printf("WINTER = %d\n", WINTER);
return 0;
}
9.4.3 枚举的应用
应用1: 状态机
#include <stdio.h>
typedef enum
{
STATE_IDLE,
STATE_RUNNING,
STATE_PAUSED,
STATE_STOPPED
} State;
void processState(State state)
{
switch (state)
{
case STATE_IDLE:
printf("空闲状态\n");
break;
case STATE_RUNNING:
printf("运行状态\n");
break;
case STATE_PAUSED:
printf("暂停状态\n");
break;
case STATE_STOPPED:
printf("停止状态\n");
break;
}
}
int main(void)
{
State state = STATE_IDLE;
processState(state);
state = STATE_RUNNING;
processState(state);
return 0;
}
应用2: 错误码
#include <stdio.h>
typedef enum
{
ERR_SUCCESS = 0,
ERR_INVALID_PARAM = -1,
ERR_FILE_NOT_FOUND = -2,
ERR_NO_MEMORY = -3,
ERR_TIMEOUT = -4
} ErrorCode;
const char* getErrorString(ErrorCode err)
{
switch (err)
{
case ERR_SUCCESS:
return "成功";
case ERR_INVALID_PARAM:
return "参数错误";
case ERR_FILE_NOT_FOUND:
return "文件未找到";
case ERR_NO_MEMORY:
return "内存不足";
case ERR_TIMEOUT:
return "超时";
default:
return "未知错误";
}
}
int main(void)
{
ErrorCode err = ERR_FILE_NOT_FOUND;
printf("错误码: %d\n", err);
printf("错误信息: %s\n", getErrorString(err));
return 0;
}
9.4.4 typedef枚举
#include <stdio.h>
typedef enum
{
FALSE,
TRUE
} Bool; // Bool是类型别名
int main(void)
{
Bool flag = TRUE; // 无需写enum
if (flag == TRUE)
{
printf("标志为真\n");
}
return 0;
}
9.5 位域
9.5.1 位域的定义
位域: 结构体成员可以指定占用的位数,节省空间。
语法:
struct 结构体名
{
数据类型 成员名 : 位数;
};
示例:
#include <stdio.h>
struct Flags
{
unsigned int flag1 : 1; // 1位
unsigned int flag2 : 1; // 1位
unsigned int flag3 : 1; // 1位
unsigned int value : 5; // 5位
};
int main(void)
{
struct Flags f = {0};
printf("sizeof(Flags): %lu 字节\n", sizeof(f)); // 4字节
f.flag1 = 1;
f.flag2 = 0;
f.flag3 = 1;
f.value = 20;
printf("flag1=%u, flag2=%u, flag3=%u, value=%u\n",
f.flag1, f.flag2, f.flag3, f.value);
return 0;
}
9.5.2 位域的应用
应用: 寄存器配置
#include <stdio.h>
typedef struct
{
unsigned int enable : 1;
unsigned int mode : 2;
unsigned int speed : 3;
unsigned int reserved : 2;
} RegisterConfig;
int main(void)
{
RegisterConfig reg = {0};
reg.enable = 1;
reg.mode = 2; // 0-3
reg.speed = 5; // 0-7
printf("使能: %u\n", reg.enable);
printf("模式: %u\n", reg.mode);
printf("速度: %u\n", reg.speed);
return 0;
}
⚠️ 注意:
- 位域不能取地址
- 位域的内存布局依赖编译器
- 不可移植,慎用
9.6 实践练习
练习1: 学生管理系统
#include <stdio.h>
#include <string.h>
#define MAX_STUDENTS 100
typedef struct
{
int id;
char name[50];
int age;
float score;
} Student;
typedef struct
{
Student students[MAX_STUDENTS];
int count;
} StudentDB;
void addStudent(StudentDB *db, int id, const char *name, int age, float score)
{
if (db->count >= MAX_STUDENTS)
{
printf("数据库已满!\n");
return;
}
Student *s = &db->students[db->count];
s->id = id;
strncpy(s->name, name, sizeof(s->name) - 1);
s->age = age;
s->score = score;
db->count++;
printf("添加学生成功: %s\n", name);
}
void printAllStudents(const StudentDB *db)
{
printf("\n========学生列表========\n");
printf("%-6s %-10s %-6s %-6s\n", "ID", "姓名", "年龄", "成绩");
for (int i = 0; i < db->count; i++)
{
const Student *s = &db->students[i];
printf("%-6d %-10s %-6d %-6.1f\n",
s->id, s->name, s->age, s->score);
}
}
Student* findStudent(StudentDB *db, int id)
{
for (int i = 0; i < db->count; i++)
{
if (db->students[i].id == id)
{
return &db->students[i];
}
}
return NULL;
}
int main(void)
{
StudentDB db = {0};
addStudent(&db, 1001, "张三", 20, 95.5);
addStudent(&db, 1002, "李四", 21, 88.0);
addStudent(&db, 1003, "王五", 19, 92.5);
printAllStudents(&db);
int searchId = 1002;
Student *s = findStudent(&db, searchId);
if (s != NULL)
{
printf("\n找到学生: ID=%d, 姓名=%s, 成绩=%.1f\n",
s->id, s->name, s->score);
}
return 0;
}
练习2: 链表节点
#include <stdio.h>
#include <stdlib.h>
typedef struct Node
{
int data;
struct Node *next;
} Node;
Node* createNode(int data)
{
Node *node = (Node*)malloc(sizeof(Node));
if (node == NULL)
{
return NULL;
}
node->data = data;
node->next = NULL;
return node;
}
void printList(Node *head)
{
Node *current = head;
while (current != NULL)
{
printf("%d -> ", current->data);
current = current->next;
}
printf("NULL\n");
}
void freeList(Node *head)
{
Node *current = head;
while (current != NULL)
{
Node *next = current->next;
free(current);
current = next;
}
}
int main(void)
{
Node *head = createNode(1);
head->next = createNode(2);
head->next->next = createNode(3);
printList(head);
freeList(head);
return 0;
}
9.7 常见错误
错误1: 访问未初始化的结构体
struct Point
{
int x;
int y;
};
struct Point p; // 未初始化
printf("%d\n", p.x); // 垃圾值
// 正确
struct Point p = {0}; // 全部初始化为0
错误2: 结构体数组越界
Student students[10];
students[10].id = 1; // 错误!越界
错误3: 共用体成员混用
union Data
{
int i;
float f;
};
union Data d;
d.i = 10;
printf("%f\n", d.f); // 错误!读取未设置的成员
错误4: 位域取地址
struct Flags
{
unsigned int flag : 1;
};
struct Flags f;
int *p = &f.flag; // 错误!位域不能取地址
错误5: 忘记typedef
struct Point
{
int x, y;
};
Point p; // 错误!Point不是类型名
// 正确
struct Point p;
// 或使用typedef
typedef struct Point Point;
Point p;
9.8 小结
本章学习了:
结构体的定义、初始化和使用 内存对齐原理及优化 共用体的特点和应用 枚举类型定义常量 位域节省内存空间 结构体嵌套、数组和指针 结构体作为函数参数
重点提示:
- 结构体成员用
.访问,指针用-> - 结构体可以直接赋值,会拷贝所有成员
- 传递结构体参数时优先使用指针
- 理解内存对齐,合理排列成员节省空间
- 共用体同时只能使用一个成员
- 枚举提高代码可读性,用于定义常量集合
- typedef简化类型名,提高可读性
设计建议:
- 相关数据用结构体组合
- 成员按大小排列,优化内存
- 使用const保护只读数据
- 枚举代替魔术数字
- typedef提高可读性
上一章: 第08章 函数
🎉 恭喜完成C语言系列教程!
继续学习:
- 文件操作 (FILE*)
- 动态内存管理 (malloc/free)
- 预处理器 (#define, #include, #ifdef)
- 多文件编程与Makefile
- 数据结构与算法