结构体
# 11.结构体
# 目录介绍
- 11.1 结构体介绍
- 11.2 结构体定义
- 11.3 结构体使用
- 11.4 结构体数组
- 11.5 结构体指针
- 11.6 函数与结构体
- 11.7 结构体和函数
- 11.8 结构体对齐
- 11.9 结构体typedef
- 11.10 注意事项
# 11.1 结构体介绍
在 C 语言中,结构体(Structure) 是一种用户定义的数据类型,用于将不同类型的数据组合在一起。
结构体可以包含多个成员变量,每个成员变量可以是不同的数据类型。结构体是 C 语言中实现复杂数据组织的重要工具。
# 11.2 结构体定义
# 11.2.1 结构体语法
使用 struct 关键字定义结构体。
struct 结构体名 {
数据类型 成员1;
数据类型 成员2;
// ...
};
2
3
4
5
# 11.2.2 结构体示例
struct Student {
int id;
char name[50];
float score;
};
2
3
4
5
# 11.2.3 案例分析
下面这三种有什么区别?
struct tagStudentA {
char name[20];
};
struct tagStudentB {
char name[20];
} StudentB;
typedef struct tagStudentC {
char name[20];
} StudentC;
2
3
4
5
6
7
8
9
10
11
# 11.3 结构体使用
# 11.3.1 结构体声明
定义结构体后,可以声明结构体变量。
语法
struct 结构体名 变量名;
示例
struct Student stu;
# 11.3.2 结构体访问
使用点运算符 . 访问结构体成员。
语法
结构体变量名.成员名;
示例
stu.id = 1;
strcpy(stu.name, "Alice");
stu.score = 95.5;
2
3
# 11.3.3 结构体初始化
可以在声明结构体变量时初始化。
语法
struct 结构体名 变量名 = {值1, 值2, ...};
示例
struct Student stu2 = {2, "Bob", 88.5};
# 11.3.5 结构体大小
使用 sizeof 运算符可以获取结构体的大小。
示例
struct Student {
int id;
char name[50];
float score;
};
printf("Size of Student: %lu bytes\n", sizeof(struct Student));
2
3
4
5
6
7
输出:
Size of Student: 60 bytes
# 11.3.6 结构体嵌套
结构体可以包含其他结构体作为成员。
示例
struct Date {
int year;
int month;
int day;
};
struct Employee {
int id;
char name[50];
struct Date hireDate;
};
struct Employee emp1 = {1, "Alice", {2020, 1, 1}};
2
3
4
5
6
7
8
9
10
11
12
13
# 11.3.7 综合案例与思考
综合案例:结构体的综合运用——学生管理系统
#include <stdio.h>
#include <string.h>
typedef struct {
int year, month, day;
} Date;
typedef struct {
int id;
char name[50];
float scores[3]; // 语文、数学、英语
Date birthday;
} Student;
// 计算平均分
float calc_avg(Student *s) {
float sum = 0;
for (int i = 0; i < 3; i++) sum += s->scores[i];
return sum / 3;
}
// 打印学生信息
void print_student(Student *s) {
printf("ID:%d 姓名:%-6s 生日:%d-%02d-%02d "
"成绩:[%.0f,%.0f,%.0f] 平均:%.1f\n",
s->id, s->name,
s->birthday.year, s->birthday.month, s->birthday.day,
s->scores[0], s->scores[1], s->scores[2],
calc_avg(s));
}
// 按平均分排序(冒泡)
void sort_by_avg(Student arr[], int n) {
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1 - i; j++) {
if (calc_avg(&arr[j]) < calc_avg(&arr[j+1])) {
Student temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
int main() {
Student students[] = {
{1, "张三", {85, 92, 78}, {2000, 3, 15}},
{2, "李四", {95, 88, 91}, {2001, 7, 20}},
{3, "王五", {72, 96, 85}, {2000, 11, 5}},
{4, "赵六", {90, 85, 93}, {2001, 1, 30}},
};
int n = sizeof(students) / sizeof(students[0]);
printf("=== 学生信息(原始) ===\n");
for (int i = 0; i < n; i++) print_student(&students[i]);
sort_by_avg(students, n);
printf("\n=== 按平均分排序(降序) ===\n");
for (int i = 0; i < n; i++) print_student(&students[i]);
// 结构体大小分析
printf("\n=== 结构体大小 ===\n");
printf("Date大小: %zu字节\n", sizeof(Date));
printf("Student大小: %zu字节\n", sizeof(Student));
printf("注意: 实际大小可能因内存对齐而大于成员大小之和\n");
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
原理说明:结构体是C语言中组织复杂数据的核心工具。结构体变量可以直接赋值(编译器生成逐字节拷贝代码),因此 Student temp = arr[j] 是合法的。嵌套结构体使数据组织更加层次化。结构体数组结合函数操作是C语言中实现"数据+操作"的基本模式,虽然没有类的语法糖,但功能上是等价的。注意结构体的大小可能因内存对齐而大于所有成员大小之和。
思考题:
- 结构体赋值
s2 = s1是浅拷贝还是深拷贝?如果成员中有指针会怎样? - 结构体能否用
==比较?为什么?如何正确比较两个结构体是否相等? - 结构体中成员的声明顺序会影响结构体大小吗?如何通过调整顺序来减少内存占用?
可以定义结构体数组来存储多个结构体变量。
示例
struct Student stuArray[3] = {
{1, "Alice", 95.5},
{2, "Bob", 88.5},
{3, "Charlie", 92.0}
};
2
3
4
5
# 11.5 结构体指针
可以使用指针访问结构体成员。
语法
struct 结构体名 *指针名;
指针名->成员名; // 通过指针访问成员
2
示例
struct Student *pStu = &stu1;
pStu->id = 3;
strcpy(pStu->name, "David");
pStu->score = 90.0;
2
3
4
# 11.6 函数与结构体
# 11.6.1 结构体作为函数参数
struct Point {
int x;
int y;
};
void printPoint(struct Point p) {
printf("(%d, %d)\n", p.x, p.y);
}
2
3
4
5
6
7
8
# 11.6.2 结构体指针作为函数参数
void movePoint(struct Point *p, int dx, int dy) {
p->x += dx;
p->y += dy;
}
2
3
4
# 11.7 结构体和函数
结构体可以作为函数的参数或返回值。
示例
#include <stdio.h>
struct Point {
int x;
int y;
};
struct Point addPoints(struct Point p1, struct Point p2) {
struct Point result;
result.x = p1.x + p2.x;
result.y = p1.y + p2.y;
return result;
}
int main() {
struct Point p1 = {1, 2};
struct Point p2 = {3, 4};
struct Point p3 = addPoints(p1, p2);
printf("Result: (%d, %d)\n", p3.x, p3.y);
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
输出:
Result: (4, 6)
# 11.8 结构体对齐
结构体的成员在内存中可能对齐,以提高访问效率。可以使用 #pragma pack 控制对齐方式。
示例
#pragma pack(1) // 1 字节对齐
struct Packed {
char a;
int b;
};
#pragma pack() // 恢复默认对齐
printf("Size of Packed: %lu bytes\n", sizeof(struct Packed));
2
3
4
5
6
7
8
输出:
Size of Packed: 5 bytes
# 11.9 结构体typedef
可以使用 typedef 为结构体定义别名,简化代码。
语法
typedef struct {
数据类型 成员1;
数据类型 成员2;
// ...
} 别名;
2
3
4
5
示例
typedef struct {
int id;
char name[50];
float score;
} Student;
Student stu1 = {1, "Alice", 95.5};
2
3
4
5
6
7
# 11.10 注意事项
成员访问:
- 使用
.访问结构体变量成员。 - 使用
->访问结构体指针成员。
- 使用
初始化:
- 结构体变量可以在声明时初始化。
- 未初始化的结构体变量成员值是未定义的。
内存对齐:
- 结构体的大小可能大于成员大小的总和,因为存在内存对齐。
结构体赋值:
- 结构体变量可以直接赋值给另一个结构体变量。
- 示例:
struct Student stu1 = {1, "Alice", 95.5}; struct Student stu2 = stu1;1
2
# 11.10.1 综合案例与思考
综合案例:结构体实现链表
#include <stdio.h>
#include <stdlib.h>
// 链表节点
typedef struct Node {
int data;
struct Node *next;
} Node;
// 创建新节点
Node *node_new(int data) {
Node *n = (Node *)malloc(sizeof(Node));
if (n) { n->data = data; n->next = NULL; }
return n;
}
// 头插法
void list_push_front(Node **head, int data) {
Node *n = node_new(data);
n->next = *head;
*head = n;
}
// 尾插法
void list_push_back(Node **head, int data) {
Node *n = node_new(data);
if (*head == NULL) { *head = n; return; }
Node *cur = *head;
while (cur->next) cur = cur->next;
cur->next = n;
}
// 打印链表
void list_print(Node *head) {
printf("链表: ");
while (head) {
printf("%d -> ", head->data);
head = head->next;
}
printf("NULL\n");
}
// 链表长度
int list_length(Node *head) {
int count = 0;
while (head) { count++; head = head->next; }
return count;
}
// 释放链表
void list_free(Node **head) {
while (*head) {
Node *temp = *head;
*head = (*head)->next;
free(temp);
}
}
int main() {
Node *list = NULL;
// 尾插法构建链表
for (int i = 1; i <= 5; i++) {
list_push_back(&list, i * 10);
}
list_print(list);
printf("长度: %d\n", list_length(list));
// 头插法添加元素
list_push_front(&list, 5);
printf("\n头插5后:\n");
list_print(list);
// 释放
list_free(&list);
printf("\n释放后 head = %p\n", (void *)list);
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
原理说明:链表是结构体最经典的应用之一——结构体中包含指向自身类型的指针,形成自引用结构。链表的优势是插入/删除为O(1)(已知位置时),缺点是不支持随机访问、每个节点有额外的指针开销。头插法和尾插法都需要传递 Node **head(二级指针),因为可能需要修改头指针本身。链表是栈、队列、哈希表等高级数据结构的基础。注意:分配的每个节点都必须最终被 free,否则会导致内存泄漏。
思考题:
- 链表操作中为什么要传递
Node **head而不是Node *head?如果传Node *head会怎样? - 如何实现链表的反转?要求时间复杂度O(n)、空间复杂度O(1)。
- 单向链表和双向链表各有什么优缺点?在什么场景下选择哪种?