Condividi tramite


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 operatori accettano operandi dei tipi numerici integrali o del tipo char .

È possibile usare questi operatori con i inttipi , longuint, ulong, nint, e nuint . 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 ^ funzionano anche con 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.

Il riferimento al linguaggio C# documenta la versione rilasciata più di recente del linguaggio C#. Contiene anche la documentazione iniziale per le funzionalità nelle versioni di anteprima pubblica per la prossima versione del linguaggio di programmazione.

La documentazione identifica tutte le funzionalità introdotte nelle ultime tre versioni della lingua o nelle anteprime pubbliche correnti.

Suggerimento

Per trovare quando una funzionalità è stata introdotta per la prima volta in C#, vedere l'articolo sulla cronologia delle versioni del linguaggio C#.

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 << sposta l'operando sinistro a sinistra in base al numero di bit specificato dal relativo operando di destra. Per altre 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 spostamento a sinistra elimina i bit di ordine elevato che non rientrano nell'intervallo del tipo di risultato. Imposta le posizioni di bit vuote in ordine basso 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 sinistro usa un altro tipo integrale (sbyte, byte, shortushort, o char), l'operazione converte il valore nel int tipo , 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. Il passaggio logico è preferibile. Evitare di eseguire il cast dell'operando sinistro a un tipo senza segno e quindi eseguire il cast del risultato di un'operazione di spostamento a un tipo firmato.

Operatore di spostamento a destra senza segno >>>

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'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

Ad eccezione del fatto 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 di 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 predefinito e il risultato dell'operazione è convertibile in modo esplicito nel tipo T di , un'espressione di xassegnazione composta del form x op= y è equivalente a x = (T)(x op y), ad eccezione del fatto 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

L'elenco seguente ordina gli operatori bit per bit e shift dalla precedenza più alta alla precedenza 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 modificare l'ordine di valutazione dalla precedenza dell'operatore predefinito:

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 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, i cinque bit dell'operando di destra definiscono il conteggio dei turni. 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, i sei bit dell'operando di destra definiscono il conteggio dei turni. 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

Ogni tipo di enumerazione supporta gli ~operatori , &|, e ^ . 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 si esegue l'overload di un operatore binario, si esegue anche l'overload implicito dell'operatore di assegnazione composta corrispondente. A partire da C# 14, un tipo definito dall'utente può sovraccaricare esplicitamente gli operatori di assegnazione composta per fornire un'implementazione più efficiente. Solitamente, un tipo sovraccarica questi operatori perché il valore può essere aggiornato direttamente, anziché allocare una nuova istanza per memorizzare il risultato dell'operazione binaria. Se un tipo non fornisce un overload esplicito, il compilatore genera l'overload implicito.

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. 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