青江的个人站

“保持热爱,奔赴星海”

  • 主页
  • 目录
  • 图床
  • 留言板
  • -关于我-
友链 搜索文章 >>

青江的个人站

“保持热爱,奔赴星海”

  • 主页
  • 目录
  • 图床
  • 留言板
  • -关于我-

【C语言学习笔记】九、结构体


阅读数: 0次    2025-12-28
字数:6.3k字 | 预计阅读时长:27分钟

1. 初识结构体struct

结构体(Structures)是一种用户可以自定义的类型,用来储存结构化的数据并实现一些功能。

结构体定义时必须以struct开头,后面是自定义的结构体名,大括号内部是这个结构体所包含的成员,这些成员可以是常见的任意基本数据类型,各个成员之间用;隔开。

例如一个日期的结构体定义:

1
2
3
4
5
struct Date {
uint16_t year;
uint8_t month;
uint8_t day;
};

这个结构体中包含了三个成员,可以将年份、月份、天数统一结构化地存储在这个结构体中,方便管理和使用。

结构体和其他数据类型一样,定义好后可以初始化使用,可以在初始化时指定每种成员变量的初始值。

结构体初始化同样需要以struct开头,后面是想要初始化的结构体名,然后写任意自定义的初始化结构体名称。初始化的各个成员之间用,隔开。

例如日期结构体的初始化:

1
struct Date today = { 2026, 1, 1 };

初始化值的各个成员需要与定义时的成员一一对应。

需要注意的是,只有初始化结构体的时候才可以整体赋值,在非初始化时只能逐个成员赋值,不能整体赋值。

1
2
3
4
5
struct Date today = { 2026, 1, 1 };	// 正确:定义时初始化

struct Date today;
//today = { 2026, 1, 1 }; // 错误:不能这样赋值
today = (struct Date){ 2026, 1, 1 }; // 正确:使用复合字面值赋值

2. 创建结构体变量与访问方式

一般来说,创建和定义结构体时必须写struct关键字,表示这是一个结构体,不过也可以使用typedef来为这个结构体创建一个别名,例如:

1
2
3
4
5
6
// 使用typedef为结构体类型定义别名为Date
typedef struct Date {
uint16_t year;
uint8_t month;
uint8_t day;
} Date;

这样就可以直接使用Date对结构体进行初始化或赋值等操作:

1
2
3
4
5
struct Date today = { 2026, 1, 1 };	// 正确:使用 struct 关键字
Date today = { 2026, 1, 1 }; // 正确:使用 typedef 定义的别名

today = (struct Date){ 2026, 1, 1 }; // 正确:使用 struct 关键字的复合字面值
today = (Date){ 2026, 1, 1 }; // 正确:使用 typedef 定义的别名的复合字面值

结构体一般会定义在全局,或直接将所有结构体放在一个单独的文件中,方便所有的函数使用。

访问结构体中成员的值有两种方式:

通过成员访问(.)

可以使用“自定义的结构体名.结构体中的某个成员”的方式访问结构体中成员的数据。例如:

1
2
Date today = { 2026, 1, 1 };
printf("Today is %" PRIu16 "-%" PRIu8 "-%" PRIu8 "\n", today.year, today.month, today.day);

通过指针访问(->)

当定义一个指向结构体的指针时,可以使用“自定义的指向结构体的指针->结构体中的某个成员”的方式访问结构体中成员的数据。例如:

1
2
3
Date today = { 2026, 1, 1 };
Date* today_ptr = &today;
printf("Today is %" PRIu16 "-%" PRIu8 "-%" PRIu8 "\n", today_ptr->year, today_ptr->month, today_ptr->day);

3. 匿名结构体

匿名结构体没有它自己的结构体标签,只有一个别名。

对于较为简单的结构体,可以使用匿名结构体,并用typedef为其定义一个别名,例如:

1
2
3
4
5
typedef struct {
uint16_t year;
uint8_t month;
uint8_t day;
} Date;

