在编写程序时,我们经常需要对数据进行计算或判断。在 C++ 中,用来对变量或具体数值执行运算操作的符号就叫做 “运算符”。比如在 score + 5 中,+ 是运算符,而参与运算的 score 和 5 则叫做 “操作数”。
由运算符和操作数组合起来、计算后能够产生一个结果的代码片段,称为 “表达式”,比如 score + 5、age >= 18。在日常开发中,最常用的 C++ 运算符主要有以下 5 类:
- 算术运算符:用来完成加、减、乘、除、求余等数学计算。
- 自增与自减运算符:用来让变量在原值的基础上加 1 或减 1。
- 赋值运算符:用来把等号右边的值保存到左边的变量中。
- 比较运算符:用来比较两个值的大小或是否相等,结果为布尔值。
- 逻辑运算符:用来把多个条件组合起来进行真假判断。
C++ 算术运算符
在 C++ 中,算术运算符主要用于日常的数学计算,包括加、减、乘、除和求余。常用的算术运算符如下表所示。
| 运算符 | 说明 | 示例 | 结果 |
|---|---|---|---|
| + | 加 | 10 + 4 | 14 |
| - | 减 | 10 - 4 | 6 |
| * | 乘 | 10 * 4 | 40 |
| / | 除 | 10 / 4 | 2 |
| % | 求余(取模) | 10 % 4 | 2 |
注意 在 C++ 中,乘号写作星号(*)而不是 ×,除号写作斜杠(/)而不是 ÷。这是因为键盘上没有专门的 × 和 ÷ 按键,编程语言统一使用键盘上现成的符号来表示乘除。
1. 加、减、乘、除运算
加(+)、减(-)、乘(*)与平时的数学计算完全一致,最需要我们留意的是除法(/):如果除号两边都是整数,执行的就是 “整数除法”,结果只保留整数部分并直接舍去小数。只有当至少有一边是浮点数时,算出的结果才是浮点数。
示例 1:整型的加减乘除
#include <iostream>
int main() {
int a = 10;
int b = 4;
std::cout << a + b << '\n';
std::cout << a - b << '\n';
std::cout << a * b << '\n';
std::cout << a / b << '\n';
return 0;
}运行结果如下。
14
6
40
2分析:
前三个算式 a + b、a - b、a * b 分别计算出 14、6 和 40。需要小伙伴们重点留意的是第四行 a / b:按平时的数学计算,10 除以 4 等于 2.5,但这里输出的却是 2。
这是因为变量 a 和 b 都是 int 整型。在 C++ 中,两个整数相除得到的依然是整数。如果商带有小数,程序不会四舍五入,而是直接抹去小数部分,所以 10 / 4 的结果是 2,同理 3 / 4 的结果会直接变成 0。
那如果我们希望除法保留小数部分,算出准确的 2.5,应该怎么写呢?下面我们再来看一个例子。
示例 2:浮点数的除法运算
#include <iostream>
int main() {
double x = 10.0;
int b = 4;
std::cout << x / b << '\n';
return 0;
}运行结果如下。
2.5分析:
在这个例子中,x 是 double 类型,b 是 int 类型。对于加、减、乘、除运算,只要参与运算的两个数中有一个是浮点型,最终结果就是浮点数,因此 x / b 算出了 2.5。
需要注意的是,如果除号两边写的都是整数字面量,比如 10 / 4,程序会先按整数除法算出 2,哪怕再赋给 double 变量也只会变成 2.0。要想保留小数,至少要将其中一个数字写成小数形式,比如写成 10.0 / 4。如果除号两边原本就是两个 int 变量,则可以借助类型转换先把其中一个转为 double。
2. 求余运算(%)
% 叫做求余运算符(也叫取模运算符),用来求两个整数相除之后的余数。求余运算在实际开发中经常用到,比如判断一个数能不能整除另一个数,或者通过看 n % 2 是否等于 0 来判断奇偶数。
示例 3:整数求余运算
#include <iostream>
int main() {
int a = 10;
int b = 4;
std::cout << a % b << '\n';
return 0;
}运行结果如下。
2分析:
a % b 表示计算 10 除以 4 的余数:10 除以 4 的商是 2,余数是 2,因此输出 2。如果参与求余的数包含负数,余数的正负号始终与左边的被除数保持一致,比如 -10 % 4 的结果是 -2,而 10 % -4 的结果是 2。
使用求余运算符时还要记住两条硬性规则:第一,% 两边的操作数都必须是整数,不能对小数求余,否则会直接编译报错。第二,除法(/)和求余(%)右边的除数不能为 0,否则会导致程序运行出错或直接崩溃。
自增与自减运算符
平时写代码时,我们经常需要让一个变量在原值的基础上加 1 或减 1,C++ 专门提供了自增运算符(++)和自减运算符(--)来简化这种操作。
语法:
++变量名; // 前置自增
变量名++; // 后置自增
--变量名; // 前置自减
变量名--; // 后置自减说明:
无论是前置形式还是后置形式,++ 都会让变量自身的值加 1,-- 都会让变量自身的值减 1。如果只是把 a++; 或 ++a; 单独写成一行语句,它们的效果完全一样。
但如果把它们放进赋值或输出等其他表达式中,前置和后置的执行先后顺序就不一样了,下面我们分开来看。
1. 自增运算符(++)
自增运算符分为前置自增(++a)和后置自增(a++)两种形式。
示例 4:前置自增
#include <iostream>
int main() {
int a = 10;
int b = ++a;
std::cout << a << '\n';
std::cout << b << '\n';
return 0;
}运行结果如下。
11
11分析:
++a 表示 “先自增,后使用”。也就是说,在把 a 的值赋给 b 之前,先让 a 自身加上 1。对于这个例子来说,上面代码等价于下面两步:
int a = 10;
a = a + 1; // a 先加 1 变成 11
int b = a; // 再赋给 b,b 也是 11示例 5:后置自增
#include <iostream>
int main() {
int a = 10;
int b = a++;
std::cout << a << '\n';
std::cout << b << '\n';
return 0;
}运行结果如下。
11
10分析:
a++ 表示 “先使用,后自增”。也就是说,先拿 a 原来的旧值 10 赋给 b,等赋值完成后,再让 a 自身加上 1。对于这个例子来说,上面代码等价于下面两步:
int a = 10;
int b = a; // 先把旧值 10 赋给 b
a = a + 1; // a 再加 1 变成 11区分 b = ++a; 和 b = a++; 时,小伙伴们只需要记住一个很直观的规律:谁写在前面,就先执行谁。++ 在前面(++a),就先加 1 再取值。a 在前面(a++),就先取出 a 的旧值参与运算,最后再加 1。
2. 自减运算符(--)
自减运算符表示在原值基础上减去 1,运算规则与自增运算符完全相同,同样分为前置自减(--a)和后置自减(a--)。
示例 6:前置与后置自减
#include <iostream>
int main() {
int a = 10;
int x = --a;
int b = 10;
int y = b--;
std::cout << a << ' ' << x << '\n';
std::cout << b << ' ' << y << '\n';
return 0;
}运行结果如下。
9 9
9 10分析:
第一组 int x = --a; 是前置自减,a 先减 1 变成 9,再赋给 x,因此 a 和 x 都是 9。第二组 int y = b--; 是后置自减,先把 b 的旧值 10 赋给 y,随后 b 自己再减 1 变成 9,因此第二行输出 9 10。
C++ 赋值运算符
在 C++ 中,赋值运算符用来把右边的值或表达式计算结果保存到左边的变量中。除了最基本的 = 之外,C++ 还提供了一系列复合赋值运算符,如下表所示。
| 运算符 | 说明 | 示例 | 等价写法 |
|---|---|---|---|
| = | 基本赋值 | a = 10 | 将 10 赋值给变量 a |
| += | 加后赋值 | a += b | a = a + b |
| -= | 减后赋值 | a -= b | a = a - b |
| *= | 乘后赋值 | a *= b | a = a * b |
| /= | 除后赋值 | a /= b | a = a / b |
| %= | 求余后赋值 | a %= b | a = a % b |
像 a += b 其实就是 a = a + b 的简写形式,这类复合赋值运算符在实际项目代码中非常常用。
示例 7:使用复合赋值运算符
#include <iostream>
int main() {
int score = 60;
score += 10;
score *= 2;
std::cout << score << '\n';
return 0;
}运行结果如下。
140分析:
在这个例子中,score += 10; 等价于 score = score + 10;,执行后 score 从 60 变成 70。接着执行 score *= 2;,等价于 score = score * 2;,因此最终输出结果为 140。
这里有一个细节值得大家留意:如果复合赋值运算符右边是一个算式,比如 n *= 2 + 1;,程序会先把右边的 2 + 1 整体算完得到 3,再与左边的 n 相乘。也就是说,n *= 2 + 1; 等价于 n = n * (2 + 1);,而不是 n * 2 + 1。
C++ 比较运算符
在 C++ 中,比较运算符(也叫关系运算符)用来比较两个值的大小或是否相等。比较完成之后,得到的结果是一个 bool 布尔值:条件成立为 true,不成立为 false,在编写 if...else 语句时会大量用到。常用的比较运算符如下表所示。
| 运算符 | 说明 | 示例 | 结果 |
|---|---|---|---|
| > | 大于 | 20 > 10 | true |
| < | 小于 | 20 < 10 | false |
| >= | 大于或等于 | 20 >= 20 | true |
| <= | 小于或等于 | 20 <= 10 | false |
| == | 等于 | 20 == 20 | true |
| != | 不等于 | 20 != 20 | false |
需要注意的是,由两个字符组成的比较运算符(>=、<=、==、!=)是一个整体,两个符号中间不能留空格,比如不能拆写成 > = 或 = =,否则会直接编译报错。
示例 8:使用比较运算符
#include <iostream>
int main() {
int age = 20;
bool isAdult = (age >= 18);
bool isTwenty = (age == 20);
bool isDifferent = (age != 20);
std::cout << isAdult << '\n';
std::cout << isTwenty << '\n';
std::cout << isDifferent << '\n';
return 0;
}运行结果如下。
1
1
0分析:
变量 age 的值为 20,因此 age >= 18 和 age == 20 的计算结果都为 true,而 age != 20 的结果为 false。由于 std::cout 默认把 true 输出为 1、把 false 输出为 0,屏幕上依次打印出 1、1、0。
= 和 == 的区别
初学编程时,最容易写错的就是把双等号(==)误写成单等号(=)。小伙伴们一定要区分清楚两者的作用:
单等号(=)是赋值运算符:表示把右边的值装进左边的变量里,比如 age = 18 表示把 18 赋值给 age。
双等号(==)是比较运算符:表示判断左右两边的值是否相等,比如 age == 18 表示判断 age 的值是不是等于 18。
C++ 逻辑运算符
单个比较运算符只能判断一个条件。如果在实际开发中需要同时满足多个条件,比如判断年龄是否在 18 到 60 岁之间,我们就需要用到逻辑运算符把多个条件组合起来。C++ 中的逻辑运算符共有 3 个,如下表所示。
| 运算符 | 名称 | 运算规则 | 示例 |
|---|---|---|---|
| && | 逻辑与 | 两边都为 true,结果才为 true;否则为 false | (a > 0) && (b > 0) |
| || | 逻辑或 | 只要有一个为 true,结果就为 true;否则为 false | (a > 0) || (b > 0) |
| ! | 逻辑非 | 把 true 变成 false,把 false 变成 true | !isAdult |
1. 逻辑运算符的基本使用
这 3 个逻辑运算符的规则很直观:&& 要求两边条件同时成立才算真,|| 只要有一边条件成立就算真,而 ! 则是把真假状态反过来。
示例 9:组合多个条件
#include <iostream>
int main() {
bool hasTicket = true;
bool isAdult = false;
bool res1 = hasTicket && isAdult;
bool res2 = hasTicket || isAdult;
bool res3 = !isAdult;
std::cout << res1 << '\n';
std::cout << res2 << '\n';
std::cout << res3 << '\n';
return 0;
}运行结果如下。
0
1
1分析:
hasTicket 为 true,isAdult 为 false。计算 hasTicket && isAdult 时有一边为假,因此输出 0。计算 hasTicket || isAdult 时有一边为真,输出 1,而 !isAdult 对 false 取反后同样输出 1。
把比较运算符和逻辑运算符结合使用时要特别小心:如果想判断变量 age 是否在 18 到 60 之间,千万不能像数学不等式那样连着写:
// 正确方式
age >= 18 && age <= 60
// 错误方式
18 <= age <= 60为什么 18 <= age <= 60 是错的呢?假设 age 是 10,程序从左往右先算左边的 18 <= 10 得到 false,继续比较大小时 false 会当成整数 0 处理。紧接着拿 0 去和右边的 60 比较,即计算 0 <= 60,结果反而变成了 true。因此判断数值区间时,必须拆成两个比较式并用 && 连接起来。
提示 书写逻辑与和逻辑或时,必须连写两个字符 && 和 ||,不要误写成单个的 & 或 |。单个的 & 和 | 用于位运算,含义完全不同。
2. && 和 || 的短路特性
在 C++ 中,逻辑与(&&)和逻辑或(||)还有一个重要特点,叫做 “短路求值”,平时常简称为 “短路”。
所谓短路,指的是 C++ 从左往右计算逻辑表达式时,如果单看左边的条件就已经能确定整个表达式的最终结果,那么右边的表达式就会直接跳过、不再执行:
- && 的短路:只要左边为
false,整个&&的结果必定是false,此时右边直接跳过不执行。 - || 的短路:只要左边为
true,整个||的结果必定是true,此时右边直接跳过不执行。
示例 10:逻辑与的短路求值
#include <iostream>
int main() {
int count = 0;
bool result = false && (++count > 0);
std::cout << result << '\n';
std::cout << count << '\n';
return 0;
}运行结果如下。
0
0分析:
从运行结果可以看到,count 输出依然是 0。我们在 && 右边放了一个前置自增 ++count,但由于左边已经是 false,直接触发了短路,右边的 (++count > 0) 根本没有执行。
短路特性在实际开发中非常实用。比如在做除法判断 b != 0 && a / b > 2 时,一旦 b 等于 0,左边 b != 0 为 false 就会立即触发短路,保护右边的 a / b 不会发生除以 0 的崩溃。
