O-2 位运算符

位运算符

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

Operator Symbol Form The operation returns a value where:
left shift << x << n the bits from x are shifted left by n positions, new bits are 0.
right shift >> x >> n the bits from x are shifted right by n positions, new bits are 0.
bitwise NOT ~ ~x each bit from x is flipped.
bitwise AND & x & y each bit is set when both corresponding bits in x and y are 1.
bitwise OR x
bitwise XOR ^ x ^ y each bit is set when the corresponding bits in x and y are different.

这些是非修饰运算符(它们不会修改其操作数)。

作者注

在以下示例中,我们将主要处理4位二进制值。此举旨在简化示例并方便说明。实际程序中使用的位数取决于对象大小(例如2字节对象将存储16位)。

为提升可读性,代码示例外的二进制常量可省略0b前缀(例如0b0101可简写为0101)。

位运算符适用于整数类型及std::bitset。示例中将使用std::bitset,因其输出结果更易于二进制展示。

避免对带符号整数操作数使用位运算符,因为在 C++20 之前,许多运算符会返回实现定义的结果,或者存在其他潜在陷阱,而使用无符号操作数(或 std::bitset)可轻松规避这些问题。

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


位移运算符:左移运算符(<<)与右移运算符(>>)

左移运算符bitwise left shift(<<)将位向左移动。左操作数是提供初始位序列的表达式,右操作数是指定位移位数的整数。例如,当我们写 x << 2 时,表示“生成一个值,其中 x 的位向左移动了 2 个位置”。

左操作数不会被修改,从右侧移入的新位均为 0。

以下是将位序列 0011 左移的示例:

0011 \<\< 1 is 0110
0011 \<\< 2 is 1100
0011 \<\< 3 is 1000

请注意在第三种情况下,我们将数字末尾的1位移除了!从位序列末尾移除的位将永远丢失。

右移运算符bitwise right shift(>>)的工作原理类似,但会将位向右移位。

以下是位序列1100向右移位的示例:

1100 />/> 1 is 0110
1100 />/> 2 is 0011
1100 />/> 3 is 0001

请注意,在第三种情况下,我们向右偏移了一点,因此数字的右端被丢失了。

下面我们用C++编写一个示例,你可以编译并运行它:

#include <bitset>
#include <iostream>

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

    std::cout << x << '\n';
    std::cout << (x >> 1) << '\n'; // shift right by 1, yielding 0110
    std::cout << (x << 1) << '\n'; // shift left by 1, yielding 1000

    return 0;
}

这将输出:

image

对于高级读者
C++中的位移操作与端序endian无关。左移始终指向最高有效位,右移则指向最低有效位。


什么!?运算符operator<<和运算符operator>>不是用于输入和输出吗?

确实如此。

如今的程序通常很少使用位移运算符来进行位移操作。相反,位移运算符更常与std::cout(或其他输出流对象)配合使用来输出文本。请看以下程序:

#include <bitset>
#include <iostream>

int main()
{
    unsigned int x { 0b0100 };
    x = x << 1; // use operator<< for left shift
    std::cout << std::bitset<4>{ x } << '\n'; // use operator<< for output

    return 0;
}

该程序输出:

image

在上面的程序中,运算符operator<<如何知道在一种情况下执行位移操作,而在另一种情况下输出x?答案在于它会检查操作数的类型。如果左操作数是整数类型,则运算符operator<<会执行常规的位移操作;如果左操作数是输出流对象(如std::cout),则它会执行输出操作。

运算符operator>>的情况也是如此。

相关内容
运算符根据实参类型改变行为的能力,正是基于运算符重载这一特性实现的。我们将在第13.5节——I/O运算符重载介绍中详细介绍该特性。

请注意,若同时使用运算符operator<<进行输出和左移操作,则需使用括号来实现左移:

#include <bitset>
#include <iostream>

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

	std::cout << x << 1 << '\n'; // print value of x (0110), then 1
	std::cout << (x << 1) << '\n'; // print x left shifted by 1 (1100)

	return 0;
}

