Ergänzungen zu Konstanten und Variablen in C++
Unterschiede zwischen Konstanten und Literalen
Der Wert einer Konstante wird dem Namen der Konstante in der Regel ganz willkürlich zugeordnet, während der Wert eines Literals sich meist in regelhafter Weise aus den Zeichen des Literals ergibt (das Wort „Literal“ kommt vom lateinischen "littera" - „Schriftzeichen“). Ersetzt man im Literal »11« das letzte Zeichen durch seinen Nachfolger, so ergibt sich wieder ein Literal, nämlich »12«, und dessen Wert ist um 1 größer. Ersetzt man hingegen in der Konstanten »elf« das letzte Zeichen durch seinen Nachfolger, so ergibt sich »elg«, was in der Regel gar nicht als Konstante definiert wurde und daher gar keinen Wert hat. Die Regel, die einem Literal seinen Wert zuordnet, ist auch meist in einem Programm überall gleich, während Namen in verschiedenen Programmteilen oft unterschiedliche Bedeutungen.
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.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""s using 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::cout
11
11
Schreibweise mit Gleichheitszeichen
Ein Gleichheitszeichen vor den geschweiften Klammern verändert die Bedeutung in Vergleich zu geschweiften Klammern alleine nicht weiter.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""s using namespace ::std::literals; int main()
{ double const d ={ 300 };
/* const int i ={ 273.15 }; */ /* nicht erlaubt, "narrowing" */
::std::cout << d << "\n"s; }::std::cout
300
Schreibweise mit runden Klammern
Im Falle der hier verwendeten Datentypen hat die Schreibweise mit runden Klammern und ohne Gleichheitszeichen dieselbe Bedeutung wie die mit einem Gleichheitszeichen und ohne geschweifte Klammern. Da sie aber in manchen Fällen (die hier noch nicht erklärt werden können) zu Mißverständnissen führen kann, wird in der Regel die Schreibweise mit geschweiften Klammern bevorzugt.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""s using namespace ::std::literals; int main()
{ double const d( 300 );
int const i( 273.15 );
::std::cout << d << ", "s << i << "\n"s; }::std::cout
300, 273
Typtreue bei Initialisierung
Der Typ des in den geschweiften Klammern stehenden Initialisierungsausdrucks muß zum Typ der Konstanten passen. Es ist (bei Verwendung der Schreibweisen mit geschweiften Klammern) nicht zulässig, daß dabei Informationen verloren gehen (“narrowing ”).
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""s using namespace ::std::literals; int main()
{ double const d { 300 };
/* int const i { 273.15 }; */ /* nicht erlaubt, "narrowing" */
::std::cout << d << "\n"s; }::std::cout
300
Schreibweise ohne geschweiften Klammern
Auch eine Schreibweise ohne geschweifte Klammern ist möglich. Sie erlaubt auch eine implizite Typwandlung mit Informationsverlust.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""s using namespace ::std::literals; int main()
{ double const d = 300;
int const i = 273.15; /* erlaubt */
::std::cout << d << ", "s << i << "\n"s; }::std::cout
300, 273
»auto« – Typableitung
Die Datentypangabe kann in der Konstantendefinition durch das Schlüsselwort »auto« ersetzt werden. Dann wird der Typ des Initialisierungsausdrucks verwendet, der Typ der Konstante wird also aus dem Typ des Initialisierungsausdrucks abgeleitet.
Im allgemeinen sollte die erste Schreibweise mit den geschweiften Klammern bevorzugt verwendet werden, doch bei Verwendung dieser Typableitung führt das zu Problemen, so daß dann meistens die Schreibweise ohne geschweifte Klammern verwendet werden muß. (Ein Ausdrucks wie »{ 300 }« ist eine „Initialisierungsliste“, und die Variable würde bei Verwendung von »auto« dann nicht den Typ »int« erhalten, sondern den Typ „Initialisierungsliste“.)
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""s using namespace ::std::literals; int main()
{ auto const i = 300; /* int */
auto const d = 273.15; /* double */
::std::cout << i << ", "s << d << "\n"s; }::std::cout
300, 273.15
Schreibweise in C++17
In C++17 ist nun auch die Kombination von »auto« und geschweiften Klammern möglich (implementiert von GCC ab Version 5.1.1).
Diese Schreibweise wird vom Autor empfohlen.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""s using namespace ::std::literals; int main()
{ auto const i { 300 }; /* int */
auto const d { 273.15 }; /* double */
::std::cout << i << ", "s << d << "\n"s; }::std::cout
300, 273.15
»auto« mit runden Klammern
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""s using namespace ::std::literals; int main()
{ auto const i( 300 ); /* int */
auto const d( 273.15 ); /* double */
::std::cout << i << ", "s << d << "\n"s; }::std::cout
300, 273.15
Der Gültigkeitsbereich einer Konstanten
Der Bereich des Quelltextes, in dem ein Name im Sinne einer bestimmten Definition verwendet werden kann, wird als Gültigkeitsbereich jener Definition bezeichnet.
Der Gültigkeitsbereich einer Konstanten beginnt an der Stelle der Verwendung des Namens der Konstanten in ihrer Definition und reicht bis zum Ende des die Definition enthaltenden Blocks.
- C++ 3.3.3p1 [basic.scope.block] 2015-03-06
- A name declared in a block is local to that block; it has block scope. Its potential scope begins at its point of declaration and ends at the end of its block. A variable declared at block scope is a local variable.
- C++ 3.3.2p1 Point of declaration [basic.scope.pdecl] 2015-03-06
- The point of declaration for a name is immediately after its complete declarator and before its initializer (if any)
Wenn betont werden soll, daß eine solche Konstante nur in dem Block mit ihrer Definition verwendet werden kann, wird sie auch als Blockkonstante oder lokale Konstante bezeichnet.
Eine Blockkonstante ist kein globaler Name, sie kann also nicht mit zwei Doppelpunkten am Anfang bezeichnet werden, also beispielsweise nicht mit »::elf«.
Bereiche in C++
Die Zuordnung von Bezeichnern zu ihren Werten ist auf den Textbereich (Gültigkeitsbereich, scope ) der Bezeichner eingeschränkt. Dieser ist zunächst durch den Block des Bezeichners gegeben.
Ende der Bereiche
In dem folgenden Beispielprogramm gilt die Definition des Bezeichners »c« als Wert »1« nur in dem Block, der mit der zweiten öffnenden geschweiften Klammer beginnt und mit der ersten schließenden geschweiften Klammer endet. In dem folgenden Block kann der gleiche Bezeichner erneut definiert werden.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""s using namespace ::std::literals; int main()
{ { auto const c = 1; ::std::cout << c; }
{ auto const c = 2; ::std::cout << c; }
::std::cout << "\n"s; }std::cout
12
main.txt
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""s using namespace ::std::literals; int main()
{ { auto const c = 1; } ::std::cout << c; ::std::cout << "\n"s; }Diagnose
"main.txt", line 4: warning: constant "c" was declared but never referenced
{ { auto const c = 1; } ::std::cout << c; ::std::cout << "\n"s; }
^
"main.txt", line 4: error: identifier "c" is undefined
{ { auto const c = 1; } ::std::cout << c; ::std::cout << "\n"s; }
^
1 error detected in the compilation of "main.txt".
Eindeutigkeit von Bezeichnern
Die Namen eines Bereiches bilden eine Menge: Das bedeutet, daß jeder Namen nur einmal darin vorkommen kann. Daher ist der folgende Text keine korrekte C++ -Übersetzungseinheit.
block.txt
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""s using namespace ::std::literals; int main()
{ auto const c = 1;
auto const c = 2;
::std::cout << c << "\n"s; }Diagnose
"block.txt", line 5: error: "c" has already been declared in the current scope
auto const c = 2;
^
"block.txt", line 4: warning: variable "c" was declared but never referenced
{ auto const c = 1;
^
Bei Fehlern durch unerlaubte Mehrfachdeklaration ein und desselben Namens spricht man auch von einem Zusammenstoß oder einer Kollision der Namen (einer Namenskollision ).
Verschachtelte Bereiche
Blöcke können ineinander verschachtelt werden, das heißt: ein Block kann einen anderen Block enthalten.
Der innere Block ist dann wieder ein eigener Namensraum. Er „erbt“ alle Definitionen umgebender Namensräume, kann aber alle geerbten Bezeichner in eigenen Definitionen „überschreiben“ (umdefinieren). (Diese Vererbung von Definitionen wird auch bei Klassen verwendet.) Wenn ein innerer Block einen Bezeichner definiert, dann gilt in dem inneren Block auch die Definition des darin definierten Bezeichners ab der Stelle ihres Auftretens. Endet der innere Block, so enden auch die im inneren Block definierten Zuordnungen der Werte zu den Namen.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""s using namespace ::std::literals; int main()
{ auto const c = 9;
auto const d = 9;
::std::cout << 0 << c << d;
{ ::std::cout << 1 << c << d; auto const c = 8;
::std::cout << 2 << c << d; }
::std::cout << 3 << c << d << "\n"s; }std::cout
099199289399
Der Leser kann jetzt vorhersagen, was das folgende Programm ausgibt.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""s using namespace ::std::literals; int main()
{ auto const c = 3;
{ auto const c = 4;
{ auto const c = 5;
::std::cout << c; }
::std::cout << c; }
::std::cout << c << "\n"s; }
Einführung einer Blockkonstanten (Refaktor)
Das folgende Programm enthält keine Blockkonstante.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""s using namespace ::std::literals; int main()
{ ::std::cout << 22 << "\n"s; }::std::cout
22
Das folgende gleichwertige Programm enthält eine Blockkonstante.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""s using namespace ::std::literals; int main()
{ auto const c = 22;
::std::cout << c << "\n"s; }::std::cout
22
Bei der Einführung einer Blockkonstanten wird ein Wertausdruck durch einen gleichwertigen Konstantennamen ersetzt. Dies ist aber nur dann zulässig, wenn die vorkommenden Auswertungen keine Wirkungen (sondern nur Werte) haben, da die Wirkungen sonst an anderer Stelle auftreten würden.
Elimination einer Blockkonstanten (Refaktor)
Dies ist eigentlich der vorige Refaktor Einführung einer Blockkonstanten mit vertauschter Reihenfolge der beiden Möglichkeiten.
Das folgende Programm enthält eine Blockkonstante.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""s using namespace ::std::literals; int main()
{ auto const c = 22;
::std::cout << c << "\n"s; }::std::cout
22
Das folgende gleichwertige Programm enthält keine Blockkonstante.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""s using namespace ::std::literals; int main()
{ ::std::cout << 22 << "\n"s; }::std::cout
22
Bei der Elimination einer Blockkonstanten wird ein Konstantennamen durch einen Ausdruck mit seinem Wert ersetzt. Dies ist aber nur dann zulässig, wenn die vorkommenden Auswertungen keine Wirkungen (sondern nur Werte) haben, da die Wirkungen sonst an anderer Stelle auftreten würden.
Umbenennen einer Blockkonstanten (Refaktor)
Der Name einer Konstanten ist für das Verhalten einer Möglichkeit egal.
Ein Name darf innerhalb seiner Deklaration und seines Gültigkeitsbereichs durch einen anderen zulässigen Namen ersetzt werden.
Der Name muß dabei überall innerhalb seines Gültigkeitsbereichs durch den neuen Namen ersetzt werden.
Wird ein neuer Name gewählt, welcher in diesem Gültigkeitsbereich schon eine Bedeutung hat spricht dann von einer „Namenskollision“. Selbst bei einer Namenskollision ist die Änderung doch manchmal noch zulässig, nämlich dann, wenn die Regeln der Sprache für diesen Fall dafür sorgen, daß der Name in der beabsichtigten Weise verstanden wird, was manchmal vorkommt. Für den Anfang ist es jedoch am besten und am einfachsten, Namenskollisionen zu vermeiden.
Das folgende Programm enthält eine Blockkonstante namens »k«.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""s using namespace ::std::literals; int main()
{ auto const k = 22;
::std::cout << k << "\n"s; }::std::cout
22
Das folgende gleichwertige Programm enthält eine Blockkonstante namens »n«.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""s using namespace ::std::literals; int main()
{ auto const n = 22;
::std::cout << n << "\n"s; }::std::cout
22
In dem folgenden Beispiel umfaßt der Gültigkeitsbereich der ersten Blockkonstanten »i« nur einen inneren Block.
Main.java
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""s using namespace ::std::literals; int main()
{ { auto const i = 3; ::std::cout << i; }
{ auto const i = 7; ::std::cout << i; }
::std::cout << "\n"s; }::std::cout
3
7
Soll die erste Blockkonstante des obigen Programms umbenannt werden, so ist sie in der Deklaration und überall innerhalb ihres Gültigkeitsbereichs umzubenennen, aber nicht außerhalb.
Main.java
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""s using namespace ::std::literals; int main()
{ { auto const j = 3; ::std::cout << j; }
{ auto const i = 7; ::std::cout << i; }
::std::cout << "\n"s; }::std::cout
3
7
Abgrenzung zur Standardbibliothek
Durch Verwendung des Namensraums »::std« kann ein lokaler Name von einem Namen der Standardbibliothek unterschieden werden.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""s using namespace ::std::literals; int main()
{ auto const cout = 27;
::std::cout << cout << "\n"s; }::std::cout
27
Verengung und Erweiterung *
Es gehen keine Informationen bei einer Wandlung verloren, wenn die Rückwandlung immer wieder den ursprünglichen Wert ergibt (egal, welcher Wert dies ist).
Das folgende Beispiel zeigt diese Bewahrung der Information beim Wandel von »300« (int) nach double.
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""s using namespace ::std::literals; int main()
{ double const d = 300 /* Wandlung von int nach double */;
int const i = d; /* Rueckwandlung */
::std::cout << i << "\n"s; }::std::cout
300
Das folgende Beispiel zeigt Informationsverlust (die Nachkommastelle »2« geht verloren).
main.cpp
#include <initializer_list>
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""s using namespace ::std::literals; int main()
{ int const i = 3.2 /* Wandlung von double nach int */;
double const d = i; /* Rueckwandlung */
::std::cout << d << "\n"s; }::std::cout
3
Explizites Herstellen einer Inkarnation
Wir hattem schon behandelt, daß das folgende Programm ein »A« ausgibt. Bei der Auswertung von »::std::putchar( 65 )« wird zuerst die Inkarnation »::std::putchar( 65 )« hergestellt und dann ausgeführt.
main.cpp
#include <iostream>
#include <ostream>
#include <cstdio>
#include <functional> int main(){ ::std::putchar( 65 ); ::std::putchar( 10 ); }::std::cout
A
Das folgende Programm veranschaulicht Inkarnationen dadurch, daß diese in einem eigenen Schritt hergestellt werden, bevor sie dann aktiviert werden.
In dem folgenden Programm wird mit »::std::bind( ::std::putchar, 65 )« explizit eine Inkarnation (also eine Verbindung zwischen einer Funktion und einem Argumentwert, hier zwischen der Funktion »::std::putchar« und dem Wert »65«) hergestellt, die dann mit »ausgabeEinesA();« zwiefach ausgeführt werden kann.
main.cpp
#include <initializer_list>
#include <iostream>
#include <ostream>
#include <string>
#include <cstdio>
#include <functional> using namespace ::std::literals; int main()
{ auto ausgabeEinesA{ ::std::bind( ::std::putchar, 65 )};
ausgabeEinesA();
ausgabeEinesA();
::std::putchar( 10 ); }::std::cout
AA