Побитовые операторы и операторы сдвига (справочник по C#)
Побитовые и сменные операторы включают унарное битовое дополнение, двоичное и правое сдвиг, без знака вправо и двоичные логические И, OR и эксклюзивные операторы OR. Эти операнды принимают операнды целочисленных числовых типов или типа char .
- Унарный оператор
~
(побитовое дополнение) - Операторы binary
<<
(shift),>>
(shift вправо) и>>>
(без знака вправо) - Бинарные операторы
&
(логическое и),|
(логическое или) и^
(логическое исключающее или)
Эти операторы определены для типов 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
Оператор shift вправо >>
Оператор >>
сдвигает левый операнд вправо на количество битов, определенное правым операндом. Сведения о том, как правый операнд определяет величину сдвига, см. в разделе Величина смещения операторов сдвига.
Операция сдвига вправо удаляет младшие разряды, как показано в следующем примере:
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
, оператор сдвига вправо выполняет арифметический сдвиг, то есть значение наиболее значимого бита (знаковый бит) левого операнда переносится в пустые битовые позиции высокого разряда. То есть для пустых битовых позиций высокого порядка задается ноль, если левый операнд неотрицательный, и единица, если он отрицательный.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
Примечание.
Используйте оператор без знака вправо, чтобы выполнить логическую смену операндов со знаком целочисленных типов. Это предпочтительнее приведение левого операнда к типу без знака, а затем приведение результата операции смены обратно к подписанному типу.
Оператор unsigned right-shift >>>
Доступно в 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
оператор &
вычисляет логическое И по своим операндам. Унарный оператор &
является оператором AddressOf.
Оператор логического исключения ИЛИ ^
Оператор ^
вычисляет побитовое логическое исключающее ИЛИ, также известное как побитовое логическое 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
оператор ^
вычисляет логическое исключающее ИЛИ по своим операндам.
Оператор логического ИЛИ |
Оператор |
вычисляет побитовое логическое ИЛИ своих целочисленных операндов:
uint a = 0b_1010_0000;
uint b = 0b_1001_0001;
uint c = a | b;
Console.WriteLine(Convert.ToString(c, toBase: 2));
// Output:
// 10110001
Для операндов типа bool
оператор |
вычисляет логическое ИЛИ по своим операндам.
Составное присваивание
Для бинарного оператора 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
может быть невозможно неявно преобразовать в тип T
из x
. В этом случае, если op
является предопределенным оператором, и результат операции является явно преобразуемым в тип T
x
, выражение составного присваивания формы 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
Приоритет операторов
Следующий список упорядочивает побитовые операторы и операторы сдвига по приоритету, от высокого до низкого:
- Оператор побитового дополнения
~
- Операторы
<<
shift ,>>
и>>>
- Оператор логического И
&
- Оператор логического исключающего ИЛИ
^
- Оператор логического ИЛИ
|
Порядок вычисления, определяемый приоритетом операторов, можно изменить с помощью скобок (()
).
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
Примечание.
Как показано в предыдущем примере, результат операции сдвига может быть ненулевым, даже если значение правого операнда больше числа битов в левом операнде.
Enumeration logical operators (Логические операторы перечисления)
Операторы ~
, &
, |
и ^
также поддерживаются для любого типа перечисления. Для операндов одного типа перечисления логическая операция выполняется по соответствующим значениям базового целочисленного типа. Например, для любого x
и y
типа перечисления T
с базовым типом U
выражение x & y
дает тот же результат, что и выражение (T)((U)x & (U)y)
.
Побитовые логические операторы обычно используются с типом перечисления, который определяется с помощью атрибута Flags. Дополнительные сведения см. в разделе Типы перечислений как битовые флаги в статье Типы перечислений.
Возможность перегрузки оператора
Определяемый пользователем тип может перегружать ~
операторы , <<
, >>>
>>
, &
а также |
^
операторы. При перегрузке бинарного оператора соответствующий оператор составного присваивания также неявно перегружается. Явная перегрузка составного оператора присваивания для пользовательского типа невозможна.
Если определяемый пользователем тип перегружает <<
оператор или >>
>>>
оператор, тип левого операнда должен бытьT
.T
В C# 10 и более ранних версиях тип операнда вправо должен быть int
; начиная с C# 11 тип операнда справа от перегруженного оператора shift может быть любым.
Спецификация языка C#
Дополнительные сведения см. в следующих разделах статьи Спецификация языка C#:
- Оператор побитового дополнения
- Операторы сдвига
- Логические операторы
- Составное присваивание
- Восходящие приведения числовых типов
- C# 11 . Требования к расслабленным сменам
- C# 11 — логический оператор shift вправо