Udostępnij za pośrednictwem


Inicjatory

Inicjator określa wartość początkową zmiennej. Można zainicjować zmienne w tych kontekstach:

  • W definicji zmiennej:

    int i = 3;
    Point p1{ 1, 2 };
    
  • Jako jeden z parametrów funkcji:

    set_point(Point{ 5, 6 });
    
  • Jako wartość zwracana funkcji:

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

Inicjatory mogą przybierać następujące formy:

  • Wyrażenie (lub rozdzielana przecinkami lista wyrażeń) w nawiasach:

    Point p1(1, 2);
    
  • Znak równości i następujące po nim wyrażenie:

    string s = "hello";
    
  • Lista inicjatorów w nawiasach klamrowych. Lista może być pusta lub może składać się z zestawu list, jak w poniższym przykładzie:

    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 } });
    }
    

Rodzaje inicjalizacji

Istnieje kilka rodzajów inicjowania, które mogą wystąpić w różnych punktach wykonywania programu. Różne rodzaje inicjowania nie wykluczają się wzajemnie — na przykład inicjowanie listy może wyzwalać inicjowanie wartości, a w innych okolicznościach może wyzwolić inicjowanie agregacji.

Inicjowanie zerowe

Inicjalizacja wartością zerową to ustawienie zmiennej na wartość zero niejawnie konwertowaną na typ:

  • Zmienne liczbowe są inicjowane na wartość 0 (lub 0,0 lub 0,0000000000 itp.).

  • Zmienne char są inicjowane na .'\0'

  • Wskaźniki są inicjowane do nullptr.

  • Tablice, klasy POD , struktury i związki mają swoje składowe zainicjowane do wartości zerowej.

Inicjalizacja z wartością zerową odbywa się w różnym czasie:

  • Podczas uruchamiania programu, dla wszystkich zmiennych nazwanych, które mają statyczny czas trwania. Te zmienne mogą być później ponownie zainicjowane.

  • Podczas inicjowania wartości, dla typów skalarnych i typów klasy POD, które są inicjowane za pomocą pustych nawiasów klamrowych.

  • Dla tablic, które mają zainicjowany tylko podzbiór swoich elementów członkowskich.

Oto kilka przykładów inicjalizacji z wartością zerową:

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'
}

Inicjowanie domyślne

Domyślna inicjalizacja klas, struktur i związków to inicjowanie za pomocą konstruktora domyślnego. Konstruktor domyślny może być wywoływany bez wyrażenia inicjalizacji lub słowa kluczowego new :

MyClass mc1;
MyClass* mc3 = new MyClass;

Jeśli klasa, struktura lub unia nie ma konstruktora domyślnego, kompilator emituje błąd.

Zmienne skalarne są domyślnie inicjowane, gdy są definiowane bez wyrażenia inicjalizacji. Mają one wartości nieokreślone.

int i1;
float f;
char c;

Tablice są domyślnie inicjowane, gdy są zdefiniowane bez wyrażenia inicjalizacji. Gdy tablica jest inicjowana domyślnie, jej składowe są inicjowane domyślnie i mają nieokreślone wartości, jak w poniższym przykładzie:

int int_arr[3];

Jeśli elementy członkowskie tablicy nie mają konstruktora domyślnego, kompilator emituje błąd.

Domyślna inicjowanie zmiennych stałych

Stałe zmienne muszą zostać zadeklarowane wraz z inicjatorem. Jeśli są typami skalarnym, powodują błąd kompilatora, a jeśli są typami klas, które mają domyślny konstruktor, powodują ostrzeżenie:

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
}

Domyślna inicjowanie zmiennych statycznych

Zmienne statyczne zadeklarowane bez inicjatora są inicjowane na wartość 0 (niejawnie konwertowane na typ).

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'}
}

Aby uzyskać więcej informacji na temat inicjowania globalnych obiektów statycznych, zobacz główne funkcje i argumenty wiersza polecenia.

Inicjowanie wartości

Inicjowanie wartości odbywa się w następujących przypadkach:

  • nazwana wartość jest inicjowana przy użyciu inicjowania pustego nawiasu klamrowego

  • anonimowy obiekt tymczasowy jest inicjowany przy użyciu pustych nawiasów lub nawiasów klamrowych

  • obiekt jest inicjowany za pomocą słowa kluczowego new oraz pustych nawiasów nawiasów klamrowych lub nawiasów klamrowych

