C++位运算符详解与用法(含代码示例与面试题)

什么是C++位运算符?

C++ 提供了 6 个用于位操作的运算符,通常称为位运算符:

运算符名称符号形式运算结果
左移«x « n将 x 的各位左移 n 位,右侧补 0
右移»x » n将 x 的各位右移 n 位,左侧补 0
按位取反~~x将 x 的每一位取反
按位与&x & y仅当 x 与 y 对应位均为 1 时,结果位为 1
按位或|x | y只要 x 或 y 对应位中有一个为 1,结果位即为 1
按位异或^x ^ y当 x 与 y 对应位不同时,结果位为 1

上述运算符均为非修改型,即不会更改其操作数的值。

作者注

为便于说明,下文示例大多采用 4 位二进制值。实际程序中,位数取决于对象大小(如 2 字节对象将存储 16 位)。为提高可读性,代码示例之外的二进制字面量可能省略前缀 0b(如 0101 即 0b0101)。
位运算符适用于所有整型及 std::bitset。以下示例使用 std::bitset,因其二进制输出更直观。
切勿将位运算符用于有符号整型操作数;在 C++20 之前,许多运算结果由实现定义,或存在其他隐患。请改用无符号整型或 std::bitset。

最佳实践
为避免意外,请使用无符号整型或 std::bitset 进行位运算。

C++左移(«)与右移(»)运算符

左移运算符将位向左移动。左操作数为初始位序列,右操作数为移动位数。例如 x << 2 表示“生成一个新值,其位序列为 x 左移 2 位”。左操作数不变,右侧新位补 0。

示例:对 0011 进行左移

0011 << 1 → 0110  
0011 << 2 → 1100  
0011 << 3 → 1000(注意:第 3 次左移时最高位 1 被移出,该位永久丢失。)

右移运算符类似,但方向向右。

示例:对 1100 右移

1100 >> 1 → 0110  
1100 >> 2 → 0011  
1100 >> 3 → 0001(最低位被移出并丢失。)

C++ 示例(可编译运行)

#include <bitset>
#include <iostream>

int main()
{
    std::bitset<4> x{ 0b1100 };

    std::cout << x << '\n';
    std::cout << (x >> 1) << '\n'; // 右移 1 位 → 0110
    std::cout << (x << 1) << '\n'; // 左移 1 位 → 1000

    return 0;
}

输出:

1100
0110
1000

进阶说明
C++ 的位移与字节序无关:左移始终向最高位移动,右移向最低位移动。

C++输入输出中的 « 和 »

疑问:运算符 « 和 » 不是用于输入/输出吗?
确实如此。
现代程序很少直接用于位移,更常见的是与 std::cout 等输出流配合输出文本。例如:

#include <bitset>
#include <iostream>

int main()
{
    unsigned int x{ 0b0100 };
    x = x << 1; // 使用 << 进行左移
    std::cout << std::bitset<4>{ x } << '\n'; // 使用 << 输出

    return 0;
}

输出:

1000

编译器通过操作数类型决定行为:若左操作数为整型,则执行位移;若为输出流对象,则执行输出。
此能力依赖“运算符重载”,后续 13.5 课将介绍。

注意:若同一表达式中既用于左移又用于输出,需加括号:

std::cout << x << 1 << '\n';   // 先输出 x,再输出 1
std::cout << (x << 1) << '\n'; // 输出 x 左移 1 位结果

C++按位取反(~)运算符

按位取反运算符将每一位取反:

  • ~0011 → 1100
  • ~0000 0100 → 1111 1011

进阶说明
将取反结果视为整数时,位数不同导致值不同。示例:

#include <bitset>
#include <iostream>

int main()
{
    std::bitset<4> b4{ 0b100 }; // 0100
    std::bitset<8> b8{ 0b100 }; // 00000100

    std::cout << "初始值:\n";
    std::cout << "Bits:  " << b4 << ' ' << b8 << '\n';
    std::cout << "Values:" << b4.to_ulong() << ' ' << b8.to_ulong() << "\n\n";

    b4 = ~b4; // 1011
    b8 = ~b8; // 11111011

    std::cout << "取反后:\n";
    std::cout << "Bits:  " << b4 << ' ' << b8 << '\n';
    std::cout << "Values:" << b4.to_ulong() << ' ' << b8.to_ulong() << '\n';

    return 0;
}

