C 语言实战:超市水果结算系统(深度解析与优化)

C 语言实战:超市水果结算系统(深度解析与优化)

你编写的这份超市结算系统功能完备、逻辑清晰,完美实现了水果累计购买、清单打印、结账找零等核心需求,是一份优秀的 C 语言实用项目代码。下面将从代码亮点、核心逻辑、潜在优化点三个方面进行全面解析,同时提供可落地的优化方案。

一、代码核心亮点(值得肯定的设计)

1. 数据结构化设计,扩展性强

采用typedef定义Fruit结构体,封装了水果的name(名称)、price(单价)、catty(累计斤数)、subtotal(小计金额)四个核心字段,通过结构体数组统一管理三种水果数据。

  • 优势:后续新增水果品类,仅需扩展结构体数组的初始化数据(如添加{"橙子", 4.5, 0.0, 0.0}),无需大幅修改核心逻辑,符合 "开闭原则";
  • 关键设计:cattysubtotal初始化为0.0,为后续累计购买和金额计算铺垫,数据状态清晰。

2. 累计购买功能,贴合实际场景

BuyFruit函数中,使用fruit->catty += buyCatty实现斤数累加 ,而非直接赋值(fruit->catty = buyCatty)。

c

运行

rust 复制代码
fruit->catty += buyCatty; // 核心累加逻辑
  • 优势:支持用户重复购买同一水果,自动累计总斤数,完全符合超市购物中 "多次选购同一商品" 的实际场景,提升了系统的实用性。

3. 全方位输入校验,保证程序健壮性

整个系统做了多层输入合法性校验,避免非法数据导致程序异常:

  1. 操作序号校验:拦截非整数输入,提示输入0-4的整数;
  2. 购买斤数校验:拦截非浮点数、负数 / 零输入,保证购买数量有效;
  3. 支付金额校验:三层校验(有效浮点数、非负数、不小于总金额),避免结账逻辑异常。

4. 模块化封装,代码结构清晰

将不同功能拆分为独立函数,各司其职,主函数仅负责流程驱动,符合模块化编程思想:

  • welcome1():菜单展示,交互友好;
  • clearInputBuffer():输入缓冲区清空,解决scanf残留问题;
  • BuyFruit():处理水果购买与斤数累计;
  • CalculateSubtotal():单独计算小计金额,职责单一;
  • printSubtotal():格式化打印结算清单;
  • CalculateChange():处理结账找零逻辑。

5. 友好的交互与格式化输出

  1. 采用表格化格式打印结算清单,添加分隔符、对齐排版,可读性强;
  2. 金额数据统一使用%.2f格式化,保留两位小数,符合货币展示规范;
  3. 操作结果实时反馈(如 "购买成功""结账成功"),提升用户使用体验。

二、核心模块逻辑解析(关键函数拆解)

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(),重置结构体数组的cattysubtotal0.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)。

优化方案

将所有金额相关变量(pricecattysubtotaltotalpaychange)改为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
  • 补充:输出格式仍使用%.2fprintf%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元	
	==============================

五、总结

  1. 这份超市结算系统的核心设计符合实用项目规范,结构体数组、模块化封装、输入校验等技巧的运用十分到位,具备实际使用价值;
  2. 优化后的代码在健壮性、扩展性、精准度上得到进一步提升,解决了原代码的潜在问题,同时保留了原有的核心功能和友好交互;
  3. 若想进一步完善,还可添加数据持久化(文件存储)、优惠活动计算、会员系统等功能,逐步提升项目的复杂度和实用性。

这份代码是 C 语言入门者向实战进阶的优秀案例,吃透其中的设计思想和核心技巧,能够为后续开发更复杂的信息管理系统打下坚实基础。

相关推荐
MM_MS2 小时前
Halcon小案例--->路由器散热口个数(两种方法)
人工智能·算法·目标检测·计算机视觉·视觉检测·智能路由器·视觉
yangminlei2 小时前
Spring Boot 响应式 WebFlux 从入门到精通
java·spring boot·后端
Apifox2 小时前
Apifox CLI + Claude Skills:将接口自动化测试融入研发工作流
前端·后端·测试
调试人生的显微镜2 小时前
使用Fiddler抓包工具获取微信公众号数据的完整教程
后端
货拉拉技术2 小时前
性能突破:星图平台架构优化
后端
UIUV2 小时前
Git 提交规范与全栈AI驱动开发实战:从基础到高级应用
前端·javascript·后端
a3158238062 小时前
大语言模型应用开发技术要求
算法·大语言模型·应用开发
倦王2 小时前
力扣日刷26112
算法·leetcode·职场和发展
程序员清风2 小时前
猿辅导二面:线上出现的OOM是如何排查的?
java·后端·面试