Operatori di scorrimento e bit per bit (Riferimenti per C#)

Gli operatori bit per bit e shift includono complemento bit per bit, spostamento a sinistra e destra binario, spostamento a destra senza segno e operatori OR logici binari, OR ed or esclusivi. Questi operandi accettano operandi dei tipi numerici integrali o del tipo char .

Questi operatori sono definiti per i tipi int, uint, long e ulong. Quando entrambi gli operandi sono di altri tipi integrali (sbyte, byte, short, ushort o char), i relativi valori vengono convertiti nel tipo int, che è anche il tipo di risultato di un'operazione. Quando gli operandi sono di tipi integrali diversi, i relativi valori vengono convertiti nel tipo integrale più vicino. Per altre informazioni, vedere la sezione Promozioni numeriche della specifica del linguaggio C#. Gli operatori composti (ad esempio >>=) non convertono i int relativi argomenti in o hanno il tipo di risultato come int.

Gli &operatori , |e ^ sono definiti anche per gli operandi del bool tipo . Per altre informazioni, vedere Operatori logici booleani.

Le operazioni di scorrimento non causano mai overflow e producono gli stessi risultati nei contesti Checked e Unchecked.

Operatore di complemento bit per bit ~

L'operatore ~ produce un complemento bit per bit del relativo operando invertendo ogni bit:

uint a = 0b_0000_1111_0000_1111_0000_1111_0000_1100;
uint b = ~a;
Console.WriteLine(Convert.ToString(b, toBase: 2));
// Output:
// 11110000111100001111000011110011

È anche possibile usare il simbolo ~ per dichiarare i finalizzatori. Per altre informazioni, vedere Finalizzatori.

Operatore di scorrimento a sinistra <<

L'operatore << scorre verso sinistra l'operando di sinistra del numero di bit specificato dall'operando di destra. Per informazioni su come l'operando di destra definisce il conteggio dei turni, vedere la sezione Conteggio turni degli operatori di spostamento .

L'operazione di scorrimento a sinistra rimuove i bit più significativi che non rientrano nell'intervallo del tipo di risultato e imposta le posizioni dei bit vuoti meno significativi su zero, come illustrato nell'esempio seguente:

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

Poiché gli operatori di scorrimento sono definiti solo per i tipi int, uint, long e ulong, il risultato di un'operazione contiene almeno 32 bit. Se l'operando di sinistra e di un tipo integrale diverso (sbyte, byte, short, ushort o char), il relativo valore viene convertito nel tipo int, come illustrato nell'esempio seguente:

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

Operatore di spostamento a destra >>

L'operatore >> scorre l'operando di sinistra verso destra del numero di bit definito dall'operando di destra. Per informazioni su come l'operando di destra definisce il conteggio dei turni, vedere la sezione Conteggio turni degli operatori di spostamento .

L'operazione di scorrimento a destra rimuove i bit meno significativi, come illustrato nell'esempio seguente:

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

Le posizioni dei bit vuoti più significativi vengono impostate in base al tipo dell'operando di sinistra come segue:

  • Se l'operando di sinistra è di tipo int o long, l'operatore di spostamento a destra esegue uno spostamento aritmetico : il valore del bit più significativo (il bit di segno) dell'operando di sinistra viene propagato alle posizioni dei bit vuoti di ordine elevato. Vale a dire, le posizioni dei bit vuoti più significativi vengono impostate su zero se l'operando di sinistra è un valore non negativo e impostate su uno se è negativo.

    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
    
  • Se l'operando di sinistra è di tipo uint o ulong, l'operatore di spostamento a destra esegue uno spostamento logico : le posizioni dei bit vuoti in ordine elevato vengono sempre impostate su 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
    

Nota

Usare l'operatore unsigned right-shift per eseguire uno spostamento logico sugli operandi di tipi integer con segno. È preferibile eseguire il cast di un operando sinistro a un tipo senza segno e quindi eseguire il cast del risultato di un'operazione di spostamento a un tipo firmato.

Operatore unsigned right-shift >>>

Disponibile in C# 11 e versioni successive, l'operatore sposta l'operando >>> di sinistra a destra in base al numero di bit definiti dal relativo operando di destra. Per informazioni su come l'operando di destra definisce il conteggio dei turni, vedere la sezione Conteggio turni degli operatori di spostamento .

L'operatore >>> esegue sempre uno spostamento logico . Ovvero, le posizioni dei bit vuoti in ordine elevato vengono sempre impostate su zero, indipendentemente dal tipo dell'operando di sinistra. L'operatore>> esegue uno spostamento aritmetico , ovvero il valore del bit più significativo viene propagato alle posizioni di bit vuote dell'ordine elevato, se l'operando sinistro è di un tipo con segno. Nell'esempio seguente viene illustrata la differenza tra >> gli operatori e >>> per un operando sinistro negativo:

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

Operatore AND logico &

L'operatore & calcola l'AND logico bit per bit dei relativi operandi integrali:

uint a = 0b_1111_1000;
uint b = 0b_1001_1101;
uint c = a & b;
Console.WriteLine(Convert.ToString(c, toBase: 2));
// Output:
// 10011000

Per bool gli operandi, l'operatore & calcola l'AND logico degli operandi. L'operatore unario & è l'operatore address-of.

Operatore OR esclusivo logico: ^

L'operatore ^ calcola l'OR logico bit per bit, noto anche come XOR logico bit per bit, dei relativi operandi integrali:

uint a = 0b_1111_1000;
uint b = 0b_0001_1100;
uint c = a ^ b;
Console.WriteLine(Convert.ToString(c, toBase: 2));
// Output:
// 11100100

Per bool gli operandi, l'operatore ^ calcola l'OR esclusivo logico dei relativi operandi.

Operatore OR logico |

L'operatore | calcola l'OR logico bit per bit dei relativi operandi integrali:

uint a = 0b_1010_0000;
uint b = 0b_1001_0001;
uint c = a | b;
Console.WriteLine(Convert.ToString(c, toBase: 2));
// Output:
// 10110001

Per bool gli operandi, l'operatore | calcola l'OR logico degli operandi.

Assegnazione composta

Per un operatore binario op, un'espressione di assegnazione composta in formato

x op= y

equivale a

x = x op y

con la differenza che x viene valutato una sola volta.

L'esempio seguente illustra l'uso dell'assegnazione composta con gli operatori di scorrimento e bit per bit:

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}");

