最近在帮很多刚学 C 语言的同学梳理位运算相关的知识点发现很多新手对这部分内容一知半解尤其是负数的位运算、逗号表达式这些很容易踩坑。今天我就把几个最常见的位运算相关的经典案例从原理到代码给大家讲透帮你彻底搞懂这些知识点看完这篇再也不用对位运算犯迷糊了一、搞懂异或运算为什么 3 ^ (-5) -8很多新手刚接触异或的时候都会被这个问题搞懵#include stdio.h int main() { int a 3; int b -5; int c a ^ b; printf(%d\n, c); // 输出-8 return 0; }正数和负数做异或结果为什么是 - 8这背后的核心就是补码。1.1 异或的基本规则异或运算符^的规则很简单相同为 0相异为 10 ^ 0 00 ^ 1 11 ^ 0 11 ^ 1 0但是要注意C 语言中所有的位运算都是基于补码来计算的不是原码1.2 负数的补码位运算的核心正数的原码、反码、补码都是一样的但是负数不一样原码最高位是符号位1 表示负数其余位是数值反码符号位不变其余位取反补码反码 1所有的位运算都是用补码来计算的这是新手最容易忽略的点1.3 3 ^ (-5) 的完整计算过程我们以 32 位 int 为例一步步计算第一步把两个数转成补码3 的补码正数00000000 00000000 00000000 00000011-5 的补码原码10000000 00000000 00000000 00000101反码11111111 11111111 11111111 11111010补码11111111 11111111 11111111 11111011第二步按位异或00000000 00000000 00000000 00000011 // 3的补码 ^ 11111111 11111111 11111111 11111011 // -5的补码 --------------------------------------- 11111111 11111111 11111111 11111000 // 结果的补码第三步把结果补码转回十进制结果的补码是负数我们要转成原码才能得到十进制补码减 111111111 11111111 11111111 11110111符号位不变其余位取反10000000 00000000 00000000 00001000原码对应的十进制就是-8和代码输出完全一致二、异或的经典用法不创建临时变量交换两个数这是面试中最常见的题目不创建临时变量交换两个整数的值。很多新手一开始会写这样的代码// 错误写法用了临时变量不符合题目要求 int main() { int a 3; int b 5; int c 0; // 这里定义了临时变量违反了题目要求 printf(交换前:a%d b%d\n, a, b); c a; a b; b c; printf(交换后:a%d b%d\n, a, b); return 0; }其实有两种不用临时变量的方法我们一个个来看2.1 加减法实现int main() { int a 3, b 5; printf(交换前: a%d b%d\n, a, b); a a b; b a - b; a a - b; printf(交换后: a%d b%d\n, a, b); return 0; }✅ 优点逻辑简单容易理解 ⚠️ 缺点如果a和b的值很大a b可能会超出int的范围导致溢出。2.2 异或运算实现推荐这就是我们上一节讲的异或的经典用法没有溢出问题int main() { int a 3; int b 5; printf(交换前: a%d b%d\n, a, b); a a ^ b; b a ^ b; a a ^ b; printf(交换后: a%d b%d\n, a, b); return 0; }✅ 优点不会溢出效率高完全符合题目要求 ⚠️ 注意如果a和b指向同一个变量比如传入同一个地址会把值清为 0所以只适用于两个独立变量的交换。2.3 三种方法对比方法是否需要临时变量是否会溢出适用场景临时变量法✅ 需要不会溢出通用场景最推荐加减法❌ 不需要可能溢出数值较小的场景异或法❌ 不需要不会溢出两个独立整数交换面试题常用 小提示实际开发中临时变量法是最推荐的代码可读性好没有溢出风险也不会有交换同一个变量的坑。不使用临时变量的写法更多是面试题或者趣味用法不要在生产代码里乱用。三、逗号表达式别被 “逗号” 骗了它的优先级最低逗号表达式是很多新手最容易懵的知识点很多人搞不懂它到底是干嘛的。逗号表达式的格式表达式1, 表达式2, 表达式3, ..., 表达式n执行顺序从左到右依次执行每个表达式返回值整个逗号表达式的结果是最后一个表达式的值优先级逗号运算符是所有运算符中优先级最低的我们通过三个经典案例来理解它3.1 案例 1带括号的逗号表达式赋值执行过程分析初始状态变量a和b的初始值分别为1和2。逗号表达式解析逗号表达式(ab, ab10, a, ba1)按从左到右顺序依次执行比较a b计算1 2结果为逻辑假0但此结果不参与赋值仅作为中间步骤被丢弃。状态保持a1,b2赋值a b 10计算2 10将结果12赋给a。更新状态a12,b2取值a直接读取a的值12但结果仍被丢弃。状态保持a12,b2赋值b a 1计算12 1将结果13赋给b。更新状态a12,b13最终结果逗号表达式的值为最后一个表达式ba1的结果13因此c被赋值为13。关键点总结逗号表达式按顺序执行但仅最后一个子表达式的结果作为整体返回值。中间步骤可能修改变量值如a和b的更新需注意状态变化。表达式(ab)和a的计算结果不影响最终赋值。3.2 案例 2if 条件中的逗号表达式if (a b 1, c a / 2, d 0) { // 业务代码 }这里的if条件是一个逗号表达式先执行a b 1再执行c a / 2最后执行d 0整个if条件的真假由d 0的结果决定前面两个表达式只是单纯执行它们的结果不会影响if的判断只有最后一个表达式决定条件是否成立。3.3 案例 3while 循环的逗号表达式优化原始代码a get_val(); count_val(a); while (a 0) { // 业务处理 a get_val(); count_val(a); }这段代码有重复的逻辑我们可以用逗号表达式优化成while (a get_val(), count_val(a), a 0) { // 业务处理 }效果和原始代码完全等价而且代码更简洁避免了重复的函数调用。3.4 逗号表达式的注意事项优先级极低逗号运算符优先级最低低于赋值运算符所以很多时候需要加括号比如案例 1 中如果不加括号int c ab, ab10, a, ba1;会被解析成多个独立语句而不是一个逗号表达式。和函数参数的逗号不一样函数参数中的逗号比如printf(a, b)不是逗号运算符它只是用来分隔参数的不会按逗号表达式的规则执行不要搞混了。适度使用虽然逗号表达式能让代码更简洁但过度使用会降低可读性实际开发中建议适度使用。四、统计二进制中 1 的个数从入门到优化这 3 种写法你都见过吗这是算法题中非常经典的题目输入一个整数统计它的二进制表示中 1 的个数。我们从入门到优化来看三种不同的写法4.1 入门写法取模 除法很多新手一开始会写这样的代码int main(){int n 0;scanf(%d, n);int count 0;while (n){if (n % 2 1)count;n / 2;}printf(%d\n, count);return 0;}核心思路每次判断最低位是不是 1然后把 n 右移一位。但是这段代码有个致命的缺陷当输入的 n 是负数时会陷入死循环 原因负数右移时高位会补 1永远不会变成 0循环永远不会结束。4.2 进阶写法移位 按位与为了解决负数的问题我们可以遍历所有 32 位逐个判断int main() { int n 0; int count 0; scanf(%d, n); int i 0; for (i 0; i 32; i) // 遍历int的32个bit位 { if (((n i) 1) 1) // 检查第i位是不是1 count; } printf(%d\n, count); return 0; }✅ 优点可以同时处理正数和负数不会死循环。 ⚠️ 缺点固定循环 32 次效率不算最优比如 n1也要循环 32 次。4.3 最优写法Brian Kernighan 算法这是目前最高效的写法循环次数等于 1 的个数int count 0; unsigned int m (unsigned int)n; while (m) { m (m - 1); // 清除最低位的1 count; }原理num (num - 1)会把二进制中最右边的 1 变成 0循环执行的次数就等于 1 的个数效率极高。比如 n13二进制1101第一次13 12 121101→1100消除了最后一个 1第二次12 11 81100→1000消除了中间的 1第三次8 7 01000→0000消除了第一个 1 循环结束count3正好是 1 的个数。4.4 三种方法对比实现方法支持负数循环次数优点缺点取模 除法❌ 不支持最多 32 次逻辑简单负数会死循环移位 按位与✅ 支持固定 32 次逻辑清晰支持负数固定循环次数效率一般Brian Kernighan 算法✅ 支持等于 1 的个数效率最高无溢出稍微难理解一点总结位运算在 C 语言中是非常高效的操作很多底层开发、算法题中都会用到。新手在学习的时候一定要注意这几个点负数的位运算都是基于补码的不要用原码去计算逗号表达式的优先级最低很多时候需要加括号而且和函数参数的逗号不一样无临时变量交换变量只是面试题实际开发还是用临时变量更安全统计二进制 1 的个数优先用 Brian Kernighan 算法效率最高希望这篇文章能帮你彻底搞懂这些位运算的知识点如果你还有其他疑问欢迎在评论区留言讨论