为什么 C 程序输入负重量会报 fee 未初始化,而负路程不会?
原因是:weight 为负数时,按重量给 fee 赋值的分支都会被跳过,程序随后却仍会读取 fee。负数检查放在读取语句之后,无法阻止错误发生。
另外,scanf_s(" %c", &fast) 缺少字符缓冲区大小参数,也需要修正。
为什么负重量会导致 fee 未初始化?
这条声明没有给变量赋初值:
float fee;
随后,程序只在三个重量范围内给它赋值:
weight < 5 && weight >= 0
weight >= 5 && weight < 20
weight >= 20
如果输入 weight = -4,三个条件都不成立,计费分支就不会给 fee 赋值。
程序接着处理加急选项。如果选择 Y 或 y:
fee = fee + 20;
计算新值时需要先读取右侧的 fee,因此会用到未初始化的变量。
如果输入其他字符,则执行:
fee = fee;
这条语句同样会读取右侧的 fee。只要走到加急处理,无论选什么,都会读取尚未初始化的 fee。把变量赋值给自身没有实际作用,可以删除这条语句。
未初始化的自动局部变量不能被当作有效数值使用,读取它会导致未定义行为。Visual Studio 的运行时检查在这里报告了问题,其他构建方式不一定会给出相同提示。
为什么仅路程为负数时没有相同的报错?
假设重量非负,程序会进入某个重量分支。负数路程满足:
length <= 100
因此,即使路程为负数,程序通常也会给 fee 赋值。例如,weight = 2、length = -5 时,会执行 fee = 20。
不过,给负数路程计算费用仍不符合你的输入要求,应该先验证输入,再开始计费。
应该怎样修改?
读取重量和路程后,先检查是否有负数;如果有,输出提示并结束程序。这样,后面的计费逻辑只处理有效输入,每条路径都能确定 fee 的值。
下面的示例保留了原来的收费标准,增加了输入返回值和加急选项检查,适用于当前使用的 Visual Studio scanf_s 环境:
#include <stdio.h>
int main(void) {
int weight, length;
float fee;
char fast;
printf("Enter the package weight: ");
if (scanf_s("%d", &weight) != 1) {
printf("Invalid weight input.\n");
return 1;
}
printf("Enter the road length (km): ");
if (scanf_s("%d", &length) != 1) {
printf("Invalid road length input.\n");
return 1;
}
if (weight < 0 || length < 0) {
printf("You made a mistake!\n");
return 1;
}
if (weight < 5) {
if (length <= 100) {
fee = 20.0f;
} else {
fee = 40.0f;
}
} else if (weight < 20) {
if (length <= 100) {
fee = 50.0f;
} else {
fee = 80.0f;
}
} else {
if (length <= 100) {
fee = 100.0f;
} else {
fee = 150.0f;
}
}
printf("Would you like a fast delivery? (Y/N): ");
if (scanf_s(" %c", &fast, (unsigned)sizeof fast) != 1) {
printf("Invalid delivery option input.\n");
return 1;
}
if (fast == 'Y' || fast == 'y') {
fee += 20.0f;
} else if (fast != 'N' && fast != 'n') {
printf("Please enter Y or N.\n");
return 1;
}
printf("Total package fee: %.2f Credits\n", fee);
return 0;
}
示例中的 float fee; 在声明时仍未初始化,但所有能到达加急处理的路径都会先给它赋予确定的费用值。
scanf_s 和初始化还需要注意什么?
在 Microsoft 的 scanf_s 中,用 %c 读取字符时,需要在目标地址后提供缓冲区大小。对于单个 char,大小为 1:
scanf_s(" %c", &fast, (unsigned)sizeof fast);
代码将大小转为 unsigned,以匹配该参数要求的类型。格式字符串中 %c 前的空格会跳过之前输入留下的换行符等空白字符。
原代码缺少这个参数,是独立于 fee 问题的调用错误,可能影响程序运行,需要一并修正。读取 %d 不需要额外的缓冲区大小参数。
将声明改为 float fee = 0.0f; 可以消除该变量的未初始化读取问题,但仍应提前检查负数,避免无效输入参与计费。
上面的输入检查适用于这个基础示例,但不能保证拒绝所有不规范的输入。例如,scanf_s 的 %d 可以接受 12abc 中的数字前缀。如果后续需要严格验证整行输入及整数范围,可以改用 fgets 配合 strtol。
备注:内容仅供参考。
