Initialisierer

Ein Initialisierer gibt den Anfangswert einer Variablen an. Sie können Variablen in diesen Kontexten initialisieren:

  • In der Definition einer Variablen:

    int i = 3;
    Point p1{ 1, 2 };
    
  • Als einer der Parameter einer Funktion:

    set_point(Point{ 5, 6 });
    
  • Als Rückgabewert einer Funktion:

    Point get_new_point(int x, int y) { return { x, y }; }
    Point get_new_point(int x, int y) { return Point{ x, y }; }
    

Initialisierer können diese Formate annehmen:

  • Ein Ausdruck (oder eine durch Komma getrennte Liste von Ausdrücken) in Klammern:

    Point p1(1, 2);
    
  • Ein Gleichheitszeichen gefolgt von einem Ausdruck:

    string s = "hello";
    
  • Eine Initialisiererliste in Klammern. Die Liste kann leer sein oder aus einer Gruppe von Listen bestehen, siehe folgendes Beispiel:

    struct Point{
        int x;
        int y;
    };
    class PointConsumer{
    public:
        void set_point(Point p){};
        void set_points(initializer_list<Point> my_list){};
    };
    int main() {
        PointConsumer pc{};
        pc.set_point({});
        pc.set_point({ 3, 4 });
        pc.set_points({ { 3, 4 }, { 5, 6 } });
    }
    

Arten von Initialisierung

Es gibt mehrere Arten von Initialisierung, die an unterschiedlichen Stellen in der Programmausführung auftreten können. Verschiedene Arten von Initialisierungen schließen sich nicht gegenseitig aus, z. B. kann die Listeninitialisierung die Wertinitialisierung auslösen, und unter anderen Umständen kann die Aggregatinitialisierung ausgelöst werden.

Initialisierung mit 0 (NULL)

Die Initialisierung mit 0 (NULL) ist die Festlegung einer Variablen auf einen NULL-Wert, der implizit in den Typ konvertiert wird:

  • Numerische Variablen werden mit 0 initialisiert (oder 0,0 oder 0,0000000000 usw.).

  • Zeichenvariablen werden initialisiert in '\0'.

  • Zeiger werden initialisiert in nullptr.

  • Arrays, POD-Klassen , -Strukturen und -Vereinigungen haben ihre Mitglieder auf einen Nullwert initialisiert.

Die Initialisierung mit 0 (NULL) wird zu unterschiedlichen Zeiten ausgeführt:

  • Bei Programmstart für alle benannten Variablen, die eine statische Dauer haben. Diese Variablen können später erneut initialisiert werden.

  • Während der Wertinitialisierung für skalare Typen und POD-Klassentypen, die mit leeren geschweiften Klammern initialisiert werden.

  • Für Arrays, die nur eine Teilmenge ihrer initialisierten Member haben.

Im Folgenden einige Beispiele für die Initialisierung mit 0 (NULL):

struct my_struct{
    int i;
    char c;
};

int i0;              // zero-initialized to 0
int main() {
    static float f1;  // zero-initialized to 0.000000000
    double d{};     // zero-initialized to 0.00000000000000000
    int* ptr{};     // initialized to nullptr
    char s_array[3]{'a', 'b'};  // the third char is initialized to '\0'
    int int_array[5] = { 8, 9, 10 };  // the fourth and fifth ints are initialized to 0
    my_struct a_struct{};   // i = 0, c = '\0'
}

Standardinitialisierung

Die Standardinitialisierung für Klassen, Strukturen und Unions ist die Initialisierung mit einem Standardkonstruktor. Der Standardkonstruktor kann ohne Initialisierungsausdruck oder mit dem new Schlüsselwort (keyword) aufgerufen werden:

MyClass mc1;
MyClass* mc3 = new MyClass;

Wenn die Klasse, Struktur oder Union keinen Standardkonstruktor aufweist, gibt der Compiler einen Fehler aus.

Skalare Variablen werden standardmäßig initialisiert, wenn sie ohne Initialisierungsausdruck definiert werden. Sie haben unbestimmte Werte.

int i1;
float f;
char c;

Arrays werden standardmäßig initialisiert, wenn sie ohne Initialisierungsausdruck definiert werden. Wenn ein Array standardmäßig initialisiert wird, werden dessen Member standardmäßig initialisiert und haben unbestimmte Werte, siehe folgendes Beispiel:

