初始化

参考:Initializers

初始化器指定了变量的初始值,c++提供了多种初始化方式:

  1. 零初始化(zero initialization
  2. 默认初始化(default initialization
  3. 值初始化(value initialization
  4. 复制初始化(copy initialization
  5. 直接初始化(direct initialization
  6. 列表初始化(list initialization
  7. 集合初始化(aggregate initialization
  8. 引用初始化(reference initialization
  9. 外部变量初始化(Initialization of external variables

零初始化

零初始化是将变量隐式转换为该类型的零值:

  • 数值变量初始化为0(或0.0)
  • char变量初始化为'\0'
  • 指针变量初始化为nullptr
  • 数组、结构体等对象的成员初始化为零值

使用场景

在以下情况下发生零初始化:

  1. 程序启动时,所有具有static持续时间的变量
  2. 在值初始化期间,对于使用空大括号初始化的标量类型和类类型
  3. 对于只显式初始化了其部分成员的数组

示例

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

默认初始化

  • 对于类、结构体和共同体而言,其默认初始化是使用默认构造函数初始化,没有初始化表达式或使用new关键字时会调用默认初始化
  • 对于标量变量而言,在没有初始化表达式定义时执行默认初始化,此时它们有不确定的值
  • 对数组而言,在没有初始化表达式的情况下定义时是默认初始化的。当数组默认初始化时,其成员默认初始化并具有不确定的值

常量变量的默认初始化

常量变量必须和初始化器同时声明。否则,对于标量类型而言会造成编译错误;对有默认构造器的类类型而言,编译器会抛出warning

静态变量的默认初始化

未使用初始值设定项声明的静态变量初始化为0(隐式转换)

示例

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

int main() {
    // 标量类型的默认初始化,不缺定值
    int i1;
    float f;
    char c;
    // 数组的默认初始化,不缺定值
    int int_arr[3];
    // 常量变量的初始化,必须加上初始化器
    // error
//    const MyClass mc1;
    // 静态变量的初始化,默认为0
}

值初始化

  • 对于至少有一个public构造器,默认构造器被调用
  • 对于没有构造器声明的非共同体类,对象是零初始化,默认呢构造器被调用
  • 对于数组,每个元素都是值初始化
  • 其他情况下,变量是零初始化

使用场景

  • 使用空大括号初始化命名值
  • 使用空括号或大括号初始化匿名临时对象
  • 使用new关键字加上空括号或大括号初始化对象

示例

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
}

复制初始化

复制初始化是使用另一个对象初始化当前对象

使用场景

  • 使用等号初始化变量
  • 输入参数到函数
  • 函数返回对象
  • 被抛出或不捕获的异常
  • 使用等号初始化的非static数据成员
  • 类、结构体和共同体成员在聚合初始化期间通过复制初始化进行初始化

示例

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

复制初始化无法调用显式构造器。某些情况下,如果类的复制构造器被删除或无法访问,复制初始化会造成编译器错误

直接初始化

直接初始化是使用(非空)大括号或圆括号进行初始化。与复制初始化不同,它可以调用显式构造函数

使用场景

  • 使用非空大括号或圆括号初始化变量
  • new关键字加上非空大括号或圆括号初始化变量
  • static_cast初始化变量
  • 在构造函数中,使用初始值列表初始化基类和非静态成员
  • lambda表达式中捕获变量的副本

示例

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

列表初始化

当使用带括号的初始值列表初始化变量时,会发生列表初始化

使用场景

  • 变量初始化
  • 类使用new关键字初始化
  • 函数返回的对象
  • 输入到函数的参数
  • 直接初始化的其中一个参数
  • 非静态成员初始化器
  • 构造器初始化列表

示例

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

集合初始化

集合初始化是数组或类类型(通常是结构体或共同体)的列表初始化形式,这些类型具有

  • 没有privateprotected成员
  • 除了显式defaulteddeleted的构造函数,没有用户提供的构造器
  • 没有基类
  • 没有虚拟成员函数

示例

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

初始化共同体/结构体

  • 如果共同体没有构造器,你可以用单个值初始化它(或者另一个共同体),该值将用于初始化第一个非静态成员
  • 对于结果体而言,按序初始化成员,其后的成员置为0
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'
}

初始化包含集合的集合

聚合类型可以包含其他聚合类型,例如数组数组、结构数组等。这些类型是通过使用嵌套的大括号集初始化的

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

引用初始化

引用类型的变量必须用派生引用类型的对象初始化,或者用可转换为派生引用类型的对象初始化

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
}

使用临时对象初始化

使用临时对象初始化引用的唯一方法是初始化常量临时对象。一旦初始化,引用类型变量总是指向同一个对象;不能修改为指向另一个对象

    const int &i = 3;
//    int &i2 = 4; // error

引用初始化 vs. 引用赋值

尽管语法可以相同,但是引用类型变量的初始化(initialize)和对引用类型变量的赋值(assign)在语义上是不同的。在前面的示例中,更改ivarlvar的赋值看起来与初始化类似,但具有不同的效果。初始化指定引用类型变量指向的对象;赋值通过引用分配给引用的对象

没有初始化

引用类型变量仅在以下情况可以没有初始化:

  1. 函数声明(原型)
  2. 返回返回值类型声明
  3. 引用类型的类成员声明
  4. 显式声明为extern的变量引用
int func( int& );
int& func( int& );
class c {public:   int& i;};
extern int& iVal;

外部变量初始化

automatic、static和external变量的声明可以包含初始值设定项。但是,只有在变量未声明为external变量时,外部变量的声明才能包含初始值设定项

// name.cpp
int iii = 333;
// main.cpp
extern int iii;
extern int iii = 3; // warning: ‘iii’ initialized and declared ‘extern’