news 2026/9/13 9:10:53

C++ 运算符完全指南:从算术、位操作到优先级总表

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++ 运算符完全指南:从算术、位操作到优先级总表

C++ 运算符完全指南:从算术、位操作到优先级总表

【免费下载链接】OI-wiki:star2: Wiki of OI / ICPC for everyone. (某大型游戏线上攻略,内含炫酷算术魔法)项目地址: https://gitcode.com/GitHub_Trending/oi/OI-wiki

本文基于 OI-wiki 语言基础章节(docs/lang/op.md)整理成文。C++ 运算符是程序表达一切计算与逻辑的基础设施,也是 OI / ICPC 参赛者最容易因细节踩坑的地方——从取模与除法的类型陷阱、移位操作的未定义行为,到if (op=1)这类赋值与比较混淆的经典 bug。读完本文,你将系统掌握 C++ 全部运算符的分类语义、运算中的类型转换规则、自增自减的前后缀差异、短路求值特性,并能熟练查阅并使用文末完整的 C++ 运算符优先级总表,在比赛中写出更少歧义、更快更稳的代码。

算术运算符

C++ 的算术运算符由两个单目运算符(一元运算符)和五个双目运算符(二元运算符)组成:

运算符功能
+(单目)
-(单目)
*(双目)乘法
/除法
%取模
+(双目)加法
-(双目)减法

单目运算符指被操作对象只有一个的运算符,双目运算符的被操作对象有两个。例如1 + 2中的加号就是双目运算符,它有12两个被操作数;此外 C++ 中还有唯一的一个三目运算符?:。在算术运算符内部,单目运算符的优先级最高。

  • 取模运算符%计算两个整数相除得到的余数,即求余数,参与运算的两个操作数必须是整数类型;
  • -为双目运算符时做减法运算,如2-1;为单目运算符时做负值运算,如-1

表达式遵循数学中加减乘除的优先规律:先进行优先级高的运算,同优先级按运算的结合性运算,括号可提高优先级。例如op=x-y*z先计算y*z,再与x相减。

算术运算中的类型转换

对于双目算术运算符,当参与运算的两个变量类型相同时,不发生类型转换,运算结果用参与运算的变量的类型容纳;否则会发生类型转换,以使两个变量的类型一致,具体规则参见类型转换。

例如,对于整型(int)变量x和双精度浮点型(double)变量y

  • x/3的结果是整型(整数除法,结果截断为整数);
  • x/3.0的结果是双精度浮点型(字面量3.0double类型);
  • x/y的结果是双精度浮点型(x被转换为double);
  • x*1/3的结果是整型(x*1仍是整型,整型除法舍去小数);
  • x*1.0/3的结果是双精度浮点型(1.0触发整型到浮点型的转换)。

从变量与类型一章可以进一步理解其底层机制:类型转换分为数值提升(值本身保持不变,如float提升为doublechar/short提升为int)和数值转换(值可能发生改变,如浮点转整数舍弃小数部分、宽类型转窄类型发生截断/取模)。算术运算符不接受小于int的类型作为实参,运算前会先实施整数提升,这正是char参与算术运算时行为常出人意料的原因。特别注意浮点数转换为整数时全部小数部分被舍弃(向零截断),例如3.99转为int得到3;而intfloat/double则会舍入到目标类型下最接近的值,若该值不能适应目标类型则行为未定义。

位操作符

另请参阅:位运算。

运算符功能
~逐位非
&(双目)逐位与
\|逐位或
^逐位异或
<<逐位左移
>>逐位右移

位运算是对整数的二进制位序列逐位应用布尔函数(取反、与、或、异或)的运算,各位的运算相互独立。位操作在 OI 中有三种典型用途(详见位运算):高效代替某些低效运算(即编译优化中的强度削减,如x*2在开启优化后与x<<1完全等价)、表示集合(常用于状压 DP)、以及题目本身要求进行位操作。仓库中的示例代码 docs/math/code/bit/bit_2.cpp 给出了大量可直接复用的位操作实现:用n << m计算n*(2^m)、用n >> m计算n/(2^m)、用(a >> b) & 1取第b位、用a & ~(1 << b)将某位清零、用a | (1 << b)将某位置 1、用a ^ (1 << b)翻转某位,以及用x & -x提取最低位 1(lowbit,常用于树状数组与 popcount 计数)。

