Operatory bitowe i shift (odwołanie w C#)
Operatory bitowe i przesunięcia obejmują jednoargumentowe uzupełnianie bitowe, binarne przesunięcie w lewo i w prawo, niepodpisane przesunięcie w prawo oraz binarne operatory logiczne AND, OR oraz wyłączne operatory OR. Te operandy przyjmują operandy całkowitych typów liczbowych lub typu char .
- Operator jednoargumentowy
~
(uzupełnienie bitowe) - Operatory binarne
<<
(przesunięcie w lewo),>>
(przesunięcie w prawo) i>>>
(bez znaku przesunięcia w prawo) - Operatory binarne
&
(logiczne AND),|
(logiczne OR) i^
(logiczne wyłączne OR)
Te operatory są definiowane dla int
typów , uint
, long
i ulong
. Gdy oba operandy mają inne typy całkowite (sbyte
, byte
, short
, ushort
lub char
), ich wartości są konwertowane na int
typ, który jest również typem wyniku operacji. Gdy operandy mają różne typy całkowite, ich wartości są konwertowane na najbliższy typ całkowity. Aby uzyskać więcej informacji, zobacz sekcję Promocje liczbowe specyfikacji języka C#. Operatory złożone (takie jak >>=
) nie konwertują argumentów na int
lub mają typ wyniku jako int
.
Operatory &
, |
i ^
są również zdefiniowane dla operandów bool
typu. Aby uzyskać więcej informacji, zobacz Operatory logiczne logiczne.
Operacje bitowe i przesunięcia nigdy nie powodują przepełnienia i generują te same wyniki w kontekstach zaewidencjonowanych i niezaznaczonej .
Operator uzupełniania bitowego ~
Operator ~
tworzy bitowe uzupełnienie operandu przez odwrócenie każdego bitu:
uint a = 0b_0000_1111_0000_1111_0000_1111_0000_1100;
uint b = ~a;
Console.WriteLine(Convert.ToString(b, toBase: 2));
// Output:
// 11110000111100001111000011110011
Możesz również użyć symbolu ~
, aby zadeklarować finalizatory. Aby uzyskać więcej informacji, zobacz Finalizatory.
Operator przesunięcia w lewo <<
Operator <<
przesuwa lewy operand po lewej stronie przez liczbę bitów zdefiniowanych przez operand po prawej stronie. Aby uzyskać informacje na temat sposobu, w jaki operand po prawej stronie definiuje liczbę zmian, zobacz sekcję Shift count of the shift operators (Liczba shift operatorów przesunięcia).
Operacja przesunięcia w lewo odrzuca bity o wysokiej kolejności, które znajdują się poza zakresem typu wyniku i ustawia puste pozycje bitów o niskiej kolejności na zero, jak pokazano w poniższym przykładzie:
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
Ponieważ operatory przesunięcia są zdefiniowane tylko dla int
typów , , long
uint
i ulong
, wynik operacji zawsze zawiera co najmniej 32 bity. Jeśli operand po lewej stronie ma inny typ całkowity (sbyte
, byte
, , short
ushort
lub char
), jego wartość jest konwertowana na int
typ, jak pokazano w poniższym przykładzie:
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
Operator przesunięcia w prawo >>
Operator >>
przesuwa lewy operand w prawo przez liczbę bitów zdefiniowanych przez operand po prawej stronie. Aby uzyskać informacje na temat sposobu, w jaki operand po prawej stronie definiuje liczbę zmian, zobacz sekcję Shift count of the shift operators (Liczba shift operatorów przesunięcia).
Operacja przesunięcia po prawej stronie odrzuca bity o niskiej kolejności, jak pokazano w poniższym przykładzie:
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
Puste pozycje bitów o wysokiej kolejności są ustawiane na podstawie typu operandu po lewej stronie w następujący sposób:
Jeśli operand po lewej stronie ma typ
int
lublong
, operator przesunięcia prawego wykonuje przesunięcie arytmetyczne : wartość najbardziej znaczącego bitu (bit znaku) operandu po lewej stronie jest propagowana do pustych pozycji bitów o wysokiej kolejności. Oznacza to, że puste pozycje bitów o wysokiej kolejności są ustawione na zero, jeśli operand po lewej stronie nie jest ujemny i ustawiony na jeden, jeśli jest ujemny.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
Jeśli operand po lewej stronie ma typ
uint
lubulong
, operator przesunięcia w prawo wykonuje zmianę logiczną : puste pozycje bitów o wysokiej kolejności są zawsze ustawione na zero.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
Uwaga
Użyj operatora niepodpisanego przesunięcia prawego, aby wykonać przesunięcie logiczne na operandach podpisanych typów liczb całkowitych. Jest to preferowane rzutowanie operandu po lewej stronie do typu niepodpisanego, a następnie rzutowanie wyniku operacji przesunięcia z powrotem do typu podpisanego.
Operator niepodpisanego przesunięcia w prawo >>>
Dostępny w języku C# 11 lub nowszym >>>
operator przesuwa lewy operand w prawo przez liczbę bitów zdefiniowanych przez operand po prawej stronie. Aby uzyskać informacje na temat sposobu, w jaki operand po prawej stronie definiuje liczbę zmian, zobacz sekcję Shift count of the shift operators (Liczba shift operatorów przesunięcia).
Operator >>>
zawsze wykonuje zmianę logiczną . Oznacza to, że puste pozycje bitów o wysokiej kolejności są zawsze ustawione na zero, niezależnie od typu operandu po lewej stronie. Operator >>
wykonuje przesunięcie arytmetyczne (czyli wartość najbardziej znaczącego bitu jest propagowana do pustych pozycji bitów o wysokiej kolejności), jeśli operand po lewej stronie jest typu ze znakiem. W poniższym przykładzie pokazano różnicę między operatorami >>
i >>>
dla ujemnego operandu po lewej stronie:
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
Operator logiczny AND i
Operator &
oblicza bitową wartość logiczną AND swoich operandów całkowitych:
uint a = 0b_1111_1000;
uint b = 0b_1001_1101;
uint c = a & b;
Console.WriteLine(Convert.ToString(c, toBase: 2));
// Output:
// 10011000
W przypadku bool
operandów &
operator oblicza logiczne AND jego operandów. Operator jednoargumentowy &
jest operatorem address-of.
Logiczny wyłączny operator OR ^
Operator ^
oblicza bitowo logiczne wyłączne OR, znane również jako bitowe logiczne XOR, jego operandów całkowitych:
uint a = 0b_1111_1000;
uint b = 0b_0001_1100;
uint c = a ^ b;
Console.WriteLine(Convert.ToString(c, toBase: 2));
// Output:
// 11100100
W przypadku bool
operandów ^
operator oblicza logiczny wyłączny OR jego operandów.
Operator logiczny OR |
Operator |
oblicza bitową wartość logiczną OR swoich operandów całkowitych:
uint a = 0b_1010_0000;
uint b = 0b_1001_0001;
uint c = a | b;
Console.WriteLine(Convert.ToString(c, toBase: 2));
// Output:
// 10110001
W przypadku bool
operandów |
operator oblicza logiczne OR jego operandów.
Przypisanie złożone
Dla operatora op
binarnego wyrażenie przypisania złożonego formularza
x op= y
jest równoważny
x = x op y
z wyjątkiem tego, że x
jest obliczany tylko raz.
W poniższym przykładzie pokazano użycie przypisania złożonego z operatorami bitowym i shift:
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}");
Ze względu na promocje liczbowe wynik op
operacji może nie być niejawnie konwertowany na typ T
x
. W takim przypadku, jeśli op
jest wstępnie zdefiniowanym operatorem, a wynik operacji jest jawnie konwertowany na typ T
x
, wyrażenie przypisania złożonego formularza x op= y
jest równoważne x = (T)(x op y)
, z wyjątkiem tego, że x
jest obliczany tylko raz. W poniższym przykładzie pokazano, że zachowanie:
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
Kolejność wykonywania działań
Następujące kolejności kolejności bitowych i przesunięć operatorów rozpoczynających się od najwyższego pierwszeństwa do najniższego:
- Operator uzupełniania bitowego
~
- Operatory
<<
shift ,>>
i>>>
- Operator logiczny AND
&
- Logiczny wyłączny operator OR
^
- Operator logiczny OR
|
Użyj nawiasów, ()
, aby zmienić kolejność oceny narzuconej przez pierwszeństwo operatora:
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}");
Aby uzyskać pełną listę operatorów języka C# uporządkowanych według poziomu pierwszeństwa, zobacz sekcję Pierwszeństwo operatora w artykule Operatory języka C#.
Liczba zmian operatorów przesunięcia
x << count
W przypadku wyrażeń , x >> count
i x >>> count
rzeczywista liczba zmian zależy od typu w x
następujący sposób:
Jeśli typ to
x
int
lubuint
, liczba zmian jest definiowana przez pięć bitów o niskiej kolejności operandu po prawej stronie. Oznacza to, że liczba zmian jest obliczana zcount & 0x1F
(lubcount & 0b_1_1111
).Jeśli typ to
x
long
lubulong
, liczba zmian jest definiowana przez sześć bitów o niskiej kolejności operandu po prawej stronie. Oznacza to, że liczba zmian jest obliczana zcount & 0x3F
(lubcount & 0b_11_1111
).
W poniższym przykładzie pokazano, że zachowanie:
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
Uwaga
Jak pokazano w poprzednim przykładzie, wynik operacji przesunięcia może być inny niż zero, nawet jeśli wartość operandu po prawej stronie jest większa niż liczba bitów w operandie po lewej stronie.
Operatory logiczne wyliczania
Operatory , , i ^
są również obsługiwane przez dowolny typ wyliczenia. |
&
~
W przypadku operandów tego samego typu wyliczenia operacja logiczna jest wykonywana na odpowiednich wartościach bazowego typu całkowitego. Na przykład w przypadku dowolnego x
typu T
i y
wyliczenia z typem x & y
U
bazowym wyrażenie generuje ten sam wynik co (T)((U)x & (U)y)
wyrażenie.
Zazwyczaj używasz operatorów logicznych bitowych z typem wyliczenia zdefiniowanym za pomocą atrybutu Flags . Aby uzyskać więcej informacji, zobacz sekcję Typy wyliczenia jako flagi bitowe artykułu Typy wyliczenia.
Przeciążenie operatora
Typ zdefiniowany przez użytkownika może przeciążać operatory ~
, , <<
, >>
&
>>>
, , |
i .^
Gdy operator binarny jest przeciążony, odpowiedni operator przypisania złożonego jest również niejawnie przeciążony. Typ zdefiniowany przez użytkownika nie może jawnie przeciążyć operatora przypisania złożonego.
Jeśli typ T
zdefiniowany przez użytkownika przeciąża <<
operator , >>
lub >>>
, typ operandu po lewej stronie musi mieć wartość T
. W języku C# 10 i starszych typ operandu po prawej stronie musi mieć int
wartość ; począwszy od języka C# 11, typ operandu po prawej stronie przeciążonego operatora przesunięcia może być dowolny.
specyfikacja języka C#
Aby uzyskać więcej informacji, zobacz następujące sekcje specyfikacji języka C#:
- Operator uzupełniania bitowego
- Operatory przesunięcia
- Operatory logiczne
- Przypisanie złożone
- Promocje liczbowe
- C# 11 — Złagodzone wymagania dotyczące zmiany
- C# 11 — operator logicznego przesunięcia prawego