C 语言实战:超市水果结算系统(深度解析与优化)
你编写的这份超市结算系统功能完备、逻辑清晰,完美实现了水果累计购买、清单打印、结账找零等核心需求,是一份优秀的 C 语言实用项目代码。下面将从代码亮点、核心逻辑、潜在优化点三个方面进行全面解析,同时提供可落地的优化方案。
一、代码核心亮点(值得肯定的设计)
1. 数据结构化设计,扩展性强
采用typedef定义Fruit结构体,封装了水果的name(名称)、price(单价)、catty(累计斤数)、subtotal(小计金额)四个核心字段,通过结构体数组统一管理三种水果数据。
- 优势:后续新增水果品类,仅需扩展结构体数组的初始化数据(如添加
{"橙子", 4.5, 0.0, 0.0}),无需大幅修改核心逻辑,符合 "开闭原则"; - 关键设计:
catty和subtotal初始化为0.0,为后续累计购买和金额计算铺垫,数据状态清晰。
2. 累计购买功能,贴合实际场景
在BuyFruit函数中,使用fruit->catty += buyCatty实现斤数累加 ,而非直接赋值(fruit->catty = buyCatty)。
c
运行
rust
fruit->catty += buyCatty; // 核心累加逻辑
- 优势:支持用户重复购买同一水果,自动累计总斤数,完全符合超市购物中 "多次选购同一商品" 的实际场景,提升了系统的实用性。
3. 全方位输入校验,保证程序健壮性
整个系统做了多层输入合法性校验,避免非法数据导致程序异常:
- 操作序号校验:拦截非整数输入,提示输入
0-4的整数; - 购买斤数校验:拦截非浮点数、负数 / 零输入,保证购买数量有效;
- 支付金额校验:三层校验(有效浮点数、非负数、不小于总金额),避免结账逻辑异常。
4. 模块化封装,代码结构清晰
将不同功能拆分为独立函数,各司其职,主函数仅负责流程驱动,符合模块化编程思想:
welcome1():菜单展示,交互友好;clearInputBuffer():输入缓冲区清空,解决scanf残留问题;BuyFruit():处理水果购买与斤数累计;CalculateSubtotal():单独计算小计金额,职责单一;printSubtotal():格式化打印结算清单;CalculateChange():处理结账找零逻辑。
5. 友好的交互与格式化输出
- 采用表格化格式打印结算清单,添加分隔符、对齐排版,可读性强;
- 金额数据统一使用
%.2f格式化,保留两位小数,符合货币展示规范; - 操作结果实时反馈(如 "购买成功""结账成功"),提升用户使用体验。
二、核心模块逻辑解析(关键函数拆解)
1. 工具函数:clearInputBuffer()(输入健壮性保障)
c
运行
csharp
void clearInputBuffer()
{
while (getchar() != '\n');
}
- 核心作用:解决
scanf读取数据后,输入缓冲区中残留的换行符 / 非法字符 导致后续scanf读取异常的问题; - 使用场景:所有
scanf操作完成后均调用该函数,是 C 语言处理输入的必备健壮性技巧; - 实现逻辑:循环读取缓冲区中的字符,直到读取到换行符
'\n'为止,清空缓冲区所有残留数据。
2. 核心功能:BuyFruit()(水果购买与累计)
c
运行
scss
void BuyFruit(Fruit *fruit)
{
float buyCatty;
printf("\t请输入购买数量(斤):\t");
// 双层校验:有效浮点数 + 正数
while(scanf("%f", &buyCatty) != 1||buyCatty<=0)
{
clearInputBuffer(); // 清空非法输入残留
printf("输入错误,请重新输入:");
}
clearInputBuffer(); // 清空正常输入后的换行符
fruit->catty += buyCatty; // 累计购买斤数
printf("购买成功!当前%s累计购买:%.2f斤\t\n",fruit->name, fruit->catty);
printf("\t==============================\t\n");
}
- 关键细节:接收
Fruit结构体指针作为参数,直接修改原结构体数组中的数据,无需返回值,高效且简洁; - 校验逻辑:先判断
scanf返回值(是否读取到有效浮点数),再判断斤数是否大于 0,层层拦截非法输入。
3. 结算核心:printSubtotal()(清单打印与总金额计算)
c
运行
swift
void printSubtotal(Fruit *fruit)
{
float total = 0;
printf("\n\t==============================\t\n");
printf("\t\t当前结算清单\t\n");
printf("\t名称\t单价\t数量\t小计\t\n");
printf("\t------------------------------\t\n");
for (int i = 0; i < 3; i++)
{
if(fruit[i].catty > 0) // 过滤未购买的水果
{
CalculateSubtotal(&fruit[i]); // 实时更新小计,避免数据过期
printf("\t%s\t%.2f\t%.2f\t%.2f\t\n", fruit[i].name, fruit[i].price, fruit[i].catty, fruit[i].subtotal);
total += fruit[i].subtotal; // 累加总金额
}
}
printf("\t------------------------------\t\n");
printf("\t总金额:\t\t\t%.2f元\t\n", total);
printf("\t==============================\t\n");
}
- 关键亮点 1:
if(fruit[i].catty > 0)过滤未购买的水果,避免清单中出现空数据,提升可读性; - 关键亮点 2:遍历过程中实时调用
CalculateSubtotal(),保证小计金额与最新购买斤数同步,避免数据过期; - 逻辑流程:遍历结构体数组→过滤未购买商品→计算实时小计→格式化输出→累加总金额。
三、潜在优化点与落地方案(提升程序完善度)
1. 优化点 1:CalculateChange()函数的校验逻辑瑕疵
问题描述
当用户输入非数字(如字母a)时,pay的初始值为随机垃圾值,可能导致pay<0的判断误触发,提示信息不准确。
c
运行
scss
// 问题代码:当scanf读取失败时,pay的值未定义,pay<0的判断可能异常
while(scanf("%f",&pay)!=1||pay<0||pay<total)
{
clearInputBuffer();
if(pay<0) // 此时pay为垃圾值,判断无意义
{
printf("\t输入金额不能小于0,请重新输入:\t");
}
else if(pay<total)
{
printf("\t支付金额不足,请重新输入:\t");
}
}
优化方案
拆分校验逻辑,先保证读取到有效浮点数,再判断数值范围,避免垃圾值干扰:
c
运行
scss
void CalculateChange(float total)
{
float pay;
printf("\t===============================\t\n");
printf("\t商品总金额:%.2f元\t\n",total);
printf("\t请输入支付金额:\t");
// 第一步:先读取有效浮点数
while(scanf("%f",&pay)!=1)
{
clearInputBuffer();
printf("\t输入格式错误,请输入有效数字:\t");
}
clearInputBuffer();
// 第二步:再判断数值范围,不符合则重新读取
while(pay<0||pay<total)
{
if(pay<0)
{
printf("\t输入金额不能小于0,请重新输入:\t");
}
else if(pay<total)
{
printf("\t支付金额不足(需≥%.2f),请重新输入:\t", total);
}
// 重新读取有效浮点数
while(scanf("%f",&pay)!=1)
{
clearInputBuffer();
printf("\t输入格式错误,请输入有效数字:\t");
}
clearInputBuffer();
}
float change=pay-total;
printf("\t找零:%.2f\n",change);
printf("\t结账成功!\n");
printf("\t===========================\t\n");
}
2. 优化点 2:菜单与商品信息解耦(提升扩展性)
问题描述
当前菜单中的水果选项(1. 苹果、2. 葡萄、3. 香蕉)是硬编码在welcome1()和main()的switch语句中,新增水果时需要修改多个函数,耦合度较高。
优化方案
通过遍历结构体数组自动生成菜单,减少硬编码,提升扩展性:
c
运行
swift
// 优化后的菜单函数:自动遍历结构体数组生成选项
void welcome1()
{
printf("\t==============================\t\n");
printf("\t欢迎来到超市结算系统\t\n");
printf("\t==============================\t\n");
printf("\t请选择您要购买的水果\t\n");
// 遍历结构体数组,自动生成水果选项
for(int i=0; i<3; i++)
{
printf("\t\t%d.%s(%.2f元/斤)\t\n", i+1, fruit[i].name, fruit[i].price);
}
printf("\t\t4.查看当前小计\t\n");
printf("\t\t0.退出系统\t\n");
printf("\t==============================\t\n");
printf("\t请输入您的选择:\t\n");
}
- 优势:新增水果时,仅需添加结构体数组元素,菜单会自动更新,无需修改
welcome1()函数,降低耦合度。
3. 优化点 3:添加 "清空购买记录" 功能(提升灵活性)
需求场景
用户可能需要重新选购水果,清空已有的购买记录,提升系统的灵活性。
实现方案
新增函数clearBuyRecord(),重置结构体数组的catty和subtotal为0.0,并在菜单中添加选项 5:
c
运行
swift
// 新增:清空购买记录函数
void clearBuyRecord()
{
for(int i=0; i<3; i++)
{
fruit[i].catty = 0.0;
fruit[i].subtotal = 0.0;
}
printf("\t购买记录已清空!\t\n");
printf("\t==============================\t\n");
}
// 菜单中添加选项5
void welcome1()
{
// ... 原有逻辑
printf("\t\t4.查看当前小计\t\n");
printf("\t\t5.清空购买记录\t\n"); // 新增选项
printf("\t\t0.退出系统\t\n");
// ... 原有逻辑
}
// main函数的switch中添加case 5
switch (choice)
{
// ... 原有case
case 5:
printf("\t===5.清空购买记录===\t\n");
clearBuyRecord();
break;
// ... 原有case
}
4. 优化点 4:解决浮点精度问题(金额计算更精准)
问题描述
float类型存在精度损失问题,可能导致金额计算出现微小误差(如2.5*5.2可能得到13.000001)。
优化方案
将所有金额相关变量(price、catty、subtotal、total、pay、change)改为double类型,提升计算精度:
c
运行
arduino
// 结构体修改
typedef struct _Fruit
{
char name[20];
double price; // 改为double
double catty; // 改为double
double subtotal; // 改为double
}Fruit;
// 结构体数组初始化修改
Fruit fruit[3]=
{
{"苹果", 5.0, 0.0, 0.0},
{"葡萄", 6.8, 0.0, 0.0},
{"香蕉", 5.2, 0.0, 0.0}
};
// 所有相关变量和函数参数均改为double
- 补充:输出格式仍使用
%.2f(printf中%f可兼容double类型),不影响展示效果,仅提升内部计算精度。
四、优化后运行效果示例(核心场景)
plaintext
diff
===超市结算系统!===
==============================
欢迎来到超市结算系统
==============================
请选择您要购买的水果
1.苹果(5.00元/斤)
2.葡萄(6.80元/斤)
3.香蕉(5.20元/斤)
4.查看当前小计
5.清空购买记录
0.退出系统
==============================
请输入您的选择:
1
===1.苹果(5.0元/斤)===
请输入购买数量(斤): 2.5
购买成功!当前苹果累计购买:2.50斤
==============================
==============================
欢迎来到超市结算系统
==============================
请选择您要购买的水果
1.苹果(5.00元/斤)
2.葡萄(6.80元/斤)
3.香蕉(5.20元/斤)
4.查看当前小计
5.清空购买记录
0.退出系统
==============================
请输入您的选择:
4
===4.查看当前小计===
==============================
当前结算清单
名称 单价 数量 小计
------------------------------
苹果 5.00 2.50 12.50
------------------------------
总金额: 12.50元
==============================
五、总结
- 这份超市结算系统的核心设计符合实用项目规范,结构体数组、模块化封装、输入校验等技巧的运用十分到位,具备实际使用价值;
- 优化后的代码在健壮性、扩展性、精准度上得到进一步提升,解决了原代码的潜在问题,同时保留了原有的核心功能和友好交互;
- 若想进一步完善,还可添加数据持久化(文件存储)、优惠活动计算、会员系统等功能,逐步提升项目的复杂度和实用性。
这份代码是 C 语言入门者向实战进阶的优秀案例,吃透其中的设计思想和核心技巧,能够为后续开发更复杂的信息管理系统打下坚实基础。