构造函数
定义
- 构造函数是类的成员函数,其名称与类相同。
- 构造函数没有返回值。
- 创建对象时会自动调用构造函数。
- 构造函数不能被声明为虚拟的。
默认的构造函数
如果没有显式定义任何构造函数时,则编译器会隐式的生成
- 默认构造函数(Default Constructor)。
- 拷贝构造函数(Copy Constructor)。
- 移动构造函数(Move Constructor)。
class Foo {
public:
    Foo() = default;
    Foo(const Foo& other) = default;
    Foo(Foo&& other) = default;
};
默认构造函数
默认构造函数是不带任何参数或所有参数都提供默认值的构造函数。
如果显式定义了构造函数(不包含拷贝构造函数和移动构造函数),则编译器不会再自动生成默认的构造函数。
拷贝构造函数
拷贝构造函数使用类的现有对象来初始化新对象,使其成为现有对象的副本。
以下情况调用复制构造函数:
- 当类的对象按值返回时。
- 当类的对象通过值作为参数传递(给函数)时。
- 当用一个对象去初始化同类的另一个对象时。
- 当编译器生成一个临时对象时。
如果类具有运行时资源分配,则需要自定义拷贝构造函数。
移动构造函数
移动构造函数用于实现对象资源的移动。
移动构造函数如何移动:
- 对于普通类型,直接使用现有对象对应的值进行赋值;
- 对于指针类型,直接将堆内存地址给新对象的指针,现有对象指针置为nullptr,避免多次delete。
- 对于类类型,则调用该类的移动构造函数。
由于移动操作“ 窃取” 资源, 所以它通常不分配任何资源。 因此, 移动操作通常不会抛出任何异常。
成员初始化顺序
初始化一个类的成员变量有三种方式,并且这三种初始化方式的执行顺序是严格固定的:
- 类内初始化:内置变量可以用=或者{},对象使用{}。
- 初始化列表:成员变量的初始化顺序只取决于它们在类中声明的顺序,而与初始化列表中的顺序无关。
- 构造函数体内赋值:实际上不是初始化,而是赋值。
class Bar {
public:
    Bar(int xval, int yval) : x(xval), y(yval) {}
    int x;
    int y;
};
class Foo {
public:
    Foo(int xval, double yval) : y(yval), x(xval) { // 2. 初始化列表进行初始化
        // 3.构造函数体内复制
        x = 2;
        y = 3.14;
        z = "world";
        bar = Bar(3, 4);
    }
    // 1. 类内初始化
    int x {1};
    double y = 1.41;
    std::string z { "hello" };
    Bar bar {1, 2};
};
int main()
{
    Foo f(5, 5.0);
    std::cout << f.x << std::endl;
    std::cout << f.y << std::endl;
    std::cout << f.z << std::endl;
    std::cout << f.bar.x << "," << f.bar.y << std::endl;
}
输出:
2
3.14
world
3,4
委托构造函数
委托构造函数通过初始化列表调用同一个类的其他构造函数。
- 当使用了委托构造函数,初始化列表就不能再初始化其他成员变量。
- 委托构造函数不能调用多个其他构造函数,只能调用一个。
class Foo {
public:
    Foo() { /* init */ }
    Foo(int xval, double yval) : Foo() {}
private:
    int x;
    double y;
};