这种定义方式较为简洁,但也有一些缺点。

这样定义时,因为是匿名结构体,在初始化或赋值时只能使用别名:

1
2
3
4
5
//struct Date today = { 2026, 1, 1 };	// 错误:struct Date 未定义
Date today = { 2026, 1, 1 }; // 正确:使用 typedef 定义的别名

//today = (struct Date){ 2026, 1, 1 }; // 错误:struct Date 未定义
today = (Date){ 2026, 1, 1 }; // 正确:使用 typedef 定义的别名的复合字面值

同样的,匿名结构体由于没有结构体标签,因此无法自引用,无法实现结构体链表。例如在有结构体标签的结构体中,可以实现引用自身:

1
2
3
4
typedef struct Node {  // ← 必须有标签名
int data;
struct Node* next; // ✅ 正确:通过标签名引用自身
} Node;

而匿名结构体中无法实现。

此外,当一个结构体的声明和定义分别在不同的文件中时,也不可以使用匿名结构体。例如:

1
2
3
4
5
6
7
8
9
10
// 头文件 a.h
typedef struct Date Date; // 前向声明
void print_date(Date* d);

// 实现文件 a.c
struct Date { // 完整定义
uint16_t year;
uint8_t month;
uint8_t day;
};

4. 函数参数为结构体

函数的参数也可以为结构体,可以在函数中实现读取或修改结构体中的成员变量的功能。

要求:一个简单的学生分数管理工具,学生的信息包括:姓名、ID号、分数,可以实现:

  • 更新学生的分数值;
  • 打印学生的所有信息。

在函数中更新结构体中成员变量值时,需要向函数中传递结构体和一个更新值。

与向函数中传递参数类似,此时函数中的所有参数都是局部变量,作用域只有函数内部,函数结束后即被销毁,因此直接在这个函数中修改结构体的值时,修改的是原结构体的一个副本,对原值无影响。

所以想要在外部函数中修改结构体中成员的值,需要向函数中传递一个结构体指针,同时使用结构体的指针访问的形式(->)来修改值,这时的值即可被成功修改。

代码实现:

1
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
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

typedef struct {
char name[50];
uint32_t id;
float score;
} Student;

// 打印学生信息
void print_student_info(const Student student);

// 修改学生的分数
void modify_student_score(Student* student, float new_score);

// C语言案例
int main(void) {
Student student = { "Alice", 1001, 95.5f };
puts("修改前的学生信息:");
print_student_info(student);

// 向函数中传递结构体指针以修改其成员
modify_student_score(&student, 98.0f);
puts("\n修改后的学生信息:");
print_student_info(student);

return 0;
}

// 打印学生信息
void print_student_info(const Student student) {
printf("Name: %s\n", student.name);
printf("ID: %" PRIu32 "\n", student.id);
printf("Score: %.2f\n", student.score);
}

// 修改学生的分数
void modify_student_score(Student* student, float new_score) {
// 通过指针修改结构体成员
student->score = new_score;
}

同样的,打印学生信息的函数也可以改为使用指针向函数中传递结构体,这样可以避免每次调用打印函数时复制整个结构体,避免浪费性能和栈空间。

5. 值语义初始化结构体变量

函数的返回值也可以是一个结构体类型,因此可以通过一个函数对结构体进行初始化,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

typedef struct {
int32_t x;
int32_t y;
} Point;

// 获取原点坐标
Point get_origin();

// C语言案例
int main(void) {
Point origin = get_origin();
printf("原点坐标: (%" PRId32 ", %" PRId32 ")\n", origin.x, origin.y);
return 0;
}

// 获取原点坐标
Point get_origin() {
// 值语义(value semantics)
Point p = { 10, 20 };
return p; // 返回结构体的副本
}

这个程序使用了值语义初始化结构体,获取坐标时操作对象的值本身,而不是操作指向对象的引用或指针。

