Finora abbiamo per lo più gestito numeri e stringhe. Per modellare il mondo reale, potremmo volere un numero limitato di valori che una variabile può assumere. Potresti voler avere un tipo dedicato con qualche valore distinto e con nomi distinti. Per esempio, in una fabbrica di skateboard, avere il materiale della tavola come scelta tra solo maple, bamboo o plastic.
Potresti usare numeri interi per codificare quei valori, ma dovresti scrivere codice extra per controllare se dal sistema arriva un valore non valido per il materiale.
Il significato di questi numeri magici è difficile da rintracciare nel codice sorgente e si prestano a facili confusioni.
Le enumerations possono essere usate per incoraggiare codice espressivo e per limitare gli errori di confronto involontari.
Il termine specifico per questo tipo di enumerazione è scoped enumeration.
Il frammento qui sotto mostra come scrivere una DeckMaterial enumeration.
Nota la parola chiave enum class e il ; alla fine della definizione:
enum class DeckMaterial {
maple,
bamboo,
plastic
};
Ora guarda una funzione per il prezzo nello skate shop e nota l'operatore di risoluzione dello scope (::) che specifica un enumerator di una enumeration:
double deck_price(double base_price, DeckMaterial material) {
if(material == DeckMaterial::plastic) {
return base_price * 0.9;
}
return base_price * 1.3;
}
Immagina di avere una seconda enumeration per il materiale delle ruote:
enum class WheelMaterial {
steel,
clay,
plastic
};
Anche se le ruote e la tavola possono essere entrambe fatte di plastic, le due non si possono confondere.
Sono tipi diversi: il plastic di DeckMaterial e il plastic di WheelMaterial.
Ogni enumeration avrà i suoi enumerators nel proprio scope, il proprio namespace.
È per questo che si chiamano scoped enumerations.
Potresti pensare che con un nome come scoped ci sarebbero anche enumerazioni unscoped, ed avresti ragione.
Le unscoped enumerations stanno diventando meno popolari perché condividono tutte lo stesso namespace globale.
Proprio per questa condivisione, non potresti avere due unscoped enumerations con gli stessi enumerators, come plastic nell'esempio qui sopra.
Inoltre, le Unscoped enumerations si convertono implicitamente in numeri interi.
Guarda l'esempio qui sotto per un risultato sorprendente:
enum CitrusFruits {
lemons, // 0
oranges, //1
};
enum IceCream {
walnut, // 0
apples, // 1
};
bool comparison{apples == oranges};
// => true
// Example from above:
bool comparison{DeckMaterial::plastic == WheelMaterial::plastic};
// => Does not compile!
Se vuoi convertire le scoped enumerations in numeri interi, puoi usare static_cast<int>.
Come altri linguaggi, anche C++ fornisce un'istruzione switch.
Le istruzioni switch sono un modo più breve per scrivere lunghe istruzioni if ... else if.
Per creare uno switch, iniziamo usando la parola chiave switch seguita da un numero intero.
Poi dichiariamo ognuna delle condizioni con la parola chiave case.
Possiamo anche dichiarare un caso default, che verrà eseguito quando nessuna delle condizioni case precedenti corrisponde.
Ogni caso dovrebbe terminare con un'istruzione break (o return).
int price{0};
int adults{3};
int kids{2};
switch (int group_size{adults + kids}) {
case 1:
price = 50;
break;
case 2:
price = 70;
break;
default:
price = group_size * 30;
}
Una cosa importante della costruzione switch è che il codice continua a essere eseguito finché non viene fermato da un'istruzione break (o return).
Questo può portare a comportamenti inattesi.
int adults{1};
int kids{0};
switch (int group_size{adults + kids}) {
case 1:
price = 50;
case 2:
price = 70;
default:
price = group_size * 30;
}
// price will be 30!
Il caso d'uso principale di questa esecuzione continuata è un'istruzione con diverse etichette. Più risultati di uno switch possono essere mappati sullo stesso pezzo di codice da eseguire. In questo modo, per esempio in un'app di prenotazioni, la funzione chiamata per gruppi di 2 e 3 persone può essere la stessa:
switch (group_size) {
case 1:
book_room();
break;
case 2:
case 3:
book_apartment(group_size);
break;
default:
book_house(group_size);
}
// book_apartment happens when group_size is 2 or 3
La tua amica Helma ha creato un piccolo gioco online che ha rapidamente guadagnato popolarità. Si chiama HellMath. La piccola comunità ha attirato alcuni troll che rendono il gioco e i forum piuttosto spiacevoli. Helma ti ha chiesto di lavorare a un nuovo sistema di permessi per separare i piantagrane.
Il forum supporta tre azioni diverse:
Ci sono quattro tipi di account, ognuno con permessi predefiniti diversi:
Helma ha notato che bandire gli account dei troll non serve a nulla. La sua strategia è far credere loro che il loro tempo sia «ben investito», ma i loro post vengono mostrati solo agli altri troll. In qualsiasi situazione che richieda un ordine di priorità, i troll sono sempre gli ultimi. Quando entrano in una partita, anche il gruppo di giocatori disponibili è limitato agli altri troll.
Questa pratica si chiama shadow-banning.
Per prima cosa, definisci un'enumerazione AccountStatus che rappresenti i quattro tipi di account: troll, guest, user e mod.
Poi, definisci un'enumerazione Action che rappresenti i tre tipi di permesso: read, write e remove.
Ogni post dei forum salva nei propri metadati l'AccountStatus di chi lo ha pubblicato.
Assicurati che i post dei troll vengano mostrati solo agli altri troll.
Helma ha bisogno di una funzione display_post, che riceve due argomenti di tipo AccountStatus e restituisce un bool.
Il primo argomento è lo stato di chi ha pubblicato, il secondo è lo stato di chi guarda.
using namespace hellmath;
display_post(AccountStatus::troll, AccountStatus::user);
// => false
display_post(AccountStatus::mod, AccountStatus::guest);
// => true
Helma ha bisogno di un modo per controllare se una certa azione è consentita a un utente.
Implementa una funzione permission_check, che prende un'Action come primo argomento e un AccountStatus su cui verificare i permessi.
Dovrebbe restituire un bool in base ai permessi elencati nell'introduzione.
permission_check(Action::remove, AccountStatus::guest);
// => false
permission_check(Action::write, AccountStatus::mod);
// => true
Per rendere i giocatori effettivi responsabili delle loro azioni, Hellmath nega l'accesso agli utenti ospiti. Come accennato sopra, Helma vuole che i troll facciano i troll con gli altri troll. Le connessioni di gioco tra gli altri utenti non hanno restrizioni.
Implementa la funzione valid_player_combination che controlla se due giocatori possono unirsi alla stessa partita.
La funzione ha due parametri di tipo AccountStatus e restituisce un bool.
valid_player_combination(AccountStatus::guest, AccountStatus::mod);
// => false
valid_player_combination(AccountStatus::troll, AccountStatus::troll);
// => true
Con la crescita enorme del gioco e dei forum, ora Helma deve distribuire potenza di calcolo e larghezza di banda tra gli utenti. Per gestire le emergenze, ai moderatori viene data la priorità più alta. Gli ospiti stanno in coda dietro gli utenti normali, e i troll vengono ordinati dietro tutti gli altri.
Implementa la funzione has_priority che prende due argomenti di tipo AccountStatus e restituisce true, se e solo se il primo account ha una priorità strettamente maggiore del secondo.
has_priority(AccountStatus::guest, AccountStatus::mod);
// => false
has_priority(AccountStatus::user, AccountStatus::troll);
// => true
Iscriviti a Exercism per imparare e padroneggiare C++ con 19 concetti100 esercizi e il mentoring di persone reali, tutto gratis.