Inicjowanie wartości wykonuje następujące czynności:

  • w przypadku klas z co najmniej jednym publicznym konstruktorem jest wywoływany domyślny konstruktor

  • w przypadku klas niezwiązanych bez zadeklarowanych konstruktorów obiekt jest inicjowany zero, a konstruktor domyślny jest wywoływany

  • w przypadku tablic każdy element jest inicjowany przez wartość

  • we wszystkich innych przypadkach zmienna jest zainicjowana zero

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
}

Inicjowanie kopiowania

Inicjowanie kopiowania to inicjowanie jednego obiektu przy użyciu innego obiektu. Występuje w następujących przypadkach:

  • zmienna jest inicjowana przy użyciu znaku równości

  • argument jest przekazywany do funkcji

  • obiekt jest zwracany z funkcji

  • wyjątek jest zgłaszany lub przechwycony

  • element członkowski danych niestacjonalizowanych jest inicjowany przy użyciu znaku równości

  • składowe klasy, struktury i unii są inicjowane przez inicjowanie kopii podczas inicjowania agregacji. Zobacz Agregowanie inicjowania , aby zapoznać się z przykładami.

Poniższy kod przedstawia kilka przykładów inicjowania kopiowania:

#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();
    }
}

Inicjowanie kopiowania nie może wywoływać jawnych konstruktorów.

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

W niektórych przypadkach, jeśli konstruktor kopiujący klasy jest usunięty lub niedostępny, inicjalizacja kopiująca powoduje błąd kompilatora.

Inicjowanie bezpośrednie

Inicjowanie bezpośrednie jest inicjowane przy użyciu nawiasów klamrowych (niepustych). W przeciwieństwie do inicjalizacji kopiującej, może wywołać jawne konstruktory. Występuje w następujących przypadkach:

  • zmienna jest inicjowana z niepustymi nawiasami klamrowymi lub nawiasami

  • zmienna jest inicjowana za pomocą słowa kluczowego new oraz nawiasów niepustych lub nawiasów

  • zmienna jest inicjowana za pomocą polecenia static_cast

  • w konstruktorze klasy bazowe i niestatyczne składowe są inicjowane przy użyciu listy inicjatorów

  • w kopii przechwyconej zmiennej wewnątrz wyrażenia lambda

Poniższy kod przedstawia kilka przykładów inicjowania bezpośredniego:

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();
}

Inicjowanie listy

Inicjowanie listy występuje, gdy zmienna jest inicjowana przy użyciu listy inicjatora nawiasów klamrowych. Listy inicjatorów nawiasów klamrowych mogą być używane w następujących przypadkach:

  • inicjowana jest zmienna

  • klasa jest inicjowana za pomocą słowa kluczowego new

  • obiekt jest zwracany z funkcji

  • argument przekazany do funkcji

  • jeden z argumentów w inicjalizacji bezpośredniej

  • w inicjatorze niestacjonalnym składowym danych

  • na liście inicjatora konstruktora

Poniższy kod przedstawia kilka przykładów inicjowania listy:

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' } };
}

Inicjowanie agregacji

Inicjalizacja agregacji jest formą inicjalizacji listy dla tablic lub typów klas (zwykle struktur lub unii), które:

  • brak prywatnych lub chronionych elementów członkowskich

  • brak konstruktorów dostarczonych przez użytkownika, z wyjątkiem jawnie domyślnych lub usuniętych konstruktorów

  • brak klas bazowych

  • brak wirtualnych funkcji składowych

Uwaga

W programie Visual Studio 2015 i starszych agregacja nie może mieć inicjatorów nawiasów klamrowych lub równych dla niestatycznych elementów członkowskich. To ograniczenie zostało usunięte w standardzie C++14 i zaimplementowane w programie Visual Studio 2017.

Inicjatory agregacji składają się z listy inicjowania nawiasów klamrowych z znakiem równości lub bez znaku równości, jak w poniższym przykładzie:

#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;
}

Powinny zostać wyświetlone następujące dane wyjściowe:

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

Ważne

Elementy członkowskie tablicy, które są zadeklarowane, ale nie są jawnie inicjowane podczas inicjowania agregacji, są inicjowane zero, jak pokazano myArr3 powyżej.

Inicjowanie związków i struktur