需要注意,位操作的优先级低于算术运算符(单目取反~除外),而按位与&、按位或|和异或^低于比较运算符(详见下文优先级总表),所以使用位操作时需多加注意,必要时添加括号。例如x & 1 == 0会被解析为x & (1 == 0)而非(x & 1) == 0

移位运算的未定义行为

移位运算中如果出现如下情况,其行为未定义

  1. 右操作数(即移位数)为负值;
  2. 右操作数大于等于左操作数的位数。

例如对于int32_t类型的变量aa<<-1a<<32都是未定义的(int32_t为 32 位,移位数32等于其位宽)。

此外还有两条与符号性相关的规则:

  • 对带符号非负数的左移操作,需确保移位后的结果能被原数的类型容纳,否则行为未定义;对一个负数执行左移操作同样未定义。
  • 对于右移操作,右侧多余的位将被舍弃,左侧的填充则取决于类型:无符号数在左侧补0(即逻辑右移);有符号数用最高位(符号位,非负数为0、负数为1)补齐(即算术右移)。

关于标准版本差异(仓库原文注释)需要注意:C++20 前,带符号类型的右移是依实现定义的,大多数实现采用算术右移;C++20 起标准规定a >> b为 $\lfloor a/2^b\rfloor$,带符号数右移由此被固定为算术右移。左移方面,C++20 前若原值为带符号类型且移位结果能被原类型的无符号版本容纳,则将该结果转换为相应的带符号值,否则行为未定义;C++20 起规定a << b为 $a\cdot 2^b$ 在模 $2^N$ 下的值($N$ 为结果类型的位宽),即带符号数与无符号数的左移均直接舍弃移出结果类型的位。此外,右移对负数是向下取整,与 C/C++ 除法向零取整并不一致——例如-1/2的值为0,而-1>>1的值为-1

自增/自减运算符

有时我们需要让变量自增 1 或自减 1,这时自增运算符++和自减运算符--就派上用场了。

自增/自减运算符可放在变量前或变量后:放在变量前称为前缀,放在变量后称为后缀。单独使用时前后缀无需特别区别;如果需要用到表达式的值则需注意求值顺序,具体可看下面的例子,详细情况可参考引用中介绍的例子部分。

i = 100; op1 = i++; // op1 = 100,先 op1 = i,然后 i = i + 1 i = 100; op2 = ++i; // op2 = 101,先 i = i + 1,然后赋值 op2 i = 100; op3 = i--; // op3 = 100,先赋值 op3,然后 i = i - 1 i = 100; op4 = --i; // op4 = 99,先 i = i - 1,然后赋值 op4

复合赋值运算符

复合赋值运算符是表达式的缩写形式,分为两类:

  • 复合算术运算符:+=-=*=/=%=
  • 复合位操作符:&=|=^=<<=>>=

例如op = op + 2可写为op += 2op = op - 2可写为op -= 2op = op * 2可写为op *= 2,位运算的复合形式同理(如flags <<= 2;flags &= new_flags;)。

条件运算符

条件运算符?:是 C++ 中唯一的三目运算符,可以看作if语句的简写:在a ? b : c中,如果表达式a成立(值为真),整个条件表达式的结果是b,否则结果是c。例如:

int i = a > b ? a : b; // 取 a、b 中的较大值

条件运算符不可重载,且是从右向左结合。

比较运算符

运算符功能
>大于
>=大于等于
<小于
<=小于等于
==等于
!=不等于

其中特别需要注意的是要将等于运算符==和赋值运算符=区分开来,这在判断语句中尤为重要。

if (op=1)if (op==1)看起来类似,但实际功能相差甚远:第一条语句是对op进行赋值,若赋值为非 0 则表达式为真值,条件始终满足,完全无法达到判断的作用;第二条语句才是对op的值进行判断。这种"赋值误写为比较"是新手最常见的隐蔽 bug 之一,建议在条件中把常量写在左侧(如if (1 == op))以便编译器在误写为=时直接报错。

逻辑运算符

