Lokale Konstanten in C++
Konstantendefinitionen
Das folgende Programm zeigt die Definition einer Konstanten.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""susing namespace ::std::literals;
int main()
{ ::std::cout << 11 << "\n"s;
auto const elf { 11 }; /* vor C++17: auto const elf = 11; */
::std::cout << elf << "\n"s; }::std::cout
11
11
Das obenstehende Programm zeigt eine Konstantendefinition »auto const elf{ 11 };«. Sie beginnt mit dem Schlüsselwort »auto«, es folgt der Deklarationsspezifizierer »const«, der festlegt, daß eine Konstante definiert wird, der Namen »elf« der Konstanten, der in geschweiften Klammern stehenden Wertausdruck »11« der Konstanten und einem Semikolon »;«.
Eine Konstantendefinition legt in der Regel Typ, Name und Wert eine Konstanten fest.
Durch diese Konstantendefinition wird der Name »elf« der Konstanten so definiert, daß er den durch den Wertausdruck »11« festgelegten Typ »int« und Wert »11« hat. In der folgenden Anweisung »::std::cout << elf << "\n";« hat der Wertausdruck »elf« dann also beispielsweise den Typ »int« und den Wert »11«.
Der Initialisierer »{ 11 }« gilt hier nicht als Verbundanweisung (Block), obwohl auch er mit einer eckigen Klammer auf beginnt und mit einer eckigen Klammer zu endet. Man kann den Initialisierer daran von einer Verbundanweisung unterscheiden, daß er an einer Stelle steht, an der eine Verbundanweisung nicht erlaubt wäre, und daß er keine Anweisungsfolge enthält.
Der Wertausdruck »11« kann auch durch einen anderen Wertausdruck ersetzt werden. Dieser Wertausdruck muß entweder den Typ der Konstanten haben oder einen anderen Typ, dessen Werte entsprechend der weiter unten vorgestellten Regeln in den Typ der Konstanten gewandelt werden können.
Für Schlüsselwörter, wie »const« und »auto«, ist kein Include-Direktive nötig. Für in einer Übersetzungseinheit definierte Namen, wie »elf« ist ebenfalls keine Include-Direktive nötig. Es war nicht ganz sicher zu ermitteln, ob bei Verwendung der geschweiften Klammern als Initialisierer die Direktive »#include <initializer_list>« benötigt wird, deswegen haben wir sie sicherheitshalber dazugeschrieben.
Konstanten als eine Art von Abkürzung
Das folgende Programm zeigt die Definition einer Konstanten als eine Art von Abkürzung.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""susing namespace ::std::literals;
int main()
{ auto const lkw { "Lastkraftwagen"s }; /* vor C++17: auto const lkw = "Lastkraftwagen"s; */
::std::cout << "Ein "s << lkw << "-Fahrer lebt praktisch in seinem Fahrzeug.\n"s; }::std::cout
Ein Lastkraftwagen-Fahrer lebt praktisch in seinem Fahrzeug.
Schreibweise mit expliziter Typangabe
An Stelle von »auto« kann der Typ eines Namens auch ausdrücklich angegeben werden. Der Initialisierungsausdruck sollte dann denselben Typ haben. Vermutlich ist besser, die Schreibweise mit »auto« zu verwenden, weil dann keine Fehler bei der Typangabe gemacht werden können.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""susing namespace ::std::literals;
int main()
{ ::std::cout << 11 << "\n"s;int const elf { 11 };
::std::cout << elf << "\n"s;double const delf { 11.0 };
::std::cout << delf << "\n"s;::std::string const self { "11"s };
::std::cout << self << "\n"s; }transcript
11
11
11
11
An Stelle von »::std::string« kann oft auch »std::string« (ohne die beiden Doppelpunkte am Anfang) geschrieben werden, aber da dies nicht ganz so eindeutig und auch nicht in allen Fällen möglich, bevorzugen wir hier die Schreibweise »::std::string«.
Einschränkung auf eine Verbundanweisung
Ein Name kann nur innerhalb der Verbundanweisung verwendet werden, welche die Definition des Namens direkt enthält.
Dies erlaubt es, einen Namen innerhalb mehrerer Verbundanweisungen wiederzuverwenden.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""susing namespace ::std::literals;
int main()
{ ::std::cout << 11 << "\n"s;{ int const elf { 11 }; ::std::cout << elf << "\n"s; }
{ double const elf { 11.0 }; ::std::cout << elf << "\n"s; }
{ ::std::string const elf { "11"s }; ::std::cout << elf << "\n"s; }}transcript
11
11
11
11
Schreibweise mit Gleichheitszeichen
Auch eine Schreibweise mit einem Gleichheitszeichen ist möglich. Die Schreibweise mit den geschweiften Klammern sollte bevorzugt werden, da sie in C++ einheitlicher an verschiedenen Stellen verwendet werden kann.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""susing namespace ::std::literals;
int main()
{ auto const i = 300;
auto const d = 273.15;
::std::cout << i << ", "s << d << "\n"s; }::std::cout
300, 273.15
Informationsverluste
Die Schreibweise mit dem Gleichheitszeichen erlaubt Informationsverluste bei der Initialisierung
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""susing namespace ::std::literals;
int main()
{ int const n = 9.876543210;
::std::cout << n << "\n"s; }Protokoll
9
Die Schreibweise mit geschweiften Klammern erlaubt keine Informationsverluste bei der Initialisierung.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""susing namespace ::std::literals;
int main()
{ int const n { 9.876543210 };
::std::cout << n << "\n"s; }Protokoll
error: narrowing conversion from 'double' to 'int'
Da unabsichtliche Informationsverluste zu Programmierfehlern führen können, wird empfohlen, bevorzugt geschweifte Klammern zu verwenden, weil sie dabei helfen auf solche Informationsverluste aufmerksam zu werden.
Initialisierung einer C++ -Zeichenfolge mit einer C -Zeichenfolge
Eine C++-Zeichenfolge kann auch mit einer C -Zeichenfolge initialisiert werden. Dabei wird die C++ -Zeichenfolge dann entsprechend auf die Schriftzeichen gesetzt, die in der C -Zeichenfolge enthalten sind.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""susing namespace ::std::literals;
int main()
{ ::std::string const s { "abc" };
::std::cout << s << "\n"s; }transcript
abc
Vor C++17
Dieser Kurs wird derzeit gerade auf C++17 umgestellt.
Vor C++17 war die Schreibweise »auto const elf{ 11 };« noch nicht möglich. Bei älteren Compilern muß in solchen Fällen die öffnende geschweifte Klammer durch ein Gleichheitszeichen ersetzt und die schließende geschweifte Klammer entfernt werden. Man muß also schreiben: »auto const elf = 11;«.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""susing namespace ::std::literals;
int main()
{ ::std::cout << 11 << "\n"s;
auto const elf = 11;
::std::cout << elf << "\n"s; }::std::cout
11
11
Statt dessen kann man aber auch die geschweiften Klammern beibehalten und auf »auto« verzichten.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""susing namespace ::std::literals;
int main()
{ ::std::cout << 11 << "\n"s;
int const elf { 11 };
::std::cout << elf << "\n"s; }::std::cout
11
11
Es ist auch möglich, die explizite Typangabe mit dem Gleichheitszeichen zu kombinieren, was einen klassischen Stil darstellt, der auch in ganz alten C++ -Versionen aus dem 20. Jahrhundert verwendet werden kann.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""susing namespace ::std::literals;
int main()
{ ::std::cout << 11 << "\n"s;
int const elf = 11;
::std::cout << elf << "\n"s; }::std::cout
11
11
Der Name einer Konstanten
Der Name der Konstanten kann verändert werden, ohne daß dies die Funktion des Programmes verändert. Ein irreführender Name, wie in dem folgenden Beispiel, sollte natürlich vermieden werden. Doch für den Computer ist der Name ohne Bedeutung: Er verknüpft nur die verschiedenen Stellen seiner Verwendung.
Bei der Ersetzung eines Namens durch einen anderen, muß dieser überall ersetzt werden, wenn sich die Bedeutung nicht ändern soll.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""susing namespace ::std::literals;
int main()
{ ::std::cout << 11 << "\n"s;
auto const zehn { 11 }; /* vor C++17: auto const zehn = 11; */
::std::cout << zehn << "\n"s; }::std::cout
11
11
Der Name einer Blockkonstanten wird normalerweise mit lateinischen Kleinbuchstaben »a« bis »z« und Grundstrichen »_« geschrieben.
Angabe des Wertes der Konstanten
Die Festlegung des Wertes der Konstanten innerhalb der Definition der Konstanten wird auch als Initialisierung bezeichnet.
Ein Initialisierungsausdruck muß keine Literal sein. Auch ein Name, ein Operatorausdruck oder ein Funktionsaufruf ist erlaubt. Auch der Name einer anderen Konstanten kann in einem Initialisierungsausdruck verwendet werden.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <climits> // INT_MAX
#include <cmath> // ::std::cos
#include <string> // ""susing namespace ::std::literals;
int main()
{ auto const i { INT_MAX }; ::std::cout << i << "\n"s; /* C++17 */
auto const j { i - 22 - 7 }; ::std::cout << j << "\n"s; /* C++17 */
auto const y { ::std::cos( 0.0 ) }; ::std::cout << y << "\n"s; /* C++17 */ }::std::cout
2147483647
2147483618
1
Anwendungsbeispiel
Das folgende Programm errechnet aus einem benannten Wert »300«, der eine Temperatur in Kelvin darstellen soll, einen anderen benannten Wert: die entsprechende Celsius-Temperatur.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""susing namespace ::std::literals;
int main()
{ auto const t_kelvin { 300.0 };
auto const versatz { 273.15 };
auto const t_celsius { t_kelvin - versatz };::std::cout << t_celsius << "\n"s; }
::std::cout
26.85
Ausdrücke, die neben Operatoren nur Konstanten und Literale enthalten, können oft bereits während der Übersetzung eines Programmes ausgewertet werden und kosten daher keine oder nur wenig Laufzeit.
Aber auch der Wert eines Ausdrucks mit Numeralia und Grundrechenarten, wie »300 - 273.15« wird bereits während der Übersetzung ausgerechnet.
Beispiele
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""s
#include <cstdio> // getcharusing namespace ::std::literals;
int main()
{ auto const lies { ::std::getchar };
::std::cout << lies() << "\n"s; }- Protokoll
65
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""s
#include <cmath> // fabsusing namespace ::std::literals;
int main()
{ auto const Betrag { ::std::fabs };
::std::cout << Betrag( 2 )<< "\n"s; }- Protokoll
error: unable to deduce 'const auto' from 'std::fabs'
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""susing namespace ::std::literals;
int main()
{ auto const out { ::std::cout };
out << 2 << "\n"s; }- Protokoll
error: use of deleted function
| { auto const out { ::std::cout };
| ^
Initialisierung mit mehreren Werten ⃗
Eine Initialisierung kann beispielsweise auch mit mehreren Werten erfolgen. Dann kann die Schreibweise mit dem Gleichheitszeichen »=« nicht verwendet werden.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""susing namespace ::std::literals;
int main()
{ ::std::string const s { 65, 66, 67 };
::std::cout << s << "\n"s; }transcript
ABC
Oben bedeutet »s { 65, 66, 67 }«, daß die Zeichenfolge »s« aus den Schriftzeichen mit den Kennzahlen 65, 66 und 67 – in dieser Reihenfolge – bestehen soll. Diese Art der Initialisierung mit mehreren kommagetrennten Werten ist aber nicht für die Datentypen »int« oder »double« möglich.
Initialisierung ohne Werte ⃗
Eine Initialisierung kann auch ohne Wert erfolgen. Dann kann die Schreibweise mit dem Gleichheitszeichen »=« nicht verwendet werden.
Paradoxerweise wird diese Initialisierung ohne Wert „Wertinitialisierung“ genannt! Vielleicht, weil der Name dabei doch einen Wert erhält, nämlich einen bestimmten Fehlwert, wie die leere Zeichenfolge beziehungsweise die Zahl Null.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""susing namespace ::std::literals;
int main()
{ ::std::string const s {};
::std::cout << s << "\n"s;int i {};
::std::cout << i << "\n"s;double d {};
::std::cout << d << "\n"s; }transcript
0
0
Die beiden Möglichkeiten der Initialisierung mit mehreren Werten und der Initialisierung ohne Werte, die beide nicht mit geschweiften Klammern geschrieben werden können, zeigen, daß die Schreibweise mit geschweiften Klammern „universeller“ ist.
Die verschiedenen Arten der Initialisierung in C++ werden im Aufbaukurs ausführlicher behandelt werden.
Übungsfragen
Die beiden folgenden Programme kommen zum selben Ergebnis. Welches gefällt Ihnen besser? Warum?
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""susing namespace ::std::literals;
int main()
{ auto const t_kelvin { 300.0 };
auto const versatz { 273.15 };
auto const t_celsius { t_kelvin - versatz };::std::cout << t_celsius << "\n"s; }
::std::cout
26.85
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""susing namespace ::std::literals;
int main()
{ ::std::cout << 300 - 273.15 << "\n"s; }::std::cout
26.85
- Übungsfrage
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""susing namespace ::std::literals;
int main()
{ auto const a { 1 };
::std::cout << a + 1 << "\n"s;
a + 1;
::std::cout << a << "\n"s; }- Welchen Wert gibt die letzte Auswertungsanweisung des obigen Quelltextes aus?
- Übungsaufgabe
- Definieren Sie eine Konstante »a« mit dem Wert »2.7« und geben Sie diese aus.
- Übungsaufgabe
- Definieren Sie eine Konstante »t« mit dem Wert »abc« und geben Sie diese aus.
- Übungsaufgabe
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <cmath> // ::std::sin, ::std::cos, ::std::log
#include <string> // ""susing namespace ::std::literals;
int main()
{ ::std::cout << ::std::sin( 1 + ::std::log( 2.178 ))<< "\n"s;
::std::cout << ::std::cos( 1 + ::std::log( 2.178 ))<< "\n"s; }std::cout
0.978526
-0.206122- Schreiben Sie die Quelldatei »main.cpp« so um, daß der längste derzeit wiederholt vorkommende Teilausdruck nur noch einmal vorkommt. Definieren Sie dazu genau eine Konstante und verwenden Sie diese Konstante dann an der Stelle jenes Teilausdrucks. Verwenden Sie aber weiterhin die im Programm »main.cpp« vorkommenden Literale, Operatoren und Funktionsnamen (ohne die Werte auszurechnen und als Literale in das Programm zu schreiben). Die Ausgabe des Programmes darf sich nicht ändern.
- Übungsaufgabe
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <cmath> // ::std::sin, ::std::sqrt, ::std::pow
#include <string> // ""susing namespace ::std::literals;
int main()
{ auto const x { ::std::cos( 0.0 )};
auto const y { ::std::sin( x )};
auto const z { ::std::sqrt( y )};
auto const u { ::std::pow( y, 0.51 )};
::std::cout << z << "\n"s;
::std::cout << u << "\n"s; }::std::cout
0.917317
0.915735- Schreiben Sie die Quelldatei »main.cpp« so um, daß dieselben Werte ausgegeben werden und dieselben Numerale und Funktionen zur Berechnung verwendet werden, aber keine Konstanten mehr in dem Programm vorkommen. Ersetzen Sie zunächst die Konstante »x« überall durch ihren Initialisierungsausdruck, also durch den Ausdruck »::std::cos( 0.0 )« und entfernen Sie die Definition der Konstante »x«. Es ergibt sich so zunächst:
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <cmath> // ::std::sin, ::std::sqrt, ::std::pow
#include <string> // ""susing namespace ::std::literals;
int main()
{ auto const y = ::std::sin( ::std::cos( 0.0 ));
auto const z = ::std::sqrt( y );
auto const u = ::std::pow( y, 0.51 );
::std::cout << z << "\n"s;
::std::cout << u << "\n"s; }::std::cout
0.917317
0.915735- Die Konstante »x« wurde also eliminiert, ohne daß sich das Verhalten des Programms verändert hat.
- Nach diesem Schema können dann alle anderen drei Definitionen eliminiert werden, indem jede Verwendung einer Konstanten durch den Initialisierungsausdruck der Konstanten ersetzt wird, bis nur noch die beiden Ausgabeausdrücke übrigbleiben.