Поделиться через


Побитовые операторы и операторы сдвига (справочник по C#)

Побитовые и сменные операторы включают унарное битовое дополнение, двоичное и правое сдвиг, без знака вправо и двоичные логические И, OR и эксклюзивные операторы OR. Эти операнды принимают операнды целочисленных числовых типов или типа char .

Эти операторы определены для типов 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 является предопределенным оператором, и результат операции является явно преобразуемым в тип Tx, выражение составного присваивания формы 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 << countx >> 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#:

См. также