项目二次开发
2352509 康雅萱 软工一班
来源:同学(2352503 王以纯)的程序设计期末大作业报告--餐厅信息管理程序
一、餐厅信息管理程序
运行环境:Dev C++
基本要求:
1.要求实现客户点菜的过程、客户结账、账目的管理、餐厅系统的维护四大功能模块,每个功能模块又分别对应一些不同操作子模块,从而完成一个餐厅信息管理信息系统。
2.可以使用三种不同的结构体来分别存储餐桌、菜以及订单信息。
3.使用链表来实现创建客户订单与客户结账等操作。
4.使用文本文件完成数据的存储与读取,完成账单的管理。
5.系统制作完成后应实现类似下图所示界面。
二、完整可编辑且加有适当注释的源代码
点击查看代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>// 客户订单结构体
typedef struct Order {int orderNumber;char dish[50];float price;struct Order* next;
} Order;struct table{int table_num;int seat;int occupied;
}tables[10];struct dish{int dishnum;char dishname[20];int price;struct dish* next;
}dishes[10];// 创建新的订单节点
Order* createOrder(int orderNumber, const char* dish, float price) {Order* newOrder = (Order*)malloc(sizeof(Order));newOrder->orderNumber = orderNumber;strcpy(newOrder->dish, dish);newOrder->price = price;newOrder->next = NULL;return newOrder;
}// 添加订单到链表
void addOrder(Order** head, Order* newOrder) {if (*head == NULL) {*head = newOrder;} else {Order* current = *head;while (current->next != NULL) {current = current->next;}current->next = newOrder;}
}// 打印订单信息
void printOrder(Order* order) {printf("订单编号: %d, 菜品: %s, 价格: %.2f\n", order->orderNumber, order->dish, order->price);
}// 保存订单到文件
void saveOrdersToFile(Order* head) {FILE* file = fopen("orders.txt", "w");if (file == NULL) {printf("无法打开文件!\n");return;}Order* current = head;while (current != NULL) {fprintf(file, "%d %s %.2f\n", current->orderNumber, current->dish, current->price);current = current->next;}fclose(file);printf("订单已保存到文件!\n");
}// 从文件加载订单
Order* loadOrdersFromFile() {Order* head = NULL;FILE* file = fopen("orders.txt", "r");if (file == NULL) {printf("无法打开文件!\n");return head;}int orderNumber;char dish[50];float price;while (!feof(file)) {fscanf(file, "%d %s %f\n", &orderNumber, dish, &price);Order* newOrder = createOrder(orderNumber, dish, price);addOrder(&head, newOrder);}fclose(file);printf("订单已从文件加载!\n");return head;
}// 释放订单链表内存
void freeOrders(Order* head) {Order* current = head;while (current != NULL) {Order* temp = current;current = current->next;free(temp);}
}int main() {Order* head = NULL;int orderNumber=1,i,choice,j,prices;struct dish dishes[10]={{1,"宫爆鸡丁",30},{2,"麻辣香锅",40},{3,"水煮鱼",50},{4,"小炒肉",25},{5,"炒青菜",15},{6,"番茄炒蛋",15},{7,"醋溜土豆丝",15},{8,"紫菜蛋花汤",20},{9,"小酥肉",15},{10,"蛋炒饭",25}};struct dish* p,*head1,*c;head1=dishes;p=head1;do {printf("==========================\n");printf("餐厅服务系统\n");printf("==========================\n");printf("1. 点菜\n");printf("2. 客户结账\n");printf("3. 账目管理\n");printf("4. 餐馆统计\n");printf("5. 退出\n");printf("请输入您的选择:");scanf("%d", &choice);switch (choice) {case 1: {printf("菜单为:\n");for(i=1;i<=10;i++){if(i<10) p->next=dishes+i;else p->next=NULL; printf("%d %s %d\n",p->dishnum,p->dishname,p->price);if(i<10) p=p->next;}int num;printf("请输入点菜数量:");scanf("%d",&num);Order* newOrder=NULL;for (i=0;i<num;i++) {int dishn;char dish[50];printf("请输入第%d个菜品序号:",i+1);scanf("%d",&dishn);p=head1;while(p!=NULL){for(j=0;j<10;j++){if(p->dishnum==dishn){prices=p->price;strcpy(dish,dishes[j].dishname);break;}else {c=p;p=p->next;}} if(j!=10) break;}p=head1;if (newOrder==NULL) {newOrder=createOrder (orderNumber,dish,prices);addOrder (&head,newOrder);} else {Order* additionalOrder=createOrder (orderNumber,dish,prices);addOrder (&(newOrder->next),additionalOrder);}orderNumber++;}printf("点菜成功!\n");break;}case 2: {if (head == NULL) {printf("当前没有订单!\n");} else {printf("订单列表:\n");Order* current = head;while (current != NULL) {printOrder(current);current = current->next;}printf("请选择结账订单编号:");int checkoutOrderNumber;scanf("%d", &checkoutOrderNumber);current = head;Order* previous = NULL;while (current != NULL) {if (current->orderNumber == checkoutOrderNumber) {if (previous != NULL) {previous->next = current->next;} else {head = current->next;}printOrder(current);free(current);break;}previous = current;current = current->next;}}break;}case 3: {if (head == NULL) {printf("当前没有订单!\n");} else {Order* current = head;while (current != NULL) {printOrder(current);current = current->next;}}break;}case 4: {saveOrdersToFile(head);break;}case 5:break;default:printf("无效的选择!\n");break;}} while (choice != 5);freeOrders(head);return 0;
}
(1) 点菜功能中的逻辑问题
在点菜功能中,newOrder 的使用逻辑存在问题。newOrder 被错误地用作链表的中间节点,而实际上应该直接将新订单添加到链表中。
修正代码:
case 1: {
printf("菜单为:\n");
for (i = 0; i < 10; i++) {
printf("%d %s %d\n", dishes[i].dishnum, dishes[i].dishname, dishes[i].price);
}
int num;
printf("请输入点菜数量:");
scanf("%d", &num);
for (i = 0; i < num; i++) {
int dishn;
printf("请输入第%d个菜品序号:", i + 1);
scanf("%d", &dishn);
int found = 0;
for (j = 0; j < 10; j++) {if (dishes[j].dishnum == dishn) {Order* newOrder = createOrder(orderNumber, dishes[j].dishname, dishes[j].price);addOrder(&head, newOrder);orderNumber++;found = 1;break;}
}
if (!found) {printf("未找到菜品编号:%d,请重新输入!\n", dishn);i--; // 重新输入当前菜品
}
}
printf("点菜成功!\n");
break;}
修正点:
修复了点菜逻辑,直接将新订单添加到链表中。
增加了菜品编号验证,如果输入的编号无效,提示用户重新输入
(2)文件加载订单时的逻辑问题
在 loadOrdersFromFile 函数中,while (!feof(file)) 的使用方式可能导致重复读取最后一行数据。
修正代码:
Order* loadOrdersFromFile() {
Order* head = NULL;
FILE* file = fopen("orders.txt", "r");
if (file == NULL) {
printf("无法打开文件!\n");
return head;
}
int orderNumber;
char dish[50];
float price;
while (fscanf(file, "%d %s %f", &orderNumber, dish, &price) == 3) {
Order* newOrder = createOrder(orderNumber, dish, price);
addOrder(&head, newOrder);
}
fclose(file);
printf("订单已从文件加载!\n");
return head;}
修正点:
使用 fscanf 的返回值判断是否成功读取一行数据,避免 feof 的误用。
三、优化实现
1.动态菜单管理
菜单结构体和链表操作:
typedef struct Dish {
int dishnum;
char dishname[20];
float price;
struct Dish* next;} Dish;
Dish* createDish(int dishnum, const char* dishname, float price) {
Dish* newDish = (Dish)malloc(sizeof(Dish));
newDish->dishnum = dishnum;
strcpy(newDish->dishname, dishname);
newDish->price = price;
newDish->next = NULL;
return newDish;}
void addDish(Dish** head, Dish newDish) {
if (head == NULL) {
head = newDish;
} else {
Dish current = head;
while (current->next != NULL) {
current = current->next;
}
current->next = newDish;
}}
void printDishes(Dish head) {
if (head == NULL) {
printf("菜单为空!\n");
return;
}
Dish current = head;
while (current != NULL) {
printf("%d %s %.2f\n", current->dishnum, current->dishname, current->price);
current = current->next;
}}
动态菜单初始化:
Dish* headDish = NULL;addDish(&headDish, createDish(1, "宫保鸡丁", 30));addDish(&headDish, createDish(2, "麻辣香锅", 40));addDish(&headDish, createDish(3, "水煮鱼", 50));// 其他菜品...
订单统计功能
统计销售额和点菜次数最多的菜品:
void printOrderStatistics(Order* head) {
if (head == NULL) {
printf("当前没有订单!\n");
return;
}
float totalSales = 0;
int maxCount = 0;
char mostOrderedDish[50] = "";
int dishCount[100] = {0}; // 假设菜品编号不超过100
Order* current = head;
while (current != NULL) {
totalSales += current->price;
dishCount[current->orderNumber]++;
if (dishCount[current->orderNumber] > maxCount) {
maxCount = dishCount[current->orderNumber];
strcpy(mostOrderedDish, current->dish);
}
current = current->next;
}
printf("总销售额:%.2f元\n", totalSales);
printf("点菜次数最多的菜品:%s,共点%d次\n", mostOrderedDish, maxCount);}
持久化存储优化
优化文件格式和错误处理:
void saveOrdersToFile(Order* head) {
FILE* file = fopen("orders.txt", "w");
if (file == NULL) {
printf("无法打开文件!\n");
return;
}
Order* current = head;
while (current != NULL) {
fprintf(file, "%d %s %.2f\n", current->orderNumber, current->dish, current->price);
current = current->next;
}
fclose(file);
printf("订单已成功保存到文件!\n");}
Order* loadOrdersFromFile() {
Order* head = NULL;
FILE* file = fopen("orders.txt", "r");
if (file == NULL) {
printf("无法打开文件!\n");
return head;
}
int orderNumber;
char dish[50];
float price;
while (fscanf(file, "%d %s %f", &orderNumber, dish, &price) == 3) {
Order* newOrder = createOrder(orderNumber, dish, price);
addOrder(&head, newOrder);
}
fclose(file);
printf("订单已成功从文件加载!\n");
return head;}
四、测试截图
五、总结与思考
1.难点与耗时点总结
(1) 动态数据结构的实现
-难点:将静态的菜单和订单管理改为动态链表结构,需要重新设计数据的存储和访问方式。这不仅涉及到链表的基本操作(如插入、删除、查找),还需要确保内存管理正确,避免内存泄漏。
-耗时点:动态菜单的实现需要对链表操作非常熟悉,尤其是在添加、删除菜品时,链表的连接关系容易出错。此外,动态菜单的初始化和遍历也需要仔细设计,以确保功能的正确性。
-解决方法:通过逐步调试和单元测试,确保每个链表操作的正确性。同时,将链表操作封装为独立函数,减少重复代码,降低出错概率。
(2) 功能模块化与复用
难点:将重复的逻辑抽象为通用函数时,需要考虑函数的通用性和扩展性。例如,订单统计功能需要同时处理销售额和点菜次数统计,如何设计一个灵活的统计框架是一个挑战。
耗时点:模块化设计需要对代码逻辑有清晰的理解,同时要确保模块之间的接口设计合理。在实现过程中,频繁的重构和测试花费了较多时间。
解决方法:通过编写伪代码和流程图,提前规划模块的功能和接口。在实现过程中,逐步验证每个模块的正确性,并通过单元测试确保模块之间的协作无误。
逆向软件工程的思考
(1) 逆向工程的意义
逆向工程不仅是对现有代码的分析和理解,更是对软件设计思路的重新审视。通过逆向分析,可以发现原代码中的设计缺陷、冗余逻辑和潜在问题,从而为优化和重构提供依据。
(2) 逆向工程的实践方法
代码分析:通过阅读代码、绘制流程图和结构图,理解代码的逻辑和功能。重点关注代码的模块划分、数据结构设计和关键算法。
功能测试:在优化过程中,保持原有功能不变是关键。通过编写测试用例,验证每个功能模块的正确性,确保优化后的代码与原代码功能一致。
逐步重构:逆向工程不是简单的重写,而是逐步优化。在重构过程中,先保留原代码的核心逻辑,逐步引入新的设计和功能,避免一次性大改导致的不可控风险。
(3) 逆向工程的挑战
代码质量差异:原代码的质量直接影响逆向工程的难度。如果原代码缺乏注释、结构混乱或存在大量冗余逻辑,逆向分析将变得非常困难。
需求变更:在逆向工程过程中,需求可能会发生变化。如何在满足新需求的同时,保持代码的可维护性和扩展性是一个挑战。
性能与功能的平衡:优化代码时,需要在性能和功能之间找到平衡。过度优化可能导致代码复杂度增加,而忽略性能优化又可能影响用户体验。
总结与反思
通过这次优化实践,我深刻体会到逆向工程的重要性。它不仅帮助我理解了原代码的设计思路,还让我学会了如何在现有基础上进行优化和扩展。以下是我的几点总结:
(1) 深入理解代码是优化的基础
在优化代码之前,必须深入理解其逻辑和结构。逆向工程是实现这一目标的有效方法,通过逐步分析和重构,可以逐步优化代码。
(2) 模块化设计的重要性
模块化设计不仅提高了代码的可读性和可维护性,还为未来的功能扩展提供了便利。在优化过程中,将重复逻辑封装为独立模块,可以减少冗余代码,降低出错概率。
(3) 优化是一个逐步的过程
优化代码时,不能一蹴而就。需要逐步重构,保持原有功能不变,同时逐步引入新的设计和功能。通过单元测试和功能测试,确保每次优化的正确性。
(4) 逆向工程的局限性
逆向工程虽然强大,但也存在局限性。如果原代码质量较差,逆向分析可能会非常困难。此外,需求变更可能导致逆向工程的方向发生变化,需要灵活调整优化策略。
通过这次实践,我不仅提升了代码优化能力,还对逆向工程有了更深刻的理解。这些经验将为我未来的工作和学习提供宝贵的参考。