Lesson 4:表达式与运算符

逐项追踪操作数类型、求值顺序、整数除法与显式转换。

学习目标

  • 能解释表达式中操作数、运算符、类型和结果值的关系
  • 能为混合算术表达式绘制分组树,区分优先级、结合性与求值顺序
  • 能分析并预测整数除法、余数、复合赋值和显式转换的结果与信息损失

来源、版次与标准边界

“Lesson 4:表达式与运算符”以 SB Creative 出版社书页核定高桥麻奈著、第 5 版、2017 年 6 月 14 日发行、ISBN 978-4-7973-9259-3;出版社页面标 596 页。CiNii Books 书目与 16 课目录记录正文 xxiii+571 页,日本国会图书馆书目记录 571 页。页数口径存在前置页差异,因此本课程不拿页码充当目录证据。

“Lesson 4:表达式与运算符”的中文说明、示例、交互、练习和答案均为独立教学重写。公开资料只确认 16 个 Lesson 标题;本页列出的细分概念是课程教学映射,不冒充原书逐级小节。技术规则参考 ISO C++ 标准入口复核,但 2017 年教材的 Visual Studio 2017 语境不会被静默升级为 C++23;现代写法只能明确标为迁移说明。

围绕“怎样在运行前预测表达式的结果类型和值,而不是凭数学直觉猜测?”,本页要求保留“原表达式、加括号版本、各中间类型和值、转换位置、边界输入和预期输出。”。若故障“把整数除法结果赋给浮点变量后才期待保留小数”无法在同一输入下制造首个分岔,应拒绝当前解释,而不是追加随机样例。

为什么一行算式也需要结构图

total = price * count + fee; 看起来像从左到右读的一行文字,编译器却必须先确定每个符号作用于哪些值、子表达式产生什么类型、最终结果如何转换并写回变量。表达式的意义来自语法分组和类型规则,不来自空格,也不总等于人眼的阅读顺序。

写成 price*count+feeprice * count + fee 或加入更多空格,语法分组都不会变化;空格只服务读者。括号能改变或强调分组,中间变量则进一步为子结果命名并提供观察点。三者职责不同:排版改善可读性,括号表达结构,命名把结构连接到业务含义。审查公式时应先画出分组,再逐个确认类型,最后才讨论结果数值。

算术运算首先受操作数类型约束

算术运算符中的加、减、乘、除和余数分别使用 + - * / %。两个 int 相除执行整数除法,小数部分被截去;只要运算前至少一个操作数是浮点类型,通常会进行浮点除法。

int a = 7;
int b = 2;
 
int whole = a / b;           // 3
int remainder = a % b;       // 1
double ratio1 = a / b;       // 3.0:先整数除法
double ratio2 = 1.0 * a / b; // 3.5:先转为浮点运算

变量 ratio1double,并不意味着右侧倒回去使用浮点除法。赋值目标只在右侧表达式已经算出后接收转换结果;a / b 先得到整数 3,再转换成 3.0。必须在运算发生前改变操作数类型。

优先级决定默认分组

乘除通常比加减优先,所以 2 + 3 * 4 被分组为 2 + (3 * 4),结果是 14。括号可以显式改变分组,(2 + 3) * 4 得 20。代码涉及业务公式时,即使默认优先级正确,也可以用少量括号表达意图。

double subtotal = unitPrice * quantity;
double discount = subtotal * discountRate;
double payable = subtotal - discount + shipping;

把中间概念命名往往比一行超长表达式更好:可逐步打印、复用和验证,也不会迫使读者同时记忆多层优先级。括号解决语法分组,命名还解决领域含义。

结合性只处理同层分组

当相同优先级运算符连续出现,结合性决定默认分组。例如减法左结合:10 - 3 - 2 等价于 (10 - 3) - 2,结果为 5;赋值右结合:a = b = 0 等价于 a = (b = 0)

结合性不等于操作数一定按相同方向求值。优先级和结合性是语法树规则,求值顺序是运行时何时计算各子表达式的规则。初学阶段最安全的做法是避免在同一表达式中多次修改并读取同一变量,把副作用拆成独立语句。

int index = 0;
int current = values[index];
++index;

这种写法比把多个 index++ 塞进一次调用更容易证明。表达式越依赖隐式顺序,越难调试,也越容易跨语言版本或重构后改变行为。

赋值修改已有对象

赋值运算符中的简单赋值 x = value 先计算右侧,再按左侧类型转换并写入对象。复合赋值 x += y 表达“读取 x、与 y 相加、把结果写回 x”,其意图比 x = x + y 更紧凑,但仍包含读取、运算、转换和写回四步。

int score = 10;
score += 5;  // 15
score *= 2;  // 30

赋值表达式本身也有结果,因此链式赋值可以成立;但不要为了少写一行把复杂计算与多对象写入混在一起。一个语句修改的状态越多,失败时越难判断哪一项已经变化。

自增与自减要看使用场景

