C++ 位运算从入门到精通(全知识点+面试题+实战应用)

C++ 位运算从入门到精通(全知识点+面试题+实战应用)

一、位运算基础概念

位运算是直接对二进制位(bit)进行操作的运算,是计算机底层最基础、最高效的运算方式。在嵌入式开发、高性能算法、网络协议、加密解密、面试高频考点中都有极其广泛的应用。C++ 提供了 6 种基础位运算符,所有运算均以二进制补码形式进行,不涉及浮点数运算,仅针对整数类型(char、short、int、long、long long 等)生效。

1.1 二进制与补码基础

计算机中所有整数均以补码形式存储和运算,这是因为补码能统一正数和负数的运算规则,避免出现"正零"和"负零"的歧义,同时简化减法运算(减法可转化为加法)。

核心规则
  • 正数:原码 = 反码 = 补码。例如:int 类型的 5(32位),原码、反码、补码均为 00000000 00000000 00000000 00000101。
  • 负数:反码 = 原码符号位不变,其余各位按位取反;补码 = 反码 + 1。例如:int 类型的 -5,原码为 10000000 00000000 00000000 00000101,反码为 11111111 11111111 11111111 11111010,补码为 11111111 11111111 11111111 11111011。
  • 符号位:二进制最高位为符号位,0 表示正数,1 表示负数;32位 int 类型的取值范围是 -2³¹ ~ 2³¹ - 1,其中 -2³¹ 是唯一无法用原码和反码表示的数,只能用补码表示。
    位运算的核心优势的是效率极高------CPU 执行位运算指令无需复杂的算术运算(如乘除、取模),直接操作内存中的二进制位,速度比普通算术运算快 10~100 倍,尤其适合对性能要求极高的场景。

1.2 六种基础位运算符(重点)

C++ 提供 6 种基础位运算符,优先级从高到低依次为:~(按位取反)> <<、>>(移位)> &(按位与)> ^(按位异或)> |(按位或)。所有运算符均针对二进制补码进行操作,具体规则如下:

| 运算符 | 名称 | 作用 | 运算规则 | 示例(x=5=0101,y=3=0011) |

|--------|------|------|----------|----------------------------|

| & | 按位与 | 对两个数的二进制位逐位进行"与"操作 | 两位均为 1,结果为 1;否则为 0 | x & y = 0001(1) |

| | | 按位或 | 对两个数的二进制位逐位进行"或"操作 | 任意一位为 1,结果为 1;否则为 0 | x | y = 0111(7) |

| ^ | 按位异或 | 对两个数的二进制位逐位进行"异或"操作 | 两位相同为 0,不同为 1 | x ^ y = 0110(6) |

| ~ | 按位取反 | 对一个数的二进制位逐位取反(包括符号位) | 0 变 1,1 变 0 | ~x = 1010(补码,对应-6) |

| << | 左移 | 将二进制位整体向左移动 n 位 | 高位丢弃,低位补 0 | x << 1 = 1010(10) |

| >> | 右移 | 将二进制位整体向右移动 n 位 | 正数:高位补 0;负数:高位补 1(算术右移) | x >> 1 = 0010(2) |

注意事项
  1. 移位运算中,移位的位数不能为负数,也不能超出当前数据类型的宽度(如 32 位 int 类型,移位位数不能 ≥32),否则行为未定义(不同编译器表现不同)。
  2. 按位取反 ~ 是单目运算符,对正数取反后结果为负数,对负数取反后结果为正数,公式:~x = -x - 1。
  3. 位运算仅针对整数类型,若对浮点数使用位运算,编译器会报错(需强制转换为整数后再操作)。

二、六种基础位运算符详解(附实战代码)

2.1 按位与 &(最常用)

核心性质
  • 任何数 & 0 = 0(清零);
  • 任何数 & 自身 = 自身(保留原值);
  • 按位与具有"掩码"作用,可用于提取某几位、判断某一位是否为 1。
高频应用场景
  1. 判断一个整数的奇偶性(最经典用法)
    原理:整数的二进制最低位为 1 时是奇数,为 0 时是偶数。用 x & 1 可快速判断------结果为 1 则是奇数,为 0 则是偶数。
    代码示例:
cpp 复制代码
#include <iostream>
using namespace std;
// 判断奇偶性
bool isOdd(int x) {
    return x & 1; // 等价于 x % 2 == 1,但效率更高
}
int main() {
    cout << isOdd(5) << endl;  // 1(奇数)
    cout << isOdd(6) << endl;  // 0(偶数)
    return 0;
}
  1. 清零二进制中最低位的 1
    原理:x & (x - 1) 会将 x 二进制中最低位的 1 变为 0,其余位保持不变。这是位运算中最核心的技巧之一,广泛用于统计 1 的个数、判断 2 的幂等场景。
    代码示例:
cpp 复制代码
#include <iostream>
using namespace std;
// 清零最低位的 1
int clearLowestOne(int x) {
    return x & (x - 1);
}
int main() {
    int x = 6; // 二进制 0110
    cout << clearLowestOne(x) << endl; // 4(0100,最低位1被清零)
    x = 5; // 0101
    cout << clearLowestOne(x) << endl; // 4(0100)
    return 0;
}
  1. 提取二进制中的特定位
    原理:用一个"掩码"(mask)与目标数进行按位与,掩码中需要保留的位设为 1,其余位设为 0,即可提取出目标位。
    示例:提取 int 类型 x 的第 3 位(从 0 开始计数,最低位为第 0 位)。
    代码示例:
cpp 复制代码
#include <iostream>
using namespace std;
// 提取第 k 位(0-based)
int getKthBit(int x, int k) {
    return (x & (1 << k)) != 0; // 1<<k 生成掩码,只有第k位为1
}
int main() {
    int x = 10; // 二进制 1010
    cout << getKthBit(x, 1) << endl; // 1(第1位是1)
    cout << getKthBit(x, 2) << endl; // 0(第2位是0)
    return 0;
}

2.2 按位或 |

核心性质
  • 任何数 | 0 = 自身;
  • 任何数 | 自身 = 自身;
  • 按位或可用于"置位",即将某一位强制设为 1,其余位保持不变。
高频应用场景
  1. 将二进制中某一位设为 1
    原理:用 1 << k 生成掩码(第 k 位为 1),与目标数进行按位或,即可将第 k 位设为 1,其余位不变。
    代码示例:
cpp 复制代码
#include <iostream>
using namespace std;
// 将第 k 位设为 1(0-based)
int setKthBit(int x, int k) {
    return x | (1 << k);
}
int main() {
    int x = 5; // 0101
    cout << setKthBit(x, 2) << endl; // 9(1001,第2位设为1)
    cout << setKthBit(x, 3) << endl; // 13(1101,第3位设为1)
    return 0;
}
  1. 合并多个标志位
    在实际开发中,常用位来表示多个独立的状态(标志位),用按位或合并多个标志位,实现多状态共存。
    代码示例(权限控制):
cpp 复制代码
#include <iostream>
using namespace std;
// 定义权限标志位(每一位代表一种权限)
const int READ = 1 << 0;  // 第0位:读权限(1)
const int WRITE = 1 << 1; // 第1位:写权限(2)
const int EXEC = 1 << 2;  // 第2位:执行权限(4)
// 合并权限
int addPerm(int perm, int p) {
    return perm | p;
}
int main() {
    int perm = 0; // 初始无权限
    perm = addPerm(perm, READ);  // 增加读权限:001
    perm = addPerm(perm, WRITE); // 增加写权限:011(3)
    cout << perm << endl; // 3
    return 0;
}

2.3 按位异或 ^(最灵活)

按位异或是位运算中最灵活的运算符,核心特性是"相同为 0,不同为 1",且满足交换律、结合律,具有可逆性。

核心性质
  1. x ^ x = 0(任何数与自身异或,结果为 0);
  2. x ^ 0 = x(任何数与 0 异或,结果为自身);
  3. 交换律:a ^ b = b ^ a;
  4. 结合律:(a ^ b) ^ c = a ^ (b ^ c);
  5. 可逆性:若 a ^ b = c,则 a = c ^ b,b = c ^ a。
高频应用场景
  1. 交换两个整数(无需临时变量)
    原理:利用异或的可逆性,无需额外临时变量即可交换两个数的值,效率极高,是面试中常见的写法。
    代码示例:
