第 05 章
第 5 章:算术、优先级与逻辑运算
本章对应长视频 4:45:33–5:55:57。讲清整数除法的截断与取模、表达式的优先级与结合性、前后缀自增自减、复合赋值、比较运算符与逻辑运算符,并解释为什么打印比较结果时必须加括号。
Chapter 5: Arithmetic, Precedence and Logic
Covers 4:45:33–5:55:57 of the long video. It explains integer division truncation and modulus, precedence and associativity, prefix and postfix increment, compound assignment, comparison operators and logical operators, and why printing a comparison requires parentheses.
篇内目录
- What this chapter covers
- Timeline
- Arithmetic
- Integer division truncates, it does not round
- Modulus: the remainder
- Precedence and associativity
- Parentheses state intent
- Increment and decrement
- Postfix: use first, change after
- Prefix: change first, use after
- Compound assignment
- Relational operators
- Why the parentheses are mandatory
- = versus ==
- Logical operators
- Combined with relational operators
- Chapter summary
- Comprehension check
- How it connects
这一章在讲什么
第 5 章覆盖 4:45:33–5:55:57。前面两章把数据放进变量,这一章开始对数据做运算。核心不是"多背几个符号",而是理解两件事:
- 整数的除法与取模和数学不一样:
7 / 2得 3,小数部分被直接丢掉。 - 一个表达式里有多个运算符时,谁先算由优先级和结合性决定;读代码的人不该被迫去查表,所以要用括号把意图写清楚。
时间轴
| 时间 | 内容 |
|---|---|
| 4:47:39 | 加减乘除与取模;整数除法的截断 |
| 4:58:05 | 优先级与结合性;运算符表 |
| 5:07:14 | 用括号明确运算顺序 |
| 5:11:54 | 前缀与后缀自增自减 |
| 5:23:30 | 复合赋值运算符 |
| 5:31:42 | 关系运算符与布尔结果 |
| 5:40:59 | 逻辑运算符:与、或、非 |
算术运算
int number1 {2};
int number2 {7};
int result {0};
result = number1 + number2; // 9
result = number2 - number1; // 5
result = number1 - number2; // -5
result = number1 * number2; // 14
这些都是整数运算,结果仍然是整数。
整数除法:截断而不是四舍五入
result = number2 / number1; // 7 / 2
std::cout << result << std::endl; // 3
7 / 2 得到 3,不是 3.5。原因是右边两个操作数都是 int,结果是 int,只能保留整数部分。可以理解为"2 能完整装进 7 几次"——三次,剩下的 1 被丢掉。
想要小数结果,至少让一个操作数是浮点数:
double quotient = 7.0 / 2; // 3.5
7 / 2.0、7.0 / 2、7.0 / 2.0 都行,编译器会把另一个也提升为 double 再相除。
取模:余数
result = number2 % number1; // 7 % 2
std::cout << result << std::endl; // 1
% 求的是整数除法的余数。 余 1,所以 31 % 10 等于 1。
取模只对整数有意义。7.5 % 2 编译不过——浮点数的"余数"没有公认定义,C++ 直接用 std::fmod 处理。
常见用途:判断奇偶(n % 2 == 0)、循环取模做环形索引(index % size)、提取十进制各位(n % 10 取个位)。
优先级与结合性
一个表达式里有多个运算符时,需要两条规则:
- 优先级(precedence):不同运算符谁先算。
*、/、%高于+、-。 - 结合性(associativity):同优先级时从左还是从右算。加减乘除都是从左到右。
看这个表达式:
int a {6}, b {4}, c {6}, d {9}, e {3}, f {2}, g {5};
int result = a + b * c - d / e - f + g;
按规则拆解:
- 先算乘除:
b * c = 24,d / e = 3。 - 表达式变成
a + 24 - 3 - f + g,即6 + 24 - 3 - 2 + 5。 - 从左到右:
6+24=30,30-3=27,27-2=25,25+5=30。
结果 30。
与之相关的优先级顺序(从高到低):
| 优先级 | 运算符 | 结合性 |
|---|---|---|
| 高 | 后缀 ++ -- | 左 |
前缀 ++ --、一元 + - | 右 | |
* / % | 左 | |
+ - | 左 | |
<< >> | 左 | |
< <= > >= | 左 | |
== != | 左 | |
&& | 左 | |
| 低 | ` | |
| 最低 | 赋值 = += 等 | 右 |
用括号表达意图
int result = a + (b * c) - (d / e) - f + g; // 同样是 30
结果相同,但读代码的人不用查表就知道先算什么。课程的建议很明确:不要过度依赖优先级表,用括号把意图写出来。
括号更大的作用是改变运算顺序:
int result = (a + b) * c - d / e - f + g;
// (6+4)*6 - 3 - 2 + 5 = 60
现在加法先做,结果从 30 变成 60。
自增与自减
++ 加 1,-- 减 1。它们只能加减 1,不能加减 5 或 7。
后缀:先用后改
int value {5};
std::cout << value++ << std::endl; // 5
std::cout << value << std::endl; // 6
value++ 先取出当前值参与运算(这里是打印),语句结束后才把 value 加 1。所以第一行打印 5,第二行打印 6。
value-- 同理:
int value {5};
std::cout << value-- << std::endl; // 5
std::cout << value << std::endl; // 4
前缀:先改后用
int value {5};
std::cout << ++value << std::endl; // 6
std::cout << value << std::endl; // 6
++value 先把 value 加 1,再取出新值参与运算,所以两行都打印 6。
记忆方法:符号在后,旧值先出场;符号在前,新值先出场。
单独写一行时(value++; 或 ++value;),两者效果相同,只有把表达式的结果拿去用时才有区别。
复合赋值
+=、-=、*=、/=、%= 把"运算"和"赋值"合成一步:
int value {45};
value += 5; // 等价于 value = value + 5; → 50
value -= 5; // 等价于 value = value - 5; → 45
value *= 2; // 等价于 value = value * 2; → 90
value /= 3; // 等价于 value = value / 3; → 30
value %= 11; // 等价于 value = value % 11; → 8
最后一步验证: 余 8,所以结果是 8。
复合赋值不是必需的语法糖,但它把"对同一个变量做累积运算"这个意图写得更紧凑,读起来也更清楚。
关系运算符
关系运算符比较两个值,结果是 bool:
| 运算符 | 含义 |
|---|---|
< | 小于 |
<= | 小于或等于 |
> | 大于 |
>= | 大于或等于 |
== | 等于 |
!= | 不等于 |
int number1 {45};
int number2 {60};
std::cout << std::boolalpha;
std::cout << "number1 < number2: " << (number1 < number2) << std::endl; // true
std::cout << "number1 > number2: " << (number1 > number2) << std::endl; // false
std::cout << "number1 == number2: " << (number1 == number2) << std::endl; // false
std::cout << "number1 != number2: " << (number1 != number2) << std::endl; // true
为什么必须加括号
把括号去掉:
std::cout << "number1 < number2: " << number1 < number2 << std::endl;
这段编译不过。原因在上一节的优先级表:<< 的优先级高于 <,编译器会先算 (std::cout << ... << number1),再拿它的结果去和 number2 比较——而 std::cout 没有定义 <,于是报错。
加括号 (number1 < number2) 先把比较算成一个 bool,再交给 cout 输出。
= 与 ==
if (number1 = number2) { } // 赋值,不是比较
if (number1 == number2) { } // 比较
单等号是赋值,双等号才是比较。if 里写单等号通常还能编译(赋值表达式的值被当作条件),但逻辑完全错了。有些编译器会给出警告,不要忽略它。
逻辑运算符
逻辑运算符作用于布尔值:
| 运算符 | 名称 | 何时为真 |
|---|---|---|
&& | 与 | 两个操作数都为真 |
|| | 或 | 至少一个为真 |
! | 非 | 操作数为假 |
真值表:
| a | b | a && b | a || b |
|---|---|---|---|
| false | false | false | false |
| false | true | false | true |
| true | false | false | true |
| true | true | true | true |
bool a {true}, b {false}, c {true};
std::cout << (a && b) << std::endl; // false
std::cout << (a && c) << std::endl; // true
std::cout << (a && b && c) << std::endl; // false(只要有一个 false 就 false)
std::cout << (a || b) << std::endl; // true
std::cout << (!a) << std::endl; // false
std::cout << (!b) << std::endl; // true
a && b && c 说明"与"可以链式使用:只要有一个假,整个表达式就是假;"或"则相反,只要有一个真就是真。
与关系运算符组合
关系运算的结果是 bool,逻辑运算接收的正是 bool,所以可以自然组合:
int d {45}, e {20}, f {11};
bool result = (d > e) && (d > f); // true && true → true
括号同样重要:它既避免优先级问题,也让意图清楚。
补充:
&&与||有"短路"行为:a && b在a为假时不再计算b,a || b在a为真时不再计算b。这在右边有副作用(比如函数调用)时非常关键,后面讲条件与循环时会经常用到。
本章结论
7 / 2得 3:整数除法截断,不四舍五入;要小数就让一个操作数变成浮点数。%求余数,只对整数有意义。- 乘除优先于加减;同优先级从左到右;括号可以改变顺序,也应当用来表达意图。
value++先用后加,++value先加后用。+=、-=、*=、/=、%=把运算与赋值合成一步。- 关系运算得到
bool;打印比较结果时必须加括号,因为<<的优先级更高。 &&全真才真,||一真即真,!取反;它们可以链式组合,也可以接在关系运算后面。
理解检验
1. 7 / 2 和 7.0 / 2 分别得到什么?为什么?
参考答案:7 / 2 得到 3(int),7.0 / 2 得到 3.5(double)。整数除法中两个操作数都是 int,结果类型也是 int,小数部分被直接丢弃,只保留"2 能装进 7 三次"。只要有一个操作数是浮点数,另一个就会被提升为 double,除法按浮点规则进行,保留小数。
2. 计算 6 + 4 * 6 - 9 / 3 - 2 + 5,并写出每一步依据的优先级规则。
参考答案:先乘除(优先级高于加减):4 * 6 = 24,9 / 3 = 3(整数除法,恰好整除)。表达式变为 6 + 24 - 3 - 2 + 5。加减同级、左结合,从左到右依次计算:6+24=30,30-3=27,27-2=25,25+5=30。结果 30。
3. std::cout << (a < b); 去掉括号后为什么编译失败?
参考答案:<< 的优先级高于 <。去掉括号后表达式被解析为 ((std::cout << ...) < b):先执行流插入,而流插入表达式的结果类型是 std::ostream&,C++ 没有为 std::ostream 定义与整数比较的 < 运算符,因此编译报错。加上括号后,比较先算出一个 bool,再交给 cout,类型正确。
4. int v = 5; int a = v++; int b = ++v; 之后,v、a、b 各是多少?
参考答案:v++ 先把旧值 5 交给 a,再把 v 加 1,所以 a = 5、v = 6;接着 ++v 先把 v 加到 7 再交给 b,所以 b = 7、v = 7。最终 v = 7、a = 5、b = 7。口诀是"符号在后用旧值,符号在前用新值"。
5. value += 5 与 value = value + 5 完全等价吗?为什么课程仍然推荐前者?
参考答案:对内置整数类型,两者语义相同,结果一样。课程推荐 += 是因为它把"在自身基础上累加"这个意图压缩成一个符号,读起来更直接,也不容易在长表达式里写错变量名。对自定义类型(后面讲的类),+= 还可以避免一次不必要的临时对象,不过那是后续内容。
6. 写出 a、b、c 分别为 true、false、true 时,(a && b) || (!c) 的值,并说明计算过程。
参考答案:先算括号内:a && b 中 b 为 false,结果为 false;!c 中 c 为 true,取反为 false。再算 false || false,结果为 false。整个表达式只有当"a 与 b 同时为真"或"c 为假"时才成立,本例两者都不满足。
与前后章的关系
本章把"数据"变成了"表达式与判断",这是条件语句和循环的直接前提。第 6 章先处理另一件事:把输出排版好——宽度、对齐、进制、精度、布尔显示等操纵器,以及 <limits> 与 <cmath> 提供的工具。第 7 章再正式进入 if、else if、switch,把本章的比较与逻辑结果用于控制流程。
What this chapter covers
Chapter 5 spans 4:45:33–5:55:57. The previous two chapters put data into variables; this one starts operating on it. The point is not to memorise symbols but to grasp two facts:
- Integer division and modulus are not like school arithmetic:
7 / 2is 3, and the fraction is thrown away. - When an expression contains several operators, precedence and associativity decide the order; a reader should not have to consult a table, so parentheses should state the intent.
Timeline
| Time | Topic |
|---|---|
| 4:47:39 | Addition, subtraction, multiplication, division, modulus; integer truncation |
| 4:58:05 | Precedence and associativity; the operator table |
| 5:07:14 | Using parentheses to fix the order |
| 5:11:54 | Prefix and postfix increment and decrement |
| 5:23:30 | Compound assignment |
| 5:31:42 | Relational operators produce booleans |
| 5:40:59 | Logical operators: and, or, not |
Arithmetic
int number1 {2};
int number2 {7};
int result {0};
result = number1 + number2; // 9
result = number2 - number1; // 5
result = number1 - number2; // -5
result = number1 * number2; // 14
All of these are integer operations; the result is an integer too.
Integer division truncates, it does not round
result = number2 / number1; // 7 / 2
std::cout << result << std::endl; // 3
7 / 2 is 3, not 3.5. Both operands are int, so the result is int and only the whole part survives. A useful reading is "how many whole times does 2 fit into 7" — three times, with 1 left over and discarded.
To get a fractional result, make at least one operand floating point:
double quotient = 7.0 / 2; // 3.5
7 / 2.0, 7.0 / 2 and 7.0 / 2.0 all work; the compiler promotes the other operand to double before dividing.
Modulus: the remainder
result = number2 % number1; // 7 % 2
std::cout << result << std::endl; // 1
% returns the remainder of integer division. Since , 31 % 10 is 1.
Modulus is defined only for integers; 7.5 % 2 does not compile, because a "remainder" for floating point has no accepted definition — use std::fmod there.
Common uses: parity (n % 2 == 0), ring indexing (index % size), and extracting decimal digits (n % 10 gives the last digit).
Precedence and associativity
Two rules settle an expression with several operators:
- Precedence: which operator binds first.
*,/,%bind tighter than+,-. - Associativity: within one precedence level, whether evaluation goes left to right or right to left. Arithmetic is left to right.
Consider:
int a {6}, b {4}, c {6}, d {9}, e {3}, f {2}, g {5};
int result = a + b * c - d / e - f + g;
Step by step:
- Multiplication and division first:
b * c = 24,d / e = 3. - The expression becomes
a + 24 - 3 - f + g, i.e.6 + 24 - 3 - 2 + 5. - Left to right:
6+24=30,30-3=27,27-2=25,25+5=30.
The result is 30.
The relevant precedence ladder, highest first:
| Precedence | Operators | Associativity |
|---|---|---|
| high | postfix ++ -- | left |
prefix ++ --, unary + - | right | |
* / % | left | |
+ - | left | |
<< >> | left | |
< <= > >= | left | |
== != | left | |
&& | left | |
| low | || | left |
| lowest | assignment = += etc. | right |
Parentheses state intent
int result = a + (b * c) - (d / e) - f + g; // still 30
Same result, but the reader does not need the table. The course's advice is explicit: do not lean on the precedence table; write the intent with parentheses.
Parentheses also change the order:
int result = (a + b) * c - d / e - f + g;
// (6+4)*6 - 3 - 2 + 5 = 60
Now the addition happens first and the answer moves from 30 to 60.
Increment and decrement
++ adds 1; -- subtracts 1. They can only change a value by one, never by five or seven.
Postfix: use first, change after
int value {5};
std::cout << value++ << std::endl; // 5
std::cout << value << std::endl; // 6
value++ yields the current value (5, printed here) and only increments after the statement finishes. Hence 5 then 6.
The same with --:
int value {5};
std::cout << value-- << std::endl; // 5
std::cout << value << std::endl; // 4
Prefix: change first, use after
int value {5};
std::cout << ++value << std::endl; // 6
std::cout << value << std::endl; // 6
++value increments first and then yields the new value, so both lines print 6.
The mnemonic: operator after, old value first; operator before, new value first.
On a line of its own (value++; or ++value;) the two are equivalent; the difference appears only when the expression's value is used.
Compound assignment
+=, -=, *=, /=, %= fuse the operation and the assignment:
int value {45};
value += 5; // value = value + 5; → 50
value -= 5; // value = value - 5; → 45
value *= 2; // value = value * 2; → 90
value /= 3; // value = value / 3; → 30
value %= 11; // value = value % 11; → 8
The last step: remainder 8, so the result is 8.
Compound assignment is not merely sugar; it makes "accumulate into this same variable" visible at a glance.
Relational operators
Relational operators compare two values and produce a bool:
| Operator | Meaning |
|---|---|
< | less than |
<= | less than or equal |
> | greater than |
>= | greater than or equal |
== | equal |
!= | not equal |
int number1 {45};
int number2 {60};
std::cout << std::boolalpha;
std::cout << "number1 < number2: " << (number1 < number2) << std::endl; // true
std::cout << "number1 > number2: " << (number1 > number2) << std::endl; // false
std::cout << "number1 == number2: " << (number1 == number2) << std::endl; // false
std::cout << "number1 != number2: " << (number1 != number2) << std::endl; // true
Why the parentheses are mandatory
Remove them:
std::cout << "number1 < number2: " << number1 < number2 << std::endl;
This does not compile. By the ladder above, << binds tighter than <, so the compiler first evaluates (std::cout << ... << number1) and then tries to compare that result with number2 — but std::ostream has no < against an integer, hence the error.
With (number1 < number2) the comparison is evaluated first into a bool, which cout can print.
= versus ==
if (number1 = number2) { } // assignment, not comparison
if (number1 == number2) { } // comparison
A single = assigns; == compares. Writing = inside an if often still compiles (the assigned value becomes the condition) while being logically wrong. Some compilers warn; do not ignore it.
Logical operators
Logical operators work on booleans:
| Operator | Name | True when |
|---|---|---|
&& | and | both operands are true |
|| | or | at least one is true |
! | not | the operand is false |
Truth table:
| a | b | a && b | a || b |
|---|---|---|---|
| false | false | false | false |
| false | true | false | true |
| true | false | false | true |
| true | true | true | true |
bool a {true}, b {false}, c {true};
std::cout << (a && b) << std::endl; // false
std::cout << (a && c) << std::endl; // true
std::cout << (a && b && c) << std::endl; // false (one false is enough)
std::cout << (a || b) << std::endl; // true
std::cout << (!a) << std::endl; // false
std::cout << (!b) << std::endl; // true
a && b && c shows that "and" chains: one false makes the whole expression false; "or" is the mirror image, one true makes it true.
Combined with relational operators
Relational operators produce bool, and logical operators consume bool, so they compose naturally:
int d {45}, e {20}, f {11};
bool result = (d > e) && (d > f); // true && true → true
Parentheses matter here as well, both for precedence and for readability.
Supplement:
&&and||short-circuit:a && bskips evaluatingbwhenais false, anda || bskipsbwhenais true. This becomes important when the right-hand side has side effects such as a function call, a situation that appears often once conditions and loops arrive.
Chapter summary
7 / 2is 3: integer division truncates rather than rounds; make one operand floating point to keep the fraction.%returns the remainder and is defined only for integers.- Multiplication and division bind before addition and subtraction; equal precedence evaluates left to right; parentheses can change the order and should express intent.
value++yields the old value then increments;++valueincrements then yields the new one.+=,-=,*=,/=,%=fuse operation and assignment.- Relational operators produce
bool; printing a comparison needs parentheses because<<binds tighter. &&needs both true,||needs one,!negates; all compose with relational results.
Comprehension check
1. What do 7 / 2 and 7.0 / 2 produce, and why?
Reference answer: 7 / 2 gives 3 (int); 7.0 / 2 gives 3.5 (double). In integer division both operands are int, so the result type is int and the fraction is discarded — "2 fits into 7 three whole times". With one floating-point operand, the other is promoted to double and the division follows floating-point rules.
2. Evaluate 6 + 4 * 6 - 9 / 3 - 2 + 5, naming the rule used at each step.
Reference answer: Multiplication and division first (higher precedence than addition): 4 * 6 = 24 and 9 / 3 = 3 (exact integer division). The expression becomes 6 + 24 - 3 - 2 + 5. Addition and subtraction share a level and associate left to right: 6+24=30, 30-3=27, 27-2=25, 25+5=30. The result is 30.
3. Why does std::cout << (a < b); fail to compile without the parentheses?
Reference answer: << has higher precedence than <, so std::cout << ... < b parses as ((std::cout << ...) < b). The insertion expression yields std::ostream&, and there is no < operator comparing std::ostream with an integer, so the compiler reports an error. With parentheses the comparison runs first and produces a bool, which cout can print.
4. After int v = 5; int a = v++; int b = ++v;, what are v, a and b?
Reference answer: v++ hands the old value 5 to a, then increments, so a = 5, v = 6. Then ++v increments to 7 and hands that to b, so b = 7, v = 7. Final values: v = 7, a = 5, b = 7. The rule: operator after the variable uses the old value, operator before uses the new one.
5. Is value += 5 exactly equivalent to value = value + 5? Why does the course still prefer it?
Reference answer: For built-in integer types the two are semantically identical and produce the same result. The course prefers += because it compresses "accumulate into this same variable" into one symbol, which reads more directly and avoids mistyping the variable name in a long expression. For user-defined types (classes, later in the course) += can also avoid a temporary object, though that belongs to a later chapter.
6. With a, b, c equal to true, false, true, evaluate (a && b) || (!c) and show the steps.
Reference answer: Inside the parentheses: a && b is false because b is false; !c is false because c is true. Then false || false is false. The whole expression holds only when "a and b are both true" or "c is false", and neither holds here.
How it connects
This chapter turns data into expressions and decisions, the direct prerequisite for conditionals and loops. Chapter 6 first handles a different matter: laying out output — width, alignment, bases, precision and boolean display — together with the tools in <limits> and <cmath>. Chapter 7 then moves into if, else if and switch, putting the comparisons and logical results of this chapter to work in control flow.