这种方法保证了传递出去的是最初始的值的一个副本,而最初始的值是一个局部变量,在函数结束之后即被销毁,不会将指针传递出去,避免外部程序通过其指针(悬垂指针)修改原始值,更为安全。

此外,使用统一的函数初始化值时,便于程序逻辑与内存分配的统一管理。

6. 结构体数组

结构体定义之后,也可以创建一个包含多个结构体的数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

typedef struct {
int32_t x;
int32_t y;
} Point;

// C语言案例
int main(void) {
// 定义并初始化一个Point类型的数组
Point points[3] = {
{10, 20},
{30, 40},
{50, 60}
};

// 输出数组中每个点的坐标
for (size_t i = 0; i < sizeof(points) / sizeof(points[0]); i++) {
printf("点 %zu 的坐标: (%" PRId32 ", %" PRId32 ")\n", i + 1, points[i].x, points[i].y);
}

return 0;
}

7. 嵌套结构体

结构体的成员也可以是结构体,被称为结构体的嵌套。

通过成员访问嵌套结构体中的成员时,可以使用多个.来逐级访问;通过指针访问嵌套结构体,第一层结构体需要使用->访问成员,成员中的结构体同样使用.访问即可。

例如定义一个人所包含的信息结构体,这个结构体中嵌套一个包含地址信息的结构体:

1
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
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

// 地址结构体
typedef struct {
char street[100];
char city[50];
char country[50];
} Address;

// 人物结构体
typedef struct {
char name[50];
uint8_t age;
Address address; // 嵌套地址结构体
} Person;

// C语言案例
int main(void) {
// 初始化人物结构体
//Person person = { "Alice", 30, { "123 Main St", "Anytown", "USA" } };

// 使用指定成员初始化,这种方式更清晰
Person person = {
.name = "Alice",
.age = 30,
.address = {
.street = "123 Main St",
.city = "Anytown",
.country = "USA"
}
};

// 通过值打印
printf("姓名: %s\n", person.name);
printf("年龄: %" PRIu8 "\n", person.age);
printf("地址: %s, %s, %s\n", person.address.street, person.address.city, person.address.country);

// 通过地址打印
Person* ptr_person = &person;
printf("姓名: %s\n", ptr_person->name);
printf("年龄: %" PRIu8 "\n", ptr_person->age);
printf("地址: %s, %s, %s\n", ptr_person->address.street, ptr_person->address.city, ptr_person->address.country);

return 0;
}

8. Enumeration枚举

枚举与结构体类似,也是可以自定义的一种数据类型。

枚举的作用类似于#define,可以将多个常量统一定义,便于后期读取和维护。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>

typedef enum {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
} Weekday;

// C语言案例
int main(void) {
// 使用枚举类型变量
Weekday today = WEDNESDAY;
printf("%d\n", today);

// 直接使用枚举常量
printf("%d\n", SUNDAY);

return 0;
}

枚举的成员默认从0开始对应,依次递增。

也可以手动指定枚举所对应的值,之后的对应值会从指定值的基础上递增。

1
2
3
4
5
6
7
8
9
typedef enum {
MONDAY = 1,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
} Weekday;

当手动指定值的成员不是第一个时,前面没有被指定的成员对应的值依然会从0开始依次递增,指定值后的成员会从指定值开始逐个加1。

1
2
3
4
5
6
7
8
9
typedef enum {
MONDAY, // 0
TUESDAY, // 1
WEDNESDAY = 5, // 5
THURSDAY, // 6
FRIDAY, // 7
SATURDAY, // 8
SUNDAY // 9
} Weekday;

由于枚举和define类似,对应的值只是一个数字,因此想要输出字符串时只能定义新函数。

