Enumerazioni

17 ottobre 2016

A parte i tipi fondamentali e quelli composti, il linguaggio C++ consente di definire le enumerazioni.

Ogni enumerazione è un tipo. I valori che possono essere assunti da una variabile enumerazione sono ristretti ad un insieme di valori interi costanti, ad ognuno dei quali viene associato un nome. La coppia nome-costante è detta enumeratore.

Per la definizione di una enumerazione viene usata la parola chiave enum, secondo la seguente sintassi:

enum <identificativo> 
{ 
	<enumeratore>, 
	<enumeratore>, 
	... , 
	<enumeratore> 
};	

Con questa sintassi, l’uso dell’identificativo è opzionale e pertanto le due dichiarazioni mostrate nell’esempio seguente sono equivalenti:

// "frutta" è l'identificativo della enum
enum frutta { mela, pera, banana, pesca, kiwi };	

// enum senza identificativo
enum { mela, pera, banana, pesca, kiwi };	

Il compilatore assegna automaticamente i valori costanti associati ai nomi degli enumeratori e il tipo intero di riferimento.

Tipicamente viene selezionato quello in grado di contenere tutti i valori della enumerazione secondo il seguente ordine di valutazione: int, unsigned int, long, unsigned long, long long e unsigned long long.

Si possono tuttavia assegnare i valori associati ad alcuni o a tutti gli enumeratori come mostrato nell’esempio seguente:

// Assegnazione esplicita di valori ad ogni enumeratore. 
enum frutta { mela = 0, pera = 1, banana = 2, pesca = 3, kiwi = 4 }; 

// Se alcuni valori non sono assegnati, essi vengono posti uguali al valore precedente + 1:
// mela = 0, pera = 1, banana = 2, pesca = 9, kiwi = 10;	
enum frutta { mela, pera=1, banana, pesca = 9, kiwi };	

// Attenzione: le costanti usate possono essere solamente intere con o senza segno
enum frutta { mela, pera = 0.5f /*valore non ammesso!!*/, banana, pesca = -10, kiwi };	

Enumerazioni con definizione esplicita del tipo sottostante

A partire dallo standard c++11 è possibile indicare esplicitamente anche il tipo sottostante della enumerazione, usando la seguente sintassi:

enum <identificativo> : <tipo> 
{
	<enumeratore = valore>, 
	...,
	<enumeratore = valore>
};	

I tipi ammessi sono char, int, short int e long int, long long con e senza segno e bool. I tipi float, double e long double non sono ammessi e produrranno un errore di compilazione se usati. Anche in questo caso l’uso di un identificativo è opzionale.

La definizione esplicita del tipo infine impone anche che i valori costanti usati, se indicati, siano rappresentabili mediante il tipo scelto, come illustrato nel seguente esempio:

enum frutta : unsigned char 
{ 
	mela, 
	pera = -1, 		// errore: valore negativo 
	banana = 32, 	// ok: 32 è un numero positivo e rappresentabile in 8 bit
	pesca = 1024, 	// errore: valore fuori intervallo
	kiwi 
};

Definire esplicitamente il tipo sottostante consente anche di applicare alle enum la dichiarazione anticipata (forward declaration). Ciò significa che è possibile dichiarare una enumerazione senza definire i suoi enumeratori. Approfondiremo meglio nel seguito l’utilità della dichiarazione anticipata, ma per adesso ci limitiamo a dire che è utile per risolvere conflitti di inclusione tra differenti listati.

enum frutta : int; // dichiarazione anticipata di enumerazione 

Enumerazioni con campo di visibilità

Negli esempi finora proposti si è fatto uso della definizione di enum senza campo di visibilità. Questa locuzione è relativa alla visibilità dei nomi usati per gli enumeratori, che in questo caso sono visibili in tutto il blocco in cui è inclusa la dichiarazione della nostra enum.

Sebbene una visibilità così estesa possa sembrare una comodità, in realtà essa costituisce un problema. Nel listato seguente sono messi in mostra alcuni effetti collaterali della definizione di enum senza ambito si visibilità:

#include <iostream>

using std::cout;
using std::endl;
 