这将输出:

image

第一行输出变量x的值(0110),随后输出字面量1。第二行输出变量x左移一位后的值(1100)。


位运算取反

位运算取反bitwise NOT运算符(~)的概念很简单:它只是将每个位从0翻转为1,或从1翻转为0。

~0011 is 1100
~0000 0100 is 1111 1011

对于高级读者

当被解释为整数时,位取反运算的结果位数会影响最终值。

以下程序演示了这一特性:

#include <bitset>
#include <iostream>

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

    std::cout << "Initial values:\n";
    std::cout << "Bits: " << b4 << ' ' << b8 << '\n';
    std::cout << "Values: " << b4.to_ulong() << ' ' << b8.to_ulong() << "\n\n";

    b4 = ~b4; // flip b4 to 1011
    b8 = ~b8; // flip b8 to 1111 1011

    std::cout << "After bitwise NOT:\n";
    std::cout << "Bits: " << b4 << ' ' << b8 << '\n';
    std::cout << "Values: " << b4.to_ulong() << ' ' << b8.to_ulong() << '\n';

    return 0;
}

这将输出:

image

初始时,b4和b8均设置为0b100。在补零后,b4最终变为0100,b8变为00000100,该结果在下一行输出。

随后我们使用 to_ulong() 成员函数将位值解释为长整型。可见 b4 和 b8 均输出数值 4。尽管位数不同,它们表示的数值相同,这是因为前导零位对解释后的整型数值不产生影响。

接着使用位反转操作翻转各位,使得 b4 变为 1011,b8 变为 1111 1011。当以整数形式输出时,分别得到 11 和 251。可见这两个值不再相同,这是因为前导 1 位会影响解释后的整数值,而 b8 的前导 1 位数量多于 b4。


位运算或

位运算或Bitwise OR(|)的工作原理与其逻辑或类似。如您所知,逻辑或运算当任一操作数为真时返回真(1),否则返回假(0)。

然而,逻辑或运算对整个操作数进行处理(产生单一的真或假结果),而位或运算则对操作数中的每对位进行处理(为每个位产生单一的真或假结果)。

让我们通过一个示例来说明。考虑表达式 0b0101 | 0b0110。

提示
要手动执行任何二进制位运算,最简便的方法是将两个操作数对齐排列如下:
0 1 0 1 OR(或你正在执行的任何位运算)
0 1 1 0
然后,对每列位执行运算,并将结果写在下方。

在第一列中,0 或 0 均为 0,因此我们在横线下方写下 0。

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

第二列,1 或 1 结果为 1。第三列,0 或 1 结果为 1。第四列,1 或 0 结果为 1。

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

我们的结果是二进制数0111。

#include <bitset>
#include <iostream>

int main()
{
	std::cout << (std::bitset<4>{ 0b0101 } | std::bitset<4>{ 0b0110 }) << '\n';

	return 0;
}

这将输出:

image

我们同样可以对复合位运算或表达式进行处理,例如 0b0111 | 0b0011 | 0b0001。若某列中存在任意位为 1,则该列结果即为 1:

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

以下是上述内容的代码:

#include <bitset>
#include <iostream>

int main()
{
	std::cout << (std::bitset<4>{ 0b0111 } | std::bitset<4>{ 0b0011 } | std::bitset<4>{ 0b0001 }) << '\n';

	return 0;
}

这将输出

image


位与运算

位与运算Bitwise AND(&)的工作原理与上述类似,但使用的是与逻辑而非或逻辑。具体而言,对于操作数中的每对位,若两位均为1,则位与运算将结果位设为真(1);否则设为假(0)。

考虑表达式 0b0101 & 0b0110。将各位对齐后,对每列位执行位与运算:

0 1 0 1 AND
0 1 1 0
--------
0 1 0 0
#include <bitset>
#include <iostream>

int main()
{
	std::cout << (std::bitset<4>{ 0b0101 } & std::bitset<4>{ 0b0110 }) << '\n';

	return 0;
}

这将输出

image

