定义

  • 构造函数是类的成员函数,其名称与类相同。
  • 构造函数没有返回值
  • 创建对象时会自动调用构造函数。
  • 构造函数不能被声明为虚拟的。

默认的构造函数

如果没有显式定义任何构造函数时,则编译器会隐式的生成

  • 默认构造函数(Default Constructor)
  • 拷贝构造函数(Copy Constructor)
  • 移动构造函数(Move Constructor)
class Foo {
public:
    Foo() = default;
    Foo(const Foo& other) = default;
    Foo(Foo&& other) = default;
};

默认构造函数

默认构造函数是不带任何参数所有参数都提供默认值的构造函数。

如果显式定义了构造函数(不包含拷贝构造函数和移动构造函数),则编译器不会再自动生成默认的构造函数。

拷贝构造函数

拷贝构造函数使用类的现有对象来初始化新对象,使其成为现有对象的副本。

以下情况调用复制构造函数:

  • 当类的对象按值返回时。
  • 当类的对象通过值作为参数传递(给函数)时。
  • 当用一个对象去初始化同类的另一个对象时。
  • 当编译器生成一个临时对象时。

如果类具有运行时资源分配,则需要自定义拷贝构造函数。

移动构造函数

移动构造函数用于实现对象资源的移动

移动构造函数如何移动:

  1. 对于普通类型,直接使用现有对象对应的值进行赋值;
  2. 对于指针类型,直接将堆内存地址给新对象的指针,现有对象指针置为nullptr,避免多次delete。
  3. 对于类类型,则调用该类的移动构造函数。

由于移动操作“ 窃取” 资源, 所以它通常不分配任何资源。 因此, 移动操作通常不会抛出任何异常

成员初始化顺序

初始化一个类的成员变量有三种方式,并且这三种初始化方式的执行顺序是严格固定的:

  1. 类内初始化:内置变量可以用=或者{},对象使用{}
  2. 初始化列表:成员变量的初始化顺序只取决于它们在类中声明的顺序,而与初始化列表中的顺序无关。
  3. 构造函数体内赋值:实际上不是初始化,而是赋值。
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;
};