运算符功能
&&逻辑与
\|\|逻辑或
!逻辑非
Result = op1 && op2; // 当 op1 与 op2 都为真时则 Result 为真 Result = op1 || op2; // 当 op1 或 op2 其中一个为真时则 Result 为真 Result = !op1; // 当 op1 为假时则 Result 为真

内建的&&||运算符进行短路求值(short-circuit evaluation):若在求值第一个操作数后结果已经确定,则不再求值第二个操作数。这在比赛中是常用技巧,例如if (i < n && a[i] > 0)可以安全地先检查下标再访问数组。但要注意,重载的&&||无此特性,会始终对两个操作数都进行求值。

逗号运算符

逗号运算符可将多个表达式分隔开来,被分隔开的表达式按从左至右的顺序依次计算,整个表达式的值是最后一个表达式的值。逗号表达式的优先级在所有运算符中最低

exp1, exp2, exp3; // 最后的值为 exp3 的运算结果 Result = 1 + 2, 3 + 4, 5 + 6; // 得到 Result 的值为 3 而不是 11,因为赋值运算符 "=" // 的优先级比逗号运算符高,先进行了赋值运算才进行逗号运算 Result = (1 + 2, 3 + 4, 5 + 6); // 若要让 Result 的值得到逗号运算的结果则应将整个表达式用括号提高优先级,此时 // Result 的值才为 11

逗号运算符最常见的实用场景是在for循环的初始化与迭代表达式中同时处理多个变量,如for (i = 0, j = 0; i < 10; i++, j++)

成员访问运算符

运算符功能
[]数组下标
.对象成员
&(单目)取地址/获取引用
*(单目)间接寻址/解引用
->指针成员

这些运算符用来访问对象的成员或者内存,除->外上述运算符都可被重载。与&*->相关的内容请阅读指针和引用教程。这里还省略了两个很少用到的运算符.*->*(类成员指针访问运算符)。

auto result1 = v[1]; // 获取 v 中下标为 1 的对象(注意 C++ 下标从 0 开始) auto result2 = p.q; // 获取 p 对象的 q 成员 auto result3 = p -> q; // 获取 p 指针指向的对象的 q 成员,等价于 (*p).q auto result4 = &v; // 获取指向 v 的指针 auto result5 = *v; // 获取 v 指针指向的对象

从指针教程可以了解到,&取地址与*解引用互为逆运算:&得到一个变量的内存地址,*则沿指针找到其所指对象,而->就是"解引用 + 取成员"的语法糖。

C++ 运算符优先级总表

下表整理自 C++ 运算符优先级规范(有修改)。掌握这张表,是理解一切表达式求值顺序的前提。C++20 起新增的三路比较运算符<=>(太空船运算符)也收录于第八级别。

