位元與移位運算子 (C# 參考)

位元和移位運算子包括一元位元補數、二進位左右移位、不帶正負號的右移位,以及二進位邏輯 AND、OR 和互斥 OR 運算子。 這些運算元會採用整數數值型別char 型別的運算元。

這些運算子已針對 intuintlongulong 型別進行定義。 當兩個運算元都是其他整數型別 (sbytebyteshortushortchar) 時,它們的值會轉換成 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

由於移位運算子僅針對 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).PadLeft(4, '0'), 4}");
// Output:
// Before: 1001
// After:  0010

會根據左邊運算元的類型來設定高位空位元位置,如下所示:

  • 若左邊運算元的型別為 intlong,則右移運算元會執行算數位移:左邊運算元之最高有效位元 (正負號位元) 的值會傳播到高階空位元位置。 也就是說,若左邊運算元不是負值且在為負值時被設定為一,則高位空位元位置會被設定為零。

    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).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 運算子 &

& 運算子會計算其運算元的位元邏輯 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 運算子

邏輯互斥 OR 運算子 ^

^ 運算子會計算其整數運算元的位元邏輯互斥 OR,也稱為位元邏輯 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

邏輯 OR 運算子 |

| 運算子會計算其運算元的位元邏輯 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 是預先定義的運算子,且作業結果可以明確轉換成 xT 型別,則形式 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

運算子優先順序

下列清單會從優先順序最高位元及移位運算子依序排序到優先順序最低的運算子:

  • 位元補充運算子 ~
  • 移位運算子 <<>>>>>
  • 邏輯 AND 運算子 &
  • 邏輯互斥 OR 運算子 ^
  • 邏輯 OR 運算子 |

使用括號 () 來變更由運算子優先順序所強制執行的評估順序:

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 列舉型別 Txyx & y 運算式會產生與 (T)((U)x & (U)y) 運算式相同結果。

您通常會搭配使用 Flags 屬性定義的列舉型別使用位元邏輯運算子。 如需詳細資訊,請參閱列舉型別文章中的作為位元旗標的列舉型別一節。

運算子是否可多載

使用者定義型別可以多載~<<>>>>>&|, 和 ^ 運算子。 當二元運算子多載時,對應的複合指派運算子也會隱含地多載。 使用者定義型別無法明確地多載複合指派運算子。

若使用者定義型別 T 多載了 <<>>>>> 運算子,則左邊運算元的型別必須是 T。 在 C# 10 和更早版本中,右邊運算元的型別必須是 int;從 C# 11 開始,多載移位運算子的右運算元可以是任意型別。

C# 語言規格

如需詳細資訊,請參閱 C# 語言規格的下列幾節:

另請參閱