news 2026/4/26 18:20:45

C语言位运算:从入门到精通

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C语言位运算:从入门到精通

最近在帮很多刚学 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,相异为 1

  • 0 ^ 0 = 0

  • 0 ^ 1 = 1

  • 1 ^ 0 = 1

  • 1 ^ 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 // 结果的补码
第三步:把结果补码转回十进制

结果的补码是负数,我们要转成原码才能得到十进制:

  • 补码减 1:11111111 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; }

✅ 优点:逻辑简单,容易理解 ⚠️ 缺点:如果ab的值很大,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; }

✅ 优点:不会溢出,效率高,完全符合题目要求 ⚠️ 注意:如果ab指向同一个变量(比如传入同一个地址),会把值清为 0,所以只适用于两个独立变量的交换。

2.3 三种方法对比

方法

是否需要临时变量

是否会溢出

适用场景

临时变量法

✅ 需要

不会溢出

通用场景,最推荐

加减法

❌ 不需要

可能溢出

数值较小的场景

异或法

❌ 不需要

不会溢出

两个独立整数交换,面试题常用

💡 小提示:实际开发中,临时变量法是最推荐的,代码可读性好,没有溢出风险,也不会有交换同一个变量的坑。不使用临时变量的写法更多是面试题或者趣味用法,不要在生产代码里乱用。


三、逗号表达式:别被 “逗号” 骗了,它的优先级最低!

逗号表达式是很多新手最容易懵的知识点,很多人搞不懂它到底是干嘛的。

逗号表达式的格式:表达式1, 表达式2, 表达式3, ..., 表达式n

  • 执行顺序:从左到右依次执行每个表达式

  • 返回值:整个逗号表达式的结果,是最后一个表达式的值

  • 优先级:逗号运算符是所有运算符中优先级最低的!

我们通过三个经典案例来理解它:

3.1 案例 1:带括号的逗号表达式赋值

执行过程分析

初始状态
变量ab的初始值分别为12

逗号表达式解析
逗号表达式(a>b, a=b+10, a, b=a+1)按从左到右顺序依次执行:

  1. 比较a > b
    计算1 > 2,结果为逻辑假(0),但此结果不参与赋值,仅作为中间步骤被丢弃。
    状态保持:a=1,b=2

  2. 赋值a = b + 10
    计算2 + 10,将结果12赋给a
    更新状态:a=12,b=2

  3. 取值a
    直接读取a的值12,但结果仍被丢弃。
    状态保持:a=12,b=2

  4. 赋值b = a + 1
    计算12 + 1,将结果13赋给b
    更新状态:a=12,b=13

最终结果
逗号表达式的值为最后一个表达式b=a+1的结果13,因此c被赋值为13

关键点总结

  • 逗号表达式按顺序执行,但仅最后一个子表达式的结果作为整体返回值。
  • 中间步骤可能修改变量值(如ab的更新),需注意状态变化。
  • 表达式(a>b)a的计算结果不影响最终赋值。

3.2 案例 2:if 条件中的逗号表达式

if (a = b + 1, c = a / 2, d > 0) { // 业务代码 }

这里的if条件是一个逗号表达式:

  • 先执行a = b + 1

  • 再执行c = a / 2

  • 最后执行d > 0,整个if条件的真假由d > 0的结果决定

前面两个表达式只是单纯执行,它们的结果不会影响if的判断,只有最后一个表达式决定条件是否成立。

3.3 案例 3:while 循环的逗号表达式优化

原始代码:

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. 优先级极低:逗号运算符优先级最低,低于赋值运算符,所以很多时候需要加括号,比如案例 1 中如果不加括号,int c = a>b, a=b+10, a, b=a+1;会被解析成多个独立语句,而不是一个逗号表达式。

  2. 和函数参数的逗号不一样:函数参数中的逗号(比如printf(a, b))不是逗号运算符!它只是用来分隔参数的,不会按逗号表达式的规则执行,不要搞混了。

  3. 适度使用:虽然逗号表达式能让代码更简洁,但过度使用会降低可读性,实际开发中建议适度使用。


四、统计二进制中 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 次,效率不算最优,比如 n=1,也要循环 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 的个数,效率极高。

比如 n=13(二进制1101):

  1. 第一次:13 & 12 = 1211011100,消除了最后一个 1)

  2. 第二次:12 & 11 = 811001000,消除了中间的 1)

  3. 第三次:8 & 7 = 010000000,消除了第一个 1) 循环结束,count=3,正好是 1 的个数。