同样地,我们也可以对复合位与表达式进行相同操作,例如 0b0001 & 0b0011 & 0b0111。若某列所有位均为 1,则该列结果为 1。

0 0 0 1 AND
0 0 1 1 AND
0 1 1 1
--------
0 0 0 1
#include <bitset>
#include <iostream>

int main()
{
	std::cout << (std::bitset<4>{ 0b0001 } & std::bitset<4>{ 0b0011 } & std::bitset<4>{ 0b0111 }) << '\n';

	return 0;
}

这将输出

image


位运算异或

最后一个运算符是位运算异或bitwise XOR(^),也称为排他或exclusive or

对于操作数中的每对位,当且仅当其中一位为1时,位异或运算将结果位设为真(1);否则设为假(0)。换言之,当配对位不同(一位为0而另一位为1)时,位异或运算将结果位设为真。

考虑表达式 0b0110 ^ 0b0011:

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

同样可以求值复合异或表达式的列样式,例如 0b0001 ^ 0b0011 ^ 0b0111。若某列中1位数的数量为偶数,则结果为0;若某列中1位数的数量为奇数,则结果为1。

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

位运算赋值运算符

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

Operator Symbol Form The operation modifies the left operand where:
left shift << x <<= n the bits in x are shifted left by n positions, new bits are 0.
right shift >> x >>= n the bits in x are shifted right by n positions, new bits are 0.
bitwise AND & x &= y each bit is set when both corresponding bits in x and y are 1.
bitwise OR x
bitwise XOR ^ x ^= y each bit is set when the corresponding bits in x and y are different.

例如,你可以用 x >>= 1; 代替 x = x >> 1;。

#include <bitset>
#include <iostream>

int main()
{
    std::bitset<4> bits { 0b0100 };
    bits >>= 1;
    std::cout << bits << '\n';

    return 0;
}

这个程序将输出

image

顺带一提……
没有位运算取反赋值运算符。这是因为其他位运算符都是二元运算符,而位运算取反是单项运算符(那么~=运算符的右侧该放什么呢?)。若需翻转对象的所有位,可使用常规赋值语法:x = ~x;


位运算符对较小的整数类型执行整数提升

如果位运算符的操作数是小于 int 的整数类型,这些操作数将被提升(转换)为 int 或 unsigned int,返回的结果也将是 int 或 unsigned int。例如,若操作数为unsigned short类型,它们将被提升(转换)为unsigned int类型,运算结果也将以unsigned int类型返回。

多数情况下这不会造成影响。

相关内容
我们在第10.2节——浮点数与整数提升中介绍了整数提升。

然而,当对小于 int 或 unsigned int 的整数类型使用位运算符时,需注意两种情况:

  • operator~ 运算符和 operator<< 运算符对宽度敏感,其结果可能因操作数的宽度而不同。
  • 将结果初始化或赋值给更小整数类型的变量属于窄化转换(因将 int 或 unsigned int 转换为更小整数类型可能导致数据丢失)。列表初始化中禁止此操作,编译器对窄化赋值的警告行为可能因编译器而异。

以下程序演示了这些问题(假设 int 为 32 位):

#include <bitset>
#include <cstdint>
#include <iostream>

int main()
{
    std::uint8_t c { 0b00001111 };

    std::cout << std::bitset<32>(~c) << '\n';     // incorrect: prints 11111111111111111111111111110000
    std::cout << std::bitset<32>(c << 6) << '\n'; // incorrect: prints 0000000000000000001111000000
    std::uint8_t cneg { ~c };                     // error: narrowing conversion from unsigned int to std::uint8_t
    c = ~c;                                       // possible warning: narrowing conversion from unsigned int to std::uint8_t

    return 0;
}

这些问题可通过使用 static_cast 将位运算结果转换回更窄的整数类型来解决。以下程序可产生正确结果:

#include <bitset>
#include <cstdint>
#include <iostream>