1
2
3
4
5
6
7
8
9
10
11
12
const char* get_weekday_string(Weekday day) {
switch (day) {
case MONDAY: return "Monday";
case TUESDAY: return "Tuesday";
case WEDNESDAY: return "Wednesday";
case THURSDAY: return "Thursday";
case FRIDAY: return "Friday";
case SATURDAY: return "Saturday";
case SUNDAY: return "Sunday";
default: return "Unknown";
}
}

此外,当定义多个枚举类型时,如果不手动指定,每个枚举类型中的第一个成员对应的值默认都会从0开始,这就会导致一个数字与不同枚举类型的多个成员对应,此时编译器也不会报错,因为在编译器眼中不同成员对应的都是一个数字,容易导致错误。

要避免这类型问题,需要手动指定枚举类型成员的起始值,避免不同枚举类型的值出现重叠,同时可以在使用枚举类型之前做范围检查,确保使用的是预期的枚举类型。

9. Union联合

联合与结构体、枚举类似,也是一种可以自定义的特殊的数据类型。

它允许在相同的内存位置储存不同的数据类型,也就是所有不同类型的成员共享同一块内存空间,大小等于占用内存空间最大的成员的大小。

因此在任一时刻,联合体只能储存一个成员的值。

一个变量可能存储多种类型的数据,但在一个给定时刻,只使用其中的一种数据类型,可以节省内存空间。

使用这种特性,可以结合枚举和结构体,自定义一种可以储存任意数据类型的结构体,可以通过枚举成员的值判断数据类型并打印。

1
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
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

// 联合体,包含整数、浮点数和字符串指针
typedef union {
int32_t intValue;
float floatValue;
char* strValue;
} Data;

// 枚举类型,表示联合体中存储的数据类型
typedef enum {
TYPE_INT,
TYPE_FLOAT,
TYPE_STRING
} DataType;

// 结构体,包含联合体和枚举类型,表示带类型信息的数据
typedef struct {
DataType type;
Data data;
} TypedData;

// 打印带类型信息的数据
void print_typed_data(const TypedData* td);

// C语言案例
int main(void) {

TypedData dataArray[3] = {
{ TYPE_INT, .data.intValue = 42 },
{ TYPE_FLOAT, .data.floatValue = 3.14f },
{ TYPE_STRING,.data.strValue = "Hello, Union!" }
};

// 通过数组指针访问并打印数据
print_typed_data(dataArray + 0);
print_typed_data(dataArray + 1);
print_typed_data(dataArray + 2);

return 0;
}

// 打印带类型信息的数据
void print_typed_data(const TypedData* td) {
switch (td->type)
{
case TYPE_INT:
printf("Integer: %" PRId32 "\n", td->data.intValue);
break;
case TYPE_FLOAT:
printf("Float: %f\n", td->data.floatValue);
break;
case TYPE_STRING:
printf("String: %s\n", td->data.strValue);
break;
default:
break;
}
}

10. 游戏设计:结构体、枚举、联合与多文件编程

对于较为复杂的程序设计,不可能在一个文件中包含所有的代码,必须涉及到多文件编程。

要求:使用多文件编程设计一个简单的游戏,包括:

  • 玩家和敌人的结构体定义,包含玩家的姓名、等级、职业、能力等,包含敌人的种类、等级、职业、能力等;
  • 多种游戏相关的函数,包括游戏初始化、生成玩家、生成敌人、玩家和敌人的战斗、打印玩家或敌人信息等。

这个简单游戏的代码示例同时也会演示标准注释的撰写方法。

game_types.h:游戏类型定义

需要先在“头文件”目录下创建一个game_types.h文件,用于存放玩家和敌人的角色种类的枚举类型:

1
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
/**
* @file game_types.h
* @brief 游戏类型定义
* @author HC
* @version 1.0
*
* @details
* 定义游戏中使用的基本枚举类型,包括角色职业和敌人类型。
*/

#ifndef GAME_TYPES_H
#define GAME_TYPES_H

/**
* @brief 角色职业枚举
*/
typedef enum {
Warrior, /**< 战士 */
Mage, /**< 法师 */
Rogue /**< 盗贼 */
} CharacterClass;