enum asse : char {x = 'x', y = 'y'};    
  
int main()
{   
	cout << "Enumeratore x: " << (char) x << endl;				// x = 'x'  

	char x = 'c';
	cout << "Variabile x: " << x << endl;							// x = 'c'

	cout << "Enumeratore asse::x: " << (char) asse::x << endl;	// x = 'x'

	return 0;
}

Per prima cosa osserviamo che l’enumerazione asse viene dichiarata al di fuori del main, nello spazio globale. I suoi enumeratori saranno pertanto visibili in ogni parte del nostro codice.

La prima istruzione nel main consiste nello stampare il valore dell’enumeratore x, che è pari al carattere ‘x’.

Tuttavia l’istruzione successiva è la dichiarazione di una variabile char, anche essa di nome x, pari a ‘c’. Questa istruzione ha l’effetto di “oscurare” l’enumeratore x all’interno della funzione main. Pertanto alla successiva riga di codice, x sarà riferito alla variabile ed il valore stampato sarà ‘c’.

La sintassi usata nella terza istruzione di stampa consente di risolvere l’ambiguità dei nomi ed il valore stampato sarà nuovamente riferito all’enumeratore.

Il fatto di usare lo stesso nome per un enumeratore ed una variabile comporta quindi delle criticità. Nell’esempio successivo, l’uso del medesimo nome x nello stesso contesto produce infatti un errore di compilazione:

	
enum asse : char {x = 'x', y = 'y'};		
int x = 1;	// errore di compilazione!!

Emerge un problema noto come inquinamento dello spazio dei nomi: il nome di un enumeratore non è riusabile nel blocco di definizione della enum stessa.

Per ovviare a questo problema in c++11 sono state introdotte le enumerazione con ambito di visibilità, la cui sintassi è la seguente:

enum class <identificativo> : <tipo> 
{ 
	<enumeratore>, 
	... , 
	<enumeratore> 
};	

La definizione del tipo è opzionale, così come l’assegnazione di valori agli enumeratori, ma in questo caso l’uso dell’identificativo è necessario. L’esempio successivo mostra l’uso di questa sintassi:

enum class asse : char {x = 'x', y = 'y'};		
int x = 1;	// nessun problema
 
cout << "asse::x: " << (char) asse::x << endl;	// x = 'x'
cout << "x: " << x << endl;						// x = 1	

L’uso di enum con e senza ambito di visibilità comporta delle differenze anche in altri costrutti sintattici; ad esempio, per le prime è necessario usare la sintassi <identificativo>::<enumeratore> poichè gli enumeratori sono visibili solo entro il blocco di definizione della enum stessa:

	
enum class forma {quadrato, cerchio, triangolo};	
forma f1 = cerchio; 		// errore: 'cerchio' non è visibile qui
forma f2 = forma::cerchio;	// ok

È inoltre necessaria una conversione di tipo per assegnare il valore di un enumeratore ad una variabile, anche se il tipo sottostante è lo stesso:

	
int f1 = triangolo;	// errore: conversione necessaria
int f2 = (forma) 2;	// ok

Gli enumeratori senza ambito di visibilità sono invece implicitamente convertiti a variabili di tipo numerico quando necessario:

enum colore : {rosso, verde, blu};			

int c1 = rosso;			// ok, 'colore' non è una enum class
colore c2 = 2;			// errore: la conversione implicita non è bidirezionale!  
colore c3 = (colore) 2; // ok 

L’uso delle enum con ambito di visibilità è sempre preferibile, poichè rafforza l’uso consapevole dei tipi, rendendo il codice più robusto e facendo emergere numerosi errori, anche concettuali, già in fase di compilazione piuttosto che in esecuzione.

Tutte le lezioni

1 ... 6 7 8 ... 66

Se vuoi aggiornamenti su Enumerazioni inserisci la tua e-mail nel box qui sotto:
Tags:
 
X
Se vuoi aggiornamenti su Enumerazioni

inserisci la tua e-mail nel box qui sotto:

Ho letto e acconsento l'informativa sulla privacy

Acconsento al trattamento di cui al punto 3 dell'informativa sulla privacy