Chapter 6:Branching Statements and Logical Operators

对齐第6版 Chapter 6:掌握 if、逻辑表达式、cctype、条件运算符、switch、break/continue、数字读取循环与简单文件输入输出。

学习目标

  • 能把输入域划分为互斥且完备的集合,再实现 if/else、条件运算符或 switch 并证明边界覆盖
  • 能推导 &&、||、! 的逻辑表达式和短路求值,使用左侧条件保护右侧索引、指针或解析前提
  • 能复现分支重叠、cctype 非法参数、switch 意外贯穿和文件打开失败,记录路径与退出原因

机制总览

Chapter 6:Branching Statements and Logical Operators:机制路径

  1. 1

    为什么先划分输入域再写 if

    直接从一个样例开始追加 if ,容易留下重叠和空洞。成绩分类应先定义合法域 [0,100] 、非法低/高域,再把合法域划为 retry、pass、excellent。每个输入必须恰好落入一个结果,条件顺序才有可审查依据。

  2. 2

    if 语句按条件选择一条路径

    if 条件求值为 true 时执行分支,else 处理剩余集合。对按阈值递减的分类,应先检查非法域,再从最高阈值向下;否则宽条件会提前吞掉更具体路径。

  3. 3

    逻辑表达式组合条件并可能短路

    && 表示两侧都真, 表示至少一侧真, ! 反转 bool。它们从左向右求值并短路: && 左侧假时跳过右侧, 左侧真时跳过右侧。

先按顺序建立机制,再进入实验切换阶段并检查失效证据。

章级决策实验

Chapter 6:Branching Statements and Logical Operators:机制与证据

切换《Chapter 6:Branching Statements and Logical Operators》的三个关键教学阶段,先解释机制,再用运行与失败证据验证结论。

选择推理阶段

当前阶段 · 为什么先划分输入域再写 if

直接从一个样例开始追加 if ,容易留下重叠和空洞。成绩分类应先定义合法域 [0,100] 、非法低/高域,再把合法域划为 retry、pass、excellent。每个输入必须恰好落入一个结果,条件顺序才有可审查依据。

可核验证据

从干净构建开始,以固定输入运行本节示例,再加入一个边界或故障场景验证「为什么先划分输入域再写 if」的状态变化。

学完《Chapter 6:Branching Statements and Logical Operators》后,应能从输入和前置条件推导状态变化,并用可重复的构建、运行或边界测试证明结果。

失效—证据矩阵

Chapter 6:Branching Statements and Logical Operators:失效与核验

为什么先划分输入域再写 if

典型失效

若只复述「为什么先划分输入域再写 if」结论而不追踪状态、所有权和失败路径,示例扩展成多文件或多对象程序后就容易偏离预期。

核验证据

从干净构建开始,以固定输入运行本节示例,再加入一个边界或故障场景验证「为什么先划分输入域再写 if」的状态变化。

if 语句按条件选择一条路径

典型失效

若只复述「if 语句按条件选择一条路径」结论而不追踪状态、所有权和失败路径,示例扩展成多文件或多对象程序后就容易偏离预期。

核验证据

从干净构建开始,以固定输入运行本节示例,再加入一个边界或故障场景验证「if 语句按条件选择一条路径」的状态变化。

逻辑表达式组合条件并可能短路

典型失效

若只复述「逻辑表达式组合条件并可能短路」结论而不追踪状态、所有权和失败路径,示例扩展成多文件或多对象程序后就容易偏离预期。

核验证据

从干净构建开始,以固定输入运行本节示例,再加入一个边界或故障场景验证「逻辑表达式组合条件并可能短路」的状态变化。

每个判断都必须能落到观测、测试或产物,不能只凭代码表面推测。

为什么先划分输入域再写 if

直接从一个样例开始追加 if,容易留下重叠和空洞。成绩分类应先定义合法域 [0,100]、非法低/高域,再把合法域划为 retry、pass、excellent。每个输入必须恰好落入一个结果,条件顺序才有可审查依据。

if 语句按条件选择一条路径

if 条件求值为 true 时执行分支,else 处理剩余集合。对按阈值递减的分类,应先检查非法域,再从最高阈值向下;否则宽条件会提前吞掉更具体路径。

std::string grade(int score) {
    if (score < 0 || score > 100) return "invalid";
    if (score >= 90) return "A";
    if (score >= 60) return "pass";
    return "retry";
}

测试 -1,0,59,60,89,90,100,101,并为每条 return 记录命中次数。只有正常样例通过,无法证明边界、非法域和不可达分支。

逻辑表达式组合条件并可能短路

&& 表示两侧都真,|| 表示至少一侧真,! 反转 bool。它们从左向右求值并短路:&& 左侧假时跳过右侧,|| 左侧真时跳过右侧。

if (index >= 0 && index < count && data != nullptr) {
    use(data[index]);
}

先检查范围再访问,短路保证右侧只在前置条件成立时求值。条件顺序也是安全契约;把 data[index] 放到最左侧会在检查边界前访问。

cctype 按字符类别判断但有参数域