/**
* @brief 敌人类型枚举
*/
typedef enum {
Goblin, /**< 小妖 */
Troll, /**< 巨魔 */
Dragon /**< 龙 */
} EnemyType;

#endif // !GAME_TYPES_H

这里头文件的头和尾是一种固定的格式,防止头文件被多重包含。

game_abilities.h:游戏能力定义

创建一个game_abilities.h文件,使用联合类型定义玩家或敌人在某些职业下的能力:

1
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
/**
* @file game_abilities.h
* @brief 游戏能力定义
* @author HC
* @version 1.0
*
* @details
* 定义不同职业的能力属性联合体。
*/

#ifndef GAME_ABILITIES_H
#define GAME_ABILITIES_H

#include <stdint.h>

/**
* @brief 角色能力联合体
*
* @note 同一时刻只能使用其中一个成员
*/
typedef union {
int32_t strength; /**< 力量属性(战士) */
float mana; /**< 魔法属性(法师) */
int32_t stealth; /**< 隐匿属性(盗贼) */
} Ability;

#endif // !GAME_ABILITIES_H

game_structs.h:游戏结构体定义

创建一个game_structs.h文件,用于定义玩家和敌人的结构体类型,成员中包含前面定义的类别和能力:

在这个文件中使用其他文件中定义的内容,必须使用include引入。

与引用官方库不同的是,引用官方库需要使用尖括号(<>),引用自己定义的库使用的是双引号(“”)。

1
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
/**
* @file game_structs.h
* @brief 游戏结构体定义
* @author HC
* @version 1.0
*
* @details
* 定义玩家和敌人的结构体,包含角色的基本属性和能力。
*/

#ifndef GAME_STRUCTS_H
#define GAME_STRUCTS_H

#include "game_types.h"
#include "game_abilities.h"

/**
* @brief 玩家结构体
*/
typedef struct {
char name[50]; /**< 角色名称 */
uint32_t exp; /**< 角色经验值 */
uint32_t level; /**< 角色等级 */
int32_t health; /**< 角色生命值 */
CharacterClass player_class; /**< 角色职业 */
Ability ability; /**< 角色能力 */
} Player;

/**
* @brief 敌人结构体
*/
typedef struct {
uint32_t level; /**< 敌人等级 */
int32_t health; /**< 敌人生命值 */
EnemyType enemy_type; /**< 敌人类型 */
Ability ability; /**< 敌人能力 */
} Enemy;

#endif // !GAME_STRUCTS_H

至此,玩家和敌人的基本属性定义完成,可以开始写一些游戏的功能函数。

game_functions.h:游戏函数声明

可以创建一个game_functions.h文件,用于声明一些游戏功能函数:

.h文件用来声明所有的函数,.c文件用来定义函数的具体功能。

其他文件中需要使用函数时,直接包含.h文件即可。

1
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
/**
* @file game_functions.h
* @brief 游戏函数声明
* @author HC
* @version 1.0
*
* @details
* 声明游戏中使用的核心函数,包括初始化、角色创建、战斗、升级和信息显示。
*/

#ifndef GAME_FUNCTIONS_H
#define GAME_FUNCTIONS_H

#include "game_structs.h"

#include <stdbool.h>

// ========== 常量定义 ==========

#define EXP_PER_LEVEL 100 /**< 每级所需经验值 */
#define MAX_LEVEL 50 /**< 最高等级 */

// ========== 函数声明 ==========

/**
* @brief 游戏初始化
*/
void initialize(void);

/**
* @brief 创建玩家角色
* @param name 玩家名称
* @param player_class 玩家职业
* @return 玩家结构体
*/
Player create_player(const char* name, CharacterClass player_class);

/**
* @brief 创建敌人
* @param enemy_type 敌人类型
* @param level 敌人等级
* @return 敌人结构体
*/
Enemy create_enemy(EnemyType enemy_type, int32_t level);