++ii++ 都会把 i 增加 1;区别在表达式结果:前置形式产生更新后的值,后置形式保留更新前的值供当前更大表达式使用。若只需要“把 i 加一”,单独写 ++i;,不要利用旧值制造额外认知负担。

int i = 3;
int before = i++; // before=3, i=4
int after = ++i;  // i=5, after=5

对迭代器等用户定义类型,前置形式还可能避免保存旧副本;但本课的第一原则是语义清楚:只有确实需要旧值时才用后置结果。

类型转换改变表示边界

隐式转换由上下文触发,例如把 intdouble 相加时,整数通常先转换为浮点表示。显式转换则由程序员说明意图,现代 C++ 使用 static_cast<T>(value) 比 C 风格强制转换更容易搜索与审查。

int sum = 7;
int count = 2;
double average = static_cast<double>(sum) / count;
int truncated = static_cast<int>(average);

显式不等于安全,只是意图更明确。将超范围值转入较小整数、将负数转为无符号、将浮点截断为整数,都要先写出允许范围和舍入策略。若需求是四舍五入,应使用相应数学操作,而不是把截断伪装成舍入。

三步验证一个业务公式

分步1 / 3

第一步:画出表达式结构

price * count + fee 标出操作数、运算符、子表达式类型和最终赋值,确认目标类型不会反向改变右侧计算。

正式节点与章专属证据

  • 表达式:在“Lesson 4:表达式与运算符”中核对输入、状态变化、失败模式和可复现证据;第 1 个节点必须能回到“每个中间表达式的类型和范围明确,关键转换通过代码显式表达。”。
  • 算术运算符:在“Lesson 4:表达式与运算符”中核对输入、状态变化、失败模式和可复现证据;第 2 个节点必须能回到“每个中间表达式的类型和范围明确,关键转换通过代码显式表达。”。
  • 赋值运算符:在“Lesson 4:表达式与运算符”中核对输入、状态变化、失败模式和可复现证据;第 3 个节点必须能回到“每个中间表达式的类型和范围明确,关键转换通过代码显式表达。”。
  • 运算符优先级:在“Lesson 4:表达式与运算符”中核对输入、状态变化、失败模式和可复现证据;第 4 个节点必须能回到“每个中间表达式的类型和范围明确,关键转换通过代码显式表达。”。
  • 类型转换:在“Lesson 4:表达式与运算符”中核对输入、状态变化、失败模式和可复现证据;第 5 个节点必须能回到“每个中间表达式的类型和范围明确,关键转换通过代码显式表达。”。

先用输入合同检查本页正式节点,再在相同初值下逐步比较正常和失败轨迹,最后只启用“把整数除法结果赋给浮点变量后才期待保留小数”完成反例与复位。三个交互都必须能独立重置,且重置后再次满足“每个中间表达式的类型和范围明确,关键转换通过代码显式表达。”。

输入与状态合同

Lesson 4:表达式与运算符

怎样在运行前预测表达式的结果类型和值,而不是凭数学直觉猜测?

必须先声明

为表达式声明输入类型、有效范围、对象生命周期和失败策略。

可复核证据

保存Lesson 4:表达式与运算符的原始输入、初值与第一条可检查诊断。

正式节点:表达式、算术运算符、赋值运算符、运算符优先级、类型转换

编译与运行轨迹

同一输入下比较正常与失败路径

  1. 01标注操作数类型
  2. 02按括号与优先级分组
  3. 03计算中间结果
  4. 04显式转换并核对范围

不变量:每个中间表达式的类型和范围明确,关键转换通过代码显式表达。

故障定位与复位

一次只破坏一个前提

基线可继续:每个中间表达式的类型和范围明确,关键转换通过代码显式表达。

小结

  • 表达式由操作数和运算符形成,子表达式先按自身类型计算,再把结果交给外层
  • 整数除法会截去小数;目标变量是 double 也不能恢复右侧已经丢失的信息
  • 优先级决定不同运算符的默认分组,结合性处理同层连续运算;二者都不等于完整求值顺序
  • 赋值包含右侧计算、目标类型转换和写回;复合赋值与自增仍然会修改状态
  • 显式转换表达意图但不保证值域安全,转换前必须检查范围与业务舍入规则

练习

  1. 问题 1:预测除法。 double result = 7 / 2; 的值是什么,如何最小修改得到 3.5?
  1. 问题 2:区分规则。 10 - 3 - 2 的结合性如何分组?这是否证明所有表达式都从左到右求值?
  1. 问题 3:审计转换。 static_cast<int>(19.75) 得什么?为什么显式写出也不代表业务正确?

名词解释

名词解释

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

表达式
可计算出值、对象或副作用的程序结构。
操作数
被运算符读取或作用的值及子表达式。
运算符
规定对操作数执行何种操作的符号或关键字。
运算符优先级
没有括号时决定不同运算符默认分组的规则。
类型转换
按目标类型表示规则产生新值的过程,可能丢失信息。

资料与写作方式声明

本章以高桥麻奈 2017 年 C++ 入门教材第 5 版权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…