<cctype> 提供 std::isdigitstd::isalphastd::isspacestd::toupper 等字符函数。它们的 int 参数必须是 EOF 或可表示为 unsigned char 的值;直接传负 signed char 会违反前置条件。

unsigned char code = static_cast<unsigned char>(ch);
if (std::isdigit(code)) {
    ++digitCount;
}

字符分类比手写 ASCII 范围更能表达意图,但 locale 和编码边界仍需声明。std::toupper 返回 int,写回 char 前同样要确认表示范围。

条件运算符适合选择一个值

条件运算符 condition ? whenTrue : whenFalse 是表达式,产生一个结果,适合窄小且两侧无复杂副作用的值选择。

const char* label = score >= 60 ? "pass" : "retry";

嵌套多层 ?: 会隐藏分支结构,应改成命名函数或 if/else。两候选还会参与共同类型规则,结果类型不一定与视觉上某一侧完全相同。

switch 处理一个离散值的多路选择

switch 对整型或枚举表达式与多个常量 case 匹配。case 是入口标签,不会自动创建独立作用域;若没有 break、return 或 throw,执行会贯穿到后续 case。

switch (command) {
case 's':
    start();
    break;
case 'q':
    return;
default:
    report_unknown(command);
    break;
}

有意贯穿应使用 [[fallthrough]] 和解释,意外遗漏 break 应由每个 case 的路径测试发现。范围判断、字符串和复合条件通常更适合 if/else。

break 与 continue 改变最近循环路径

break 离开最近循环或 switch,continue 跳到下一轮循环。它们能让异常路径提前结束,但过多跳转会让不变量和后置条件分散。

使用 continue 时确保推进步骤仍发生。while 循环若在更新前 continue,可能永远停在同一状态;for 的更新表达式仍会执行,但循环体内其他必要清理可能被跳过。

数字读取循环同时拥有格式分支

读取数字不仅有“继续/结束”,还可能遇到格式错误。while (std::cin >> value) 在每次成功提取后处理;退出时 EOF 与非数字失败需要不同消息。若要恢复,清除状态后还要消费非法记录,保证输入推进。

double value{0.0};
double sum{0.0};
while (std::cin >> value) {
    sum += value;
}
if (!std::cin.eof()) {
    std::cerr << "invalid numeric input\n";
}

非法输入不是关系表达式能修复的问题,它属于流解析状态。分支设计要把外部失败纳入输入域,而不是默认所有字符都能变成数字。

简单文件输入输出先检查打开状态

本章初步使用 std::ifstream 把文件当输入流。路径解析和打开是第一道分支;文件不存在不能与“合法空文件”混为一谈。打开成功后,读取循环仍可能因 EOF、格式或 I/O 错误结束,Chapter 17 再完整处理。

std::ifstream input{"numbers.txt"};
if (!input) {
    std::cerr << "cannot open numbers.txt\n";
    return 1;
}
 
double number{0.0};
while (input >> number) {
    process(number);
}

记录当前工作目录和解析后路径,才能复现“IDE 能开、终端找不到”。打开、读取与业务验证是三层独立条件。

四类故障验证完整分支责任

先预测每个故障会命中哪条路径、哪些表达式不会求值、退出后状态是什么,再运行。分支覆盖率只能说明语句执行过,仍需边界断言证明每个输入被正确分类。

三步设计一个分支系统

分步1 / 3

第一步:划分完整输入域

列合法、非法、边界和外部失败类别,为每类指定唯一结果,再翻译成 if/else 或 switch。

小结

  • 输入域先划为互斥完备类别,再实现 if/else,可避免宽条件吞掉高优先级分支
  • 逻辑表达式会短路,左侧可以保护右侧前置条件,关键副作用不应藏在可能跳过的位置
  • cctype 参数必须是 EOF 或 unsigned char 可表示值,字符编码和 locale 仍需声明
  • 条件运算符适合选一个简单值,switch 适合一个离散值,case 不会自动停止
  • 数字和文件输入有打开、格式、EOF 与业务验证分支,不能只处理正常数据

练习

  1. 问题 1:设计分类。 为成绩 0–100 与非法输入写互斥区间和最小边界样例。
  1. 问题 2:推导短路。 为什么 ptr != nullptr && index < count && ptr[index] > 0 的顺序重要?
  1. 问题 3:区分文件结果。 缺失文件、空文件、非法首记录分别如何报告?

名词解释

名词解释

本章出现的专业名词,用大白话再讲一遍。

输入域划分
把所有输入分为互斥完备类别并指定唯一处理结果。
if 语句
根据 bool 条件选择真分支与可选假分支的控制语句。
逻辑表达式
以关系结果和逻辑运算符组合条件的表达式。
短路求值
结果由左操作数确定时跳过右操作数的规则。
cctype 字符函数
按字符类别判断或转换、具有受限参数域的标准函数族。
条件运算符
按条件在两个表达式中选择一个结果的三操作数表达式。
switch 语句
按离散表达式与 case 常量匹配入口的多路分支。

讨论

评论区加载中…