位运算符和移位运算符(C# 参考)
位运算符和移位运算符包括一元位补、二进制左移和右移、无符号右移、二进制逻辑 AND、OR 和异或运算符。 这些操作数采用整型数值类型或字符型操作数。
这些运算符是针对 int
、uint
、long
和 ulong
类型定义的。 如果两个操作数都是其他整数类型(sbyte
、byte
、short
、ushort
或 char
),它们的值将转换为 int
类型,这也是一个运算的结果类型。 如果操作数是不同的整数类型,它们的值将转换为最接近的包含整数类型。 有关详细信息,请参阅 C# 语言规范的数值提升部分。 复合运算符(如 >>=
)不会将其参数转换为 int
,也不会具有结果类型 int
。
&
、|
和 ^
运算符也是为 bool
类型的操作数定义的。 有关详细信息,请参阅布尔逻辑运算符。
位运算和移位运算永远不会导致溢出,并且不会在已检查和未检查的上下文中产生相同的结果。
按位求补运算符 ~
~
运算符通过反转每个位产生其操作数的按位求补:
uint a = 0b_0000_1111_0000_1111_0000_1111_0000_1100;
uint b = ~a;
Console.WriteLine(Convert.ToString(b, toBase: 2));
// Output:
// 11110000111100001111000011110011
也可以使用 ~
符号来声明终结器。 有关详细信息,请参阅终结器。
左移位运算符 <<
<<
运算符将其左侧操作数向左移动右侧操作数定义的位数。 有关右侧操作数如何定义移位计数的信息,请参阅移位运算符的移位计数部分。
左移运算会放弃超出结果类型范围的高阶位,并将低阶空位位置设置为零,如以下示例所示:
uint x = 0b_1100_1001_0000_0000_0000_0000_0001_0001;
Console.WriteLine($"Before: {Convert.ToString(x, toBase: 2)}");
uint y = x << 4;
Console.WriteLine($"After: {Convert.ToString(y, toBase: 2)}");
// Output:
// Before: 11001001000000000000000000010001
// After: 10010000000000000000000100010000
由于移位运算符仅针对 int
、uint
、long
和 ulong
类型定义,因此运算的结果始终包含至少 32 位。 如果左侧操作数是其他整数类型(sbyte
、byte
、short
、ushort
或 char
),则其值将转换为 int
类型,如以下示例所示:
byte a = 0b_1111_0001;
var b = a << 8;
Console.WriteLine(b.GetType());
Console.WriteLine($"Shifted byte: {Convert.ToString(b, toBase: 2)}");
// Output:
// System.Int32
// Shifted byte: 1111000100000000
右移位运算符 >>
>>
运算符将其左侧操作数向右移动右侧操作数定义的位数。 有关右侧操作数如何定义移位计数的信息,请参阅移位运算符的移位计数部分。
右移位运算会放弃低阶位,如以下示例所示:
uint x = 0b_1001;
Console.WriteLine($"Before: {Convert.ToString(x, toBase: 2), 4}");
uint y = x >> 2;
Console.WriteLine($"After: {Convert.ToString(y, toBase: 2).PadLeft(4, '0'), 4}");
// Output:
// Before: 1001
// After: 0010
高顺序空位位置是根据左侧操作数类型设置的,如下所示:
如果左侧操作数的类型是
int
或long
,则右移运算符将执行 算术移位:左侧操作数的最高有效位(符号位)的值将传播到高顺序空位位置。 也就是说,如果左侧操作数为非负,高顺序空位位置设置为零,如果为负,则将该位置设置为 1。int a = int.MinValue; Console.WriteLine($"Before: {Convert.ToString(a, toBase: 2)}"); int b = a >> 3; Console.WriteLine($"After: {Convert.ToString(b, toBase: 2)}"); // Output: // Before: 10000000000000000000000000000000 // After: 11110000000000000000000000000000
如果左侧操作数的类型是
uint
或ulong
,则右移运算符执行逻辑移位:高顺序空位位置始终设置为零。uint c = 0b_1000_0000_0000_0000_0000_0000_0000_0000; Console.WriteLine($"Before: {Convert.ToString(c, toBase: 2), 32}"); uint d = c >> 3; Console.WriteLine($"After: {Convert.ToString(d, toBase: 2).PadLeft(32, '0'), 32}"); // Output: // Before: 10000000000000000000000000000000 // After: 00010000000000000000000000000000
注意
使用无符号右移运算符可以对带符号整数类型的操作数执行逻辑移位。 这优于将左侧操作数强制转换为无符号类型,然后将移位操作的结果强制转换回带符号类型。
无符号右移运算符 >>>
在 C# 11 及更高版本中可用,>>>
运算符将其左侧操作数向右移动其右侧操作数定义的位数。 有关右侧操作数如何定义移位计数的信息,请参阅移位运算符的移位计数部分。
>>>
运算符始终执行逻辑移位。 也就是说,无论左侧操作数的类型如何,高顺序空位位置始终设置为零。 如果左侧操作数是带符号类型,>>
运算符将执行算术移位(即,最高有效位的值传播到高顺序空位位置)。 以下示例演示了对于负左操作数,>>
和 >>>
运算符之间的差别:
int x = -8;
Console.WriteLine($"Before: {x,11}, hex: {x,8:x}, binary: {Convert.ToString(x, toBase: 2), 32}");
int y = x >> 2;
Console.WriteLine($"After >>: {y,11}, hex: {y,8:x}, binary: {Convert.ToString(y, toBase: 2), 32}");
int z = x >>> 2;
Console.WriteLine($"After >>>: {z,11}, hex: {z,8:x}, binary: {Convert.ToString(z, toBase: 2).PadLeft(32, '0'), 32}");
// Output:
// Before: -8, hex: fffffff8, binary: 11111111111111111111111111111000
// After >>: -2, hex: fffffffe, binary: 11111111111111111111111111111110
// After >>>: 1073741822, hex: 3ffffffe, binary: 00111111111111111111111111111110
逻辑“与”运算符 &
&
运算符计算其整型操作数的位逻辑 AND:
uint a = 0b_1111_1000;
uint b = 0b_1001_1101;
uint c = a & b;
Console.WriteLine(Convert.ToString(c, toBase: 2));
// Output:
// 10011000
对于 bool
操作数,&
运算符对其操作数执行逻辑 AND 运算。 一元 &
运算符是 address-of 运算符。
逻辑异或运算符 ^
^
运算符计算其整型操作数的位逻辑异或,也称为位逻辑 XOR:
uint a = 0b_1111_1000;
uint b = 0b_0001_1100;
uint c = a ^ b;
Console.WriteLine(Convert.ToString(c, toBase: 2));
// Output:
// 11100100
对于 bool
操作数,^
运算符对其操作数执行逻辑异或运算。
逻辑或运算符 |
|
运算符计算其整型操作数的位逻辑 OR:
uint a = 0b_1010_0000;
uint b = 0b_1001_0001;
uint c = a | b;
Console.WriteLine(Convert.ToString(c, toBase: 2));
// Output:
// 10110001
对于 bool
操作数,|
运算符对其操作数执行逻辑 OR 运算。
复合赋值
对于二元运算符 op
,窗体的复合赋值表达式
x op= y
等效于
x = x op y
不同的是 x
只计算一次。
以下示例演示了使用位运算符和移位运算符的复合赋值的用法:
uint INITIAL_VALUE = 0b_1111_1000;
uint a = INITIAL_VALUE;
a &= 0b_1001_1101;
Display(a); // output: 10011000
a = INITIAL_VALUE;
a |= 0b_0011_0001;
Display(a); // output: 11111001
a = INITIAL_VALUE;
a ^= 0b_1000_0000;
Display(a); // output: 01111000
a = INITIAL_VALUE;
a <<= 2;
Display(a); // output: 1111100000
a = INITIAL_VALUE;
a >>= 4;
Display(a); // output: 00001111
a = INITIAL_VALUE;
a >>>= 4;
Display(a); // output: 00001111
void Display(uint x) => Console.WriteLine($"{Convert.ToString(x, toBase: 2).PadLeft(8, '0'), 8}");
由于数值提升,op
运算的结果可能不会隐式转换为 x
的 T
类型。 在这种情况下,如果 op
是预定义的运算符并且运算的结果可以显式转换为 x
的类型 T
,则形式为 x op= y
的复合赋值表达式等效于 x = (T)(x op y)
,但 x
仅计算一次。 以下示例演示了该行为:
byte x = 0b_1111_0001;
int b = x << 8;
Console.WriteLine($"{Convert.ToString(b, toBase: 2)}"); // output: 1111000100000000
x <<= 8;
Console.WriteLine(x); // output: 0
运算符优先级
以下列表按位运算符和移位运算符从最高优先级到最低优先级排序:
- 按位求补运算符
~
- 移位运算符
<<
、>>
和>>>
- 逻辑与运算符
&
- 逻辑异或运算符
^
- 逻辑或运算符
|
使用括号 ()
可以更改运算符优先级决定的计算顺序:
uint a = 0b_1101;
uint b = 0b_1001;
uint c = 0b_1010;
uint d1 = a | b & c;
Display(d1); // output: 1101
uint d2 = (a | b) & c;
Display(d2); // output: 1000
void Display(uint x) => Console.WriteLine($"{Convert.ToString(x, toBase: 2), 4}");
要了解按优先级排序的完整 C# 运算符列表,请参阅 C# 运算符一文中的运算符优先级部分。
移位运算符的移位计数
对于 x << count
、x >> count
和 x >>> count
表达式,实际移位计数取决于 x
的类型,如下所示:
如果
x
的类型为int
或uint
,则移位计数由右侧操作数的低阶五位定义。 也就是说,移位计数通过count & 0x1F
(或count & 0b_1_1111
)计算得出。如果
x
的类型为long
或ulong
,则移位计数由右侧操作数的低阶六位定义。 也就是说,移位计数通过count & 0x3F
(或count & 0b_11_1111
)计算得出。
以下示例演示了该行为:
int count1 = 0b_0000_0001;
int count2 = 0b_1110_0001;
int a = 0b_0001;
Console.WriteLine($"{a} << {count1} is {a << count1}; {a} << {count2} is {a << count2}");
// Output:
// 1 << 1 is 2; 1 << 225 is 2
int b = 0b_0100;
Console.WriteLine($"{b} >> {count1} is {b >> count1}; {b} >> {count2} is {b >> count2}");
// Output:
// 4 >> 1 is 2; 4 >> 225 is 2
int count = -31;
int c = 0b_0001;
Console.WriteLine($"{c} << {count} is {c << count}");
// Output:
// 1 << -31 is 2
注意
如前例所示,即使右侧操作符的值大于左侧操作符中的位数,移位运算的结果也不可为零。
枚举逻辑运算符
所有枚举类型还支持 ~
、&
、|
和 ^
运算符。 对于相同枚举类型的操作数,对底层整数类型的相应值执行逻辑运算。 例如,对于具有底层类型 U
的枚举类型 T
的任何 x
和 y
,x & y
表达式生成与 (T)((U)x & (U)y)
表达式相同的结果。
通常使用具有枚举类型的位逻辑运算符,该枚举类型使用 Flags 特性定义。 有关详细信息,请参阅枚举类型一文的作为位标记的枚举类型部分。
运算符可重载性
用户定义的类型可以重载~
、<<
、>>
、>>>
、&
、|
和 ^
运算符。 重载二元运算符时,对应的复合赋值运算符也会隐式重载。 用户定义类型不能显式重载复合赋值运算符。
如果用户定义类型 T
重载了 <<
、>>
或 >>>
运算符,则左侧操作数的类型必须为 T
。 在 C# 10 及更早版本中,右侧操作数的类型必须为 int
;从 C# 11 开始,重载移位运算符的右侧操作数的类型可以是任意类型。
C# 语言规范
有关更多信息,请参阅 C# 语言规范的以下部分: