1. 项目概述
最近完成了一个有趣的C语言课程设计项目——将传统的"电影票务系统"改造为"铠甲勇士管理系统"。这个项目不仅让我巩固了C语言的核心知识点,还让我体会到了编程与实际应用结合的乐趣。
作为一名C语言初学者,我最初拿到这个课程设计题目时有些无从下手。但通过分析需求、设计结构和逐步实现功能,最终完成了一个功能完整、具有一定实用性的管理系统。在这个过程中,我遇到了不少问题,也积累了很多实战经验,现在把这些心得分享给大家。
2. 系统设计思路
2.1 整体架构设计
系统采用模块化设计,主要包含以下几个核心模块:
- 用户管理模块:处理召唤人(用户)的登录和管理
- 铠甲管理模块:管理各类铠甲的基本信息和状态
- 订单管理模块:记录铠甲授权(租用)情况
- 怪兽管理模块:维护怪兽信息库
- 文件存储模块:实现数据的持久化存储
2.2 数据结构选择
考虑到系统需要管理多种实体(用户、铠甲、订单、怪兽)以及它们之间的关系,我选择了链表作为主要的数据结构。链表具有以下优势:
- 动态内存分配,可以灵活地添加和删除节点
- 不需要预先知道数据规模
- 实现相对简单,适合课程设计的复杂度要求
3. 核心代码实现
3.1 结构体定义
结构体是整个系统的基础,我采用了复用原有movie结构体的策略,而不是完全新建armor结构体。这样做的好处是:
- 可以复用原有的链表操作和文件读写逻辑
- 只需要在原有结构体基础上添加铠甲特有的字段
- 减少代码修改量,降低出错概率
c复制// 铠甲属性枚举(火/水/雷/土/风)
typedef enum {
ATTR_FIRE,
ATTR_WATER,
ATTR_THUNDER,
ATTR_EARTH,
ATTR_WIND
} MonsterAttr;
// 铠甲状态枚举(闲置/战斗中/维修中/破损)
typedef enum {
ARMOR_IDLE,
ARMOR_IN_BATTLE,
ARMOR_REPAIRING,
ARMOR_DAMAGED
} ArmorStatus;
// 用户(铠甲召唤人)结构体
typedef struct user {
int id;
char name[50];
char password[50];
int isAdmin; // 0=普通用户 1=管理员
struct user* next;
} user;
// 铠甲结构体(复用原movie结构体,核心改造)
typedef struct movie {
int id;
char title[50]; // 铠甲名称
float price; // 授权费用(元/小时)
char type[50]; // 铠甲类型(攻击型/全能型/防御型)
ArmorStatus status; // 铠甲状态
MonsterAttr attr; // 铠甲属性
struct movie* next;
} movie;
// 订单(铠甲授权记录)结构体
typedef struct order {
int id;
int userId; // 召唤人ID
int movieId; // 铠甲ID
char movieName[50]; // 铠甲名称
int quantity; // 授权时长(小时)
float totalPrice;// 总费用
int monsterId; // 讨伐怪兽ID(0=未讨伐)
int battleResult;// 战斗结果(1=胜 0=败 -1=未战)
struct order* next;
} order;
// 怪兽结构体(铠一+铠二经典怪兽)
typedef struct monster {
int id;
char name[50];
int level;
MonsterAttr attr;
int danger; // 危险等级
int isDefeated; // 是否被封印(1=是 0=否)
struct monster* next;
} monster;
3.2 文件存储与加载
数据持久化是系统的重要功能,我实现了将链表数据保存到文件和从文件加载的功能。这里有几个关键点需要注意:
- 枚举类型需要转换为数字存储
- 不同类型的数据需要添加标记以便区分
- 加载时需要根据标记将数据还原到对应的链表中
c复制// 保存所有数据到文件
void save_to_file() {
FILE* fp = fopen("armor_system.txt", "w");
if (!fp) { printf("文件打开失败!\n"); return; }
// 保存用户数据
user* p = head;
while (p) {
fprintf(fp, "USER %d %s %s %d\n", p->id, p->name, p->password, p->isAdmin);
p = p->next;
}
// 保存铠甲数据(含新增的类型/属性/状态)
movie* m = movieList;
while (m) {
fprintf(fp, "ARMOR %d %s %.2f %s %d %d\n",
m->id, m->title, m->price, m->type, m->attr, m->status);
m = m->next;
}
// 保存订单数据
order* o = orderList;
while (o) {
fprintf(fp, "ORDER %d %d %d %s %d %.2f %d %d\n",
o->id, o->userId, o->movieId, o->movieName, o->quantity,
o->totalPrice, o->monsterId, o->battleResult);
o = o->next;
}
// 保存怪兽数据
monster* ms = monsterList;
while (ms) {
fprintf(fp, "MONSTER %d %s %d %d %d %d\n",
ms->id, ms->name, ms->level, ms->attr, ms->danger, ms->isDefeated);
ms = ms->next;
}
fclose(fp);
printf("数据保存成功!\n");
}
3.3 铠甲搜索功能
为了提高用户体验,我实现了多维度搜索功能,支持按名称、类型和属性搜索铠甲:
c复制void searchMovie() {
int searchType;
char keyword[50];
printf("===== 铠甲搜索 =====\n");
printf("1. 按铠甲名称搜索\n");
printf("2. 按铠甲类型搜索(攻击型/全能型等)\n");
printf("3. 按铠甲属性搜索(火/水/雷等)\n");
printf("请选择搜索方式:");
scanf("%d", &searchType);
printf("请输入搜索关键词:");
scanf("%s", keyword);
movie* p = movieList;
int found = 0;
printf("\n===== 铠甲搜索结果 =====\n");
printf("铠甲ID\t名称\t\t授权价格\t类型\t\t属性\t\t状态\n");
while (p != NULL) {
int match = 0;
switch (searchType) {
case 1: // 按名称模糊匹配
match = (strstr(p->title, keyword) != NULL);
break;
case 2: // 按类型模糊匹配
match = (strstr(p->type, keyword) != NULL);
break;
case 3: // 按属性精确匹配
if (strcmp(keyword, "火") == 0 || strcmp(keyword, "火属性") == 0)
match = (p->attr == ATTR_FIRE);
else if (strcmp(keyword, "水") == 0 || strcmp(keyword, "水属性") == 0)
match = (p->attr == ATTR_WATER);
else if (strcmp(keyword, "雷") == 0 || strcmp(keyword, "雷属性") == 0)
match = (p->attr == ATTR_THUNDER);
else if (strcmp(keyword, "土") == 0 || strcmp(keyword, "土属性") == 0)
match = (p->attr == ATTR_EARTH);
else if (strcmp(keyword, "风") == 0 || strcmp(keyword, "风属性") == 0)
match = (p->attr == ATTR_WIND);
break;
default:
printf("搜索方式错误!\n");
return;
}
if (match) {
found = 1;
printf("%d\t%s\t\t%.2f\t\t%s\t\t%s\t\t%s\n",
p->id, p->title, p->price, p->type,
p->attr == ATTR_FIRE ? "火属性" : (p->attr == ATTR_WATER ? "水属性" :
(p->attr == ATTR_THUNDER ? "雷属性" : (p->attr == ATTR_EARTH ? "土属性" : "风属性"))),
p->status == ARMOR_IDLE ? "闲置" : (p->status == ARMOR_IN_BATTLE ? "战斗中" :
(p->status == ARMOR_REPAIRING ? "维修中" : "破损")));
}
p = p->next;
}
if (found == 0) {
printf("未找到符合条件的铠甲\n");
}
printf("\n按任意键继续。。。\n");
getchar(); getchar();
}
4. 关键功能实现
4.1 铠甲授权功能
铠甲授权是系统的核心业务逻辑,相当于电影票务系统中的购票功能。我添加了多项防护逻辑:
- 未登录用户不能进行授权
- 铠甲状态异常(战斗中或破损)不能授权
- 授权时长必须为正数
- 动态生成订单ID
c复制void buyTicket() {
if (currentUser == NULL) {
printf("请先登录铠甲召唤系统!\n");
getchar(); getchar();
return;
}
browseMovies();
int armorId, hourNum;
float totalPrice;
printf("请输入申请授权的铠甲ID:\n");
scanf("%d", &armorId);
movie* p = movieList;
while (p != NULL) {
if (p->id == armorId) {
// 铠甲状态校验
if (p->status == ARMOR_IN_BATTLE) {
printf("【%s】正在战斗中,暂时无法授权!\n", p->title);
getchar(); getchar();
return;
}
if (p->status == ARMOR_DAMAGED) {
printf("【%s】已破损,维修完成前无法授权!\n", p->title);
getchar(); getchar();
return;
}
printf("选中铠甲:%s\n", p->title);
printf("铠甲类型:%s | 属性:%s\n", p->type,
p->attr == ATTR_FIRE ? "火属性" : (p->attr == ATTR_WATER ? "水属性" :
(p->attr == ATTR_THUNDER ? "雷属性" : (p->attr == ATTR_EARTH ? "土属性" : "风属性"))));
printf("授权费用: %.2f 元/小时\n", p->price);
printf("请选择授权时长(小时):\n");
scanf("%d", &hourNum);
if (hourNum <= 0) {
printf("授权时长必须大于0!\n");
getchar(); getchar();
return;
}
totalPrice = p->price * hourNum;
order* neworder = (order*)malloc(sizeof(order));
if (neworder == NULL) {
printf("授权失败:系统资源不足!\n");
getchar(); getchar();
return;
}
// 生成订单ID
int maxOrderId = 0;
order* o = orderList;
while (o != NULL) {
if (o->id > maxOrderId) maxOrderId = o->id;
o = o->next;
}
neworder->id = maxOrderId + 1;
neworder->movieId = p->id;
strcpy(neworder->movieName, p->title);
neworder->totalPrice = totalPrice;
neworder->quantity = hourNum;
neworder->userId = currentUser->id;
neworder->monsterId = 0;
neworder->battleResult = -1;
neworder->next = orderList;
orderList = neworder;
printf("====================\n");
printf("铠甲授权成功!\n");
printf("召唤人:%s\n", currentUser->name);
printf("授权铠甲:%s\n", p->title);
printf("授权时长:%d 小时\n", hourNum);
printf("总费用:%.2f 元\n", totalPrice);
printf("====================\n");
save_to_file();
getchar(); getchar();
return;
}
p = p->next;
}
printf("未找到ID为%d的铠甲!\n", armorId);
printf("按任意键继续。。。\n");
getchar(); getchar();
}
4.2 订单查看功能
为了让用户能清晰地查看自己的授权记录,我实现了订单查看功能,并做了以下优化:
- 将订单与铠甲信息关联展示
- 使用美观的边框格式化输出
- 显示详细的铠甲状态和属性信息
c复制void myOrders() {
if (currentUser == NULL) {
printf("请先登录系统后再查看授权记录!\n");
getchar(); getchar();
return;
}
if (orderList == NULL) {
printf("系统暂无任何铠甲授权记录\n");
getchar(); getchar();
return;
}
printf("\n=======你的铠甲授权记录=======\n");
order* p = orderList;
int count = 0;
while (p != NULL) {
if (p->userId == currentUser->id) {
count++;
// 匹配订单对应的铠甲信息
movie* armor = movieList;
char armorType[20] = "未知";
char armorAttr[20] = "未知";
char armorStatus[20] = "未知";
while (armor != NULL) {
if (armor->id == p->movieId) {
strcpy(armorType, armor->type);
if (armor->attr == ATTR_FIRE) strcpy(armorAttr, "火属性");
else if (armor->attr == ATTR_WATER) strcpy(armorAttr, "水属性");
else if (armor->attr == ATTR_THUNDER) strcpy(armorAttr, "雷属性");
else if (armor->attr == ATTR_EARTH) strcpy(armorAttr, "土属性");
else if (armor->attr == ATTR_WIND) strcpy(armorAttr, "风属性");
if (armor->status == ARMOR_IDLE) strcpy(armorStatus, "闲置");
else if (armor->status == ARMOR_IN_BATTLE) strcpy(armorStatus, "战斗中");
else if (armor->status == ARMOR_REPAIRING) strcpy(armorStatus, "维修中");
else if (armor->status == ARMOR_DAMAGED) strcpy(armorStatus, "破损");
break;
}
armor = armor->next;
}
// 美化订单展示格式
printf("┌─────────────────────────────┐\n");
printf("│ 授权单号: %d\n", p->id);
printf("│ 铠甲名称: %s\n", p->movieName);
printf("│ 铠甲类型: %s | 属性: %s\n", armorType, armorAttr);
printf("│ 铠甲状态: %s\n", armorStatus);
printf("│ 授权时长: %d 小时\n", p->quantity);
printf("│ 总消耗: %.2f 元\n", p->totalPrice);
if (p->monsterId == 0) {
printf("│ 战斗记录: 未讨伐任何怪兽\n");
} else {
printf("│ 战斗记录: 讨伐怪兽ID-%d(%s)\n",
p->monsterId, p->battleResult == 1 ? "胜利" : (p->battleResult == 0 ? "失败" : "未完成"));
}
printf("└─────────────────────────────┘\n");
}
p = p->next;
}
if (count == 0) {
printf("你暂无任何铠甲授权记录,快去申请授权吧!\n");
} else {
printf("\n共查询到 %d 条授权记录\n", count);
}
printf("\n按任意键继续。。。\n");
getchar(); getchar();
}
5. 项目经验总结
5.1 开发过程中的关键经验
-
结构体复用策略:通过复用原有movie结构体而不是新建armor结构体,节省了大量时间和代码量。原有链表操作和文件读写逻辑可以直接复用,只需要添加铠甲特有的字段即可。
-
枚举类型的处理:枚举类型不能直接存储到文件中,需要先转换为数字,读取时再转换回来。这是很多初学者容易忽略的地方。
-
空指针防护:在访问currentUser->id等成员前,一定要先检查currentUser是否为NULL,否则程序会崩溃。
-
链表遍历的边界条件:在查找最大ID等操作时,必须确保遍历到next=NULL,否则会漏掉最后一个节点的数据。
-
用户体验优化:通过美化输出格式、将状态码转换为文字描述、提供清晰的错误提示等方式,可以显著提升用户体验。
5.2 可能的扩展方向
- 铠甲战斗系统:根据铠甲属性和怪兽属性的克制关系,实现自动战斗结果判断。
- 铠甲维修功能:允许管理员将破损铠甲状态改为维修中或闲置。
- 用户管理功能:增加密码修改、用户注册等功能。
- 怪兽封印记录:在讨伐成功后自动更新怪兽的isDefeated字段。
6. 项目心得
通过这个课程设计项目,我深刻体会到:
-
理解需求比直接编码更重要。在开始编码前,花时间分析需求、设计数据结构,可以事半功倍。
-
代码复用是提高开发效率的有效手段。合理复用已有代码,可以减少重复劳动和潜在错误。
-
防御性编程能提高代码健壮性。对可能的异常情况进行预判和处理,可以避免很多运行时错误。
-
用户体验不容忽视。即使是课程设计项目,良好的交互和输出格式也能给人留下深刻印象。
-
将个人兴趣融入项目能增加学习动力。把电影票务系统改造成铠甲勇士管理系统,让整个开发过程变得更有趣。
这个项目虽然不大,但涵盖了C语言的很多核心知识点:结构体、指针、链表、文件操作等。通过实际项目的锻炼,我对这些概念的理解更加深入了。希望我的经验能对其他学习C语言的同学有所帮助。