Initialisierungslistenkonstruktoren in C++
Der Konstruktor für Zeichenwiederholungen
Bei der folgenden Direktinitialisierung wird ein Konstruktor herangezogen, der eine Zeichenfolge mit der angegebenen Anzahl (»3«) von Wiederholungen eines Zeichens (»'B'«) initialisiert.
main.cpp
#include <initializer_list>
#include <ostream>
#include <iostream>int main()
{ ::std::string s( 3, 'B' );
::std::cout << s << '\n'; }transcript
BBB
Man sieht, daß sich eine Zeichenfolge aus drei »B« ergibt.
Die Klasse »::std::string« enthält einen parametrisierten Konstruktor »string( int n, char c )«, mit dem eine Zeichenfolge durch eine Zahl »n« und ein Zeichen »c« initialisiert werden kann, so daß sie das Zeichen »c« so oft enthält, wie die Zahl »n« angibt.
- Proklamation der Klasse »::std::string« (vereinfacht)
namespace std
{ using string = basic_string< char >;template< class charT >
class basic_string
{ basic_string( basic_string const str );
basic_string( charT const * );
basic_string( int n, charT c );
charT const * c_str() const;
charT const * data() const; }}
Es ist jener Konstruktor, welcher zur Initialisierung des Objektes »s« bei der Ausführung der Deklarationsanweisung »::std::string s( 3, 'B' );« herangezogen wird. Dies führt dazu, daß die Zeichenfolge »s« als eine Folge von drei »B« angelegt wird.
Kennzahlen für Schriftzeichen
Ein Zeichen kann auch durch seine Kennzahl angegeben werden. Die Kennzahl eines Zeichens ist aber implementationsspezifisch. Unter vielen Implementationen hat das Zeichen »A« beispielsweise die Kennzahl »65«. Wir nehmen im folgenden eine C++ -Implementation mit der untenstehenden Zeichentabelle an.
- Zeichentabelle
Zeichen Kennzahl
A 65
B 66
Wenn im Initialisierer zwei int-Ausdrücke stehen, wird weiterhin der parametrisierten Konstruktor »string( int n, char c )« herangezogen (und der Wert des zweiten int-Ausdrucks nach »char« gewandelt).
main.cpp
#include <initializer_list>
#include <ostream>
#include <iostream>
#include <string>int main()
{ { ::std::string s( 3, 66 ); ::std::cout << s << '\n'; }
{ ::std::string s( 3, 65 ); ::std::cout << s << '\n'; }}transcript
BBB
AAA
In dem obenstehenden Programm werden drei Zeichen mit der Kennzahl »66« ausgegeben, also »BBB«. Es folgenden 3 »A« (Zeichen mit der Kennzahl 65).
Direkt-Listeninitialisierung
Wir können jetzt aber beobachten, daß wir bei Verwendung eines Initialisierers mit geschweiften Klammern andere Ergebnisse erhalten.
main.cpp
#include <initializer_list>
#include <ostream>
#include <iostream>
#include <string>int main()
{ { ::std::string s( 65, 66 ); ::std::cout << s << '\n'; }
{ ::std::string s{ 65, 66 }; ::std::cout << s << '\n'; }
{ ::std::string s( 66, 65 ); ::std::cout << s << '\n'; }
{ ::std::string s{ 66, 65 }; ::std::cout << s << '\n'; }}transcript
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
AB
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
BA
Bei Verwendung des Initialisierers »{ 65, 66 }« wird ein Konstruktor herangezogen, den wir bisher noch nicht vorgestellt haben.
Es handelt sich um den Listenkonstruktor »string( ::std::initializer_list< charT > )«.
- Proklamation der Klasse »::std::string« (vereinfacht)
namespace std
{ using string = basic_string< char >;template< class charT >
class basic_string
{ basic_string( basic_string const str );
basic_string( charT const * );
basic_string( int n, charT c );
basic_string(::std::initializer_list< charT > );
charT const * c_str() const;
charT const * data() const; }}
Dies ist ein Konstruktor, der für den Fall einer Initialisierung mit einer Initialisierungsliste, zu der auch die Direkt-Listeninitialisierung gehört, gedacht ist. Er wird immer dann herangezogen, wenn eine Initialisierungsliste zur Initialisierung verwendet wird, auch wenn es noch einen passenden anderen Konstruktor gegeben hätte.
Der Konstruktor »basic_string(::std::initializer_list< charT > )« initialisiert eine Zeichenfolge mit den Schriftzeichen, deren Kennzahlen in der Initialisierungsliste stehen. Somit führt »::std::string s{ 65, 66 };« zur Initialisierung einer Zeichenfolgen mit den beiden Zeichen, deren Kennzahlen »65« beziehungsweise »66« lauten, also ergibt sich die Zeichenfolge »"AB"«.
Wenn es einen passenden Listenkonstruktor gibt, so erhält dieser bei einer Listen-Initialisierung den Vorrang.
In C++ wird heute allgemein die Listeninitialisierung empfohlen. die als „universelle“ gilt. Sie kann jedoch nicht verwendet werden, wenn bestimmte Konstruktoren herangezogen werden sollen, die eine geringerere Priorität als ein Listenkonstruktor haben. Daher kann man nur sagen, daß man die Direkt-Listeninitialisierung verwenden sollte, falls keine andere Form der Initialisierung nötig ist, um einen gewünschten Konstruktor heranzuziehen. Im Falle der Klasse »::std::string« muß ein Initialisierer mit runden Klammern verwendet werden, wenn eine Initialisierung verwendet werden soll, die den Konstruktor »basic_string( int n, charT c )« heranzieht, der eine Initialisierung mit der Angabe einer Anzahl von Wiederholungen eines Zeichens erlaubt.
Umgekehrt können wir sagen, daß in Klassen, die keine Listenkonstruktoren enthalten, die Direktinitialisierung mit runden und geschweiften Klammern jeweils dieselben Konstruktoren heranzieht.
- Zitat C++ 2016-11
- 13.3.1.7 Initialization by list-initialization [over.match.list]
- 1 When objects of non-aggregate class type T are list-initialized such that 8.6.4 specifies that overload resolution is performed according to the rules in this section, overload resolution selects the constructor in two phases:
- (1.1) — Initially, the candidate functions are the initializer-list constructors (8.6.4) of the class T and the argument list consists of the initializer list as a single argument.
- (1.2) — If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.
- If the initializer list has no elements and T has a default constructor, the first phase is omitted.
- In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed.
Initialisierungen von Vektoren
Auch bei Vektoren gibt es die Unterscheidung zwischen einem Listenkonstruktor und einem Nicht-Listen-Konstruktor.
main.cpp
#include <initializer_list>
#include <ostream>
#include <iostream>
#include <string>
#include <vector> int main()
{ { ::std::vector< int >v( 2, 3 ); ::std::cout << v.size() << v.at( 0 )<< v.at( 1 )<< '\n'; }
{ ::std::vector< int >v{ 2, 3 }; ::std::cout << v.size() << v.at( 0 )<< v.at( 1 )<< '\n'; }}transcript
233
223
Während der Nicht-Listen-Konstruktor mit zwei Parametern einen Vektor mit einer durch das erste Argument angegebenen Anzahl von Wiederholung des zweiten Argumentes initialisiert, initialisiert der Listenkonstruktor mit den in der Liste angegebenen Werten.
Die leere Initialisierungsliste
Es gibt eine Sonderregel für die leere Initialisierungsliste »{}«: Sie führt bei einer Klasse als Typ immer zur Verwendung des Fehlkonstruktors. Ein etwaiger Initialisierungslistenkonstruktor wird hier nie herangezogen.
main.cpp
#include <initializer_list>
#include <ostream>
#include <iostream>
#include <string>int main()
{ ::std::string s{}; ::std::cout << s << '\n'; }Protokoll
Dementsprechend hat die Initialisierung von »s« in dem folgenden Programm dieselbe Bedeutung wie in dem direkt über diesem Satz stehendem Programm.
main.cpp
#include <ostream>
#include <iostream>
#include <string>int main()
{ ::std::string s; ::std::cout << s << '\n'; }Protokoll
Übungsfragen
? Initialisierungen
Es sei „T “ ein Typ, „x “ ein Name, „A “ eine kommagetrennte Ausdruckliste, und „a “ ein Ausdruck.
T x ( A ); wird Direktinitialisierung genannt
T x { A }; wird Direktinitialisierung und auch Direkt-Listeninitialisierung genannt.
T x = a ; wird Kopierinitialisierung genannt.
Bestimmen Sie nun um welche Art von Initialisierung es sich bei den folgenden Initialisierungen handelt!
- Anton
::std::string s( 65, 66 );
- Berta
::std::string s{ 65, 66 };
- Cäsar
auto first { 8 };
- Dora
::std::string const b = a;
- Emil
auto const compound = ::std::make_pair( 2, 7 );
- Friedrich
int const a( 7 );
- Gustav
double const x = 2;
- Heinrich
auto const compound { ::std::make_pair( 2, 7 ) };
- Ida
::std::string const b =( a );
? Konstruktoren
Falls eine Listeninitialisierung verwendet wird, so werden Listenkonstruktoren bevorzugt herangezogen. Bei Klassen ohne Listenkonstruktor wird für eine Direkt-Listeninitialisierung in der Regel hingegen derselbe Konstruktor herangezogen wie bei einer entsprechenden Listeninitialisierung mit runden Klammern.
Sagen Sie nun voraus, welcher Konstruktor der Beispielklassen für eine gegebene Initialisierung herangezogen wird.
- Proklamation der Klasse »::std::vector« (vereinfacht)
namespace std
{ template< class T > class vector
{ using size_type = unsigned int;
vector( size_type n, T value ); /* Anton */
vector( initializer_list< T > list ); /* Berta */ }}main.cpp
#include <initializer_list>
#include <ostream>
#include <iostream>
#include <vector>int main()
{ {
::std::vector< int > v( 65, 66 ); /* Caesar */
::std::cout << v.at( 0 )<< '\n'; }
{
::std::vector< int > v{ 65, 66 }; /* Dora */
::std::cout << v.at( 0 )<< '\n'; }}- Dokumentation (vereinfacht)
// defined in header <utility>
namespace std
{ template< class T1, class T2 >
struct pair
{ T1 first;
T2 second;
pair( pair ); /* Emil */
pair( const T1 x, const T2 y ); /* Friedrich */
}; }main.cpp
#include <initializer_list>
#include <ostream>
#include <iostream>
#include <vector>int main()
{ {
::std::pair< int, int > p{ 1, 2 }; /* Gustav */
::std::cout << p.first << '\n'; }
{
::std::pair< int, int > p( 1, 2 ); /* Heinrich */
::std::cout << p.first << '\n'; }}
Übungsaufgaben
/ Unterstreichen
Schreiben Sie eine Definition einer Funktion »u«, welche ihre Argumentzeichenfolge so unterstreicht, wie unten gezeichnet.
- Argumentzeichenfolge
abc
- Ergebnistext
abc
---
Die Länge der Unterstreichung soll sich der Länge der Argumentzeichenfolge (0 – 40 Zeichen) anpassen.
- Argumentzeichenfolge
Zypressenlabyrinth
- Ergebnistext
Zypressenlabyrinth
------------------
/ Wertfolgen
Verwenden Sie die Typschablone »::std::vector« mit dem Typargument »double«, um ein Objekt anzulegen, das eine Folge der drei double-Werte »1.2«, »2.2« und »3.6« enthält. Jene drei Werte sollen schon bei der Initialisierung festgelegt werden und nicht erst später mit einer Funktion wie »push_back« hinzugefügt werden. Geben Sie dann die ersten drei Komponente jenes Objektes aus, indem Sie die Elementfunktion »at(int)« (vereinfachter Parametertyp) mit den Werten »0«, »1« und »2« aufrufen.
- Ausgabe des Programms
1.2
2.2
3.6
/ Einrahmen ⃗
Schreiben Sie eine Definition einer Funktion »u«, welche ihre Argumentzeichenfolge so einrahmt, wie unten gezeichnet.
- Argumentzeichenfolge
abc
- Ergebnistext
*******
* abc *
*******
Der Rahmen soll sich der Länge der Argumentzeichenfolge (0 – 40 Zeichen) anpassen.
- Argumentzeichenfolge
Zypressenlabyrinth
- Ergebnistext
**********************
* Zypressenlabyrinth *
**********************- 8.6.4 List-initialization [dcl.init.list]