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

Gli operatori bit per bit e shift includono complemento bit per bit, spostamento binario a sinistra e destra, spostamento a destra senza segno e operatori OR logici binari, OR ed esclusivi. Questi operandi accettano operandi dei tipi numerici integrali o il tipo di 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 relativi argomenti in int o hanno il tipo di risultato come int.

Gli operatori &, | e ^ sono definiti anche per gli operandi del tipo bool. 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 sul modo in cui l'operando di destra definisce il conteggio dei turni, vedere la sezione numero di 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 destro >>

L'operatore >> scorre l'operando di sinistra verso destra del numero di bit definito dall'operando di destra. Per informazioni sul modo in cui l'operando di destra definisce il conteggio dei turni, vedere la sezione numero di 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 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 dell'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 di spostamento a destra senza segno 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 con segno.

Operatore di spostamento a destra senza segno >>>

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

L'operatore >>> esegue sempre uno spostamento logico. Ovvero, le posizioni dei bit vuoti di 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 a sinistra è di un tipo con segno. L'esempio seguente illustra 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 gli operandi bool, l'operatore & calcola l’AND logico dei relativi operandi. L'operatore unario & è l'operatore address-of.

Operatore OR esclusivo logico: ^

L'operatore ^ calcola l'OR esclusivo 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 gli operandi bool, 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 gli operandi di bool, l'operatore | calcola l’OR logico dei relativi 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}");

In ragione 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 scorrimento <<, >> 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 per livello di priorità, vedere la sezione Priorità degli operatori nell'articolo Operatori C#.

Conteggio degli scorrimenti degli operatori di scorrimento

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

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

  • Se il tipo di x è int o uint, il conteggio degli scorrimenti viene definito dall'ordine minimo 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 degli scorrimenti viene definito dall'ordine minimo 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 sinistro.

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 flag. 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 definito dall'utente T 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 destro di un operatore di spostamento in overload può essere qualsiasi.

Specifiche del linguaggio C#

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

Vedi anche