int int_arr[3];

Wenn die Arraymember keinen Standardkonstruktor haben, gibt der Compiler einen Fehler aus.

Standardinitialisierung konstanter Variablen

Konstante Variablen müssen zusammen mit einem Initialisierer deklariert werden. Wenn sie skalare Typen sind, verursachen sie einen Compilerfehler, und wenn sie Klassentypen sind, die einen Standardkonstruktor haben, führen sie zu einer Warnung:

class MyClass{};
int main() {
    //const int i2;   // compiler error C2734: const object must be initialized if not extern
    //const char c2;  // same error
    const MyClass mc1; // compiler error C4269: 'const automatic data initialized with compiler generated default constructor produces unreliable results
}

Standardinitialisierung statischer Variablen

Statische Variablen, die ohne einen Initialisierer deklariert werden, werden mit 0 (NULL) initialisiert (implizit in den Typ konvertiert).

class MyClass {
private:
    int m_int;
    char m_char;
};

int main() {
    static int int1;       // 0
    static char char1;     // '\0'
    static bool bool1;   // false
    static MyClass mc1;     // {0, '\0'}
}

Weitere Informationen zur Initialisierung globaler statischer Objekte finden Sie unter Standard Funktions- und Befehlszeilenargumente.

Wertinitialisierung

Wertinitialisierung findet in den folgenden Fällen statt:

  • Ein benannter Wert wird mit leeren geschweiften Klammern initialisiert.

  • Ein anonymes temporäres Objekt wird mithilfe von leeren runden oder geschweiften Klammern initialisiert.

  • ein Objekt wird mit dem new Schlüsselwort (keyword) plus leeren Klammern oder geschweiften Klammern initialisiert.

Eine Wertinitialisierung führt Folgendes aus:

  • Bei Klassen, die mindestens einen öffentlichen Konstruktor haben, wird der Standardkonstruktor aufgerufen.

  • für Nichtunion-Klassen ohne deklarierte Konstruktoren wird das Objekt null initialisiert, und der Standardkonstruktor wird aufgerufen.

  • Bei Arrays wird jedes Element wertinitialisiert.

  • In allen anderen Fällen wird die Variable mit 0 (NULL) initialisiert.

class BaseClass {
private:
    int m_int;
};

int main() {
    BaseClass bc{};     // class is initialized
    BaseClass*  bc2 = new BaseClass();  // class is initialized, m_int value is 0
    int int_arr[3]{};  // value of all members is 0
    int a{};     // value of a is 0
    double b{};  // value of b is 0.00000000000000000
}

Kopierinitialisierung

Die Kopierinitialisierung ist die Initialisierung eines Objekts mithilfe eines anderen Objekts. Sie findet in den folgenden Fällen statt:

  • Eine Variable wird mithilfe eines Gleichheitszeichens initialisiert.

  • Ein Argument wird an eine Funktion übergeben.

  • Ein Objekt wird von einer Funktion zurückgegeben.

  • Eine Ausnahme wird ausgelöst oder abgefangen.

  • Ein nicht statisches Datenmember wird mithilfe eines Gleichheitszeichens initialisiert.

  • Klassen-, Struktur- und Union-Member werden anhand der Kopierinitialisierung während der Aggregatinitialisierung initialisiert. Beispiele finden Sie unter "Aggregierte Initialisierung ".

Der folgende Code zeigt mehrere Beispiele für die Kopierinitialisierung:

#include <iostream>
using namespace std;

class MyClass{
public:
    MyClass(int myInt) {}
    void set_int(int myInt) { m_int = myInt; }
    int get_int() const { return m_int; }
private:
    int m_int = 7; // copy initialization of m_int

};
class MyException : public exception{};
int main() {
    int i = 5;              // copy initialization of i
    MyClass mc1{ i };
    MyClass mc2 = mc1;      // copy initialization of mc2 from mc1
    MyClass mc1.set_int(i);    // copy initialization of parameter from i
    int i2 = mc2.get_int(); // copy initialization of i2 from return value of get_int()

    try{
        throw MyException();
    }
    catch (MyException ex){ // copy initialization of ex
        cout << ex.what();
    }
}

Die Kopierinitialisierung kann keine expliziten Konstruktoren aufrufen.

vector<int> v = 10; // the constructor is explicit; compiler error C2440: can't convert from 'int' to 'std::vector<int,std::allocator<_Ty>>'
regex r = "a.*b"; // the constructor is explicit; same error
shared_ptr<int> sp = new int(1729); // the constructor is explicit; same error

In einigen Fällen, wenn auf den Kopierkonstruktor der Klasse nicht zugegriffen werden kann oder dieser gelöscht wird, verursacht die Kopierinitialisierung einen Compilerfehler.

Direkte Initialisierung

Die direkte Initialisierung verwendet (nicht leere) geschweifte oder runde Klammern. Im Gegensatz zur Kopierinitialisierung können hier explizite Konstruktoren aufgerufen werden. Sie findet in den folgenden Fällen statt:

  • Eine Variable wird mithilfe von nicht leeren geschweiften oder runden Klammern initialisiert.

  • Eine Variable wird mit der new Schlüsselwort (keyword) und nicht leeren geschweiften Klammern oder Klammern initialisiert.

  • Eine Variable wird mit static_cast

  • In einem Konstruktor werden Basisklassen und nicht statische Member mithilfe einer Initialisierungsliste initialisiert.

  • In der Kopie einer erfassten Variable in einem Lambdaausdruck.

Der folgende Code zeigt einige Beispiele für die direkte Initialisierung.

class BaseClass{
public:
    BaseClass(int n) :m_int(n){} // m_int is direct initialized
private:
    int m_int;
};

class DerivedClass : public BaseClass{
public:
    // BaseClass and m_char are direct initialized
    DerivedClass(int n, char c) : BaseClass(n), m_char(c) {}
private:
    char m_char;
};
int main(){
    BaseClass bc1(5);
    DerivedClass dc1{ 1, 'c' };
    BaseClass* bc2 = new BaseClass(7);
    BaseClass bc3 = static_cast<BaseClass>(dc1);

    int a = 1;
    function<int()> func = [a](){  return a + 1; }; // a is direct initialized
    int n = func();
}

Listeninitialisierung

Listeninitialisierung findet statt, wenn eine Variable mithilfe einer Initialisiererliste in geschweiften Klammern initialisiert wird. In geschweifte Klammern gesetzte Initialisiererlisten können in den folgenden Fällen verwendet werden:

  • Eine Variable wird initialisiert.

  • eine Klasse wird mit dem new Schlüsselwort (keyword) initialisiert.

  • Ein Objekt wird von einer Funktion zurückgegeben.

  • Ein Argument wird an eine Funktion übergeben.

  • Eines der Argumente in einer direkten Initialisierung.

  • In einem nicht statischen Datenmemberinitialisierer.

  • In einer Konstruktorinitialisiererliste.

Der folgende Code zeigt einige Beispiele für die Listeninitialisierung:

class MyClass {
public:
    MyClass(int myInt, char myChar) {}
private:
    int m_int[]{ 3 };
    char m_char;
};
class MyClassConsumer{
public:
    void set_class(MyClass c) {}
    MyClass get_class() { return MyClass{ 0, '\0' }; }
};
struct MyStruct{
    int my_int;
    char my_char;
    MyClass my_class;
};
int main() {
    MyClass mc1{ 1, 'a' };
    MyClass* mc2 = new MyClass{ 2, 'b' };
    MyClass mc3 = { 3, 'c' };

    MyClassConsumer mcc;
    mcc.set_class(MyClass{ 3, 'c' });
    mcc.set_class({ 4, 'd' });

    MyStruct ms1{ 1, 'a', { 2, 'b' } };
}

Aggregierte Initialisierung

Die Aggregatinitialisierung ist eine Form der Listeninitialisierung für Arrays oder Klassentypen (häufig Strukturen oder Unions), die Folgendes aufweisen:

  • Keine private- oder protected-Member

  • Keine von Benutzern bereitgestellten Konstruktoren, mit Ausnahme der explizit auf den Standardwert festgelegten oder gelöschten Konstruktoren

  • Keine Basisklassen

  • Keine virtuellen Memberfunktionen

Hinweis

In Visual Studio 2015 und früheren Versionen darf ein Aggregat keine Klammer- oder Gleichheitsinitialisierer für nicht statische Member aufweisen. Diese Einschränkung wurde im C++14-Standard entfernt und in Visual Studio 2017 implementiert.

Aggregatinitialisierer bestehen wie im folgenden Beispiel aus einer Initialisierungsliste in geschweiften Klammern mit oder ohne Gleichheitszeichen:

#include <iostream>
using namespace std;

struct MyAggregate{
    int myInt;
    char myChar;
};

struct MyAggregate2{
    int myInt;
    char myChar = 'Z'; // member-initializer OK in C++14
};

int main() {
    MyAggregate agg1{ 1, 'c' };
    MyAggregate2 agg2{2};
    cout << "agg1: " << agg1.myChar << ": " << agg1.myInt << endl;
    cout << "agg2: " << agg2.myChar << ": " << agg2.myInt << endl;

    int myArr1[]{ 1, 2, 3, 4 };
    int myArr2[3] = { 5, 6, 7 };
    int myArr3[5] = { 8, 9, 10 };

    cout << "myArr1: ";
    for (int i : myArr1){
        cout << i << " ";
    }
    cout << endl;

    cout << "myArr3: ";
    for (auto const &i : myArr3) {
        cout << i << " ";
    }
    cout << endl;
}

Die folgende Ausgabe wird angezeigt.

agg1: c: 1
agg2: Z: 2
myArr1: 1 2 3 4
myArr3: 8 9 10 0 0

Wichtig

Arraymmber, die während der Aggregatinitialisierung deklariert, jedoch nicht explizit initialisiert werden, werden wie myArr3 oben beschrieben null initialisiert.

Initialisieren von Unions und Strukturen

Wenn eine Union nicht über einen Konstruktor verfügt, können Sie ihn mit einem einzelnen Wert (oder mit einer anderen Instanz einer Union) initialisieren. Der Wert wird verwendet, um das erste nicht statische Feld zu initialisieren. Dies unterscheidet sich von der Strukturinitialisierung, in der mithilfe des ersten Werts im Initialisierer das erste Feld initialisiert wird, mithilfe des zweiten Werts das zweite Feld initialisiert wird usw. Vergleichen Sie die Initialisierung von Unions und Strukturen im folgenden Beispiel:

struct MyStruct {
    int myInt;
    char myChar;
};
union MyUnion {
    int my_int;
    char my_char;
    bool my_bool;
    MyStruct my_struct;
};

int main() {
    MyUnion mu1{ 'a' };  // my_int = 97, my_char = 'a', my_bool = true, {myInt = 97, myChar = '\0'}
    MyUnion mu2{ 1 };   // my_int = 1, my_char = 'x1', my_bool = true, {myInt = 1, myChar = '\0'}
    MyUnion mu3{};      // my_int = 0, my_char = '\0', my_bool = false, {myInt = 0, myChar = '\0'}
    MyUnion mu4 = mu3;  // my_int = 0, my_char = '\0', my_bool = false, {myInt = 0, myChar = '\0'}
    //MyUnion mu5{ 1, 'a', true };  // compiler error: C2078: too many initializers
    //MyUnion mu6 = 'a';            // compiler error: C2440: cannot convert from 'char' to 'MyUnion'
    //MyUnion mu7 = 1;              // compiler error: C2440: cannot convert from 'int' to 'MyUnion'

    MyStruct ms1{ 'a' };            // myInt = 97, myChar = '\0'
    MyStruct ms2{ 1 };              // myInt = 1, myChar = '\0'
    MyStruct ms3{};                 // myInt = 0, myChar = '\0'
    MyStruct ms4{1, 'a'};           // myInt = 1, myChar = 'a'
    MyStruct ms5 = { 2, 'b' };      // myInt = 2, myChar = 'b'
}

Initialisierung von Aggregaten, die Aggregate enthalten

Aggregattypen können andere Aggregattypen enthalten, z. B. Arrays von Arrays, Arrays von Strukturen usw. Diese Typen werden initialisiert, indem geschachtelte Gruppen von geschweiften Klammern verwendet werden, z. B.:

struct MyStruct {
    int myInt;
    char myChar;
};
int main() {
    int intArr1[2][2]{{ 1, 2 }, { 3, 4 }};
    int intArr3[2][2] = {1, 2, 3, 4};
    MyStruct structArr[]{ { 1, 'a' }, { 2, 'b' }, {3, 'c'} };
}

Verweisinitialisierung

Variablen des Referenztyps müssen mit einem Objekt des Typs, von dem der Referenztyp abgeleitet wird, oder mit einem Objekt eines Typs initialisiert werden, der in den Typ konvertiert werden kann, von dem der Referenztyp abgeleitet wird. Beispiel:

