第2.6章: 运算符
什么是运算符?
在 C++ 中,运算符是对一个或多个对象执行动作的符号。
运算符包括算术(+、-、*、/、%)、关系(<、>、==)、逻辑(&&、||、!)、赋值(=、+= 等)等等。
运算符作用于对象,而不是“变量”。
配合 ::std::size_t 的算术运算符
#include <fast_io.h>
#include <cstddef>
int main()
{
using namespace ::fast_io::iomnp;
::std::size_t a{10};
::std::size_t b{3};
println("a + b = ", a + b); // 13
println("a - b = ", a - b); // 7
println("a * b = ", a * b); // 30
println("a / b = ", a / b); // 3
println("a % b = ", a % b); // 1
}
取模运算符 %
% 运算符计算除法之后的余数。
对无符号类型,结果是直截了当的。
对有符号类型,余数与左操作数(被除数)同号。
#include <fast_io.h>
#include <cstdint>
int main()
{
using namespace ::fast_io::iomnp;
::std::int32_t x = 10;
::std::int32_t y = -3;
println("10 % 3 = ", x % 3); // 1
println("10 % -3 = ", x % y); // 1 (same sign as 10)
println("-10 % 3 = ", -x % 3); // -1 (same sign as -10)
}
溢出与回绕
对 ::std::int32_t 这类有符号整数类型,算术溢出是未定义行为(UB)。
对 ::std::uint32_t 或 ::std::size_t 这类无符号类型,算术以 2N 取模回绕,这是有良好定义的。
#include <fast_io.h>
#include <cstdint>
int main()
{
using namespace ::fast_io::iomnp;
// Signed overflow: UB (do not rely on this!)
::std::int32_t si = 2147483647; // max 32-bit signed
// si = si + 1; // UB: signed overflow
// Unsigned wrapping: well-defined
::std::uint32_t ui = 4294967295u; // max 32-bit unsigned
println("ui = ", ui); // 4294967295
ui = ui + 1; // wraps to 0
println("ui after wrap = ", ui); // 0
}
一元加与一元减
一元加(+a)与一元减(-a)运算符作用于单个操作数。
它们与需要左右两个操作数的二元 + 和 - 不同。
+a只是原样给出a的值。很少显式使用它。-a产生a的加法逆元(取负)。对有符号整数,这会翻转符号。
一元加与一元减
一元加(+a)与一元减(-a)运算符作用于单个操作数。
它们与需要左右两个操作数的二元 + 和 - 不同。
+a只是原样给出a的值。很少显式使用它。-a产生a的加法逆元(取负)。对有符号整数,这会翻转符号。
#include <fast_io.h>
#include <cstdint>
int main()
{
using namespace ::fast_io::iomnp;
::std::int32_t x{42};
println("+x = ", +x); // 42 (unary plus does nothing)
println("-x = ", -x); // -42 (unary minus negates the value)
// With unsigned types, -a wraps modulo 2^N
::std::size_t u{5};
println("-u = ", -u); // wraps around (large value depending on size_t width)
// Special case: INT_LEAST32_MIN
::std::int_least32_t mn{INT_LEAST32_MIN};
// println(-mn); // UB: negating INT_LEAST32_MIN cannot be represented in 32-bit signed
}
要点:一元加实际上什么也不做,而一元减对操作数取负。
对无符号类型,取负以 2N 取模进行,所以 -u 的结果是回绕而非真正的负数。
对有符号类型,要小心 INT_LEAST32_MIN:它的绝对值无法用 32 位表示,因此 -INT_LEAST32_MIN 是未定义行为。
特殊情况:INT_LEAST32_MIN
常量 INT_LEAST32_MIN 表示 ::std::int_least32_t
能存储的最小值。在大多数系统上它是 -2147483648。
用一元减运算符对这个值取负(-mn)是一个危险的边缘情况:
- 从数学上讲,
-(-2147483648)应当是+2147483648。 - 但
::std::int_least32_t最多只能表示到+2147483647。 - 这意味着结果无法被表示,在 C++ 中这是未定义行为(UB)。
- 请使用更宽的类型,例如
::std::int64_t safe = -static_cast<::std::int64_t>(mn);—— 这将在后续章节中讲解。
#include <fast_io.h>
#include <cstdint>
int main()
{
using namespace ::fast_io::iomnp;
::std::int_least32_t mn{INT_LEAST32_MIN};
println("mn = ", mn); // prints -2147483648
// println(-mn); // UB: cannot represent +2147483648 in 32-bit signed
}
要点:在 32 位有符号类型中对 INT_LEAST32_MIN 取负是未定义行为。
安全的做法是使用更宽的类型或无符号类型,但这些策略的细节将在本教程后续部分讲解。
自增与自减
C++ 提供了 ++ 与 -- 运算符。
我们应当优先使用 ++i(前置自增)而不是 i++(后置自增),因为 i++ 会创建一个临时副本,效率更低。
#include <fast_io.h>
#include <cstddef>
int main()
{
using namespace ::fast_io::iomnp;
::std::size_t i{5};
println("i = ", i); // 5
++i; // pre-increment
println("++i = ", i); // 6
// Post-increment pseudo-code:
// auto temp = i;
// ++i;
// return temp;
i = 5;
println("i++ returns ", i++); // prints 5 (old value)
println("i after i++ = ", i); // prints 6
}
比较运算符
#include <fast_io.h>
#include <cstddef>
int main()
{
using namespace ::fast_io::iomnp;
::std::size_t a{10};
::std::size_t b{20};
println("a == b: ", a == b); // 0
println("a != b: ", a != b); // 1
println("a < b: ", a < b); // 1
println("a > b: ", a > b); // 0
}
三路比较运算符 <=>
三路比较运算符(<=>)执行三方比较。
它返回一种序关系结果:小于、等于或大于。
使用 fast_io,你可以直接把结果打印为 <、= 或 >。
#include <fast_io.h>
#include <compare>
#include <cstddef>
int main()
{
using namespace ::fast_io::iomnp;
::std::size_t a{10};
::std::size_t b{20};
auto result = (a <=> b);
println(result); // fast_io prints < (since a < b)
a = 20;
b = 20;
println(a <=> b); // prints =
a = 30;
b = 20;
println(a <=> b); // prints >
}
条件运算符 ?:
条件运算符(?:)根据条件在两个表达式之间选择。
它有时被称为“三元运算符”。
#include <fast_io.h>
#include <cstddef>
int main()
{
using namespace ::fast_io::iomnp;
::std::size_t a{10};
::std::size_t b{20};
::std::size_t max = (a > b) ? a : b;
println("max = ", max); // 20
}
操作数与优先级
每个运算符作用于一个或多个操作数。
在 a + b 这样的二元运算符表达式中,a 是左操作数,b 是右操作数。
++i 这类一元运算符作用于单个操作数。
运算符还有优先级,它决定运算的求值顺序。
圆括号 () 优先级最高,可用于显式控制求值顺序。
完整的优先级表见
cppreference:运算符优先级
。
#include <fast_io.h>
#include <cstddef>
int main()
{
using namespace ::fast_io::iomnp;
::std::size_t a{2};
::std::size_t b{3};
::std::size_t c{4};
println("a + b * c = ", a + b * c); // multiplication first → 14
println("(a + b) * c = ", (a + b) * c); // parentheses force addition first → 20
}
汇总表
| 类别 | 运算符 | 示例 | 说明 |
|---|---|---|---|
| 算术 | + - * / % |
a + b |
对对象进行基本数学运算。 |
| 取模 | % |
-10 % 3 |
余数与左操作数同号。 |
| 比较 | == != < > <= >= |
a < b |
返回布尔结果。 |
| 三路比较 | <=> |
println(a <=> b) |
配合 fast_io 可打印 <、= 或 >。 |
| 逻辑 | && || ! |
x && y |
与布尔对象一起使用。 |
| 赋值 | = += -= *= /= |
a += 5 |
把算术运算与赋值结合。 |
| 自增/自减 | ++ -- |
++i |
优先使用前置自增 ++i 而不是后置自增 i++。 |
| 条件 | ?: |
(a > b) ? a : b |
三元运算符在两个值之间选择。 |
| 溢出 | — | ::std::int32_t |
有符号溢出是 UB;无符号以 2N 取模回绕。 |
| 优先级 | () |
(a + b) * c |
圆括号优先级最高。 |