ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

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

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 (op1)这类赋值与比较混淆的经典 bug。读完本文你将系统掌握 C 全部运算符的分类语义、运算中的类型转换规则、自增自减的前后缀差异、短路求值特性并能熟练查阅并使用文末完整的 C 运算符优先级总表在比赛中写出更少歧义、更快更稳的代码。算术运算符C 的算术运算符由两个单目运算符一元运算符和五个双目运算符二元运算符组成运算符功能单目正-单目负*双目乘法/除法%取模双目加法-双目减法单目运算符指被操作对象只有一个的运算符双目运算符的被操作对象有两个。例如1 2中的加号就是双目运算符它有1和2两个被操作数此外 C 中还有唯一的一个三目运算符?:。在算术运算符内部单目运算符的优先级最高。取模运算符%计算两个整数相除得到的余数即求余数参与运算的两个操作数必须是整数类型-为双目运算符时做减法运算如2-1为单目运算符时做负值运算如-1。表达式遵循数学中加减乘除的优先规律先进行优先级高的运算同优先级按运算的结合性运算括号可提高优先级。例如opx-y*z先计算y*z再与x相减。算术运算中的类型转换对于双目算术运算符当参与运算的两个变量类型相同时不发生类型转换运算结果用参与运算的变量的类型容纳否则会发生类型转换以使两个变量的类型一致具体规则参见类型转换。例如对于整型int变量x和双精度浮点型double变量yx/3的结果是整型整数除法结果截断为整数x/3.0的结果是双精度浮点型字面量3.0为double类型x/y的结果是双精度浮点型x被转换为doublex*1/3的结果是整型x*1仍是整型整型除法舍去小数x*1.0/3的结果是双精度浮点型1.0触发整型到浮点型的转换。从变量与类型一章可以进一步理解其底层机制类型转换分为数值提升值本身保持不变如float提升为double、char/short提升为int和数值转换值可能发生改变如浮点转整数舍弃小数部分、宽类型转窄类型发生截断/取模。算术运算符不接受小于int的类型作为实参运算前会先实施整数提升这正是char参与算术运算时行为常出人意料的原因。特别注意浮点数转换为整数时全部小数部分被舍弃向零截断例如3.99转为int得到3而int转float/double则会舍入到目标类型下最接近的值若该值不能适应目标类型则行为未定义。位操作符另请参阅位运算。运算符功能~逐位非双目逐位与\|逐位或^逐位异或逐位左移逐位右移位运算是对整数的二进制位序列逐位应用布尔函数取反、与、或、异或的运算各位的运算相互独立。位操作在 OI 中有三种典型用途详见位运算高效代替某些低效运算即编译优化中的强度削减如x*2在开启优化后与x1完全等价、表示集合常用于状压 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提取最低位 1lowbit常用于树状数组与 popcount 计数。需要注意位操作的优先级低于算术运算符单目取反~除外而按位与、按位或|和异或^又低于比较运算符详见下文优先级总表所以使用位操作时需多加注意必要时添加括号。例如x 1 0会被解析为x (1 0)而非(x 1) 0。移位运算的未定义行为移位运算中如果出现如下情况其行为未定义右操作数即移位数为负值右操作数大于等于左操作数的位数。例如对于int32_t类型的变量aa-1和a32都是未定义的int32_t为 32 位移位数32等于其位宽。此外还有两条与符号性相关的规则对带符号非负数的左移操作需确保移位后的结果能被原数的类型容纳否则行为未定义对一个负数执行左移操作同样未定义。对于右移操作右侧多余的位将被舍弃左侧的填充则取决于类型无符号数在左侧补0即逻辑右移有符号数用最高位符号位非负数为0、负数为1补齐即算术右移。关于标准版本差异仓库原文注释需要注意C20 前带符号类型的右移是依实现定义的大多数实现采用算术右移C20 起标准规定a b为 $\lfloor a/2^b\rfloor$带符号数右移由此被固定为算术右移。左移方面C20 前若原值为带符号类型且移位结果能被原类型的无符号版本容纳则将该结果转换为相应的带符号值否则行为未定义C20 起规定a b为 $a\cdot 2^b$ 在模 $2^N$ 下的值$N$ 为结果类型的位宽即带符号数与无符号数的左移均直接舍弃移出结果类型的位。此外右移对负数是向下取整与 C/C 除法向零取整并不一致——例如-1/2的值为0而-11的值为-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 (op1)与if (op1)看起来类似但实际功能相差甚远第一条语句是对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 运算符优先级规范有修改。掌握这张表是理解一切表达式求值顺序的前提。C20 起新增的三路比较运算符太空船运算符也收录于第八级别。运算符描述例子可重载性第一级别::作用域解析符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_cast、static_cast、dynamic_cast、reinterpret_cast、typeid、sizeof...、noexcept及alignof等运算符因为它们的使用形式与函数调用相同不会出现歧义。实战要点速记结合上文与仓库中相关的变量与类型、位运算、指针、引用等章节可提炼出几条在 OI 实战中最容易踩坑、也最值得记忆的要点整数除法与类型转换int/int结果为整数向零截断至少一个操作数为浮点类型结果才为浮点%只适用于整数操作数。位运算优先级低、^、|低于比较运算符混用位运算与比较/算术运算时务必加括号。移位安全移位数必须小于左操作数的位宽且非负带符号负数的左移、结果溢出类型的左移均为未定义行为右移对负数向下取整与除法向零取整不同。与不可混用判断语句中写会造成条件恒真属于隐蔽 bug。短路求值内建、||短路可安全地组合边界检查与数组访问重载版本不短路。前后缀自增减单独使用时无区别参与表达式时后缀先用后增、前缀先增后用。逗号表达式优先级最低若要整体取值需用括号包住。以上全部内容来自 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),仅供参考
返回列表