cpp 复制代码
#include <iostream>
using namespace std;
// 异或交换两个整数(无临时变量)
void swap(int& a, int& b) {
    if (a == b) return; // 避免 a == b 时,异或后变为 0
    a ^= b; // a = a ^ b
    b ^= a; // b = b ^ (a ^ b) = a
    a ^= b; // a = (a ^ b) ^ a = b
}
int main() {
    int a = 5, b = 10;
    swap(a, b);
    cout << "a = " << a <<, "b = " << b << endl; // a=10, b=5
    return 0;
}

注意:当 a 和 b 指向同一块内存(如 swap(x, x))时,会导致 x 变为 0,因此需添加 a == b 的判断。

  1. 翻转二进制中的某一位

原理:用 1 << k 生成掩码,与目标数异或------若第 k 位为 0,则变为 1;若为 1,则变为 0,实现翻转。

代码示例:

cpp 复制代码
#include <iostream>
using namespace std;
// 翻转第 k 位(0-based)
int flipKthBit(int x, int k) {
    return x ^ (1 << k);
}
int main() {
    int x = 5; // 0101
    cout << flipKthBit(x, 0) << endl; // 4(0100,第0位翻转)
    cout << flipKthBit(x, 1) << endl; // 7(0111,第1位翻转)
    return 0;
}
  1. 找出数组中唯一出现一次的数字
    题目:数组中只有一个数字出现一次,其余数字均出现两次,找出这个唯一的数字(面试高频题)。
    原理:利用 x ^ x = 0 和 x ^ 0 = x 的性质,将数组中所有元素异或,出现两次的数字会相互抵消(结果为 0),最终结果就是唯一出现一次的数字。
    代码示例:
cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;
int singleNumber(vector<int>& nums) {
    int res = 0;
    for (int x : nums) {
        res ^= x; // 所有元素异或,抵消出现两次的数字
    }
    return res;
}
int main() {
    vector<int> nums = {2, 3, 2, 4, 4};
    cout << singleNumber(nums) << endl; // 3(唯一出现一次的数字)
    return 0;
}

2.4 按位取反 ~

按位取反是单目运算符,对二进制的每一位(包括符号位)进行取反,0 变 1,1 变 0。由于计算机存储的是补码,因此取反后的结果需要结合补码规则解读。

核心规律

对于整数 x,~x = -x - 1(无论 x 是正数还是负数,均成立)。

示例:

  • x = 5(补码 00000101),~x = 11111010(补码),对应十进制 -6,满足 -5 -1 = -6;
  • x = -5(补码 11111011),~x = 00000100(补码),对应十进制 4,满足 -(-5) -1 = 4。
应用场景
  1. 生成全 1 的掩码
    例如,生成 32 位全 1 的掩码(用于提取所有位),可使用 ~0(0 的补码是全 0,取反后是全 1)。
    代码示例:
cpp 复制代码
#include <iostream>
using namespace std;
int main() {
    int mask = ~0; // 32位全1,对应十进制 -1
    cout << mask << endl; // -1
    // 生成低 k 位全 1 的掩码(如 k=3,掩码为 00000111)
    int k = 3;
    int lowMask = (1 << k) - 1; // 等价于 ~(~0 << k)
    cout << lowMask << endl; // 7(00000111)
    return 0;
}
  1. 快速计算负数的绝对值(辅助作用)
    结合取反和加法,可快速计算负数的绝对值,公式:abs(x) = ~x + 1(仅对负数有效)。
    代码示例:
cpp 复制代码
#include <iostream>
using namespace std;
int absNeg(int x) {
    if (x < 0) {
        return ~x + 1; // 负数取反加1,得到绝对值
    }
    return x;
}
int main() {
    cout << absNeg(-5) << endl; // 5
    cout << absNeg(5) << endl;  // 5
    return 0;
}

2.5 左移 <<

左移运算将二进制位整体向左移动 n 位,高位丢弃,低位补 0。左移 n 位等价于乘以 2ⁿ(前提是不发生溢出),效率远高于乘法运算。

核心规律

x << n = x * 2ⁿ(x 为正数,且不溢出)。

示例:

  • 5 << 1 = 10(5 * 2¹);
  • 5 << 2 = 20(5 * 2²);
  • 5 << 3 = 40(5 * 2³)。
