位运算符和移位运算符(C# 参考)

以下运算符使用整数类型char 类型的操作数执行位运算或移位运算:

这些运算符是针对 intuintlongulong 类型定义的。 如果两个操作数都是其他整数类型(sbytebyteshortushortchar),它们的值将转换为 int 类型,这也是一个运算的结果类型。 如果操作数是不同的整数类型,它们的值将转换为最接近的包含整数类型。 有关详细信息,请参阅 C# 语言规范数值提升部分。

&|^ 运算符也是为 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

由于移位运算符仅针对 intuintlongulong 类型定义,因此运算的结果始终包含至少 32 位。 如果左侧操作数是其他整数类型(sbytebyteshortushortchar),则其值将转换为 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), 4}");
// Output:
// Before: 1001
// After:    10

高顺序空位位置是根据左侧操作数类型设置的,如下所示:

  • 如果左侧操作数的类型是 intlong,则右移运算符将执行 算术移位:左侧操作数的最高有效位(符号位)的值将传播到高顺序空位位置。 也就是说,如果左侧操作数为非负,高顺序空位位置设置为零,如果为负,则将该位置设置为 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
    
  • 如果左侧操作数的类型是 uintulong,则右移运算符执行逻辑移位:高顺序空位位置始终设置为零。

    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), 32}");
    // Output:
    // Before: 10000000000000000000000000000000
    // After:     10000000000000000000000000000
    

逻辑与运算符 &

& 运算符计算其整型操作数的位逻辑 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: 1111000

a = INITIAL_VALUE;
a <<= 2;
Display(a);  // output: 1111100000

a = INITIAL_VALUE;
a >>= 4;
Display(a);  // output: 1111

void Display(uint x) => Console.WriteLine($"{Convert.ToString(x, toBase: 2), 8}");

由于数值提升op 运算的结果可能不会隐式转换为 xT 类型。 在这种情况下,如果 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# 运算符一文中的运算符优先级部分。

移位运算符的移位计数

对于移位运算符 <<>>,右侧操作数的类型必须为 int,或具有预定义隐式数值转换int 的类型。

对于 x << countx >> count 表达式,实际移位计数取决于 x 的类型,如下所示:

  • 如果 x 的类型为 intuint,则移位计数由右侧操作数的低阶五位定义。 也就是说,移位计数通过 count & 0x1F(或 count & 0b_1_1111)计算得出。

  • 如果 x 的类型为 longulong,则移位计数由右侧操作数的低阶六位定义。 也就是说,移位计数通过 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

备注

如前例所示,即使右侧操作符的值大于左侧操作符中的位数,移位运算的结果也不可为零。

枚举逻辑运算符

所有枚举类型还支持 ~&|^ 运算符。 对于相同枚举类型的操作数,对底层整数类型的相应值执行逻辑运算。 例如,对于具有底层类型 U 的枚举类型 T 的任何 xyx & y 表达式生成与 (T)((U)x & (U)y) 表达式相同的结果。

通常使用具有枚举类型的位逻辑运算符,该枚举类型使用 Flags 特性定义。 有关详细信息,请参阅枚举类型一文的作为位标记的枚举类型部分。

运算符可重载性

用户定义的类型可以重载~<<>>&|^ 运算符。 重载二元运算符时,对应的复合赋值运算符也会隐式重载。 用户定义类型不能显式重载复合赋值运算符。

如果用户定义类型 T 重载 <<>> 运算符,则左侧操作数的类型必须为 T 且右侧操作数的类型必须为 int

C# 语言规范

有关更多信息,请参阅 C# 语言规范的以下部分:

另请参阅