Charakterystycznym dla C/C++ typem (a właściwie typami) danych są wyliczenia (zwane też enumeracjami, z angielskiego enumeration).
Formalnie
Na przykład
enum dni {pon, wto, sro, czw, pia, sob, nie};definiuje typ wyliczeniowy o nazwie dni. Zmienne tego typu będą mogły przybierać dokładnie siedem wartości, wyliczonych w nawiasach klamrowych w definicji tego typu (stąd nazwa wyliczenia, typ wyliczeniowy).
Definicja typu wyliczeniowego składa się ze słowa kluczowego enum, nazwy wprowadzanego typu oraz ujętej w nawiasy klamrowe listy nazw symbolicznych wartości elementów wyliczenia. Symbolom można przypisywać wartości liczbowe (patrz niżej), a nazwę typu można opuścić. Jeśli opuszczamy nazwę typu, to zwykle tworzymy od razu zmienne tego anonimowego typu, bo inaczej byłby on bezużyteczny — utworzenie później takich zmiennych byłoby już niemożliwe, bo nie byłoby jak określić ich typu w ich deklaracji/definicji. Na przykład po
enum {pik, kier, karo, trefl} karta1, karta2; ... karta1 = kier; karta2 = karta1; ... if (karta2 == trefl) { ... }mamy dwie zmienne (karta1, karta2) anonimowego typu wyliczeniowego opisującego kolory kart; żadnej innej zmiennej tego typu nie da się już utworzyć, bo typ ten nie ma nazwy. Innym zastosowaniem wyliczeń anonimowych jest wprowadzenie do programu nazwanych stałych, które mogą na przykład służyć do wymiarowania tablic wtedy, gdy definiowanie stałych za pomocą słowa kluczowego const byłoby niewygodne (np. wewnątrz definicji klas; patrz rozdział o listach inicjalizacyjnych ).
Wewnętrznie zmienne typu dni będą reprezentowane za pomocą kolejnych liczb całkowitych poczynając od zera: tak więc symbol pon odpowiadać będzie liczbie 0, a np. symbol sob liczbie 5.
Elementom wyliczenia można nadać odpowiadające im wartości liczbowe „ręcznie”,
enum dni {pon, wto=0, sro=0, czw=0, pia=0, sob, nie};przy czym obowiązują następujące zasady:
Przypisane elementom wyliczenia wartości nie tylko nie muszą być różne dla różnych elementów wyliczenia, ale nie muszą też być wartościami kolejnymi; mogą występować „dziury”. Na przykład definicja dni mogłaby wyglądać tak
enum dni {pon, wto=0, sro=0, czw=0, pia=0, sob, nie=3};Teraz elementowi sob odpowiada w dalszym ciągu wartość 1, ale niedzieli (nie) odpowiada teraz 3; wartości 2 nie odpowiada zaś teraz żaden element wyliczenia.
W nowym standardzie (C++11), możemy jawnie określić typ stałych wyliczeniowych (musi to być typ całkowitoliczbowy). Robimy to poprzez podanie nazwy typu po nazwie wyliczenia i dwukropku:
enum kolor : unsigned {pik, kier, karo, trefl};Według nowego standardu, jeśli nie określimy typu stałych wyliczniowych, to będzie nim int.
Przyjrzyjmy się następnemu przykładowi.
Definiujemy w nim (➊) wyliczenie
dni. Definicja ta jest
globalna, bo jest umieszczona poza wszystkimi funkcjami i klasami
(klas tu zresztą w ogóle nie ma). Jest to konieczne, by definicja typu
dni
była widoczna
zarówno w funkcji
main, jak i w funkcji
info.
Następnie definiujemy funkcję
info, której
parametr jest właśnie typu
dni.
W programie głównym
main
wywołujemy tę
funkcję. Jako argument podajemy zmienną o wartości typu
dni.
W linii ➎ jest to
pon
— literał jednego z elementów
wyliczenia
dni, a w liniach ➐ i ➒ jest to zmienna
dzien
zadeklarowana jako zmienna typu
dni.
Jak widzimy, nazwę
dni
traktujemy tak jak nazwę innych typów
(np.
int
czy
double): deklarując zmienną typu
dni
(➏), podajemy najpierw nazwę typu, potem nazwę
zmiennej i inicjujemy ją jedną z dozwolonych wartości (w tym
przypadku wartością
sob). W linii ➑ do tej zmiennej
przypisujemy inną wartość typu
dni, a mianowicie
nie.
1. #include <iostream> 2. #include <string> 3. using namespace std; 4. 5. enum dni {pon, wto=0, sro=0, czw=0, pia=0, sob, nie}; ➊ 6. 7. void info(dni day) { 8. static string typDnia[]={" powszedni", ➋ 9. " sobotni", "swiateczny"}; 10. int stawka = 200*(1 + day); ➌ 11. cout << "Dzien " << typDnia[day] << ". " ➍ 12. << "Stawka wynosi: " << stawka << " PLN\n"; 13. } 14. 15. int main() { 16. info(pon); ➎ 17. 18. dni dzien = sob; ➏ 19. info(dzien); ➐ 20. 21. dzien = nie; ➑ 22. info(dzien); ➒ 23. }
Wewnątrz funkcji info definiujemy trzyelementową tablicę obiektów typu string (słowem static na razie się nie przejmujmy). Do elementów tej tablicy odwołujemy się w linii ➍. Zauważmy, że w linii tej używamy wartości zmiennej typu dni jako indeksów tablicy: jest to dozwolone, gdyż nastąpi automatyczna konwersja (promocja) tych wartości do typu int zgodnie z wartościami całkowitymi przypisanymi poszczególnym elementom wyliczenia. W naszym przypadku mogą to być wyłącznie wartości 0, 1 i 2, co akurat odpowiada dozwolonym wartościom indeksu tablicy typDnia. Podobnie, w wierszu ➌, widzimy dodawanie ' 1 + day'. Ponieważ jeden argument jest typu int, a drugi typu dni, nastąpi również promocja wartości zmiennej day do wartości typu int. Co jest jednak ważne, to fakt, że nie będzie konwersji w przeciwną stronę: od int do dni. Tak więc niemożliwe byłoby wywołanie funkcji info z argumentem innego typu niż typ dni; na przykład, wywołanie ' int k = 1; info(k);' spowodowałoby błąd kompilacji, mimo że jedynka odpowiada jednej z dozwolonych wartości dla zmiennej typu dni. Ta cecha jest bardzo pożyteczną cechą wyliczeń: użycie typu wyliczeniowego zapewnia, że funkcja info zostanie zawsze wywołana z legalnym argumentem; musi on bowiem być typu dni a więc na pewno odpowiadać którejś z jedynych dopuszczalnych wartości całkowitych: w naszym przypadku 0, 1 lub 2. Dzięki temu nie musimy już sprawdzać, czy indeks w linii ➍ nie wykracza poza zakres — użytkownik funkcji info zostanie zmuszony do świadomego wyboru argumentu, który na pewno nie spowoduje błędu.
Ponieważ nie ma konwersji od typu int do typu dni, zmiennym zadeklarowanym jako zmienne typu dni można przypisywać wartości wyłącznie tego typu. Choć symbolowi sob odpowiada wartość 1, to
dni day = 1; // ZLEbyłoby nielegalne; należy użyć
dni day = sob;
Czytelnik może sprawdzić, że wydruk z powyższego programu wygląda następująco:
Dzien powszedni. Stawka wynosi: 200 PLN Dzien sobotni. Stawka wynosi: 400 PLN Dzien swiateczny. Stawka wynosi: 600 PLN
Kontrola legalności argumentów wysyłanych do funkcji to bardzo częste zastosowanie typów wyliczeniowych.
Nowy standard C++11 wprowadza dodatkowo inny sposób na definiowanie typów wyliczeniowych — powiemy o tym po wprowadzeniu pojęcia klas.
T.R. Werner, 21 lutego 2016; 20:17