/**
* @brief 玩家与敌人战斗
* @param player 玩家指针
* @param enemy 敌人指针
*/
void battle(Player* player, Enemy* enemy);

/**
* @brief 玩家升级
* @param exp 玩家经验值指针
* @param level 玩家等级指针
* @return 是否升级成功
*/
bool level_up(uint32_t* exp, uint32_t* level);

/**
* @brief 打印玩家信息
* @param player 玩家指针
*/
void print_player_info(const Player* player);

/**
* @brief 打印敌人信息
* @param enemy 敌人指针
*/
void print_enemy_info(const Enemy* enemy);

#endif // !GAME_FUNCTIONS_H

game_functions.c:实现功能函数

接着就可以在“源文件”的目录下创建一个game_functions.c文件,对声明的功能函数做具体的定义和实现,这些函数就是游戏的核心功能:

1
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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
/**
* @file game_functions.c
* @brief 游戏函数实现
* @author HC
* @version 1.0
*
* @details
* 实现游戏的核心功能,包括初始化、角色创建、战斗系统、升级机制和信息显示。
*/

#include "game_functions.h"

#include <stdio.h>
#include <string.h>
#include <inttypes.h>

// ========== 内部函数声明 ==========

static Ability get_player_ability(CharacterClass player_class);
static Ability get_enemy_ability(EnemyType enemy_type, int32_t level);
static const char* get_player_class_name(CharacterClass player_class);
static const char* get_enemy_type_name(EnemyType enemy_type);
static Player* reset_player(Player* player);

// ========== 公共函数实现 ==========

/**
* @brief 游戏初始化
*
* @details 处理玩家输入、创建角色和敌人、执行战斗流程
*/
void initialize(void) {
printf("欢迎来到游戏世界!\n");

// 读取玩家名称
printf("请输入您的角色名称:\n");
char name[50];
scanf_s("%49s", name, (unsigned)sizeof(name));

// 读取职业选择
printf("请选择职业(0-战士、1-法师、2-盗贼):\n");
int class_choice;
scanf_s("%d", &class_choice);

// 创建玩家
Player player = create_player(name, (CharacterClass)class_choice);
print_player_info(&player);

// 创建敌人
Enemy enemy = create_enemy(Goblin, 1);
print_enemy_info(&enemy);
Enemy enemy2 = create_enemy(Dragon, 10);
print_enemy_info(&enemy2);

// 执行战斗
battle(&player, &enemy);
print_player_info(&player);
battle(&player, &enemy2);
print_player_info(&player);

printf("\n游戏结束,感谢游玩!\n");
}

/**
* @brief 创建玩家角色
* @param name 玩家名称
* @param player_class 玩家职业
* @return 玩家结构体
*/
Player create_player(const char* name, CharacterClass player_class) {
Player player = {0};
strncpy_s(player.name, sizeof(player.name), name, _TRUNCATE); // 安全复制名称,防止缓冲区溢出
player.level = 1;
player.health = 100;
player.player_class = player_class;
player.ability = get_player_ability(player_class);
return player;
}

/**
* @brief 创建敌人
* @param enemy_type 敌人类型
* @param level 敌人等级
* @return 敌人结构体
*/
Enemy create_enemy(EnemyType enemy_type, int32_t level) {
Enemy enemy = {0};
enemy.level = level;
enemy.health = level * 10;
enemy.enemy_type = enemy_type;
enemy.ability = get_enemy_ability(enemy_type, level);
return enemy;
}

