C++11 bietet benutzerdefinierte Literale für Zeichen, C-Strings, natürliche Zahlen und Fließkommazahlen an. Für natürliche Zahlen und Fließkommazahlen in raw und cooked Form. Mit C++14 gibt es darüber hinaus neue built-in Literale für Binärzahlen, C++-Strings, komplexe Zahlen und Zeitwerte. Es gibt daher noch viel zu erzählen.
Die vier benutzerdefinierten Literale
Nach dem Vorgeschmack im Artikel Benutzerdefinierte Literale folgen in diesem Artikel - wie versprochen - die Details. Um der Verwirrung entgegenzuwirken, stellt die Tabelle die vier benutzerdefinierten Literal-Typen samt ihrer raw und cooked Variationen gegenüber.
Wie ist die Tabelle zu lesen? Der Datentyp Zeichen besitzt die Form Zeichen_Suffix. Ein Beispiel dafür ist 's'_c. In diesem Fall versucht der Compiler den Literal Operator operator"" _c('s') aufzurufen. Das Zeichen ist in dem konkreten Fall vom Typ char. Neben dem Datentyp char unterstützt C++ als Zeichentyp noch wchar_t, char16_t und char32_t. Die gleichen Zeichentypen können als auch Basis für den C-Strings verwendet werden. In der Tabelle kommt ein char zum Einsatz. Schön ist an dem Beispiel zu sehen, dass der Compiler den C-String "hi"_i18 auf den Literal-Operator operator"" _i18n("hi",2) abbildet. Die 2 steht für die Länge des C-Strings.
Natürliche Zahlen bzw. Fließkommazahlen kann der Compiler sowohl auf natürliche Zahlen (unsigned long long int) bzw. Fließkommazahlen (long double) als auch auf C-Strings abbilden. Die erste Variate wird als cooked Form, die zweite Variante als raw Form bezeichnet. Der Compiler verwendet die raw Form genau dann, wenn der Literal Operator sein Argument als C-String erwartet. Andernfalls verwendet er die cooked Form. Stehen beide Varianten zur Verfügung, besitzt die cooked Form die höhere Priorität.
Zugegeben. In den letzten Zeilen steckt einiges an Verwirrungspotential. Daher habe ich in der nächsten Tabelle die Literal-Operatoren aus der Perspektive ihrer möglichen Signaturen zusammengefasst. Die erste Spalte enthält die Signatur des Literal-Operators, die zweite den Typ des benutzerdefinierten Literals und die dritte ein Beispiel für ein benutzerdefiniertes Literal, das der Signatur des Literal-Operators entspricht.
Rechne es nochmals
Im Artikel Benutzerdefinierte Literale habe ich berechnet, wie viele Meter ich im Schnitt in der Woche mit meinem Auto zurücklege. Diese Berechnung basierte auf benutzerdefinierten Literalen vom Typ long double in der cooked Form. Um die Berechnung auf die raw Form umzustellen, müssen nur die Literal-Operatoren leicht angepasst werden.
In dem konkreten Fall ist es notwendig, die Argumente der Literal-Operatoren vom Typ C-String in einen long double zu konvertieren. Das geht mit der neuen Funktion std::stold leicht von der Hand.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
// unit.h #ifndef UNIT_H #define UNIT_H #include <distance.h> namespace Distance{ namespace Unit{ MyDistance operator "" _km(const char* k){ return MyDistance(1000* std::stold(k)); } MyDistance operator "" _m(const char* m){ return MyDistance(std::stold(m)); } MyDistance operator "" _dm(const char* d){ return MyDistance(std::stold(d)/10); } MyDistance operator "" _cm(const char* c){ return MyDistance(std::stold(c)/100); } } } #endif |
Weder muss die Klasse MyDistance verändert werden.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
// distance.h #ifndef DISTANCE_H #define DISTANCE_H #include <iostream> #include <ostream> namespace Distance{ class MyDistance{ public: MyDistance(double i):m(i){} friend MyDistance operator+(const MyDistance& a, const MyDistance& b){ return MyDistance(a.m + b.m); } friend MyDistance operator-(const MyDistance& a,const MyDistance& b){ return MyDistance(a.m - b.m); } friend MyDistance operator*(double m, const MyDistance& a){ return MyDistance(m*a.m); } friend MyDistance operator/(const MyDistance& a, int n){ return MyDistance(a.m/n); } friend std::ostream& operator<< (std::ostream &out, const MyDistance& myDist){ out << myDist.m << " m"; return out; } private: double m; }; } Distance::MyDistance getAverageDistance(std::initializer_list<Distance::MyDistance> inList){ auto sum= Distance::MyDistance{0.0}; for (auto i: inList) sum = sum + i ; return sum/inList.size(); } #endif |
Noch benötigt das Hauptprogramm eine Modifikation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
// average.cpp #include <distance.h> #include <unit.h> using namespace Distance::Unit; int main(){ std:: cout << std::endl; std::cout << "1.0_km: " << 1.0_km << std::endl; std::cout << "1.0_m: " << 1.0_m << std::endl; std::cout << "1.0_dm: " << 1.0_dm << std::endl; std::cout << "1.0_cm: " << 1.0_cm << std::endl; std::cout << std::endl; std::cout << "0.001 * 1.0_km: " << 0.001 * 1.0_km << std::endl; std::cout << "10 * 1_dm: " << 10 * 1.0_dm << std::endl; std::cout << "100 * 1.0cm: " << 100 * 1.0_cm << std::endl; std::cout << "1_.0km / 1000: " << 1.0_km / 1000 << std::endl; std::cout << std::endl; std::cout << "1.0_km + 2.0_dm + 3.0_dm + 4.0_cm: " << 1.0_km + 2.0_dm + 3.0_dm + 4.0_cm << std::endl; std::cout << std::endl; auto work= 63.0_km; auto workPerDay= 2 * work; auto abbrevationToWork= 5400.0_m; auto workout= 2 * 1600.0_m; auto shopping= 2 * 1200.0_m; auto distPerWeek1= 4*workPerDay-3*abbrevationToWork+ workout+ shopping; auto distPerWeek2= 4*workPerDay-3*abbrevationToWork+ 2*workout; auto distPerWeek3= 4*workout + 2*shopping; auto distPerWeek4= 5*workout + shopping; std::cout << "distPerWeek1: " << distPerWeek1 << std::endl; auto averageDistance= getAverageDistance({distPerWeek1,distPerWeek2,distPerWeek3,distPerWeek4}); std::cout<< "averageDistance: " << averageDistance << std::endl; std::cout << std::endl; } |
Die Entfernungen ändern sich natürlich nicht.
Neue built-in Literale mit C++14
Mit C++14 erweitert C++ das Repertoire seiner Literale um Literale für Binärzahlen, C++-Strings, komplexe Zahlen und Zeitwerte. Zuerst folgt wieder die kompakte Übersicht.
Ein paar Besonderheiten bieten die neuen Literale. Binärzahlen werden durch das Präfix 0b definiert. Alle Literale besitzen keinen Unterstrich. Das unterscheidet sie von den benutzerdefinierten Literalen. Mit C++14 gibt es zum ersten Mal ein C++-String Literal. Bisher gab es nur ein C-String Literal. Das heißt, das in klassischem C++ für die Initialisierung eines C++-Strings immer ein C-String Literal notwendig war. Sehr viel Komfort bieten die Zeit-Literale, den sie respektieren automatisch ihre Einheit. Sie sind Literale vom Typ std::chrono::duration.
Die Grundeinheit für die Zeit ist die Sekunde. Da stellt sich mir natürlich die Frage. Wie viele Sekunden benötigt mein Sohn täglich für alle Tätigkeiten rund um seine Schule? Die Antwort gibt das Programm.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
// literals.cpp #include <iostream> #include <chrono> using namespace std::literals::chrono_literals; int main(){ std::cout << std::endl; auto schoolHour= 45min; auto shortBreak= 300s; auto longBreak= 0.25h; auto schoolWay= 15min; auto homework= 2h; auto schoolDayInSeconds= 2*schoolWay + 6 * schoolHour + 4 * shortBreak + longBreak + homework; std::cout << "School day in seconds: " << schoolDayInSeconds.count() << std::endl; std::cout << "School day in minutes: " << schoolDayInSeconds.count() / 60 << std::endl; std::cout << "School day in hours: " << schoolDayInSeconds.count() / 3600 << std::endl; std::cout << std::endl; } |
Ich denke, das Programm benötigt keine Erläuterungen. Die Suffixe sind aussagekräftig genug. Für die richtige Addition der verschiedenen Zeiteinheiten sorgt der Compiler. Natürlich unterstützen die Zeit-Literale darüber hinaus die Grundrechenarten Addition, Subtraktion, Multiplikation, Division und die Division mit Rest (%).
Gerade habe ich leider keinen C++14-Compiler zur Hand. Kein Problem. Der Online-Compiler auf der Seite en.cppreference.com leistet mir wertvolle Hilfe.
Mein Sohn benötigt täglich 27300 Sekunden für alle Tätigkeiten rund um seine Schule. In Stunden umgerechnet ist dies fast ein voller Arbeitstag.
Wie geht's weiter?
Die klassischen Aufzählungstypen (enum) in C++ besitzen drei große Nachteile.
- Sie konvertieren implizit zu int.
- Sie führen ihren Bezeichner in den umgebenden Bereich ein.
- Der zugrundeliegende Typ kann nicht angegeben werden.
Insbesondere die Eigenschaften 1 und 2 sind gerne Grund von böse Überraschungen. Mit diesen räumen die streng typisierten Aufzählungstypen im nächsten Artikel auf.
Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library". Hole dir dein E-Book. Unterstütze meinen Blog.
Weiterlesen...