Ergänzungen zu Konstanten und Variablen in C++ [] (Ergänzungen zu Konstanten und Variablen in C++), Lektion, Seite 723467
https://www.purl.org/stefan_ram/pub/ergaenzungen_variablen_c++ (Permalink) ist die kanonische URI dieser Seite.
Stefan Ram
C++-Kurs

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

Seiteninformationen und Impressum   |   Mitteilungsformular  |   "ram@zedat.fu-berlin.de" (ohne die Anführungszeichen) ist die Netzpostadresse von Stefan Ram.   |   Eine Verbindung zur Stefan-Ram-Startseite befindet sich oben auf dieser Seite hinter dem Text "Stefan Ram".)  |   Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten. Diese Seite ist eine Veröffentlichung von Stefan Ram. Schlüsselwörter zu dieser Seite/relevant keywords describing this page: Stefan Ram Berlin slrprd slrprd stefanramberlin spellched stefanram723467 stefan_ram:723467 Ergänzungen zu Konstanten und Variablen in C++ Stefan Ram, Berlin, and, or, near, uni, online, slrprd, slrprdqxx, slrprddoc, slrprd723467, slrprddef723467, PbclevtugFgrsnaEnz Erklärung, Beschreibung, Info, Information, Hinweis,

Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten. Diese Seite ist eine Veröffentlichung von Stefan Ram.
https://www.purl.org/stefan_ram/pub/ergaenzungen_variablen_c++