资讯详情

一次聊透五大运算符:算术、赋值、比较、逻辑与位运算的坑与实战

📅 2026/10/13 11:08:44 | 华诺云谱 👁 阅读
一次聊透五大运算符:算术、赋值、比较、逻辑与位运算的坑与实战
2. 算数运算符、赋值运算符、比较运算符、逻辑运算符、位运算符写代码这件事有个特别容易忽略的真相你写的前一万行代码本质上都是在跟运算符打交道。赋值、判断、循环、位操作看着基础但恰恰是这些基础东西最容易埋坑。我见过太多人对着莫名其妙的bug熬夜排查最后发现不过是一个赋值写成了比较或者逻辑运算符的短路特性没搞清楚。今天我想把这五类运算符一次性聊透不只是列个表格告诉你“加减乘除是什么”而是把每一类背后的运行机制、容易踩的坑、实际场景中的正确用法全部摆出来。不管你是刚接触编程的新手还是写了两三年的老手这五千字里应该都有你用得上的东西。为什么要专门写这个话题因为运算符是几乎所有编程语言的公共底座你换语言、换框架、换项目这些东西始终在那里。而且不同语言之间运算符的行为差异非常大一个Java老手去看Python的赋值操作或者一个Python选手去写C的位运算多多少少会踩到对方的“语言陷阱”。我尽量把这些横向差异也点出来你以后切语言的时候至少心里有数。1. 算数运算符、赋值运算符、比较运算符、逻辑运算符、位运算符的整体设计思路1.1 五类运算符各自的定位与适用场景先把五类运算符摆在一起看。它们不是五座孤岛而是一条处理数据的流水线算数运算符负责“生成新值”赋值运算符负责“存储新值”比较运算符负责“判断值的关系”逻辑运算符负责“组合条件”位运算符负责“在二进制层面直接操作值”。举个例子你写一个简单的用户登录校验boolean canLogin (username ! null password.length() 8) isAccountActive;这一行里五种运算符全用上了。username ! null是比较运算符password.length() 8也是是逻辑运算符是赋值运算符。看起来简单平淡但每个运算符都有自己的执行顺序和潜在陷阱。我拆开讲。算数运算符不只是 - * / %这五个还有自增自减 --以及各种语言的幂运算**或Math.pow()。它的核心定位是“数值计算”但它真正的难点不在计算本身在于类型转换和精度问题。最经典的例子int a 5 / 2;在Java和C里结果是2不是2.5。如果你不是刻意做整除这就是个隐形的bug。赋值运算符的核心功能是“把右边的值存入左边的变量”但它延伸出的复合赋值运算符、-、*这些才是最常用的。这里有个很多新手不知道的细节a b在Java中其实隐含了一次类型转换等价于a (int)(a b)而不是简单的a a b。有些场景下两者行为完全不同。比较运算符是条件判断的基础但不同语言的分裂非常严重。C/C/Java里比较基本类型时比的是值比较引用类型时比的是内存地址。JavaScript里会做隐式类型转换才是严格的类型和值同时比较。Python里直接比较内容。这些差异意味着你不能拿着一门语言的习惯去写另一门语言。逻辑运算符、||、!核心机制是“短路求值”。false anything()后面的anything()根本不会执行true || anything()也一样。这个特性用好了是优化用不好就是隐藏的bug尤其当你把带副作用的函数调用放在短路表达式的右侧时。位运算符、|、^、~、、、是五类里最“底层”的直接对二进制位操作。实际业务代码里用得相对少但在权限系统、标志位处理、性能敏感场景、图像处理和底层驱动中它依然是王者。2. 算数运算符的细节与实操要点2.1 整数除法、取模运算和精度陷阱算数运算符第一个容易踩的坑就是整数除法。Java里int result 7 / 2; // result 3直接截断小数部分如果要保留小数至少要有一方是浮点类型7.0 / 2、7 / 2.0、或者(double) 7 / 2都可以。但注意(double)(7 / 2)没用因为括号里先做了整数除法得到3再转成double也还是3.0。取模运算%同样有细节。正整数取模没问题但负数的取模结果不同语言不一样// Java中 int a -7 % 3; // 结果是-1Java的结果符号和左操作数一致Python中同样的表达式结果是2因为Python的取模结果是“结果与除数同号或为0”的数学意义上更贴近模运算。这就是典型的语言差异。做跨平台算法移植的时候遇到负数取模必须单独写测试用例验证否则极容易出错。浮点数运算更是重灾区。0.1 0.2 0.3在几乎所有主流语言中都是false因为二进制浮点数无法精确表示0.1和0.2。这个问题在Java、C、JavaScript、Python里都存在。我见过一个真实案例一个财务系统统计金额直接用double累加跑了一个月后对不上账最后排查出来是精度累计误差。处理金额正确姿势是使用整数类型加最小单位分来算或者使用语言自带的十进制类型——Java用BigDecimalPython有Decimal模块。2.2 自增自减的前置与后置i和i的区别每个面试都会问但实际工作中同样容易踩坑。后置自增i是“先使用后自增”表达式的结果是自增前的值前置自增i是“先自增后使用”表达式结果是自增后的值。int i 0; int a i; // a0, i1 int j 0; int b j; // b1, j1这本身不难难在组合使用int result i i;这种代码我在实际项目里见过不止一次它的结果在不同编译器环境下都不一定一样甚至属于未定义行为。这种代码除了在面试题里存在在日常开发中应该严格避免。你要自增就单独一行写i别跟其他运算混在一起。注意在Java和C中i和i在单独使用时性能差异微乎其微现代编译器基本都会优化成同一套指令。纠结i还是i性能没有意义明确语义才是第一位的。还有一点Python没有运算符。你写i在Python里会直接报语法错误i 1才是正解。每门语言的运算符设计理念不同切语言时这些内建语法是第一个要适应的。3. 赋值运算符与比较运算符的用法辨析3.1 赋值与比较的经典混淆“赋值运算符”和“比较运算符”放在一节里讲是因为它们之间的混淆实在太常见了。是赋值是比较这个基础中的基础但在C/C里有个著名的坑——赋值表达式本身也有值可以直接嵌入到条件判断中int x 5; if (x 10) { // 这个判断永远为true // 因为x10这个赋值表达式的结果就是1010是非零值所以为真 }更可怕的是这个代码编译期不会报错顶多给个warning。C/C老手都建议给条件分支里的变量加常量前置比如写成if (10 x)这样万一少写一个等号变成if (10 x)编译器会直接报错因为不能给常量赋值。这叫“尤达条件”我不是说它漂亮但在C/C里确实是个防御技巧。Java在这点上做了改进赋值表达式不支持作为条件判断if (x 10)直接编译错误。JavaScript更特殊if (x 10)依然是合法的而且赋值表达式后的值是真值所以条件成立依然是隐藏bug的重灾区。3.2 相等比较“内容”还是“引用”Java和C里比较引用类型时比的是内存地址不是内容。String是引用类型所以String a new String(hello); String b new String(hello); boolean r1 (a b); // false两个不同对象 boolean r2 a.equals(b); // true内容相同但如果你这样写String c hello; String d hello; boolean r3 (c d); // 通常是true因为字符串常量池复用了同一个对象这就有意思了。同一个换个写法结果就不同底层是JVM的字符串常量池机制。所以Java里比较字符串内容永远用equals别用。这几乎是每个Java新手都会踩的坑我当年也栽过。Python则没有这个问题Python的默认比较内容调用__eq__方法is才是比较内存地址。这跟Java的语义正好反过来。3.3 复合赋值运算符的隐藏类型转换复合赋值运算符、-、*这些看似只是语法糖实际有额外功能。Java中最经典的问题short s 1; s s 1; // 编译错误s1的结果是int不能自动转回short s 1; // 编译通过复合赋值运算符隐含强转short类型做算术运算时会自动提升为int因此s 1是int类型赋值给short需要强转。但不同它在Java的JLSJava语言规范中定义是E1 (T)((E1) op (E2))也就是自带一次强制转换。在实际编码中如果你明知会有超出范围的赋值依然会静默溢出这一点要小心。类似地C/C的没有这种Java式的强制转换而是完全等价于展开表达式。所以Java里的这个行为不是“通用规则”是Java特有的换语言时别想当然。4. 逻辑运算符的短路机制与应用实例4.1 短路求值的完整图解与异常规避逻辑运算符最核心的机制是短路求值理解这一点等于理解了逻辑运算符的一半。A B如果A是falseB不会被执行。A || B如果A是trueB不会被执行。很多人知道这条规则但真正写代码时不一定能主动利用它。短路求值最经典的用途是“先判定再操作”比如Java中判空// 不这么写 if (user ! null) { if (user.getAddress() ! null) { String city user.getAddress().getCity(); } } // 可以这么写逻辑等价且更紧凑 if (user ! null user.getAddress() ! null) { String city user.getAddress().getCity(); }如果不依赖短路user.getAddress()会在user为null时直接抛空指针异常。短路求值保证了前面条件不成立时后面的代码不会执行。这是空指针防御最常用的手法。短路的另一个负面场景是副作用丢失boolean flag fetchFlag(); if (flag updateCount() 0) { // 如果flag为falseupdateCount()不会执行 }如果updateCount()有修改数据库、计数加一等副作用那么当flag为false时这个副作用也不会发生。所以不要让有副作用的函数作为或||的右操作数。先把副作用执行完再用逻辑运算符组合结果这样逻辑清晰也不容易被短路坑到。4.2 不同语言逻辑运算返回值对比这部分是个大坑。C/C和Java的逻辑运算符返回值是booleanC中为int 0/1但Python和JavaScript的逻辑运算符返回值是操作数本身。Pythona 0 or hello # a hello因为0是假值or返回右边的操作数 b 1 and world # b world因为1是真值and返回右边的操作数 c and never # c 因为空字符串是假值and直接返回左边的假值JavaScript逻辑类似undefined、null、0、、NaN都是假值let name user.name || 匿名用户; // 如果user.name是空字符串或undefinedname就是匿名用户这种写法在Python和JS里是设置默认值的最常见手段。但如果你是从Java/C转到Python或JS刚开始肯定不习惯——Java里你已经给boolean c a b;做好了只有true/false的心理预期而Python里return a and b可能返回一个对象、一个数字、甚至一个None内容完全不同。提示如果要判断逻辑真假而不是取操作数Python中务必用bool()包一层JS里用!!也能达到同样效果。4.3 逻辑运算符真值表速查不要觉得真值表太基础越是基础越需要清晰掌握。这里我重新整理一份三个操作数组合的速查真值表覆盖AND、OR、NOT三种运算ABA BA || B!Atruetruetruetruefalsetruefalsefalsetruefalsefalsetruefalsetruetruefalsefalsefalsefalsetrue实际写代码时我常建议大家在心里默念两句话是“两者同时为真才为真一假即假”||是“两者同时为假才为假一真即真”。这两句话背熟了逻辑表达式的化简和优化就会快很多。5. 位运算符的底层原理与高频实战场景5.1 从二进制理解按位与、按位或、异或、取反与移位位运算符操作的是二进制补码表示。Java中int是32位第一位是符号位。位运算符包括按位与两边都是1结果才是1。|按位或任一边是1结果就是1。^按位异或两边不同结果为1相同为0。~按位取反0变11变0包括符号位。左移低位补0相当于乘以2的n次方。右移带符号右移高位补符号位相当于除以2的n次方。无符号右移高位补0Java特有。用两个简单的例子走一遍// 按位与示例 int a 6; // 二进制 0000 0110 int b 3; // 二进制 0000 0011 int c a b; // 逐位计算0000 0110 0000 0011 0000 0010 2 // 左移示例 int d 1 4; // d 16这里我要重点说明和的区别。-16 2的结果是-4因为带符号右移时高位补的是符号位1而-16 2在Java中的结果是1073741820因为无符号右移高位补0负数符号位被丢掉后变成了大正数。这个差异在底层数据结构实现中非常关键比如哈希表的扩容计算里经常能看到用来保证非负。5.2 位运算经典应用权限管理、掩码与快速运算位运算最经典的应用场景是权限系统设计。假设一个用户有四种权限读1、写2、执行4、管理8。这些权限值都是2的幂次二进制上各自占据一个独立的位public static final int PERMISSION_READ 1; // 0001 public static final int PERMISSION_WRITE 2; // 0010 public static final int PERMISSION_EXECUTE 4; // 0100 public static final int PERMISSION_ADMIN 8; // 1000 // 给用户赋权读 写 执行 int userPerm PERMISSION_READ | PERMISSION_WRITE | PERMISSION_EXECUTE; // 结果二进制为 0111 // 校验是否有写权限 boolean hasWrite (userPerm PERMISSION_WRITE) ! 0; // 移除执行权限 userPerm userPerm ~PERMISSION_EXECUTE; // 切换管理员权限有变无、无变有 userPerm userPerm ^ PERMISSION_ADMIN;这个方案比用多个boolean字段优雅得多一个整数就能装下32种独立权限数据库存储也方便查询权限时直接与运算性能极好。我在实际项目中用这种方式做过物联网设备的控制权限系统几百万个设备在线时单个整数的位运算只需要几条CPU指令比查表快几个数量级。再说一个常见的位运算优化判断奇偶性。n 1的结果为1则n是奇数0则是偶数。相比n % 2位运算在很多语言里性能更优语义上也更底层。同样乘以2的幂次时用n 1代替n * 2但不是所有编译器都能自动优化你手写明白原理至少知道为什么有些代码跑得快。异或运算还有个著名的骚操作不借助第三个变量交换两个数。假设a 3、b 5a a ^ b; // a 3 ^ 5 b a ^ b; // b (3 ^ 5) ^ 5 3 a a ^ b; // a (3 ^ 5) ^ 3 5这个技巧面试题喜欢考但实际开发中我并不推荐可读性太差编译器对临时变量的优化也不差。我写出来是为了让你理解异或的性质一个数与自身异或结果为0与0异或结果为自身还可以用来做简单的数据校验和加密。5.3 位运算在算法中的性能优势位运算号称“程序员的数学加速器”典型场景是判断一个数是不是2的幂次boolean isPowerOfTwo(int n) { return n 0 (n (n - 1)) 0; }这个技巧的逻辑2的幂次在二进制上只有一个位是1。比如8的二进制是10008 - 1是0111两者与运算结果为0非2的幂次比如10的二进制是101010 - 1是1001与运算结果是1000不为0。类似的位运算还有取二进制中1的个数n (n - 1)每次消掉最右边的1循环计数。判断符号是否相反(a ^ b) 0异或后符号位为1说明一正一负。求平均且防溢出(a b) 1或者(a b) ((a ^ b) 1)。这些算法在竞赛题、底层库、嵌入式代码里很常见。就算日常业务代码不直接写位运算理解它们也能帮你读懂框架源码里的那些“神秘算法”。6. 常见问题与排查技巧实录6.1 运算符优先级踩坑清单运算符优先级是隐藏bug的重灾区。当多个运算符混在一个表达式里时优先级不搞清楚就是猜。我整理一份常用优先级表从高到低优先级运算符说明最高() [] .括号、数组访问、成员访问高 -- ! ~自增自减、逻辑非、按位取反中上* / %乘除取模中 -加减中下 移位运算低 instanceof大小比较更低 !相等比较更低 ^ |按位运算低 ||逻辑短路运算最低?: 三元运算符、赋值最容易出问题的是位运算符的优先级还低于。比如// 本意判断(a b) 0 // 实际因为优先级高于所以按下式计算 if (a b 0) { ... } // 实际执行的是 a (b 0)b0的结果是boolean再按位与int直接编译报错或语义错误所以我建议逻辑运算符、位运算符混合比较运算时无条件加括号。加括号不是可选项是必须项。我自己职业生涯里因为优先级问题排查过的bug少说也有十几次每一次最初都以为是算法逻辑写错了最后都是优先级在捣乱。6.2 逻辑运算符与位运算符混合场景的真实bug这个bug是我在实际项目中印象最深的一个。当时有一段判断权限的代码// 代码需求只有当用户拥有执行权限时才允许操作 int perm user.getPermission(); // 比如值是 4执行权限 if (perm PERMISSION_EXECUTE) { // 执行操作 }这是C语言风格写法C里if接受整数判断但在Java里if (perm PERMISSION_EXECUTE)编译不通过因为的返回结果是int不是boolean。不少人看到错误提示后改成if (perm PERMISSION_EXECUTE PERMISSION_EXECUTE) { ... }结果依然不对——因为优先级高于实际执行顺序是if (perm (PERMISSION_EXECUTE PERMISSION_EXECUTE)) // 等价于 perm true编译报错正确写法是if ((perm PERMISSION_EXECUTE) PERMISSION_EXECUTE) { ... }这种bug在面试题里经常“钓”人在实际代码review里我也提过类似的问题。解决之道就是位运算比较运算必须整体括起来不要相信记忆中的优先级编译器会无条件遵循规则不会按你的“本意”执行。6.3 常见问题速查表我把平时群里、技术论坛里问得最多的问题整理成一张速查表直接对照查就行问题原因分析解决方案0.1 0.2 ! 0.3浮点数二进制存储精度问题用BigDecimal/Decimal或者转整数计算i和i混在复杂表达式里结果不确定执行顺序依赖编译器实现单独成行使用自增自减字符串用比较却返回false比较的是引用地址不是内容Java用equalsJS用Python用if (x 5)总是成立赋值表达式的结果是55是真值写if (5 x)或在能编译报错的语言用IDE提示右侧的函数没有执行短路求值生效false...直接跳过右侧单独执行副作用函数后再组合判断1 32结果意外为1移位数量对32取模32移位等于不移注意数据类型的位宽超过位宽用长整型Python里i报语法错误Python没有自增运算符使用i 1Javashort s; s 1;编译通过复合赋值隐含强转注意范围溢出问题-7 % 3在不同语言结果不同负数取模的定义差异移植算法时单独验证负值情况a b c结果奇怪优先级高于括号明确顺序别信记忆这张表我建议你收藏一下没事翻翻比临时搜Stack Overflow快得多。6.4 我的几条独家避坑心得最后分享几条写代码的实际体会不算教科书内容但都是我踩过坑之后沉淀下来的。第一任何涉及运算符的复杂表达式先拆行再合并。比如条件判断不要写一长串if (a b || c !d)要么加括号分清楚要么拆成多个布尔变量单独表达再组合。代码可读性永远比少写两行的优越感重要。第二切语言时先看运算符语义差异。Java转Python的人最容易在与is、与、返回boolean与and返回操作数这三个点上出问题。写代码第一周不用管别人怎么说把自己当新手多写几个测试用例验证运算符行为后面就顺畅了。第三位运算要用就整段注释。因为位运算写出来读起来难过三个月你自己都未必能一眼看懂。在代码里把二进制的计算过程注释出来比如// 权限 读(1) | 写(2) 3 (二进制0011)这行注释能省下后人无数时间。第四短路求值不止用于判空还能控制初始化逻辑。比如在配置加载场景中config loadFromRedis() || loadFromDb() || defaultConfig();这种表达式能让配置的降级链路异常清晰。前提是你用的语言支持这种返回操作数语义Python和JS可以Java/C不行。但你要注意如果前面的加载函数抛异常而不是返回假值短路挡不住异常所以还是要配合异常处理一起用。
📝

华诺云谱内容团队

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

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

你可能需要的服务

订阅华诺云谱资讯周报

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

↑