Jeśli unia nie ma konstruktora, możesz zainicjować ją za pomocą pojedynczej wartości (lub innego wystąpienia unii). Wartość służy do inicjowania pierwszego pola niestatycznego. Różni się to od inicjowania struktury, w którym pierwsza wartość w inicjatorze służy do inicjowania pierwszego pola, druga do zainicjowania drugiego pola itd. Porównaj inicjowanie związków i struktur w poniższym przykładzie:

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'
}

Inicjowanie agregacji zawierających agregacje

Typy agregacji mogą zawierać inne typy agregacji, na przykład tablice tablic, tablice struktur itd. Te typy są inicjowane przy użyciu zagnieżdżonych zestawów nawiasów klamrowych, na przykład:

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'} };
}

Inicjowanie odwołania

Zmienne typu referencyjnego muszą być inicjowane z obiektem typu, który dziedziczy ten typ referencyjny lub obiektem typu, który może zostać przekonwertowany na typ, z którego dziedziczy typ referencyjny. Na przykład:

// 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
}

Jedynym sposobem, aby zainicjować odwołanie tymczasowego obiektu, jest zainicjowanie tymczasowego obiektu stałego. Po zainicjowaniu zmienna typu odwołania zawsze wskazuje ten sam obiekt; nie można go zmodyfikować w celu wskazania innego obiektu.

Mimo że składnia może być taka sama, zainicjowanie zmiennych typu odwołania i przypisanie do zmiennych typu odwołania są semantycznie różne. W poprzednim przykładzie, przydziały, które zmieniają iVar i lVar wyglądają podobnie do inicjalizacji, ale dają różne efekty. Inicjalizacja określa obiekt, na który wskazuje zmienna typu odwołania; przypisanie przypisuje do określonego obiektu poprzez odniesienie.

Ponieważ zarówno przekazywanie argumentu typu odwołania do funkcji i zwracanie wartości typu odwołania przez funkcję są inicjalizacjami, formalne argumenty do funkcji są inicjowane poprawnie podczas zwracania odwołania.

Zmienna typu odwołania może być deklarowana bez inicjatorów tylko w następujących przypadkach:

  • Deklaracje funkcji (prototypy). Na przykład:

    int func( int& );
    
  • Deklaracje typu funkcji zwracanej. Na przykład:

    int& func( int& );
    
  • Deklaracja składowej klasy typu odwołania. Na przykład:

    class c {public:   int& i;};
    
  • Deklaracja zmiennej jawnie określonej jako extern. Na przykład:

    extern int& iVal;
    

Podczas inicjowania zmiennej typu odwołania kompilator używa grafu decyzyjnego pokazanego na poniższej ilustracji, aby wybrać między utworzeniem odwołania do obiektu lub utworzeniem tymczasowego obiektu, do którego wskazuje odwołanie:

Decision graph for initialization of reference types.

Wykres decyzyjny zaczyna się od: czy inicjator lvalue tego samego typu lub typu pochodzącego z typu odwołania? Jeśli tak, odwołanie odwołuje się do obiektu określonego w inicjatorze. Jeśli nie, następną decyzją jest to, czy zmienna typu odwołania jest zainicjowanym odwołaniem const T i czy inicjator może zostać niejawnie przekonwertowany na T? Jeśli tak, zostanie utworzona tymczasowa, a zmienna referencyjna stanie się nazwą tego tymczasowego. Jeśli nie, jest to błąd.

Wykres decyzyjny na potrzeby inicjowania typów referencyjnych

Odwołania do volatile typów (zadeklarowanych jako volatiletypename&identifier) można zainicjować za pomocą volatile obiektów tego samego typu lub obiektów, które nie zostały zadeklarowane jako volatile. Nie można jednak zainicjować ich z obiektami const tego typu. Podobnie odwołania do const typów (zadeklarowanych jakoconst typename&identifier) można zainicjować za pomocą const obiektów tego samego typu (lub dowolnych elementów, które mają konwersję na ten typ lub z obiektami, które nie zostały zadeklarowane jako ).const Nie można jednak zainicjować ich z obiektami volatile tego typu.

Odwołania, które nie są kwalifikowane za pomocą słowa kluczowego const lubvolatile, mogą być inicjowane tylko z obiektami zadeklarowanymi jako ani const ani .volatile

Inicjowanie zmiennych zewnętrznych

Deklaracje zmiennych automatycznych, statycznych i zewnętrznych mogą zawierać inicjatory. Jednak deklaracje zmiennych zewnętrznych mogą zawierać inicjatory tylko wtedy, gdy zmienne nie są deklarowane jako extern.