image

image

Zu diesem Buch – sowie zu vielen weiteren O’Reilly-Büchern – können Sie auch das entsprechende E-Book im PDF-Format herunterladen. Werden Sie dazu einfach Mitglied bei oreilly.plus+:

www.oreilly.plus

3. AUFLAGE

C++

kurz & gut

Kyle Loudon & Rainer Grimm

image

Kyle Loudon und Rainer Grimm

Lektorat: Alexandra Follenius

Bibliografische Information Der Deutschen Nationalbibliothek

ISBN:

Dieses Buch erscheint in Kooperation mit O’Reilly Media, Inc. unter dem Imprint »O’REILLY«. O’REILLY ist ein Markenzeichen und eine eingetragene Marke von O’Reilly Media, Inc. und wird mit Einwilligung des Eigentümers verwendet.

3. Auflage

Die vorliegende Publikation ist urheberrechtlich geschützt. Alle Rechte vorbehalten. Die Verwendung der Texte und Abbildungen, auch auszugsweise, ist ohne die schriftliche Zustimmung des Verlags urheberrechtswidrig und daher strafbar. Dies gilt insbesondere für die Vervielfältigung, Übersetzung oder die Verwendung in elektronischen Systemen.

Es wird darauf hingewiesen, dass die im Buch verwendeten Soft- und Hardware-Bezeichnungen sowie Markennamen und Produktbezeichnungen der jeweiligen Firmen im Allgemeinen warenzeichen-, marken- oder patentrechtlichem Schutz unterliegen.

Die Informationen in diesem Buch wurden mit größter Sorgfalt erarbeitet. Dennoch können Fehler nicht vollständig ausgeschlossen werden. Verlag, Autoren und Übersetzer übernehmen keine juristische Verantwortung oder irgendeine Haftung für eventuell verbliebene Fehler und deren Folgen.

5 4 3 2 1 0

Inhalt

Einführung

1Programmstruktur

Programmstart

Programmende

Header-Dateien

Quelldateien

Präprozessoranweisungen

Präprozessormakros

2Lexikalische Elemente

Kommentare

Bezeichner

Reservierte Schlüsselwörter

Literale

Operatoren

Ausdrücke

3Typen

Fundamentale Typen

Zusammengesetzte Typen

Deklarationen

Automatische Typableitung

Implizite Typkonvertierungen und explizite Casts

Typdefinitionen

Typinformationen

Speicherverwaltung

4Anweisungen

Ausdrucksanweisungen

Null-Anweisungen

Zusammengesetzte Anweisungen

Iterationsanweisungen (Schleifen)

Verzweigungen

Sprunganweisungen

5Ausnahmebehandlung

try

throw

catch

noexcept

6Zusicherungen

static_assert

7Sichtbarkeit

Geltungsbereiche

Namensräume

8Funktionen

Funktionen deklarieren

Alternative Funktionssyntax

Funktionsdefinition

Default-Argumente

Funktionen überladen

Inline-Funktionen

Lambda-Funktionen

9Klassentypen

Objekte

Attribute

Methoden

Zugriffsrechte für Klassenmitglieder

Deklarationen

Freunde

Strukturen

Unions

10Vererbung

Basisklassen

Methoden

Mehrfachvererbung

11Templates

Funktions-Templates

Klassen-Templates

Template-Parameter

Template-Argumente

Spezialisierung

12Die C++-Standardbibliothek

Der Namensraum std

Header-Dateien

I/O-Streams

Wichtige Datentypen

Index

Einführung

C++ – kurz & gut ist eine Schnellreferenz zum aktuellen C++-Standard C++17. Der internationale Standard ISO/IEC 14882:2017 umfasst gut 1.600 Seiten und wurde 2017 veröffentlicht. C++17 setzt die Tradition der C++-Standards fort, die mit C++11 und C++14 begonnen haben und im Dreijahreszyklus veröffentlicht werden. Der C++11-Standard, der 13 Jahre nach dem bis zu diesem Zeitpunkt einzigen C++-Standard C++98 verabschiedet wurde, steht für modernes C++. Formal betrachtet, ist C++03 zwar ein weiterer C++-Standard, der aber nur den Charakter einer technischen Korrektur besitzt.