输出:

初始值:
Bits:  0100 00000100
Values:4 4
取反后:
Bits:  1011 11111011
Values:11 251

C++按位或(|)运算符

按位或运算与逻辑或类似,但应用于每一位。若对应位中任一(或两者)为 1,结果位为 1。

示例:0b0101 | 0b0110

0 1 0 1 OR
0 1 1 0
---------
0 1 1 1

C++ 代码:

std::cout << (std::bitset<4>{0b0101} | std::bitset<4>{0b0110}) << '\n';
// 输出 0111

多操作数示例:

0 1 1 1 OR
0 0 1 1 OR
0 0 0 1
---------
0 1 1 1

C++按位与(&)运算符

按位与运算仅在两对应位均为 1 时结果位为 1。

示例:0b0101 & 0b0110 → 0100

多操作数示例:

0 0 0 1 AND
0 0 1 1 AND
0 1 1 1
---------
0 0 0 1

C++按位异或(^)运算符

按位异或(^)又称“排他或”。对应位不同时结果位为 1。

示例:0b0110 ^ 0b0011 → 0101

多操作数示例:

0 0 0 1 XOR
0 0 1 1 XOR
0 1 1 1
---------
0 1 0 1

C++位赋值运算符

与算术赋值运算符类似,C++ 提供位赋值运算符,这些运算符会修改左操作数:

运算符符号形式作用
左移赋值«=x «= n左移 n 位,结果写回 x
右移赋值»=x »= n右移 n 位,结果写回 x
与赋值&=x &= y按位与结果写回 x
或赋值|=x |= y按位或结果写回 x
异或赋值^=x ^= y按位异或结果写回 x

例如,可写 x >>= 1; 代替 x = x >> 1;

示例:

std::bitset<4> bits{ 0b0100 };
bits >>= 1;
std::cout << bits << '\n'; // 输出 0010

注意:无按位取反赋值运算符,因按位取反为单目运算。若需翻转所有位,可使用普通赋值:x = ~x;

C++位运算与整型提升(进阶)

当操作数整型宽度小于 int 时,会被提升为 intunsigned int,结果亦为 intunsigned int

相关说明见 10.2 课。

需特别注意两种情况:

  1. operator~operator<< 对宽度敏感,结果可能因操作数宽度而异。
  2. 将结果初始化或赋值给较小整型为窄化转换,列表初始化禁止,普通赋值可能警告。

示例(假设 32 位 int):

std::uint8_t c{ 0b00001111 };
std::cout << std::bitset<32>(~c) << '\n';      // 错误:输出 111...10000
std::cout << std::bitset<32>(c << 6) << '\n';  // 错误:输出 111...000000
std::uint8_t cneg{ ~c };                       // 错误:窄化转换
c = ~c;                                        // 可能警告

修正:使用 static_cast 转回目标类型:

std::cout << std::bitset<32>(static_cast<std::uint8_t>(~c)) << '\n';
std::cout << std::bitset<32>(static_cast<std::uint8_t>(c << 6)) << '\n';
std::uint8_t cneg{ static_cast<std::uint8_t>(~c) };
c = static_cast<std::uint8_t>(~c);

警告
位运算符会将小于 int 的整型提升为 int/unsigned int
operator~operator<< 对宽度敏感,使用前务必将结果 static_cast 回目标类型。

最佳实践
尽量避免对小于 int 的整型进行位移操作。

C++位运算小结

使用列式法总结位运算:

  • 按位或:列中任一为 1,则结果为 1。
  • 按位与:列中全为 1,则结果为 1。
  • 按位异或:列中 1 的个数为奇数,则结果为 1。

预告:C++位掩码与进阶技巧

下一课将探讨如何结合位掩码进行位操作。

C++位运算符测验与面试题

问题 1

  • a) 0110 » 2 的二进制结果?
  • b) 0011 | 0101 的二进制结果?
  • c) 0011 & 0101 的二进制结果?
  • d) (0011 | 0101) & 1001 的二进制结果?

问题 2

位旋转(rotate)与位移类似,但移出的位会绕回另一端。例如 0b1001 « 1 得 0b0010,而左旋转 1 位得 0b0011。请实现一个对 std::bitset<4> 的左旋转函数 rotl,可使用 test()set()

问题 3(加分)

不使用 test()set(),仅用位运算符重做问题 2。

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"