next up previous contents index
Next: Funzioni Up: Costrutti condizionali e iterativi Previous: Il costrutto di scelta   Indice   Indice analitico

Gli operatori di confronto bit a bit

Concludiamo il capitolo, come promesso, con la trattazione degli operatori bit a bit del C++. Conoscenza base per l'utilizzo di tali operatori è il sistema binario, di cui forniamo una veloce introduzione. Il sistema che utilizziamo comunemente è il sistema decimale, così detto perché ogni numero 3.6può essere rappresentato come sequenza di dieci cifre (0,1,2,3,4,5,6,7,8,9); questo sistema è, come quello binario che vedremo tra poco, posizionale: ogni cifra assume un diverso peso a seconda della posizione che esso occupa; per esempio sappiamo che

\begin{displaymath}
256 = 2 \cdot 10^2 + 5 \cdot 10^1 + 6 \cdot 10^0
\end{displaymath}

Del tutto analogo ad esso è il sistema binario, costituito però da due sole cifre: 0 e 1; così è ad esempio

\begin{displaymath}
100101 = 1 \cdot 2^5 + 0 \cdot 2^4 + 0 \cdot 2^3 + 1 \cdot 2^2 + 0 \cdot 2^1 + 1 \cdot 2^0
\end{displaymath}

quindi è $100101_2 = 37_{10}$. Tale esempio fornisce anche un metodo pratico per la conversione dei numeri dalla base $2$ alla base $10$. Per effettuare la conversione inversa il metodo più veloce è il metodo dei resti: si divide il numero in base $10$ per $2$ segnando i resti, fino a quando il quoziente non è nullo; a questo punto basta prendere i resti nell'ordine inverso rispetto a quello con il quale li abbiamo ottenuti; esempio:
37 :2  = 18  1
18 :2  = 9  0
9 :2  = 4  1
4 :2  = 2  0
2 :2  = 1  0
1 :2  = 0  1
per cui $37_{10} = 100101_2$. Per inciso, si sappia che il processore effettua i calcoli esclusivamente in base binaria.

In C++ esiste il tipo unsigned int che permette di memorizzare variabili con valori interi non negativi, i quali sono rappresentati nel sistema binario; sfruttando tale rappresentazione e gli operatori di confronto bit a bit, possiamo manipolare sequenze di zeri e uno, un cui utilizzo tipico è mostrato nell'esempio a fine paragrafo. I primi operatori bit a bit (bitwise operators) sono i seguenti:

& and bit a bit
| or bit a bit
^ xor bit a bit
~ not bit a bit
dei quali i primi tre sono binari mentre l'ultimo è unario; per gli operatori binari esistono anche delle scorciatoie sintattiche: &=, |=, ^=, le quali permettono di effettuare un'operazione sui bit e un assegnamento con una sola espressione; per esse vale il medesimo discorso condotto sulle forme contratte degli operatori aritmetici (+=, ...).

Gli operatori binari accettano come argomenti due unsigned int e, dopo un opportuno confronto bit a bit (cioè cifra binaria per cifra binaria: bit = Binary digIT), ne restituiscono un terzo come risultato; le tabelle di verità di tali operatori sono le seguenti:

& 1 0
1 1 0
0 0 0
| 1 0
1 1 1
0 1 0
^ 1 0
1 0 1
0 1 0
~  
1 0
0 1

L'operatore XOR (xor = eXclusive OR) è l'OR esclusivo: esso torna $1$ solo nel caso i due bit a confronto siano diversi. L'operatore NOT (~) corrisponde alla negazione logica del bit, così come gli operatori AND e OR corrispondono alla intersezione e all'unione logica; si noti la differenza sintattica tra tali operatori e gli operatori logici (AND logico = &&, OR logico = ||). Qualche esempio 3.7:

00010110 &
10110101 =
00010100  
00010110 |
10110111 =
10110111  

00010110 ^
10110101 =
10100011  
00010110 ~
11101001  

Esistono infine altri due operatori bit a bit, detti operatori di shift (scorrimento); essi sono:

<< shift a sinistra
>> shift a destra
Essi sono entrambi binari: l'argomento sinistro è un unsigned int corrispondente al numero da shiftare 3.8, l'argomento destro è un int che indica di quante cifre far scorrere tale numero; esempi:
10010011 <<
2 =
01001100  
10010011 >>
2 =
00100100  