int main()
{
    std::uint8_t c { 0b00001111 };

    std::cout << std::bitset<32>(static_cast<std::uint8_t>(~c)) << '\n';     // correct: prints 00000000000000000000000011110000
    std::cout << std::bitset<32>(static_cast<std::uint8_t>(c << 6)) << '\n'; // correct: prints 0000000000000000000011000000
    std::uint8_t cneg { static_cast<std::uint8_t>(~c) };                     // compiles
    c = static_cast<std::uint8_t>(~c);                                       // no warning

    return 0;
}

警告
位运算符会将较窄整数类型的操作数提升为 int 或 unsigned int。
~ 运算符和 << 运算符对宽度敏感,可能根据操作数的宽度产生不同结果。使用前请将此类位运算的结果通过 static_cast 转换回较窄整数类型,以确保结果正确。

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


总结

总结如何利用列法评求值运算:

求值位或运算bitwise OR时,若某列存在任意位为1,则该列结果为1。
求值位与运算bitwise AND时,若某列所有位均为1,则该列结果为1。
求值位异或运算bitwise XOR时,若某列1位数为奇数,则该列结果为1。

在下一课中,我们将探讨如何结合位掩码使用这些运算符来实现位操作。


测验时间

问题 #1

a) 0110 >> 2 在二进制中计算结果是什么?

显示答案

0110 >> 2 计算结果为 0001

b) 下列运算结果在二进制中是什么:0011 | 0101?

显示答案

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

c) 0011 & 0101 的二进制结果是什么?

显示答案

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

d) (0011 | 0101) & 1001 的二进制结果是什么?

显示答案

Inside the parenthesis:

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

Then:

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

问题 #2

位移运算类似于位移操作,但不同之处在于:移出的一端位数会补回另一端。例如 0b1001 << 1 结果为 0b0010,但左移 1 位则得到 0b0011。请实现一个对 std::bitset<4> 执行左移的函数。此处允许使用 test() 和 set() 方法。

以下代码应能执行:

#include <bitset>
#include <iostream>

// "rotl" stands for "rotate left"
std::bitset<4> rotl(std::bitset<4> bits)
{
// Your code here
}

int main()
{
	std::bitset<4> bits1{ 0b0001 };
	std::cout << rotl(bits1) << '\n';

	std::bitset<4> bits2{ 0b1001 };
	std::cout << rotl(bits2) << '\n';

	return 0;
}

并打印以下内容:

0010
0011

显示解答

#include <bitset>
#include <iostream>

std::bitset<4> rotl(std::bitset<4> bits)
{
	// keep track of whether the leftmost bit was a 1
	const bool leftbit{ bits.test(3) };

	bits <<= 1; // do left shift (which shifts the leftmost bit off the end)

	// if the left bit was a 1
	if (leftbit)
		bits.set(0); // set the rightmost bit to a 1

	return bits;
}

int main()
{
	std::bitset<4> bits1{ 0b0001 };
	std::cout << rotl(bits1) << '\n';

	std::bitset<4> bits2{ 0b1001 };
	std::cout << rotl(bits2) << '\n';

	return 0;
}

我们将该函数命名为“rotl”而非“rotateLeft”,因为“rotl”是计算机科学领域公认的标准名称,同时也是标准库函数std::rotl的名称。

image


问题 #3

额外加分题:重新完成测验 #2,但不要使用test和set函数(使用位运算符)。

显示提示

提示:我们如何将最左边的位移到最右边的位置?

显示提示

提示:不妨尝试转换视角

显示解答

#include <bitset>
#include <iostream>

// h/t to reader Chris for this solution
std::bitset<4> rotl(std::bitset<4> bits)
{
	// bits << 1 does the left shift
	// bits >> 3 handle the rotation of the leftmost bit
	return (bits<<1) | (bits>>3);
}

int main()
{
	std::bitset<4> bits1{ 0b0001 };
	std::cout << rotl(bits1) << '\n';

	std::bitset<4> bits2{ 0b1001 };
	std::cout << rotl(bits2) << '\n';

	return 0;
}

image

posted @ 2026-02-20 18:26  游翔  阅读(0)  评论(0)    收藏  举报