// initializing_references.cpp
int iVar;
long lVar;
int main()
{
    long& LongRef1 = lVar;        // No conversion required.
    long& LongRef2 = iVar;        // Error C2440
    const long& LongRef3 = iVar;  // OK
    LongRef1 = 23L;               // Change lVar through a reference.
    LongRef2 = 11L;               // Change iVar through a reference.
    LongRef3 = 11L;               // Error C3892
}

Die einzige Möglichkeit, einen Verweis mit einem temporären Objekt zu initialisieren, besteht darin, ein konstantes temporäres Objekt zu initialisieren. Nach der Initialisierung verweist eine Referenztypvariable immer auf dasselbe Objekt. sie kann nicht geändert werden, um auf ein anderes Objekt zu verweisen.

Obwohl die Syntax identisch sein kann, sind die Initialisierung von Referenztypvariablen und die Zuweisung zu Referenztypvariablen semantisch unterschiedlich. Im vorherigen Beispiel ähneln die Zuweisungen, die iVar und lVar ändern, den Initialisierungen zwar, haben jedoch unterschiedliche Auswirkungen. Die Initialisierung gibt das Objekt an, auf das die Referenztypvariable zeigt. Die Zuweisung weist über die Referenz auf das verwiesene Objekt hin.

Da beide ein Argument des Referenztyps an eine Funktion übergeben und einen Wert des Referenztyps einer Funktion zurückgeben und daher Initialisierungen sind, werden die formalen Argumente einer Funktion ebenso korrekt initialisiert wie die zurückgegebenen Verweise.

Referenztypvariablen können ohne Initialisierer nur in folgenden Objekten deklariert werden:

  • Funktionsdeklarationen (Prototypen). Beispiel:

    int func( int& );
    
  • Funktionsrückgabetyp-Deklarationen. Beispiel:

    int& func( int& );
    
  • Deklaration eines Referenztypklassenmembers. Beispiel:

    class c {public:   int& i;};
    
  • Deklaration einer Variablen, die explizit als extern. Beispiel:

    extern int& iVal;
    

Beim Initialisieren einer Referenztypvariable verwendet der Compiler das in der folgenden Abbildung gezeigte Entscheidungsdiagramm, um zwischen dem Erstellen eines Verweises auf ein Objekt oder dem Erstellen eines temporären Objekts auszuwählen, auf das die Referenzpunkte verweisen:

Decision graph for initialization of reference types.

Das Entscheidungsdiagramm beginnt mit: Ist der Initialisierer ein Lvalue desselben Typs oder ein Typ, der vom Typ des Bezugs abgeleitet ist? Wenn ja, bezieht sich der Verweis auf das im Initialisierer angegebene Objekt. Wenn nein, ist die nächste Entscheidung, ob die Referenztypvariable ein T-Bezug ist, der initialisiert wird und der Initialisierer implizit in einen T konvertiert werden kann? Wenn ja, wird die temporäre Erstellt, und die Referenzvariable wird zu einem Namen für diesen temporären. Wenn nein, ist dies ein Fehler.

Entscheidungsdiagramm für die Initialisierung von Referenztypen

Verweise auf volatile Typen (deklariert als volatileTypename&identifier) können mit volatile Objekten desselben Typs oder mit Objekten initialisiert werden, die nicht als volatiledeklariert wurden. Sie können jedoch nicht mit const Objekten dieses Typs initialisiert werden. Ebenso können Verweise auf const Typen (deklariert als constTypename&identifier) mit const Objekten desselben Typs (oder mit objekten mit einer Konvertierung in diesen Typ oder mit Objekten initialisiert werden, die nicht als constdeklariert wurden) initialisiert werden. Sie können jedoch nicht mit volatile Objekten dieses Typs initialisiert werden.

Verweise, die nicht mit dem const oder volatile Schlüsselwort (keyword) qualifiziert sind, können nur mit Objekten initialisiert werden, die weder als auch constvolatilenicht deklariert wurden.

Initialisierung von externen Variablen

Deklarationen von automatischen, statischen und externen Variablen können Initialisierer enthalten. Deklarationen externer Variablen können jedoch nur Initialisierer enthalten, wenn die Variablen nicht als externdeklariert werden.