Si noti che tramite questi ultimi due operatori si perde un numero di cifre esattamente uguale alla lunghezza dello scorrimento; le posizione che restano vacanti dopo lo scorrimento, vengono riempite con zeri. Si faccia attenzione: (a << n) >> è una sequenza binaria che, in generale, è diversa da a; ad esempio:

10010011 << 3 = 10011000
10011000 >> 3 = 00010011
ma 10010011 è ben diverso da 00010011. Si noti che far scorrere di $n$ cifre un numero binario corrisponde a moltiplicarlo per $2^n$, e analogamente facendolo scorrere a destra lo si divide per $2^n$:

\begin{eqnarray*}
& 1011_2 = 11_{10} \\
& 1011_2 << 3_{10} = 1011000_2 = 88_{10...
...^3 \\
& 1011_2 >> 2_{10} = 0010_2 = 2_{10} = 11_{10} : 2_{10}^2
\end{eqnarray*}



Se questa proprietà può stupire, si pensi solo alle regole che ci sono state insegnate alle scuole elementari per moltiplicare o dividere per 10:

\begin{eqnarray*}
& 1200 \cdot 10^2 = 120000 \\
& 1200 : 10^2 = 12 \\
\end{eqnarray*}



in realtà non facciamo altro che scorrere a destra o sinistra il numero dato.

Un utilizzo tipico degli unsigned int è la rappresentazione di flags (bandierine), cioè di insiemi di variabili che possono assumere solo due valori (bianco e nero, acceso e spento, vero e falso, abilitato e disabilitato, ...); presentiamo un esempio che permette di accendere o spegnere una serie di 8 lampadine:


// ex3_7_1.cpp
#include <iostream.h>
void main() {
   unsigned int lampadine = 0;  // inizialmente le lampadine sono
                                // sono tutte spente
   bool continua = true;
   unsigned int maschera;       // variabile utilizzata per
                                // operare sui bit
   int posizione;               // utilizzata nello switch
   unsigned int risultato;      // idem
   do {
      cout << "\n\nscegli l'operazione da eseguire: "
         "\n\t (a) stampa la serie di lampadine"
         "\n\t (b) accendi una lampadina"
         "\n\t (c) spegni una lampadina"
         "\n\t (d) accendi tutte le lampadine"
         "\n\t (e) spegni tutte le lampadine"
         "\n\t (q) esci";
      cout << "\n? ";
      char comando;
      cin >> comando;
      switch (comando) {
         case 'a': case 'A':
            maschera = 1;  // 1 = 00000001
            for (int i = 0; i < 8; i++) {
               risultato = lampadine & maschera;
               risultato >>= i;
               if (risultato == 1)    // lampadina ON
                  cout << "A";
               else                   // lampadina OFF
                  cout << "S";
               maschera <<= 1;
            }
            break;
         case 'b': case 'B':
            maschera = 1;
            cout << "\n\tposizione? ";
            cin >> posizione;
            if ( (posizione < 1) || (posizione > 8) )
               break;
            maschera <<= posizione - 1;
            lampadine |= maschera;
            break;
         case 'c': case 'C':
            maschera = 1;
            cout << "\n\tposizione? ";
            cin >> posizione;
            if ( (posizione < 1) || (posizione > 8) )
               break;
            maschera <<= posizione - 1;
            maschera = ~maschera;
            lampadine &= maschera;
            break;
         case 'd': case 'D':
            maschera = 1;
            for (int i = 0; i < 8; i++) {
               lampadine |= maschera;
               maschera <<= 1;
            }
            break;
         case 'e': case 'E':
            lampadine = 0;    // 0 = 00000000
            break;
         case 'q': case 'Q':
            continua = false;
            break;
         default:
            cout << "\n\n d'oh \n";
      }
   } while (continua == true);
}

Se ad una prima lettura il codice sembra di facile comprensione, ciò significa che si ha già una discreta confidenza con i manipolatori di cifre binarie, la qual cosa non è del tutto comune per uno studente di scuola media superiore, e tantomeno per un appassionato di informatica personale; non ci si preoccupi dunque delle difficoltà che probabilmente si incontrano su tale argomento, il quale meriterebbe una ben più ampia discussione e tempi lunghi di assimilazione; in mancanza di entrambi, si provi a modificare il precedente esempio aggiungendo semplici operazioni come: ``accendi la lampadina 3'' oppure ``accendi le lampadine spente e spegni quelle accese'' o ancora ``accenti una lampadina sì ed una no''.


next up previous contents index
Next: Funzioni Up: Costrutti condizionali e iterativi Previous: Il costrutto di scelta   Indice   Indice analitico
Claudio Cicconetti
2000-09-06