跳过正文

丙加·第5章·答案

·671 字·4 分钟·
  1. 郝哥卖瓜:
#include <cstdio>
#include <iostream>
#include <iomanip>
#include <string>

int main() {
    std::string name1 = "西瓜";
    double price1 = 2.0;
    int weight1 = 15;
    double total1 = price1 * weight1;

    std::cout << "买" << name1 << ","
              << std::left << std::setw(8) << std::fixed << std::setprecision(2) << price1 << "元/斤,"
              << std::setw(8) << std::setfill('0') << weight1 << "斤,"
              << std::fixed << std::setprecision(2) << total1 << "元。"
              << std::endl;

    const char* name2 = "大象";
    double price2 = 267.1;
    int weight2 = 5.4 * 2000;
    double total2 = price2 * weight2;

    printf("买%s,%-8.2f元/斤,%08d斤,%.2f元。\n", 
           name2, price2, weight2, total2);

    return 0;
}
  1. 简易计算器:
    C++版本
#include <iostream>
#include <limits>
#include <cmath>
#include <iomanip>

int main() {
    double num1, num2;
    // operator是关键字所以不要用
    char op;

    std::cout << "简单计算器 (支持 + - * /)" << std::endl;

    // 读取第一个数字
    while (true) {
        std::cout << "请输入第一个数字: ";
        if (std::cin >> num1) {
            break;
        }
        std::cout << "错误:请输入有效的数字!" << std::endl;
        std::cin.clear();
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    }

    // 读取运算符
    while (true) {
        std::cout << "请输入运算符 (+, -, *, /): ";
        std::cin >> op;
        if (op == '+' || op == '-' || op == '*' || op == '/') {
            break;
        }
        std::cout << "错误:无效的运算符!请重新输入。" << std::endl;
        std::cin.clear();
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    }

    // 读取第二个数字
    while (true) {
        std::cout << "请输入第二个数字: ";
        if (std::cin >> num2) {
            if (op == '/' && std::abs(num2) < 1e-10) {
                std::cout << "错误:除数不能为零!请重新输入。" << std::endl;
                std::cin.clear();
                std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
                continue;
            }
            break;
        }
        std::cout << "错误:请输入有效的数字!" << std::endl;
        std::cin.clear();
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    }

    // 计算结果
    std::cout << "结果: ";
    switch(op) {
        case '+':
            std::cout << std::fixed << std::setprecision(2) << num1 + num2 << std::endl;
            break;
        case '-':
            std::cout << std::fixed << std::setprecision(2) << num1 - num2 << std::endl;
            break;
        case '*':
            std::cout << std::fixed << std::setprecision(2) << num1 * num2 << std::endl;
            break;
        case '/':
            std::cout << std::fixed << std::setprecision(2) << num1 / num2 << std::endl;
            break;
    }

    return 0;

}

这个版本只会在出错的时候清空缓冲区,所以,读者如果一开始就输入 60/55,也能得到结果。交互如下:

简单计算器 (支持 + - * /)
请输入第一个数字: 60/55
请输入运算符 (+, -, *, /): 请输入第二个数字: 结果: 1.09

分析易知,读取第一个数字的时候,std::cin 会一直读取数字一直到空格或者非数字,然后开始读取运算符(一个字符),郑海缓冲区剩下的第一个字符是 /,所以读取也成功。最后读取 55\n,然后丢掉 \n,所以程序正常运行。

C 版本

#include <stdio.h>
#include <math.h>

int main() {
    double num1, num2;
    char op;
    int result;

    printf("简单计算器 (支持 + - * /)\n");

    // 读取第一个数字
    while (1) {
        printf("请输入第一个数字: ");
        result = scanf("%lf", &num1);
        if (result == 1) {
            break;
        }
        printf("错误:请输入有效的数字!\n");
        // 清除输入缓冲区
        while (getchar() != '\n');
    }

    // 读取运算符
    while (1) {
        printf("请输入运算符 (+, -, *, /): ");
        // 清除缓冲区中的换行符
        while (getchar() != '\n');
        result = scanf("%c", &op);
        if (result == 1 && (op == '+' || op == '-' || op == '*' || op == '/')) {
            break;
        }
        printf("错误:无效的运算符!请重新输入。\n");
        // 清除输入缓冲区
        while (getchar() != '\n');
    }

    // 读取第二个数字
    while (1) {
        printf("请输入第二个数字: ");
        result = scanf("%lf", &num2);
        if (result == 1) {
            if (op == '/' && fabs(num2) < 1e-10) {
                printf("错误:除数不能为零!请重新输入。\n");
                // 清除输入缓冲区
                while (getchar() != '\n');
                continue;
            }
            break;
        }
        printf("错误:请输入有效的数字!\n");
        // 清除输入缓冲区
        while (getchar() != '\n');
    }

    // 计算结果
    printf("结果: ");
    switch(op) {
        case '+':
            printf("%.2f\n", num1 + num2);
            break;
        case '-':
            printf("%.2f\n", num1 - num2);
            break;
        case '*':
            printf("%.2f\n", num1 * num2);
            break;
        case '/':
            printf("%.2f\n", num1 / num2);
            break;
    }

    return 0;
}

