第2.6章: 运算符

English

什么是运算符?

在 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 的加法逆元(取负)。对有符号整数,这会翻转符号。

#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 圆括号优先级最高。