相关文章推荐
温文尔雅的铁链  ·  20%的微软SQL ...·  1 年前    · 
心软的黑框眼镜  ·  apache httpd ...·  1 年前    · 
卖萌的口罩  ·  Query Processing ...·  2 年前    · 
细心的桔子  ·  LINQ 基础概念 - 简书·  2 年前    · 

位运算符和移位运算符包括一元位补、二进制左移和右移、无符号右移、二进制逻辑 AND、OR 和异或运算符。 这些操作数采用 整型数值类型 字符型 操作数。

  • 一元 ~ (按位求补) 运算符
  • 二进制 << (左移) >> (右移) >>> (无符号右移) 运算符
  • 二进制 & (逻辑 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 运算的结果可能不会隐式转换为 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 >> 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 int count = -31; int c = 0b_0001; Console.WriteLine($"{c} << {count} is {c << count}"); // Output: // 1 << -31 is 2

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

    枚举逻辑运算符

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

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

    运算符可重载性

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

    如果用户定义类型 T 重载了 <<>>>>> 运算符,则左侧操作数的类型必须为 T。 在 C# 10 及更早版本中,右侧操作数的类型必须为 int;从 C# 11 开始,重载移位运算符的右侧操作数的类型可以是任意类型。

    C# 语言规范

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

  • 按位求补运算符
  • 移位运算符
  • 逻辑运算符
  • C# 11 - 宽松的移位要求
  • C# 11 - 逻辑右移运算符
  • C# 参考
  • C# 运算符和表达式
  • Boolean 逻辑运算符
  •