注意事项
  1. 溢出风险:左移可能导致数值超出当前数据类型的取值范围,溢出后行为未定义。例如,32 位 int 类型的最大值是 2147483647(0x7FFFFFFF),左移 1 位后变为 0xFFFFFFFE,对应十进制 -2,发生溢出。
  2. 左移位数不能为负数,也不能超出数据类型宽度(如 32 位 int 不能左移 ≥32 位)。
应用场景
  1. 快速乘法(乘以 2 的幂)
    在高性能算法中,常用左移替代乘以 2 的幂,提升运算速度。
    代码示例:
cpp 复制代码
#include <iostream>
using namespace std;
// 快速乘以 2^n
int fastMultiply(int x, int n) {
    return x << n;
}
int main() {
    cout << fastMultiply(5, 1) << endl; // 10(5*2)
    cout << fastMultiply(5, 3) << endl; // 40(5*8)
    return 0;
}
  1. 生成掩码
    左移可快速生成"某一位为 1,其余位为 0"的掩码,用于置位、提取位等操作(前面已多次用到)。

2.6 右移 >>

右移运算将二进制位整体向右移动 n 位,分为两种类型:

  1. 算术右移(主流编译器默认):正数高位补 0,负数高位补 1(保持符号不变);
  2. 逻辑右移:无论正数还是负数,高位均补 0(仅部分编译器支持,如 GCC 需加 -funsigned-bitfields 选项)。
核心规律

x >> n = x / 2ⁿ(向下取整,适用于正数和负数)。

示例:

  • 10 >> 1 = 5(10 / 2 = 5,向下取整);
  • 9 >> 1 = 4(9 / 2 = 4.5,向下取整);
  • -10 >> 1 = -5(-10 / 2 = -5);
  • -9 >> 1 = -5(-9 / 2 = -4.5,向下取整为 -5)。
应用场景
  1. 快速除法(除以 2 的幂)
    与左移对应,右移可快速实现除以 2 的幂,效率高于除法运算。
    代码示例:
cpp 复制代码
#include <iostream>
using namespace std;
// 快速除以 2^n(向下取整)
int fastDivide(int x, int n) {
    return x >> n;
}
int main() {
    cout << fastDivide(10, 1) << endl; // 5
    cout << fastDivide(9, 1) << endl;  // 4
    cout << fastDivide(-9, 1) << endl; // -5
    return 0;
}
  1. 二进制位遍历
    通过右移,可逐位遍历二进制的每一位,用于统计 1 的个数、判断某一位是否为 1 等操作。
    代码示例(遍历二进制每一位):
cpp 复制代码
#include <iostream>
using namespace std;
// 遍历 x 的每一位(32位)
void printBits(int x) {
    for (int i = 31; i >= 0; i--) {
        // 右移 i 位,提取第 i 位
        cout << ((x >> i) & 1);
        if (i % 4 == 0) cout << " ";
    }
    cout << endl;
}
int main() {
    int x = 10; // 00000000 00000000 00000000 00001010
    printBits(x);
    return 0;
}

三、位运算经典技巧(面试必备)

位运算的核心价值在于"高效"和"简洁",以下是面试中高频出现的经典技巧,涵盖判断、统计、计算等场景,全部附带可直接运行的代码。

3.1 判断一个数是否是 2 的幂

核心原理

2 的幂的二进制只有一个 1(如 2=10、4=100、8=1000),因此满足:x > 0 且 x & (x - 1) == 0。

注意:x 必须大于 0,因为 0 和负数不是 2 的幂。

代码示例:

cpp 复制代码
#include <iostream>
using namespace std;
// 判断 x 是否是 2 的幂
bool isPowerOfTwo(int x) {
    return x > 0 && (x & (x - 1)) == 0;
}
int main() {
    cout << isPowerOfTwo(8) << endl;  // 1(是)
    cout << isPowerOfTwo(6) << endl;  // 0(不是)
    cout << isPowerOfTwo(0) << endl;  // 0(不是)
    cout << isPowerOfTwo(-4) << endl; // 0(不是)
    return 0;
}

3.2 统计二进制中 1 的个数(汉明重量)

方法一:循环清零最低位 1(最优解)

原理:利用 x & (x - 1) 清零最低位的 1,每执行一次,计数器加 1,直到 x 变为 0,计数器的值就是 1 的个数。

代码示例:

cpp 复制代码
#include <iostream>
using namespace std;
// 统计 x 二进制中 1 的个数
int countOneBits(int x) {
    int cnt = 0;
    while (x) {
        x &= x - 1; // 清零最低位的 1
        cnt++;
    }
    return cnt;
}
int main() {
    cout << countOneBits(5) << endl;  // 2(0101)
    cout << countOneBits(7) << endl;  // 3(0111)
    cout << countOneBits(-1) << endl; // 32(32位全1)
    return 0;
}
方法二:逐位遍历(兼容所有场景)

原理:通过右移逐位判断每一位是否为 1,适合对负数也需要统计所有位(包括符号位)的场景。

代码示例:

cpp 复制代码
#include <iostream>
using namespace std;
int countOneBits(int x) {
    int cnt = 0;
    for (int i = 0; i < 32; i++) {
        if ((x >> i) & 1) {
            cnt++;
        }
    }
    return cnt;
}
int main() {
    cout << countOneBits(5) << endl;  // 2
    cout << countOneBits(-1) << endl; // 32
    return 0;
}

3.3 获取二进制中最低位的 1(树状数组核心)

核心原理

x & -x(利用补码特性),可快速获取最低位的 1 所在的位置,返回值是"只有最低位 1"的数。

示例:

  • x = 6(0110),-x = 11111010(补码),x & -x = 0010(2);
  • x = 5(0101),-x = 11111011(补码),x & -x = 0001(1);
  • x = 8(1000),-x = 11111000(补码),x & -x = 1000(8)。
    代码示例:
cpp 复制代码
#include <iostream>
using namespace std;
// 获取最低位的 1
int lowBit(int x) {
    return x & -x;
}
int main() {
    cout << lowBit(6) << endl; // 2
    cout << lowBit(5) << endl; // 1
    cout << lowBit(8) << endl; // 8
    return 0;
}
应用场景

lowBit 是树状数组(Fenwick Tree)的核心操作,用于快速更新和查询前缀和,在算法竞赛中广泛应用。

3.4 反转二进制位(面试高频题)

题目:将一个 32 位无符号整数的二进制位反转,例如:输入 00000010100101000001111010011100,输出 00111001011110000010100101000000。

核心思路

逐位提取原数的每一位,依次放到结果的对应位置,通过左移和或运算拼接结果。

代码示例:

cpp 复制代码
#include <iostream>
using namespace std;
// 反转 32 位无符号整数的二进制位
unsigned int reverseBits(unsigned int x) {
    unsigned int res = 0;
 for (int i = 0; i < 32; i++) {
        res = (res << 1) | (x & 1); // 提取x的最低位,放到res的最低位
        x >>= 1; // x右移,处理下一位
    }
    return res;
}
int main() {
    unsigned int x = 0b00000010100101000001111010011100;
    cout << reverseBits(x) << endl; // 输出 0b00111001011110000010100101000000 对应的十进制
    return 0;
}
相关推荐
小李小李快乐不已3 小时前
docker(1)-环境和基本概念
运维·c++·docker·容器
青岛少儿编程-王老师3 小时前
CCF编程能力等级认证GESP—C++1级—20260314
开发语言·c++
重庆小透明3 小时前
【java基础内容】ArrayList与LinkedList的区别及ArrayList源码解析
java·开发语言·后端·面试·职场和发展
東雪木3 小时前
Java学习——重载 (Overload) 与重写 (Override) 的核心区别、底层实现规则
java·开发语言·jvm·学习·java面试
爱丽_3 小时前
JVM GC 调优:内存指标、泄漏排查与线上自救
java·开发语言·jvm
AI自动化工坊3 小时前
OpenFang实战指南:用Rust构建高并发AI Agent操作系统
开发语言·人工智能·ai·rust·agent·ai agent
liu****3 小时前
LangChain-AI应用开发框架(一)
c++·python·langchain·本地部署大模型
承渊政道3 小时前
【优选算法】(实战剖析链表核心操作技巧)
开发语言·数据结构·c++·vscode·学习·算法·链表
wjs20243 小时前
Shell 变量
开发语言