/**
* @brief 玩家与敌人战斗
* @param player 玩家指针
* @param enemy 敌人指针
*
* @details 回合制战斗,玩家和敌人轮流攻击直到一方生命值归零
*/
void battle(Player* player, Enemy* enemy) {
printf("\n%s 与 %s 开始战斗!\n", player->name, get_enemy_type_name(enemy->enemy_type));

while (player->health > 0 && enemy->health > 0) {
// 玩家攻击敌人
enemy->health -= player->ability.strength;
if (enemy->health <= 0) {
printf("%s 击败了 %s!\n", player->name, get_enemy_type_name(enemy->enemy_type));
printf("获得了 %" PRIu32 " 点经验值!\n", enemy->level * 20);
player->exp += enemy->level * 20;
level_up(&player->exp, &player->level);
break;
}

// 敌人攻击玩家
player->health -= enemy->ability.strength / 2;
if (player->health <= 0) {
printf("%s 被 %s 击败了!\n", player->name, get_enemy_type_name(enemy->enemy_type));
reset_player(player);
break;
}
}
}

/**
* @brief 玩家升级
* @param exp 玩家经验值指针
* @param level 玩家等级指针
* @return 是否升级成功
*/
bool level_up(uint32_t* exp, uint32_t* level) {
// 检查是否已达最高等级
if (*level >= MAX_LEVEL) {
if (*exp >= EXP_PER_LEVEL) {
printf("已达到最高等级 %u,经验不再计入。\n", MAX_LEVEL);
*exp = 0;
}
return false;
}
// 检查是否有足够经验升级
if (*exp >= EXP_PER_LEVEL) {
while (*exp >= EXP_PER_LEVEL && *level < MAX_LEVEL) {
*exp -= EXP_PER_LEVEL;
(*level)++;
printf("恭喜!你已升级到等级 %" PRIu32 "。\n", *level);
}

// 如果升到满级后还有剩余经验
if (*level >= MAX_LEVEL && *exp > 0) {
printf("已达到最高等级,剩余经验已清空。\n");
*exp = 0;
}
return true;
}
else {
printf("经验不足,无法升级。\n");
return false;
}
}

/**
* @brief 打印玩家信息
* @param player 玩家指针
*/
void print_player_info(const Player* player) {
printf("\n玩家信息:\n");
printf("名称: %s\n", player->name);
printf("经验值: %" PRIu32 "\n", player->exp);
printf("等级: %" PRIu32 "\n", player->level);
printf("生命值: %" PRId32 "\n", player->health);
printf("职业: %s\n", get_player_class_name(player->player_class));

switch (player->player_class) {
case Warrior:
printf("力量: %" PRIu32 "\n", player->ability.strength);
break;
case Mage:
printf("魔法: %.2f\n", player->ability.mana);
break;
case Rogue:
printf("隐匿: %" PRIu32 "\n", player->ability.stealth);
break;
default:
break;
}
}

/**
* @brief 打印敌人信息
* @param enemy 敌人指针
*/
void print_enemy_info(const Enemy* enemy) {
printf("\n敌人信息:\n");
printf("等级: %" PRIu32 "\n", enemy->level);
printf("生命值: %" PRId32 "\n", enemy->health);
printf("类型: %s\n", get_enemy_type_name(enemy->enemy_type));

switch (enemy->enemy_type) {
case Goblin:
case Troll:
printf("力量: %" PRIu32 "\n", enemy->ability.strength);
break;
case Dragon:
printf("魔法: %.2f\n", enemy->ability.mana);
break;
default:
break;
}
}

// ========== 内部辅助函数实现 ==========

/**
* @brief 根据职业获取玩家能力值
* @param player_class 玩家职业
* @return 能力值联合体
*/
static Ability get_player_ability(CharacterClass player_class) {
Ability ability = { 0 };
switch (player_class) {
case Warrior:
ability.strength = 15;
break;
case Mage:
ability.mana = 100.0f;
break;
case Rogue:
ability.stealth = 20;
break;
default:
ability.strength = 0;
break;
}
return ability;
}

