Klasy enum i silne typowanie
Część sekcji Programowanie obiektowe ścieżki C++ w Coddy. Lekcja 80 z 104.
W C++11 wprowadzono klasy enum (zwane również enumeracjami zakresowymi), aby rozwiązać problemy tradycyjnych enumeracji w stylu C. Zapewniają większe bezpieczeństwo typów i zapobiegają przypadkowemu niewłaściwemu użyciu wartości enumeracji.
Tradycyjne typy wyliczeniowe mają problemy: ich wartości przedostają się do otaczającego zakresu i niejawnie konwertują się na liczby całkowite, co prowadzi do trudnych do wykrycia błędów:
// Tradycyjny enum – problematyczny
enum Color { Red, Green, Blue };
enum TrafficLight { Red, Yellow, Green }; // Błąd! Red i Green są już zdefiniowane
int x = Red; // Niejawnie konwertuje na int (0)
if (Red == 0) { } // Kompiluje się – porównanie enum z intKlasy enum rozwiązują te problemy, ograniczając zakres wartości i zapobiegając niejawnym konwersjom:
enum class Color { Red, Green, Blue };
enum class TrafficLight { Red, Yellow, Green }; // OK - brak konfliktu
Color c = Color::Red; // Trzeba użyć operatora zakresu
// int x = c; // Błąd! Brak konwersji niejawnej
int x = static_cast<int>(c); // Wymagana konwersja jawna
// if (c == 0) { } // Błąd! Nie można porównać z int
if (c == Color::Red) { } // OK - porównywane są wartości tego samego typuMożesz także określić typ bazowy, aby kontrolować pamięć:
enum class Status : uint8_t {
Inactive = 0,
Active = 1,
Pending = 2
};Typy wyliczeniowe dobrze integrują się z projektami OOP, dzięki czemu kod jest czytelniejszy, a błędy typów są wykrywane podczas kompilacji, a nie w czasie działania programu.
Wyzwanie
ŁatwyStwórzmy system priorytetów zadań, który wykorzystuje klasy enum do reprezentowania różnych poziomów priorytetów i statusów zadań. Pokazuje to, jak klasy enum zapewniają bezpieczeństwo typów i zapobiegają błędom występującym w przypadku tradycyjnych enumów.
Podzielisz kod na trzy pliki:
TaskTypes.h: Zdefiniuj klasy enum dla systemu zadań.Utwórz klasę enum
Priorityz wartościami:Low,Medium,HighiCritical. Określintjako typ bazowy, a wartości ustaw kolejno na 1, 2, 3 i 4.Utwórz klasę enum
Statusz wartościami:Pending,InProgressiCompleted.Dodaj dwie funkcje pomocnicze:
priorityToString(Priority p): zwraca priorytet jako ciąg znaków ("Low", "Medium", "High" lub "Critical")statusToString(Status s): zwraca status jako ciąg znaków ("Pending", "InProgress" lub "Completed")
Task.h: Zdefiniuj klasęTask, która korzysta z Twoich klas enum.Klasa
Taskpowinna przechowywać nazwę (ciąg znaków), wartośćPriorityi wartośćStatus. Konstruktor powinien przyjmować nazwę i priorytet, a domyślnym statusem powinno byćStatus::Pending.Zaimplementuj następujące metody:
setStatus(Status s): aktualizuje status zadaniagetPriorityValue(): zwraca bazową wartość całkowitą priorytetu przy użyciustatic_castdisplay(): wyświetla zadanie w formacie:[name] - Priority: [priority] (Value: [value]) - Status: [status]
main.cpp: Wczytaj trzy wartości:- Nazwę zadania (ciąg znaków)
- Poziom priorytetu jako liczbę całkowitą (1=Low, 2=Medium, 3=High, 4=Critical)
- Status jako liczbę całkowitą (0=Pending, 1=InProgress, 2=Completed)
Utwórz obiekt
Taskz podaną nazwą i priorytetem (przekonwertuj liczbę całkowitą na odpowiednią wartość enumPriorityprzy użyciustatic_cast). Następnie ustaw jego status na podstawie trzeciej wartości wejściowej. Na koniec wywołajdisplay(), aby wyświetlić szczegóły zadania.
Na przykład, dla wartości wejściowych Fix bug, 3 i 1:
Fix bug - Priority: High (Value: 3) - Status: InProgressDla wartości wejściowych Write docs, 1 i 2:
Write docs - Priority: Low (Value: 1) - Status: CompletedDla wartości wejściowych Deploy app, 4 i 0:
Deploy app - Priority: Critical (Value: 4) - Status: PendingZwróć uwagę, że klasy enum całkowicie oddzielają wartości priorytetu i statusu. Nie możesz przypadkowo porównać wartości Priority z Status ani przypisać jednej do drugiej. Jawne użycie static_cast, wymagane do konwersji między liczbami całkowitymi a wartościami enum, jasno określa intencję kodu i zapobiega subtelnym błędom.
Spróbuj swoich sił
#include <iostream>
#include <string>
#include "Task.h"
using namespace std;
int main() {
// Wczytaj dane wejściowe
string taskName;
int priorityInt;
int statusInt;
getline(cin, taskName);
cin >> priorityInt;
cin >> statusInt;
// TODO: Utwórz Task o podanej nazwie i priorytecie
// Użyj static_cast, aby przekonwertować priorityInt na typ wyliczeniowy Priority
// TODO: Ustaw status zadania na podstawie statusInt
// Użyj static_cast, aby przekonwertować statusInt na typ wyliczeniowy Status
// TODO: Wywołaj display(), aby wyświetlić szczegóły zadania
return 0;
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneBudowanie i kompilacja C++Pliki nagłówkowe i pliki źródłowePrzestrzenie nazw i zakresWprowadzenie do programowania obiektowego w C++Klasy a obiektyWskaźnik „this”Metody (funkcje składowe)Atrybuty (składowe danych)Podstawy konstruktorów i destruktorówPowtórzenie – prosty kalkulator4Właściwości klas
Elementy instancji a elementy statyczneGettery i setteryStałe funkcje składoweSłowo kluczowe mutableMetody i zmienne statyczneFunkcje i klasy zaprzyjaźnionePodsumowanie – menedżer konta bankowego7Dziedziczenie
Podstawy dziedziczeniaPoziomy dostępu w dziedziczeniuKolejność wywołań konstruktora i destruktoraNadpisywanie metodFunkcje wirtualne i VTableWielokrotne dziedziczenieDziedziczenie wirtualnePowtórzenie — hierarchia pracowników2Zarządzanie pamięcią
Pamięć stosu a stertyWskaźniki i referencjePamięć dynamiczna (new/delete)Inteligentne wskaźniki w C++RAII w C++Podsumowanie — menedżer tablicy dynamicznej5Hermetyzacja
Specyfikatory dostępu w C++Specyfikatory dostępu — szczegółowoUkrywanie informacjiStruktura a klasaKlasy zagnieżdżone i wewnętrznePodsumowanie — system ewidencji studentów8Polimorfizm
Polimorfizm czasu kompilacji i wykonaniaPrzeciążanie funkcjiFunkcje wirtualne — powtórkaCzysto wirtualne funkcjeKlasy abstrakcyjneProjektowanie interfejsów w C++Rzutowanie dynamiczne i RTTIPodsumowanie — kalkulator kształtów11Zaawansowane koncepcje OOP
Kompozycja a dziedziczenieMixiny z użyciem CRTPIdiom PimplWymazywanie typówKlasy enum i silne typowanieObsługa wyjątków w OOPWłasne hierarchie wyjątków3Konstruktory i destruktory
Konstruktor domyślnyKonstruktor z parametramiKonstruktor kopiującyKonstruktor przenoszącyListy inicjalizacyjne konstruktoraKonstruktory delegująceDestruktor — szczegółowe omówienieZasada trzech / pięciu / zeraPodsumowanie — klasa String6Przeciążanie operatorów
Wprowadzenie do przeciążania operatorówPrzeciążanie operatorów arytmetycznychPrzeciążanie operatorów porównaniaOperatory strumieniowePrzeciążanie operatora przypisaniaPrzeciążanie operatorów [] i ()Operatory konwersji typówPodsumowanie — klasa Matrix9Szablony
Szablony funkcjiSzablony klasSpecjalizacja szablonówSzablony wariadycznePodstawy SFINAE i cech typówPodsumowanie — kontener generycznyPoćwicz samodzielnie: Kompilator C++ online