这个版本,因为 scanf 在使用 %c 时会无条件读取下一个字符(包括空格、换行符等),所以需要先用 while (getchar() != '\n') 来清除缓冲区中的换行符,否则空格、换行符会被识别为运算符的输入而必然报错一次。而 std::cin 没有这个问题,因为 std::cin 在读取字符时会自动跳过前导的空白字符。 因此这个版本一次性输入 60/55,只会读取 60。当然,继续一次性输入 /55 是可以正常运行。

  1. 首先你会收到警告(Windows 系统的 gcc 不会):

    test.c: In function ‘main’:
    test.c:10:12: warning: format not a string literal and no format arguments [-Wformat-security]
       10 |     printf(str);
          |            ^~~
    

    如果你硬要运行,就会惊喜地发现:

    %s
    段错误 (核心已转储)
    

    或者 Windows 下:

    %s
    纵?   ---> 内存中读取的数据
    

    当用户输入 "%s" 时,printf 会把它当作格式化字符串来解析,尝试从栈上读取一个字符串指针并打印,很可能导致程序崩溃或泄露内存数据。真的有黑客通过类似的方式非法读取内存数据。如果需要直接输出,可以使用 puts(str);

  2. (跑了遍 DeepSeek,说的比我全面,就它了吧)

    1. 银行卡插入阶段

    异常情况:

    • 插入非银行卡(会员卡、身份证等)

    • 插入损坏的银行卡(磁条损坏、芯片损坏)

    • 插入已挂失/冻结的银行卡

    防范措施:

    • 磁条/芯片读取验证机制

    • 与银行系统实时验证卡状态

    • 物理结构设计防止插入非标准卡片

    • 提示 “无法识别卡片,请取出”

    2. 密码输入阶段

    异常情况:

    • 输入非 6 位数字(过长、过短、含字母)

    • 3 次密码错误

    • 输入超时(60 秒内未完成)

    • 连续尝试破解密码

    防范措施:

    • 输入框只允许数字输入,自动截断为 6 位

    • 3 次错误后吞卡并提示联系银行

    • 倒计时显示剩余时间

    • 异常多次尝试触发安全警报

    3. 操作选择阶段

    异常情况:

    • 选择不存在的操作选项(如按 5、6 等)

    • 选择后长时间不操作

    • 连续快速切换选项

    防范措施:

    • 只显示有效选项(1-4),其他输入无效

    • 操作选择后启动二级倒计时

    • 异常操作频率检测,可能触发安全保护

    4. 金额输入阶段

    异常情况:

    • 输入非数字字符

    • 输入金额为 0 或负数

    • 超过单笔限额 5000 元

    • 取款金额非 100 的整数倍

    • 存款金额包含不可识别纸币

    • 余额不足(取款、转账时)

    防范措施:

    • 数字输入验证,自动过滤非数字字符

    • 最小值验证(≥100)

    • 最大值验证(≤5000)

    • 取款金额必须为 100 的倍数校验

    • 实时余额检查,提前提示不足

    • 存款口纸币识别技术验证

    5. 交易确认阶段

    异常情况:

    • 长时间不确认

    • 反复取消重试

    • 确认后突然断电/断网

    防范措施:

    • 确认倒计时(10-15 秒)

    • 交易次数限制防止重复操作

    • 交易事务机制,确保数据一致性

    • 应急电源和网络冗余

    6. 系统级异常

    异常情况:

    • 网络连接中断

    • 硬件故障(读卡器、出钞口、存款口)

    • 电源故障

    • 恶意破坏或盗窃尝试

    防范措施:

    • 离线交易缓存机制

    • 硬件自检和故障报警

    • UPS 备用电源

    • 安全摄像头和震动传感器

    • 现金箱安全锁机制

    作者注:关于 6.,物理防护 的确是 防御的重要的一环。这里我们知道就好,还是将主要注意力放在代码上吧。

命令提示符@CommandPrompt-Wang
作者
命令提示符@CommandPrompt-Wang