A causa delle promozioni numeriche il risultato dell'operazione op potrebbe non essere convertibile in modo implicito nel tipo T di x. In questo caso, se op è un operatore già definito e il risultato dell'operazione è convertibile in modo esplicito nel tipo T di x, un'espressione di assegnazione composta nel formato x op= y equivale a x = (T)(x op y), con la differenza che x viene valutato una sola volta. L'esempio seguente illustra questo comportamento:

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

Precedenza tra gli operatori

Nell'elenco seguente gli operatori di scorrimento e bit per bit sono ordinati dalla precedenza più elevata a quella più bassa:

  • Operatore di complemento bit per bit ~
  • Operatori <<di spostamento , >>e >>>
  • Operatore AND logico &
  • Operatore OR esclusivo logico ^
  • Operatore OR logico |

Usare le parentesi, (), per cambiare l'ordine di valutazione imposto dalla precedenza tra gli operatori:

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}");

Per l'elenco completo degli operatori C# ordinati in base al livello di precedenza, vedere la sezione Precedenza deglioperatori C# dell'articolo Operatori C# .

Conteggio degli scorrimenti degli operatori di scorrimento

Per gli operatori <<di spostamento predefiniti , >>e >>>, il tipo dell'operando di destra deve essere int o un tipo con una conversione numerica implicita predefinita in int.

Per le x << countespressioni , x >> counte x >>> count , il conteggio effettivo dei turni dipende dal tipo di , come indicato di x seguito:

  • Se il tipo di x è int o uint, il conteggio dei turni viene definito dai cinque bit dell'operando di destra. Vale a dire, il conteggio degli scorrimenti viene calcolato da count & 0x1F (o count & 0b_1_1111).

  • Se il tipo di x è long o ulong, il conteggio dei turni viene definito dai sei bit dell'operando di destra. Vale a dire, il conteggio degli scorrimenti viene calcolato da count & 0x3F (o count & 0b_11_1111).

L'esempio seguente illustra questo comportamento:

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

Nota

Come illustrato nell'esempio precedente, il risultato di un'operazione di spostamento può essere diverso da zero anche se il valore dell'operando di destra è maggiore del numero di bit nell'operando di sinistra.

Operatori logici di enumerazione

Gli ~operatori , &, |e ^ sono supportati anche da qualsiasi tipo di enumerazione . Per gli operandi dello stesso tipo di enumerazione, viene eseguita un'operazione logica sui valori corrispondenti del tipo integrale sottostante. Ad esempio, per qualsiasi x e y di un tipo di enumerazione T con un tipo sottostante U, l'espressione x & y produce lo stesso risultato dell'espressione (T)((U)x & (U)y).

In genere si usano operatori logici bit per bit con un tipo di enumerazione definito con l'attributo Flags . Per altre informazioni, vedere la sezione Tipi di enumerazione come flag di bit dell'articolo Tipi di enumerazione.

Overload degli operatori

Un tipo definito dall'utente può eseguire l'overload degli ~operatori , <<, >>>>>, &, |, e ^ . Quando viene eseguito l'overload di un operatore binario, viene anche eseguito in modo implicito l'overload dell'operatore di assegnazione composta corrispondente. Un tipo definito dall'utente non può eseguire in modo esplicito l'overload di un operatore di assegnazione composta.

Se un tipo T definito dall'utente esegue l'overload dell'operatore <<, >>o >>> , il tipo dell'operando sinistro deve essere T. In C# 10 e versioni precedenti, il tipo dell'operando di destra deve essere int; a partire da C# 11, il tipo dell'operando di destra di un operatore shift di overload può essere qualsiasi.

Specifiche del linguaggio C#

Per altre informazioni, vedere le sezioni seguenti delle specifiche del linguaggio C#:

Vedi anche