/**
* @brief 根据敌人类型和等级获取能力值
* @param enemy_type 敌人类型
* @param level 敌人等级
* @return 能力值联合体
*/
static Ability get_enemy_ability(EnemyType enemy_type, int32_t level) {
Ability ability = { 0 };
switch (enemy_type) {
case Goblin:
ability.strength = level * 5;
break;
case Troll:
ability.strength = level * 10;
break;
case Dragon:
ability.mana = level * 50.0f;
break;
default:
ability.strength = 0;
break;
}
return ability;
}

/**
* @brief 获取职业名称
* @param player_class 玩家职业
* @return 职业名称字符串
*/
static const char* get_player_class_name(CharacterClass player_class) {
switch (player_class) {
case Warrior:
return "战士";
case Mage:
return "法师";
case Rogue:
return "盗贼";
default:
return "未知职业";
}
}

/**
* @brief 获取敌人类型名称
* @param enemy_type 敌人类型
* @return 敌人类型名称字符串
*/
static const char* get_enemy_type_name(EnemyType enemy_type) {
switch (enemy_type) {
case Goblin:
return "小妖";
case Troll:
return "巨魔";
case Dragon:
return "龙";
default:
return "未知敌人";
}
}

/**
* @brief 重置玩家状态
* @param player 玩家指针
* @return 重置后的玩家指针
*/
static Player* reset_player(Player* player) {
printf("\n你被打败了,正在重置角色...\n");
player->level = 1;
player->exp = 0;
player->health = 100;
return player;
}

最后,在主函数文件main.c中包含功能头文件,并调用一个初始化函数即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

#include "game_functions.h"
#include "game_structs.h"

// C语言案例
int main(void) {
initialize();

return 0;
}

这个案例只是一个简单的游戏逻辑,还有很多可以改进和优化的地方,例如增加游戏循环逻辑、增加升级后的属性提升系统等,有兴趣的朋友可以在此基础上进一步修改和完善。

11. 第九章结束语

结构体、枚举、联合的重点是如何根据想要实现的功能,去设计一个自定义的类型。

要想清楚自定义的类型中应该包含哪些成员,是否需要结构体和枚举、联合的嵌套。

理解、消化、训练,都是必不可少的。

本文来源: 青江的个人站
本文链接: https://hanqingjiang.com/2025/12/28/20251228_C_struct/
版权声明: 本作品采用 CC BY-NC-SA 4.0 进行许可。转载请注明出处!
知识共享许可协议
赏

谢谢你请我喝可乐~

支付宝
微信
  • Notes
  • C

扫一扫,分享到微信

微信分享二维码
2026新年快乐!
【C语言学习笔记】八、指针
  1. 1. 1. 初识结构体struct
  2. 2. 2. 创建结构体变量与访问方式
  3. 3. 3. 匿名结构体
  4. 4. 4. 函数参数为结构体
  5. 5. 5. 值语义初始化结构体变量
  6. 6. 6. 结构体数组
  7. 7. 7. 嵌套结构体
  8. 8. 8. Enumeration枚举
  9. 9. 9. Union联合
  10. 10. 10. 游戏设计:结构体、枚举、联合与多文件编程
    1. 10.1. game_types.h:游戏类型定义
    2. 10.2. game_abilities.h:游戏能力定义
    3. 10.3. game_structs.h:游戏结构体定义
    4. 10.4. game_functions.h:游戏函数声明
    5. 10.5. game_functions.c:实现功能函数
  11. 11. 11. 第九章结束语
© 2021-2026 青江的个人站
晋ICP备2024051277号-1
powered by Hexo & Yilia
  • 友链
  • 搜索文章 >>

tag:

  • 生日快乐🎂
  • 新年快乐!
  • 小技巧
  • Linux
  • 命令
  • 语录
  • 复刻
  • Blog
  • Notes
  • Android
  • C
  • Homework
  • MATLAB
  • FPGA
  • Server
  • Vivado
  • Git

  • 引路人-稚晖
  • Bilibili-稚晖君
  • 超有趣讲师-Frank
  • Bilibili-Frank