Das Ziel dieser Kurzreferenz ist es, die Kernsprache von C++ kompakt vorzustellen. Trotzdem werden in diesem Buch aus reinem Pragmatismus einige Features aus der Standardbibliothek verwendet. Denn um ehrlich zu sein, ist die Sprache C++ ohne ihre Standardbibliothek nicht einmal eine halbe Sprache. Da die Komponenten der Standardbibliothek durch den Namensraum std:: gekennzeichnet sind, sollte dies nicht zu Verwirrung führen. Zur C++-Standardbibliothek gehören die Standard Template Library (STL) mit den Klassen std::string, std::vector und std::map, die I/O-Streams mit den Objekten std::cout, std::cerr und std::cin, eine mächtige Bibliothek zum automatischen Speichermanagement, die neue Multithreading-Bibliothek und eine Bibliothek für reguläre Ausdrücke in C++, um nur die prominentesten zu nennen.

Dieses Buch wurde für Leser geschrieben, die bereits eine gewisse Vertrautheit mit C++ besitzen. Ein erfahrener C++-Programmierer wird aus der konzentrierten Referenz der Sprachmerkmale von C++ den größten Nutzen ziehen. Wenn Sie hingegen C++-Einsteiger sind, sollten Sie im ersten Schritt ein Lehrbuch dieser Kurzreferenz vorziehen. Haben Sie das Lehrbuch aber gemeistert, hilft Ihnen dieses Werk mit seinen vielen kurzen Codebeispielen, die Sprachmerkmale von C++ in einem weiteren Schritt sicher anzuwenden. Dadurch erwerben Sie solide C++-Kenntnisse und können sich anschließend in die Untiefen dieser Sprache vorwagen.

Typografische Konventionen

In diesem Buch werden die folgenden typografischen Konventionen angewandt:

Kursiv

Diese Schrift wird für Dateinamen und Hervorhebungen verwendet.

Nichtproportionalschrift

Diese Schrift wird für Code, Befehle, Schlüsselwörter und Namen von Typen, Variablen, Funktionen und Klassen verwendet.

image

Hinweis

Dieser Abschnitt enthält weiterführende Informationen.

image

Warnung

Dieser Abschnitt warnt oder mahnt zur Vorsicht.

Danksagungen zur 2. Auflage

Ich möchte Alexandra Follenius, meiner Lektorin bei O’Reilly, für ihre Unterstützung und Anleitung bei der Arbeit mit diesem Buch danken. Besonderer Dank gilt natürlich auch dem Autor der 1. Auflage dieses Werks, Kyle Loudon. Es war ein sehr spannendes Unternehmen, das Werk meines Vorgängers zu überarbeiten, um all die neuen Features von C++11 einzuarbeiten. Ich danke vor allem aber Karsten Ahnert, Guntram Berti, Dmitry Ganyushin, Peter Gottschling, Sven Johannsen, Torsten Robitzki, Jörn Seger und Detlef Wilkening, die sich die Zeit genommen haben, das Manuskript auf sprachliche und insbesondere inhaltliche Fehler zu durchleuchten.

3. Auflage

Es freut mich sehr, die mittlerweile dritte Auflage dieses Buchs zu schreiben. Die dritte Auflage wird neben einigen Verbesserungen vor allem die zwei neuen Standards C++14 und C++17 enthalten. Features, deren Verhalten vom verwendeten C++-Standard abhängen oder die neu mit C++14 oder C++17 zum C++-Standard hinzugekommen sind, werde ich optisch hervorheben.

Dass die 3. Auflage dieses Buchs nur vier Jahre nach der 2. Auflage erscheint, zeigt vor allem eines: Die nun 40 Jahre alte Programmiersprache C++ ist immer noch dynamisch und erfreut sich großer Beliebtheit.

KAPITEL 1

Programmstruktur

Auf der höchsten Ebene besteht ein C++-Programm aus einer oder mehreren Quelldateien, die C++-Quellcode enthalten. C++-Quell-dateien binden oft zusätzlichen Quellcode in Form von Header-Dateien ein. Der C++-Präprozessor ist für das Einbinden des Codes aus diesen Dateien vor der Kompilierung jeder Datei zuständig. Gleichzeitig kann der Präprozessor aber auch mittels sogenannter Präprozessoranweisungen verschiedene andere Operationen ausführen. Eine Quelldatei wird nach der Vorverarbeitung durch den Präprozessor Übersetzungseinheit (Translation Unit) genannt.

Programmstart

Die Funktion main, die genau einmal definiert werden muss, ist der Einstiegspunkt für das C++-Programm. In der standardisierten Form erwartet diese Funktion kein Argument oder aber zwei Argumente, die das Betriebssystem beim Programmstart mitliefert. Viele C++-Implementierungen erlauben zusätzliche Parameter. Der Rückgabetyp der main-Funktion ist int:

int main()

int main(int argc, char* argv[])

argc gibt die Anzahl der Argumente auf der Kommandozeile an. argv hingegen ist ein Array von C-Strings, das die Argumente in der Reihenfolge ihres Auftretens enthält. Der Name des ausführbaren Programms steht in dem Element argv[0]. Der Wert von argv[argc] ist 0.

Das folgende Beispiel zeigt die main-Funktion eines einfachen C++-Programms, das den Benutzer nach den Aktionen fragt, die auf einem Konto durchgeführt werden sollen:

#include <iostream>

#include <cstdlib>

#include "Account.h"

int main(int argc, char* argv[]){

Account account(0.0);

if (argc > 1) account.deposit(std::atof(argv[1]));

char action;

double amount;

while (true){

std::cout << "The balance is

"<< account.getBalance()

<< std::endl;

std::cout << "Choice: d, w or e: ";

std::cin >> action;

switch (action){

case 'd':

std::cout << "Amount paid in: ";

std::cin >> amount;

account.deposit(amount);

break;

case 'w':

std::cout << "Amount paid out: ";

std::cin >> amount;

account.withdraw(amount);

break;

case 'e':

exit(0);

default:

std::cout << "Error" << std::endl;

}

}

return 0;

}

Die Definition für die Klasse Account folgt später. Auf der Kommandozeile wird beim Programmstart eine initiale Einzahlung angegeben. Die Funktion atof aus der C++-Standardbibliothek dient dazu, das Kommandozeilenargument von einem C-String in einen double-Wert zu konvertieren.

Programmende

Mit dem Verlassen der main-Funktion ist das C++-Programm beendet. Der Rückgabewert wird an das Betriebssystem weitergegeben und wird zum Rückgabewert des ausgeführten Programms. Falls main keine return-Anweisung enthält, wird ein implizites return 0 am Ende des Funktionskörpers von main ausgeführt. Durch das explizite Aufrufen der exit-Funktion aus der C++-Standardbibliothek kann ein Programm direkt beendet werden. Diese Funktion erwartet den Rückgabewert des ausführbaren Programms als Argument.

Header-Dateien

Header-Dateien, die typischerweise die Dateinamenserweitung .h besitzen, enthalten Quellcode, der in mehreren Dateien eingebunden werden kann. Eine Header-Datei sollte dagegen nie eines der folgenden Dinge enthalten:

image

Header-Dateien in der C++-Standardbibliothek verwenden keine Dateinamenserweiterung. So ist die std::string in der Header-Datei string definiert.

Meistens wird für jede wichtige Klasse, die definiert wird, eine Header-Datei angelegt. Zum Beispiel ist die unmittelbar folgende Klasse Account in der Header-Datei Account.h definiert.

#ifndef ACCOUNT_H

#define ACCOUNT_H

class Account{

public:

Account(double b);

void deposit(double amt);

void withdraw(double amt);

double getBalance() const;

private:

double balance;

};

#endif

Die Implementierung dieser Klasse befindet sich in der Datei Account.cpp. Durch die Präprozessoranweisung #include wird die Header-Datei in die Quelldatei eingebunden.

Da Header-Dateien wiederum häufig von anderen Header-Dateien eingebunden werden, ist es notwendig, darauf zu achten, dass ein und dieselbe Header-Datei nicht mehr als einmal eingebunden wird. Ansonsten kann dies zu Compiler-Fehlern führen. Durch das Verwenden der Präprozessoranweisungen #ifndef, #define und #endif in der Definition der Klasse Account wird das mehrfache Einbinden verhindert.

Die Strategie, die Header-Datei mit #define und #endif zu umgeben, zwingt den Prozessor, den Bezeichner mittels #ifndef abzufragen. Falls dieser nicht definiert ist, definiert der Präprozessor ihn und übersetzt den Inhalt der Datei. So wird in der Datei Account.cpp der Inhalt von Account.h nur dann übersetzt, wenn ACCOUNT_H nicht definiert ist. Das Erste, was während dieser Übersetzung passiert, ist die Definition von ACCOUNT_H, um sicherzustellen, dass die Header-Datei nicht noch ein zweites Mal übersetzt wird. Zur Gewährleistung der Eindeutigkeit wird per Konvention der Bezeichner HEADER_H (Include-Guard) verwendet. Dabei steht HEADER für den Namen der Header-Datei ohne Dateinamenserweiterung.

image

Häufig werden Include-Guards verwendet, die mit einem Unterstrich (_) beginnen. Diese Namen sind jedoch für die Sprachimplementierung reserviert.

Quelldateien

C++-Quelldateien enthalten C++-Quellcode. Sie haben in der Regel die Dateinamenserweiterung .cpp. Während der Kompilierung übersetzt der Compiler Quelldateien in Objektdateien, die typischerweise die Erweiterung .obj oder .o besitzen. Die Objektdateien werden vom Linker zusammengebunden, um ein fertiges, ausführbares Programm oder eine Bibliothek zu erzeugen.

Meist wird eine Quelldatei für jede wichtige Klasse anlegt. Beispielsweise findet sich die Implementierung von Account in der folgenden Datei Account.cpp:

#include "Account.h"

Account::Account(double b): balance(b){}

void Account::deposit(double amt){

balance += amt;

}

void Account::withdraw(double amt){

balance -= amt;

}

double Account::getBalance() const{

return balance;

}

Präprozessoranweisungen

Der C++-Präprozessor kann eine Reihe nützlicher Operationen durchführen, die über Anweisungen gesteuert werden. Jede Anweisung beginnt mit einem Doppelkreuz (#) als erstem nicht leeren Zeichen einer Zeile. Präprozessoranweisungen können sich über mehrere Zeilen erstrecken, wenn ein Backslash (\) am Ende aller Zeilen mit Ausnahme der letzten steht.

#define

Die Anweisung #define ersetzt einen Bezeichner, wo immer er auftritt, durch den darauffolgenden Text:

#define BUFFER_SIZE 80

char buffer[BUFFER_SIZE]; // Wird zu char buffer[80];

Wenn kein Text nach dem Bezeichner angegeben wird, definiert der Präprozessor den Bezeichner als leer. Damit lässt sich der Bezeichner aber abfragen, ob er definiert ist. Dieses Feature kam bereits bei der Definition von ACCOUNT_H zum Einsatz.

image

In C++ ist die Verwendung von Aufzählungen sowie von Variablen und Instanzvariablen, die mit den Schlüsselwörtern const oder static const definiert sind, der Anweisung #define für die Definition von Konstanten eindeutig vorzuziehen.

Die Anweisung #define kann Argumente zur Makroersetzung von Text entgegennehmen:

#define MIN(a, b) (((a) < (b)) ? (a):(b))

int x= 5, y= 10, z;

z= MIN(x, y); // Setzt z auf 5.

Um unerwartete Probleme mit der Operatorpräzedenz zu vermeiden, sollten die Operatoren vollständig von Klammern umgeben werden.

image

In C++ sollten statt Makros Templates oder Inline-Funktionen verwendet werden. Templates und Inline-Funktionen verhindern unerwartete Ergebnisse von Makros vollständig. So wird durch MIN(x++,y) x zweimal inkrementiert, falls das erste Argument kleiner als das zweite ist. Die Makroersetzung verwendet in diesem konkreten Fall x++ und nicht etwa das Ergebnis von x++ als ersten Parameter.

#undef

Die #undef-Anweisung hebt die Definition eines Symbols auf, sodass ein Test auf Definition false ergibt:

#undef LOGGING_ENABLED

#ifdef, #ifndef, #else, #endif

#ifdef, #ifndef , #else und #endif werden zusammen verwendet. Die #ifdef-Anweisung veranlasst den Präprozessor je nachdem, ob ein Symbol definiert ist oder nicht, unterschiedlichen Code einzubinden:

#ifdef LOGGING_ENABLED

std::cout << "Logging enabled" << std::endl;

#else

std::cout << "Logging disabled" << std::endl;

#endif

Die Verwendung von #else ist optional. #ifndef funktioniert ähnlich, aber der Code nach der #ifndef-Anweisung wird nur dann eingebunden, wenn das Symbol nicht definiert ist.

#if, #elif, #else, #endif

Die Anweisungen #if, #elif, #else und #endif werden wie die #ifdef-Anweisungen zusammen verwendet. Sie veranlassen den Präprozessor, Code abhängig davon einzubinden, ob ein Ausdruck zu wahr oder zu falsch ausgewertet wird:

#if (LOGGING_LEVEL == LOGGING_MIN && LOGGING_FLAG)

std::cout << "Minimal Logging" << std::endl;

#elif (LOGGING_LEVEL == LOGGING_MAX && LOGGING_FLAG)

std::cout << "Maximal Logging" << std::endl;

#elif LOGGING_FLAG

std::cout << "Standard Logging" << std::endl;

#endif

Die #elif-(else-if-)Anweisung wird genutzt, um eine Reihe von Tests miteinander zu verketten.

#include

Die #include-Anweisung veranlasst den Präprozessor, eine andere Datei, normalerweise eine Header-Datei, einzubinden. Standard-Header-Dateien werden in spitze Klammern, benutzerdefinierte in doppelte Anführungsstriche eingeschlossen:

#include <iostream>

#include "Account.h"

Der Präprozessor durchsucht je nachdem, ob die Datei in spitze Klammern oder Anführungszeichen eingeschlossen wird, unterschiedliche Pfade. Welche Pfade es sind, hängt vom System ab.

#error

Die #error-Anweisung veranlasst das Anhalten der Kompilierung und die Ausgabe eines Texts:

#ifdef LOGGING_ENABLED

#error Logging should not be enabled

#endif

#line

Die #line-Anweisung veranlasst den Präprozessor, die intern vom Compiler im Makro __LINE__ gespeicherte Zeilennummer zu ändern:

#line 100

Optional kann hinter der Zeilennummer ein Dateiname in doppelten Anführungsstrichen angegeben werden. Dadurch wird der intern im Makro __FILE__ gespeicherte Name geändert:

#line 100 "NewName.cpp"

#pragma

Manche Operationen, die der Präprozessor durchführen kann, sind implementierungsabhängig. Mit der Anweisung #pragma lassen sich diese Operationen steuern, indem zusätzlich die jeweils benötigten Parameter angeben werden:

#ifdef LOGGING_ENABLED

#pragma message("Logging enabled")

#endif

Präprozessormakros

Der C++-Präprozessor definiert mehrere Makros, um während der Kompilierung Informationen in eine Quelldatei einzufügen. Jedes Makro beginnt und endet mit zwei Unterstrichen. Einzige Ausnahme bildet __cplusplus, das keine Unterstriche am Ende hat.

__LINE__

Wird zur aktuellen Zeilennummer in der kompilierten Quelldatei expandiert.

__FILE__

Wird zum Namen der gerade kompilierten Quelldatei expandiert.

__DATE__

Wird zum Datum der Kompilierung expandiert.

__TIME__

Wird zur Uhrzeit der Kompilierung expandiert.

__STDC__

Ist dann definiert, wenn der Compiler dem ANSI-C-Standard vollständig folgt.

__cplusplus

Ist definiert, wenn das kompilierte Programm ein C++-Programm ist. Wie der Compiler bestimmt, ob es sich bei einem bestimmten Programm um ein C++-Programm handelt, ist Compiler-spezifisch. Möglicherweise muss ein Compiler-Schalter angegeben werden, oder der Compiler verwendet die Dateinamenserweiterung der Quelldatei.

KAPITEL 2

Lexikalische Elemente

Auf der fundamentalsten Ebene besteht ein C++-Programm aus einzelnen lexikalischen Elementen, sogenannten Token. Token sind logisch zusammenhängende Einheiten, die durch den Lexer gebildet werden, indem er den Textstrom in einen Tokenstrom zerlegt. Token werden in der Regel durch Leerraum (Leerzeichen, Zeilenwechsel, Tabulatoren usw.) voneinander abgegrenzt, können aber auch gebildet werden, wenn der Start des nächsten Tokens erkannt wird. Dies ist in dem nächsten Beispiel schön zu sehen:

ival+3

Dieser Tokenstrom besteht aus drei Token: ival, + und 3. Wenn kein Leerraum vorhanden ist, bildet der Compiler die Token, indem er von links nach rechts nach der längstmöglichen logischen Einheit sucht.

Die Token werden an den Parser übergeben. Dieser bestimmt, ob der Tokenstrom die korrekte Syntax besitzt. Zusammen bilden die Token komplexere semantische Konstrukte wie Deklarationen, Ausdrücke und Anweisungen, die sich auf den Ausführungsfluss auswirken.

Kommentare

Kommentare sind Anmerkungen im Quellcode. Sie richten sich an Entwickler und werden vom Compiler vollständig ignoriert. Sie werden in Leerzeichen konvertiert.

Ein Kommentar ist ein beliebiger Textblock, der entweder in /* und */ eingeschlossen wird oder hinter zwei Schrägstrichen (//) auf einer einzelnen Zeile folgt. Kommentare der ersten Form können nicht ineinander verschachtelt werden. Sie erstrecken sich gerne über mehrere Zeilen:

/* Dieser Kommentar hat mehr als eine Zeile.

Hier ist ein weiterer Teil des Kommentars.*/

Kommentare der zweiten Form sind nützlich für kurze Erläuterungen, die nicht mehr als eine einzige Zeile benötigen:

z = MIN(x, y); // z ist der kleinste Wert.

Wenn ein einzeiliger Kommentar beginnt, erstreckt er sich bis zum Ende der Zeile. Es gibt keine Möglichkeit, den Kommentar vorher zu beenden.

Bezeichner

Bezeichner in C++ sind Zeichenfolgen, die als Namen von Variablen, Funktionen, Parametern, Typen, Sprungmarken, Namensräumen und Präprozessormakros verwendet werden. Bezeichner können aus Buchstaben, Ziffern und Unterstrichen bestehen, dürfen aber nicht mit einer Ziffer beginnen. Die folgenden Zeichenketten sind allesamt zulässige C++-Bezeichner:

i addressBook Mgr item_count

ptr2 NAME_LENGTH class_ showWindow

Folgende Regeln gelten für Bezeichner:

image

Es gibt keine einheitlichen Stilkonventionen für Bezeichner. Viele Programmierer setzen aber Namen, die mit Kleinbuchstaben beginnen, für lokale Variablen, Instanzvariablen und Funktionen ein. Namen, die mit Großbuchstaben beginnen, werden dementsprechend für Typen, Namensräume und globale Variablen verwendet. Namen, die der Präprozessor verarbeitet, und Konstanten werden ganz in Großbuchstaben geschrieben.

Reservierte Schlüsselwörter

C++ definiert eine Reihe von Schlüsselwörtern und alternativen Token. Dies sind Zeichenfolgen mit besonderer Bedeutung in der Sprache. Diese Wörter sind reserviert und können nicht als Bezeichner verwendet werden. Die reservierten Schlüsselwörter in C++ lauten:

alignas

alignof

and

and_eq

asm

auto

bitand

bitor

bool

break

case

catch

char

char16_t

char32_6

class

compl

const

constexpr

const_cast

continue

decltype

default

delete

do

double

dynamic_cast

else

enum

explicit

export

extern

false

final

float

for

friend

goto

if

inline

int

long

mutable

namespace

new

noexcept

not

not_eq

nullptr

operator

or

or_eq

override

private

protected

public

register

reinterpret_cast

return

short

signed

sizeof

static

static_assert

static_cast

struct

switch

template

this

thread_local

throw

true

try

typedef

typeid

typename

union

unsigned

using

virtual

void

volatile

wchar_t

while

xor

xor_eq

Literale

Literale sind lexikalische Elemente, die explizite Werte in einem Programm repräsentieren. C++ definiert viele verschiedene Typen von Literalen, die alle unter ihrem jeweiligen Typ im Abschnitt »Fundamentale Typen« auf Seite 45 beschrieben werden. Darüber hinaus kennt C++ Funktionsliterale, die auch als Lambda-Funktionen bezeichnet werden.

Benutzerdefinierte Literale

Benutzerdefinierte Literale sind selbst definierte Literale. Diese werden in C++ für ganze Zahlen, Fließkommazahlen, C-Strings und auch Zeichen unterstützt. Für sie gilt die folgende Syntax: Built-in-Literal + _ + Suffix. Das Suffix stellt in der Regel eine Einheit dar:

101000101_b

63_s

10345.5_dm

123.45_km

100_m

131094_cm

33_cent

"Hallo"_i18n

Benutzerdefinierte Literale erlauben es, Werte direkt mit ihrer Einheit zu verknüpfen. Damit lassen sich Operanden in C++ implementieren, die bei Operationen ihre Einheit berücksichtigen: Dist myDis= 10345.5_dm + 123.45_km – 100_m + 131094_cm. Die C++-Laufzeitumgebung bildet die benutzerdefinierten Literale auf die entsprechenden Literaloperatoren ab (siehe Abschnitt »Operatoren überladen« auf Seite 146). Diese müssen vom Programmierer implementiert werden. So definiert in dem folgenden Beispiel der Namensraum Unit alle Literaloperatoren, die für das richtige Interpretieren des arithmetischen Ausdrucks Dist myDis= 10345.5_dm + 123.45_km – 100_m + 131094_cm benötigt werden. In Kombination mit der Klasse Dist, die einen Konstruktor, einen Plus- und einen Minusoperator anbietet, lässt sich der ganze Ausdruck evaluieren. Dist verwendet als Einheit cm:

class Dist{

public:

explicit Dist(double i):cm(i){}

friend Dist operator +(const Dist& a, const Dist& b){

return Dist(a.cm + b.cm);

}

friend Dist operator -(const Dist& a, const Dist& b){

return Dist(a.cm - b.cm);

}

private:

double cm;

};

namespace Unit{

Dist operator "" _km(long double d){

return Dist(100000 * d);

}

Dist operator "" _m(long double m){

return Dist(100 * m);

}

Dist operator "" _dm(long double d){

return Dist(10 * d);

}

Dist operator "" _cm(long double c){

return Dist(c);

}

}

...

Dist myDis= 10345.5_dm + 123.45_km

- 100_m + 131094_cm; // 12589549_cm

C++ unterstützt benutzerdefinierte Literale für Fließkommazahlen und natürliche Zahlen in zwei Formen: in der sogenannten raw- und in der cooked-Form. Das erste Beispiel stellte die cooked-Form vor, das anschließende zweite Beispiel hingegen die raw-Form. Während bei der cooked-Form der Literaloperator das benutzerdefinierte Literal ohne Unterstrich und Suffix annimmt, erhält er bei der raw-Form das Literal als const char*-Typ. Im Fall des C-Strings erhält er es sogar als Paar (const char*, size_t). So führt das Literal "10345.5"_dm zu einem Aufruf des Literaloperators mit den Argumenten ("10345.5",7). Für Zeichen und C-Strings existiert nur die raw-Form für benutzerdefinierte Literale. Im Zweifelsfall besitzt die cooked-Form die höhere Präzedenz. Unter der Voraussetzung, dass Instanzen der Klasse Dist durch einen const char* und eine natürliche Zahl initialisiert werden können, besitzen die entsprechenden Literaloperatoren in der raw-Form die folgende Implementierung:

namespace Unit{

Dist operator "" _km(const char* k, std::size_t s){

return Dist(k, 100000);

}

Dist operator "" _m(const char* m, std::size_t s){

return Dist(m, 100);

}

Dist operator "" _dm(const char* d, std::size_t s){

return Dist(d, 10);

}

Dist operator "" _cm(const char* c, std::size_t s){

return Dist(c);

}

}

...

Dist myDis= "10345.5"_dm + "123.45"_km

- "100"_m + "131094"_cm; // "12589549"_cm

Im letzten Beispiel ist schön zu sehen, dass nicht nur das Argument des Literaloperators ein C-String ist, sondern auch das benutzerdefinierte Literal.

image

Da die Suffixe der benutzerdefinierten Literale kurz sind und gern für Einheiten verwendet werden, sind Namenskollisionen unvermeidlich. Daher sollten sie in Namensräumen definiert und anschließend importiert werden.

Neue Built-in-Literale

C++14 enthält neue Built-in-Literale für Binärzahlen, C++-Strings, komplexe Zahlen und Zeiteinheiten.

Tabelle 2-1: Built-in-Literale