4.4 三种方法对比

实现方法

支持负数?

循环次数

优点

缺点

取模 + 除法

❌ 不支持

最多 32 次

逻辑简单

负数会死循环

移位 + 按位与

✅ 支持

固定 32 次

逻辑清晰,支持负数

固定循环次数,效率一般

Brian Kernighan 算法

✅ 支持

等于 1 的个数

效率最高,无溢出

稍微难理解一点


总结

位运算在 C 语言中是非常高效的操作,很多底层开发、算法题中都会用到。新手在学习的时候,一定要注意这几个点:

  1. 负数的位运算都是基于补码的,不要用原码去计算

  2. 逗号表达式的优先级最低,很多时候需要加括号,而且和函数参数的逗号不一样

  3. 无临时变量交换变量只是面试题,实际开发还是用临时变量更安全

  4. 统计二进制 1 的个数,优先用 Brian Kernighan 算法,效率最高

希望这篇文章能帮你彻底搞懂这些位运算的知识点,如果你还有其他疑问,欢迎在评论区留言讨论!


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

如何通过Kafka-King解决企业级Kafka集群运维的三大核心挑战

如何通过Kafka-King解决企业级Kafka集群运维的三大核心挑战 【免费下载链接】Kafka-King A modern and practical kafka GUI client &#x1f495;&#x1f389;Kafka-King 是一款现代化、实用的 Kafka GUI 客户端&#xff0c;旨在通过直观的桌面界面简化 Apache Kafka 管理。作…

作者头像 李华
网站建设 2026/4/26 18:19:44

Win11Debloat:Windows 11终极优化指南 - 一键清理臃肿系统

Win11Debloat&#xff1a;Windows 11终极优化指南 - 一键清理臃肿系统 【免费下载链接】Win11Debloat A simple, lightweight PowerShell script that allows you to remove pre-installed apps, disable telemetry, as well as perform various other changes to declutter an…

作者头像 李华
网站建设 2026/4/26 18:17:59

PE-bear:如何成为恶意软件分析师的首选PE文件分析工具?

PE-bear&#xff1a;如何成为恶意软件分析师的首选PE文件分析工具&#xff1f; 【免费下载链接】pe-bear Portable Executable reversing tool with a friendly GUI 项目地址: https://gitcode.com/gh_mirrors/pe/pe-bear 在数字安全领域&#xff0c;PE&#xff08;Por…

作者头像 李华
网站建设 2026/4/26 18:14:34

Flink智能体:流处理与LLM融合的实时AI应用开发指南

1. 项目概述&#xff1a;当Flink遇见智能体&#xff0c;一个面向未来的流处理新范式最近在开源社区里&#xff0c;一个名为apache/flink-agents的项目悄然出现&#xff0c;引起了我们这些常年和流处理打交道的工程师的注意。乍一看标题&#xff0c;可能会有点困惑&#xff1a;A…

作者头像 李华
网站建设 2026/4/26 18:13:37

2026届必备的六大降AI率平台横评

Ai论文网站排名&#xff08;开题报告、文献综述、降aigc率、降重综合对比&#xff09; TOP1. 千笔AI TOP2. aipasspaper TOP3. 清北论文 TOP4. 豆包 TOP5. kimi TOP6. deepseek 当进行学术或者职业文档的撰写之时&#xff0c;要是需要去降低文本被人工智能检测工具识别的…

作者头像 李华