运算符描述例子可重载性
第一级别
::作用域解析符Class::age = 2;不可重载
第二级别
++后自增运算符for (int i = 0; i < 10; i++) cout << i;可重载
--后自减运算符for (int i = 10; i > 0; i--) cout << i;可重载
type() type{}强制类型转换unsigned int a = unsigned(3.14);可重载
()函数调用isdigit('1')可重载
[]数组数据获取array[4] = 2;可重载
.对象型成员调用obj.age = 34;不可重载
->指针型成员调用ptr->age = 34;可重载
第三级别(从右向左结合)
++前自增运算符for (i = 0; i < 10; ++i) cout << i;可重载
--前自减运算符for (i = 10; i > 0; --i) cout << i;可重载
+正号int i = +1;可重载
-负号int i = -1;可重载
!逻辑取反if (!done) …可重载
~按位取反flags = ~flags;可重载
(type)C 风格强制类型转换int i = (int) floatNum;可重载
*指针取值int data = *intPtr;可重载
&值取指针int *intPtr = &data;可重载
sizeof返回类型内存int size = sizeof floatNum; int size = sizeof(float);不可重载
new动态元素内存分配long *pVar = new long; MyClass *ptr = new MyClass(args);可重载
new []动态数组内存分配long *array = new long[n];可重载
delete动态析构元素内存delete pVar;可重载
delete []动态析构数组内存delete [] array;可重载
第四级别
.*类对象成员引用obj.*var = 24;不可重载
->*类指针成员引用ptr->*var = 24;可重载
第五级别
*乘法int i = 2 * 4;可重载
/除法float f = 10.0 / 3.0;可重载
%取余数(模运算)int rem = 4 % 3;可重载
第六级别
+加法int i = 2 + 3;可重载
-减法int i = 5 - 1;可重载
第七级别
<<位左移int flags = 33 << 1;可重载
>>位右移int flags = 33 >> 1;可重载
第八级别
<=>三路比较运算符if ((i <=> 42) < 0) ...可重载
第九级别
<小于if (i < 42) ...可重载
<=小于等于if (i <= 42) ...可重载
>大于if (i > 42) ...可重载
>=大于等于if (i >= 42) ...可重载
第十级别
==等于if (i == 42) ...可重载
!=不等于if (i != 42) ...可重载
第十一级别
&位与运算flags = flags & 42;可重载
第十二级别
^位异或运算flags = flags ^ 42;可重载
第十三级别
\|位或运算flags = flags \| 42;可重载
第十四级别
&&逻辑与运算if (conditionA && conditionB) ...可重载
第十五级别
\|\|逻辑或运算if (conditionA \|\| conditionB) ...可重载
第十六级别(从右向左结合)
? :条件运算符int i = a > b ? a : b;不可重载
throw异常抛出throw EClass("Message");不可重载
=赋值int a = b;可重载
+=加赋值运算a += 3;可重载
-=减赋值运算b -= 4;可重载
*=乘赋值运算a *= 5;可重载
/=除赋值运算a /= 2;可重载
%=模赋值运算a %= 3;可重载
<<=位左移赋值运算flags <<= 2;可重载
>>=位右移赋值运算flags >>= 2;可重载
&=位与赋值运算flags &= new_flags;可重载
^=位异或赋值运算flags ^= new_flags;可重载
\|=位或赋值运算flags \|= new_flags;可重载
第十七级别
,逗号分隔符for (i = 0, j = 0; i < 10; i++, j++) ...可重载

需要注意的是,表中并未列出const_caststatic_castdynamic_castreinterpret_casttypeidsizeof...noexceptalignof等运算符,因为它们的使用形式与函数调用相同,不会出现歧义。

实战要点速记

结合上文与仓库中相关的变量与类型、位运算、指针、引用等章节,可提炼出几条在 OI 实战中最容易踩坑、也最值得记忆的要点:

  1. 整数除法与类型转换int/int结果为整数(向零截断),至少一个操作数为浮点类型结果才为浮点;%只适用于整数操作数。
  2. 位运算优先级低&^|低于比较运算符,混用位运算与比较/算术运算时务必加括号。
  3. 移位安全:移位数必须小于左操作数的位宽且非负;带符号负数的左移、结果溢出类型的左移均为未定义行为;右移对负数向下取整,与除法向零取整不同。
  4. ===不可混用:判断语句中写=会造成条件恒真,属于隐蔽 bug。
  5. 短路求值:内建&&||短路,可安全地组合边界检查与数组访问;重载版本不短路。
  6. 前后缀自增减:单独使用时无区别,参与表达式时后缀"先用后增"、前缀"先增后用"。
  7. 逗号表达式优先级最低:若要整体取值需用括号包住。

以上全部内容来自 OI-wiki 的 docs/lang/op.md 及其关联章节,可作为日常查阅 C++ 运算符语义与优先级的速查手册。

【免费下载链接】OI-wiki:star2: Wiki of OI / ICPC for everyone. (某大型游戏线上攻略,内含炫酷算术魔法)项目地址: https://gitcode.com/GitHub_Trending/oi/OI-wiki

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 9:10:28

Java生成PPT实战:Apache POI-OOXML深度指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 9:09:07

AI-GEO:人工智能与地理空间技术的融合应用

1. 项目概述&#xff1a;AI-GEO的定位与核心价值AI-GEO这个名称由"AI"和"GEO"两部分组成&#xff0c;暗示着这是一个结合人工智能与地理空间技术的创新项目。从技术角度来看&#xff0c;这类系统通常指利用机器学习算法处理地理空间数据&#xff08;如卫星…

作者头像 李华
网站建设 2026/9/13 9:03:41

ADBMS1818伪I²C通信详解:从时序手撕到温度电压精准解码

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华