1. 水果店收银系统开发实录
最近在准备东华大学复试OJ题目时,遇到一个很有意思的编程题——水果店收银系统。这个题目看似简单,但涉及字符串处理、浮点数运算、格式化输出等多个C语言核心知识点。下面我将详细解析这个项目的实现过程,并分享一些在解题过程中积累的宝贵经验。
1.1 需求分析
题目要求开发一个水果店收银程序,需要处理四种水果的销售记录:
- 苹果(代码a)单价1.5元/公斤
- 橘子(代码o)单价1.4元/公斤
- 香蕉(代码b)单价1.48元/公斤
- 菠萝(代码p)单价1.08元/公斤
程序需要实现以下功能:
- 接收用户输入的水果代码和重量
- 计算各水果的总价和全部水果的总价
- 按照特定格式输出结果表格
1.2 核心数据结构设计
首先我们需要设计合适的数据结构来存储水果信息:
c复制// 单价数组,按a/o/b/p顺序存储
float price[4] = {1.5, 1.4, 1.48, 1.08};
// 重量数组,用于累计各水果的总重量
float weight[4] = {0, 0, 0, 0};
这种数组存储方式简单高效,通过下标就能快速访问对应水果的数据。需要注意的是,浮点数在计算机中存储会有精度问题,这在后续计算中需要特别注意。
2. 输入处理与数据统计
2.1 多组数据输入处理
题目要求支持多组测试数据,每组数据以水果种类数kinds开头。我们使用EOF循环来实现:
c复制while(scanf("%d",&kinds) != EOF) {
// 处理单组数据
}
这种写法可以持续接收输入,直到遇到文件结束符(EOF),非常适合OJ题目的测试用例模式。
2.2 字符串匹配与数据累加
对于每组数据中的每条记录,我们需要:
- 读取水果代码和重量
- 根据代码将重量累加到对应水果的总重量中
c复制char name[10];
float TempWeight;
scanf("%s %f", name, &TempWeight);
if(strcmp(name, "a") == 0) {
weight[0] += TempWeight;
}
else if(strcmp(name, "o") == 0) {
weight[1] += TempWeight;
}
// 其他水果类似处理
这里使用strcmp进行字符串精确匹配,确保能正确识别各种水果代码。
注意:在实际商业系统中,通常会使用更高效的查找方式,比如哈希表。但在这个简单题目中,if-else判断已经足够。
3. 计算与格式化输出
3.1 金额计算与四舍五入
计算各水果总价时,直接使用单价乘以重量:
c复制float sumP = 0;
for(int i=0; i<4; i++){
sumP += (weight[i] * price[i]);
}
但题目要求金额保留两位小数,且需要四舍五入。C语言中浮点数运算可能存在精度误差,因此需要特殊处理:
c复制roundf(price[0]*weight[0] * 100.0f) / 100.0f
这个技巧先将金额放大100倍,四舍五入后再缩小100倍,确保小数点后两位的精度。
3.2 复杂格式控制
输出格式是本题的一大难点,具体要求包括:
- 水果名称前的空格数量不同
- 价格和重量的对齐方式不同
- 不同列的宽度要求不同
实现代码如下:
c复制printf(" apple orange banana pineapple sum\n");
printf("price %-7.2f%-7.2f%-7.2f%-10.2f%-7.2f\n",
(roundf(price[0]*weight[0] * 100.0f) / 100.0f),
(roundf(price[1]*weight[1] * 100.0f) / 100.0f),
(roundf(price[2]*weight[2] * 100.0f) / 100.0f),
(roundf(price[3]*weight[3] * 100.0f) / 100.0f),
sumP);
printf("weight %-7.2f%-7.2f%-7.2f%-10.2f%-7.2f\n",
weight[0], weight[1], weight[2], weight[3], sumW);
格式说明符解析:
%-7.2f:左对齐,总宽度7,保留2位小数%-10.2f:菠萝价格需要更宽的空间
4. 常见问题与调试技巧
4.1 浮点数精度问题
在测试过程中,我发现直接使用浮点数计算有时会出现精度误差。例如:
c复制float a = 1.5 * 2.3; // 可能得到3.4499998而不是3.45
解决方案就是前面提到的四舍五入技巧。此外,在商业系统中,通常会使用定点数或专门的钱币类型来处理金额计算。
4.2 输出格式对齐
调试格式输出时,建议:
- 先在纸上画出期望的输出表格
- 逐个字段确认宽度和对齐方式
- 使用printf的
%*格式动态控制宽度
4.3 多组数据分隔
题目要求每组数据间用空行分隔,但最后一组后不要有空行。实现方法是:
c复制if(first != 1) printf("\n"); // 不是第一组就先打印空行
first = 0; // 标记已经不是第一组
5. 代码优化建议
虽然上述代码已经能正确解决问题,但还可以进一步优化:
- 使用枚举或宏定义代替魔法数字:
c复制#define APPLE 0
#define ORANGE 1
// ...
weight[APPLE] += TempWeight;
- 将水果信息封装为结构体数组:
c复制struct Fruit {
char code;
char name[10];
float price;
} fruits[4] = {
{'a', "apple", 1.5},
// ...
};
- 使用查找表代替if-else链:
c复制int index = -1;
for(int i=0; i<4; i++) {
if(strcmp(name, fruit_codes[i]) == 0) {
index = i;
break;
}
}
if(index != -1) {
weight[index] += TempWeight;
}
这些优化虽然会增加一些代码量,但能大大提高程序的可读性和可维护性。
6. 扩展思考
这个简单的收银系统可以进一步扩展为完整的商店管理系统:
- 添加商品库存管理
- 支持会员折扣
- 增加销售记录存储功能
- 添加图形用户界面
在实际开发中,我们还需要考虑:
- 并发访问控制
- 数据持久化存储
- 异常处理机制
- 性能优化
通过这个小项目,我深刻体会到即使是看似简单的需求,在实现时也会遇到各种细节问题。良好的编码习惯和细致的测试是开发可靠软件的关键。
