资讯详情

运算符与表达式练习:从陷阱到综合刷题指南

📅 2026/10/17 5:45:04 | 华诺云谱 👁 阅读
运算符与表达式练习:从陷阱到综合刷题指南
写运算符和表达式的练习这事听起来特别基础但说句实话我带过不少初学者也看过很多工作两三年的开发者在代码评审里翻车问题往往就出在这块。不管是C、C、Java还是Python运算符和表达式永远是语言的第一道门槛而且是一道“看着会、一写就错”的门槛。你觉得自己懂了 - * /实际上连个自增运算符都能倒下一片人。这篇文章我就围绕“运算符与表达式题目综合练习”这个主题把我实际刷题、出题、带人复盘过程中积攒的经验全部摊开来讲从原理到易错点再到一套可以直接拿来练手的题目和解析一次说透。1. 这类题目到底在练什么为什么值得花大力气1.1 几乎所有程序Bug的源头都藏在这里很多人写代码出问题第一反应是查逻辑、查框架、查网络绕了一大圈最后发现是表达式求值顺序跟想象的不一样。比如int x 5; int y x x;这种代码在不同语言里结果可能不一样甚至在同一门语言的不同版本里都可能存在未定义行为。很多公司面试题特别喜欢出这种题不是为了炫技而是因为运算符和表达式直接决定了你对语言底层机制的理解程度。从实际项目来看运算符相关的Bug主要集中在几个场景复合赋值运算符用反了比如total * price tax到底先加还是先乘位运算误用在逻辑判断上把和搞混浮点数直接用比较短路求值带来的副作用忽略比如if (a ! null a.length() 5)里顺序写反导致空指针三目运算符嵌套过深代码审查时没人看得懂所以练运算符与表达式本质上是在训练两种能力第一是对语言规则的精确记忆第二是对代码执行过程的脑内模拟能力。1.2 这套练习适合谁解决什么问题如果你是刚学编程的学生这套题能帮你把语法基础打牢避免后续学数组、函数、指针时被表达式的各种隐含细节反复绊倒。如果你已经在工作中写业务代码这套题能帮你快速定位那些藏在条件判断和计算逻辑里的隐蔽Bug跟同事对代码时也能更有底气。如果你正准备面试或者刷算法题那更是绕不开很多算法题的第一步就是正确写出表达式边界条件处理就是运算符基本功的直接体现。我以前带过一个某高校的实习生A同学写排序算法时比较器返回值理解和运算顺序搞错导致排序结果时好时坏排查了快一天。后来我把运算符和表达式的常见陷阱按类型整理成练习给他做一周之后他写代码的老毛病明显减少。这种东西不是看一遍文档就能会必须靠题量堆出敏感度。2. 算数运算符最基础但最阴险的五个陷阱2.1 整数除法不是“除”是“整除”看这段代码int a 7 / 2; // a 3 double b 7 / 2; // b 3.0不是3.5 double c 7.0 / 2; // c 3.5很多初学者摔在第一行甚至摔在第二行。第二行是重灾区右边两个整数相除结果先按整数除法算成3再转换成double赋给b所以拿到的不是3.5而是3.0。正确的写法是7 / 2.0或者(double)7 / 2。这点在C/C和Java里都一样Python3好一点直接出浮点数但Python2又不一样所以日常写代码前先确认语言版本没错。我自己刷题时习惯把“整型变量参与除法”看成两个步骤先做整数除法再做类型转换。这个思路能避免九成以上的类型转换错误。2.2 取模运算的符号问题比你想的复杂取模运算%在正整数下很简单一旦涉及负数就容易出错。C和C里结果符号跟被除数一致int a -7 % 3; // a -1 int b 7 % -3; // b 1Python则不同结果符号跟除数一致a -7 % 3 # a 2 b 7 % -3 # b -2如果你写代码时没有意识到这个差异做哈希、分页、循环队列这类场景时索引可能直接越界或者出现诡异行为。我建议做题的时候先问自己三个问题被除数是正是负除数是正是负语言规定的结果符号规则是什么2.3 自增自减全局最常见翻车点i和i的区别字面上是“先使用后自增”和“先自增后使用”但真正做题时经常被复合表达式绕晕。我给你们出个经典题猜输出int i 1; int x i i;问x是多少实话告诉你在C和C里这个表达式的结果是未定义行为编译器可能给出任何结果。因为同一个表达式里对i进行了多次有副作用的修改并且没有序列点隔开。这种题拿去做只会误导初学者。真正规范的教学题应该把副作用拆分开int i 1; int x i; // x1, i2 int y i; // i3, y3所以做题要会分辨哪些题是考察规则哪些题本身就是垃圾题。有个简单的判断标准一个变量在同一个表达式中被修改了两次以上且没有隐含的顺序约束这种题可以直接跳过别浪费时间去猜。2.4 溢出与类型提升边界值踩死人比如Java里这个常见题int x Integer.MAX_VALUE; x x 1; // 变负数溢出再比如混合运算的类型提升long result 100000 * 100000; // 先按int运算溢出后再提升为long结果是负数 long correct 100000L * 100000; // 正确写法我在做题时的习惯是混合类型运算先判断最大操作数的类型再看结果能否准确放下。特别是涉及字面量时别嫌麻烦该加后缀就加后缀。1e9这种double字面量参与取整运算时也要注意。2.5 浮点数比较永远别用等号double a 0.1 0.2; if (a 0.3) { ... } // 大概率不成立这是老生常谈但每次练习还是要放进来。正确的比较方式是设定一个极小误差阈值double eps 1e-9; if (fabs(a - 0.3) eps) { ... }做题时涉及到浮点数相等判断第一反应就该想到精度问题。有些练习题故意用a 0.3; b 0.1 0.2;让你判断是否相等这就是在考察浮点存储原理。3. 逻辑运算符与短路求值隐藏的行为决定成败3.1 短路求值到底是什么逻辑与和逻辑或||有一个重要性质如果左边的结果已经能决定整个表达式的结果右边就不执行了。这就是短路求值。int a 5; boolean result (a 3) || (a 0); // 左边为true右边a不执行a仍然是5这类题看起来简单但一旦换成函数调用或者赋值表达式就很容易写错if (list ! null list.size() 0) { // 顺序反了就会空指针 } if (list.size() 0 list ! null) { // 危险写法 }第二行一旦list为空就抛异常。所以我们在做逻辑判断时常量、判空条件必须写在左边这是铁律。3.2 逻辑运算的返回值陷阱在C和C里和||返回的是整数1或0而在Java里只能用在boolean上还能用在整数上做位运算。有些题会故意混搭int x 2; int y 1; int z x y; // C语言中结果是1不是2Python则不一样and和or返回的是参与运算的对象本身而不是布尔值a 0 and 2 # a 0 b 1 and 2 # b 2 c 0 or 3 # c 3这个特性常常被用来写“默认值”逻辑比如name input_name or default但前提是你清楚知道空字符串、0、None这些值会被当作假来处理否则容易埋雷。3.3 从题目里识别“副作用陷阱”我在综合练习里专门设计了这样一道题已知a1, b2, c3求(a 0) (b 0) (c 0)之后 a、b、c 各自的值。答案不是简单的都加了1而是要从左到右一步步模拟先算a 0a变成2结果是true接着算b 0b变成3true接着算c 0c变成4true。如果第一项是false后面两个都不会执行。这种题的做题方法就是一个词逐步展开。把每个带副作用的子表达式拆开标注前后值变化千万别用“直觉”一口气判断。4. 位运算笔试和面试的最爱也是实用技能4.1 基础位运算要形成肌肉记忆位运算总共就那么几种左移、右移、无符号右移仅Java等、按位与、按位或|、按位异或^、取反~。做题时必考的几个点判断奇偶x 1 1比x % 2 1更高效交换两个数不用临时变量a ^ b; b ^ a; a ^ b;清除最低位的1x (x - 1)计算二进制中1的个数Brian Kernighan算法源起处判断是不是2的幂x 0 (x (x - 1)) 0比如 2 的幂判断原理在于2的幂二进制只有一个1减1之后借位让后面的位全变1与原数相与必然为0。学了原理之后就不需要背结论。4.2 右移的高频坑算术右移还是逻辑右移C/C里对有符号数是算术右移最左边补符号位对无符号数是逻辑右移最左边补0。负数右移不等于除以2的向下取整有些题会拿这个做文章。int x -8; int y x 1; // -4常见实现是算术右移Java里分两种int x -8; int a x 1; // -4算术右移 int b x 1; // 2147483644逻辑右移最左边补0做题时看到和混在一起第一反应就是检查符号位处理。这个点我在实际面试中见过很多次经常出现在“不用乘除法实现除以2”的问题里直接把人送走。4.3 位运算综合题状态压缩初体验有一类经典题用整型变量的若干位来表示多个布尔开关比如一个字节的高4位表示4种权限低4位表示另外4种状态。然后要求写代码去设置、清除、反转某一位。// 按位设置第n位 x | (1 n); // 清除第n位 x ~(1 n); // 取反第n位 x ^ (1 n); // 读取第n位 (x n) 1这类题考的是位掩码构造和“先清零再置位”的思路非常贴近底层开发的日常。写业务代码的人可能接触不多但做嵌入式、网络协议、高性能计算一定会用到。4.4 位运算的优先级是所有人的公认噩梦按位运算符、^、|的优先级都低于关系运算符比如x 1 0会先算1 0结果完全不是你想的那样。正确的写法是(x 1) 0。C语言里这段代码是个经典反面教材int x 5; if (x 1 0) { // 等价于 x (10)即 x 0恒为0条件恒为假 }正确写法是if ((x 1) 0)。我在综合练习题里面专门放一道这种题想让读者建立条件反射位运算两侧一定要加括号。5. 赋值运算符与复合赋值运算符你以为你懂其实你不懂5.1 复合赋值自带强转属性Java中x 1并不完全等价于x x 1区别在于复合赋值会自动进行一次窄化转换short s 1; s s 1; // 编译错误s1是int不能赋给short s 1; // 编译通过等效于 s (short)(s 1)这题很多人第一次看到都表示震惊。C语言类似但细节不同所以做题必须先锁定语言。我的建议是把所有复合赋值运算符的“类型转换行为”单独整理一遍这比死记运算符优先级划算得多。5.2 连等运算要注意结合方向Java里a b c 3;从右往左结合最终a、b、c都是3。但C语言里连等不是总有合法类型例如a b c 3;虽然从右往左赋值但链式赋值要求两侧类型匹配。实际项目里我几乎不写连等赋值可读性太差Debug时又没法在中间打断点观察。练习题里考这个主要是让你理解赋值运算符是右结合这一点。5.3 复合赋值中表达式只求值一次有一个隐藏细节a[i] 2等价于a[i] a[i] 2吗答案是否定的。复合赋值中左边的变量或数组引用只求值一次所以a[i] 2实际上只是先取出i计算位置再在该位置上加2i自增发生在哪一步需要看语言规范。C标准里这个行为在某些场景下也涉及到未定义问题像a[i] 2这种C里既有修改副作用又有读取非常危险。我刷题看到这类代码第一反应就是跑一遍看看不同编译器的结果不建议死记答案。真正规范的教学练习里应该写成int idx i; a[idx] 2;这样语义清晰、所有编译器结果一致。6. 运算符优先级从根源上理解而不是背表格6.1 一张表搞定95%的题目优先级的表格网上到处都是背下来难度其实不大真正难的是在复合表达式里灵活应用。我的建议是记几组关键分层第一层括号、数组下标、函数调用、成员访问 第二层单目运算符! ~ -- 正负号 第三层算术运算符* / %高于 - 第四层移位运算符 第五层关系运算符 第六层相等运算符 ! 第七层位运算符高于^高于| 第八层逻辑运算符高于|| 第九层三目运算符 第十层赋值运算符有了这个分层做题时先在脑内给表达式套括号再逐步求值。6.2 两个典型的高频坑先来看第一个int x 2; if (x 3) { // 赋值而不是比较条件恒为true }很多语言里赋值表达式的结果就是被赋的值非零即为真。这种题在C/C的老题里频繁出现考察的是你对“赋值也是表达式”的理解。严谨的代码应该写成if (3 x)避免手滑少写一个等号。再看第二个int a 2, b 3; int result a b ? a : b 1; // 先算b1再取两者较大值错了条件运算符优先级很低实际上这个表达式的解析是a b ? a : (b 1)因为条件运算符优先级低于关系运算符。做题时如果没有形成“低优先级运算符让整个表达式分段”的意识很容易按从左到右的顺序晕掉。我的做题习惯是找出表达式里优先级最低的运算符先从那里切一刀把原式拆成若干个子表达式再逐步求值。6.3 括号策略代码是写给人看的然后是机器虽然规则背熟了可以不用括号但是我在实际项目里强烈建议只要混合用了两种以上运算符就加括号。比如a b 0就算你知道它的解析是a (b 0)阅读你代码的人不一定知道。练习题里那些“不加括号算结果”的题只是训练真实工程里代码可读性优先。7. 综合练习题库直接上手刷起来7.1 基础入门的10道题先说结论再讲思路我设计了以下题组覆盖算数、逻辑、位运算和优先级// 第一组算数基础 1. int a 7 / 2; a 的值? 2. int b 7.0 / 2; b 的值? 3. int c -7 % 3; c 的值? (C语言) 4. double d 0.1 0.2; d 0.3 是否成立? 5. int i 2; i i i; i 是?第一题答案是3第二题如果直接赋给int会编译报错Java里直接编译不过C里会有警告但结果还是3。第三题C里是-1Python里是2。第四题不成立浮点精度问题。第五题是典型垃圾题存在未定义行为不给出答案才是正确的答案。做题时碰到这种第五题要能识别出来比计算出某个具体值更重要。7.2 中级挑战的10道题结合真项目需求进入中级我更倾向于包装成“小情景”而不是干巴巴的算术题。比如这道题就来自一个分页功能int page 0; int totalPages ...; boolean hasNext page totalPages; // 问题是page和hasNext分别变成多少答案是先比较page0是否小于totalPages再把page变成1hasNext取决于比较结果。这里若把page totalPages改成page totalPages行为就完全不同了。这类题在实际业务代码中出现频率奇高你能在几秒内说出两个写法的区别基本就能秒杀绝大多数面试场景。再放一道经典的位运算升级题int x 0b101100; int count 0; while (x ! 0) { x x (x - 1); count; } // count等于几先把0b101100转成十进制是44二进制含3个1循环每次清除最右边的1所以count是3。这类题考察的就是x (x - 1)到底干了什么属于刷题高频考点。7.3 进阶项目的10道题一题多解才有意思进阶题我不追求偏难怪而是追求一题多解和边界覆盖。比如这道不用条件语句求x的绝对值int abs (x ^ (x 31)) - (x 31);把x 31当成一个掩码若x为正则右移31位得0若为负则得-1全1通过异或和减法算出绝对值。能理解这行代码的人对算术右移、异或、补码的理解就到位了。再给一个经典题判断某年是否是闰年要求只能用一条表达式boolean leap (year % 4 0 year % 100 ! 0) || year % 400 0;这题不是运算符技巧本身有多难而是考察你把数学条件和逻辑表达式结合的能力顺序、括号都不能错。初学者常常少写一个括号导致算错题目设计得很值得练。7.4 每道题的评分标准建议做练习时建议给自己打分能一次写对并且能解释为什么这样算满分能写对但说不出理由算半对说明还要回头啃基础写错了别急着看答案先用断点调试或者手写步骤推导再看解析我强烈建议把“写出解题过程”作为训练核心而不是只看最终结果。你每推导一次就是在模拟一次CPU执行流程这对后续学指针、递归、并发都有巨大帮助。8. 刷题的高效方法我踩过坑也总结出规律8.1 分类整理远比盲目刷题重要我见过不少新人上来就啃几百道“运算符题”结果题型一变就懵。更合理的做法是按本节前面拆的类别——算数陷阱、短路求值、位运算、优先级——每种类型集中刷10到20道直到一个类型做到90%以上正确率再换下一个。这样每类题型都形成了独立的知识组块。我自己刷题时每做完一道题会顺手贴到自己的错题本里分成两类一类是“规则不知道”这类错题说明知识点有漏洞需要回到文档里补背景一类是“步骤跳步”这类错题说明习惯不好做题太依赖直觉。两类错误的复习频率不太一样前者需要每周回顾后者每天提醒自己放慢速度就好。8.2 用“口述复现法”检验掌握程度关于运算符表达式题有一个我屡试不爽的检验方法把题目遮住向身边人或者镜子口述这道题的完整推演过程包括每一步的运算符结合方向和值的变化。如果你能流畅讲出来说明真正掌握了如果讲到一半去抓笔算说明还没形成内化记忆。我当年在组织某模拟项目X的代码评审时要求参与者轮流讲解自己写的表达式为什么这样写结果半个小时的会议活活开成了两个小时。过程中暴露了大量“能跑但说不清”的代码这在团队协作里是隐患因为后续没人敢重构它。8.3 实战项目里怎么继续保持敏感度刷阶段题只是建立基础真正让运算符与表达式的感觉上升一层还得靠写实际项目。我建议在日常开发中刻意做三件事每个条件表达式的运算符两侧强制加括号先不用管“多余不多余”每次用完取模、位运算和复合赋值在代码旁边写一行注释说明这里在干什么数学变换定期回看一个礼拜前自己写的表达式代码能一眼看懂就说明可读性合格需要仔细想才看懂说明当初的表达方式就有问题这样持续一个月你对运算符的敏感度会明显超过很多人。很多资深开发者所谓的“代码直觉”其实就是大量规则内化之后的表达习惯。最后再分享一个小技巧做运算符与表达式练习时建议自己在本地写一个很小的测试工程把每道题用printf输出结果验证然后用眼睛对着输出看一遍再跟推导过程对照。有时候推导和实际输出不一致不是规则记错了而是没考虑编译器的具体实现这种时候也要值得高兴因为你正好抓到了语言规范和编译器实现之间的灰色地带。这个灰色地带恰恰才是区分你和普通开发者的分水岭。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