构造函数是一种可初始化其类的实例的成员函数。构造函数具有与类相同的名称,没有返回值。构造函数可以具有任意数量的参数,类可以具有任意数量的重载构造函数。构造函数可以具有任何可访问性(公共、受保护或私有)。如果未定义任何构造函数,则编译器会生成不采用任何参数的默认构造函数;可以通过将默认构造函数声明为已删除来重写此行为。
构造函数顺序
构造函数按此顺序执行工作:
按声明顺序调用基类和成员构造函数。
如果类派生自虚拟基类,则会将对象的虚拟基指针初始化。
如果类具有或继承了虚函数,则会将对象的虚函数指针初始化。虚函数指针指向类中的虚函数表,确保虚函数正确地调用绑定代码。
它执行自己函数体中的所有代码。
下面的示例显示,在派生类的构造函数中,基类和成员构造函数的调用顺序。首先,调用基构造函数,然后按照基类成员在类声明中出现的顺序对这些成员进行初始化,然后,调用派生构造函数。
#include <iostream> using namespace std; class Contained1 { public: Contained1() { cout << "Contained1 constructor." << endl; } }; class Contained2 { public: Contained2() { cout << "Contained2 constructor." << endl; } }; class Contained3 { public: Contained3() { cout << "Contained3 constructor." << endl; } }; class BaseContainer { public: BaseContainer() { cout << "BaseContainer constructor." << endl; } private: Contained1 c1; Contained2 c2; }; class DerivedContainer : public BaseContainer { public: DerivedContainer() : BaseContainer() { cout << "DerivedContainer constructor." << endl; } private: Contained3 c3; }; int main() { DerivedContainer dc; int x = 3; }</div>
这是输出:
Contained1 constructor. Contained2 constructor. BaseContainer constructor. Contained3 constructor. DerivedContainer constructor.</div>
如果构造函数引发异常,析构的顺序与构造的顺序相反:
构造函数主体中的代码将展开。
基类和成员对象将被销毁,顺序与声明顺序相反。
如果是非委托构造函数,所有完全构造的基类对象和成员均将被销毁。但是,对象本身不是完全构造的,因此析构函数不会运行。
成员列表
使用成员初始值设定项列表从构造函数参数初始化类成员。此方法使用直接初始化,这比在构造函数体内使用赋值运算符更高效。
class Box { public: Box(int width, int length, int height) : m_width(width), m_length(length), m_height(height) // member init list {} int Volume() {return m_width * m_length * m_height; } private: int m_width; int m_length; int m_height; };</div>
创建 Box 对象:
Box b(42, 21, 12); cout << "The volume is " << b.Volume();</div>
显式构造函数
如果类具有带一个参数的构造函数,或是如果除了一个参数之外的所有参数都具有默认值,则参数类型可以隐式转换为类类型。例如,如果 Box 类具有一个类似于下面这样的构造函数:
Box(int size): m_width(size), m_length(size), m_height(size){}</div>
可以初始化 Box,如下所示:
Box b = 42;</div>
或将一个 int 传递给采用 Box 的函数:
class ShippingOrder { public: ShippingOrder(Box b, double postage) : m_box(b), m_postage(postage){} private: Box m_box; double m_postage; } //elsewhere... ShippingOrder so(42, 10.8);</div>
这类转换可能在某些情况下很有用,但更常见的是,它们可能会导致代码中发生细微但严重的错误。作为一般规则,应对构造函数使用 explicit 关键字(和用户定义的运算符)以防止出现这种隐式类型转换:
explicit Box(int size): m_width(size), m_length(size), m_height(size){}</div>
构造函数是显式函数时,此行会导致编译器错误:ShippingOrder so(42, 10.8);。
默认构造函数
默认构造函数没有参数;它们遵循略有不同的规则:
默认构造函数是一个特殊成员函数;如果没有在类中声明构造函数,则编译器会提供默认构造函数:
class Box { Box(int width, int length, int height) : m_width(width), m_length(length), m_height(height){} }; int main(){ Box box1{}; // call compiler-generated default ctor Box box2; // call compiler-generated default ctor }</div>
当你调用默认构造函数并尝试使用括号时,系统将发出警告:
class myclass{}; int main(){ myclass mc(); // warning C4930: prototyped function not called (was a variable definition intended?) }</div>
这是“最棘手的解析”问题的示例。这种示例表达式既可以解释为函数的声明,也可以解释为对默认构造函数的调用,而且 C++ 分析器更偏向于声明,因此表达式会被视为函数声明。
如果声明了任何非默认构造函数,编译器不会提供默认构造函数:
class Box { Box(int width, int length, int height) : m_width(width), m_length(length), m_height(height){} }; private: int m_width; int m_length; int m_height; }; int main(){ Box box1(1, 2, 3); Box box2{ 2, 3, 4 }; Box box4; // compiler error C2512: no appropriate default constructor available }</div>
如果类没有默认构造函数,将无法通过单独使用方括号语法来构造该类的对象数组。例如,在前面提到的代码块中,框的数组无法进行如下声明:
Box boxes[3]; // compiler error C2512: no appropriate default constructor available</div>
但是,你可以使用初始值设定项列表将框的数组初始化:
Box boxes[3]{ { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };</div>
复制和移动构造函数
复制构造函数是特殊成员函数,它采用对相同类型对象的引用作为输入,并创建它的副本。移动也是特殊成员函数构造函数,它将现有对象的所有权移交给新变量,而不复制原始数据。
显式默认构造函数和已删除构造函数
你可以显式设置默认复制构造函数、设置默认构造函数、移动构造函数、复制赋值运算符、移动赋值运算符和析构函数。你可以显式删除所有特殊成员函数。
派生类中的构造函数
派生类构造函数始终调用基类构造函数,因此,在完成任何额外任务之前,它可以依赖于完全构造的基类。调用基类构造函数进行派生,例如,如果 ClassA 派生自 ClassB,ClassB 派生自 ClassC,那么首先调用 ClassC 构造函数,然后调用 ClassB 构造函数,最后调用 ClassA 构造函数。
如果基类没有默认构造函数,则必须在派生类构造函数中提供基类构造函数参数:
class Box { public: Box(int width, int length, int height){ m_width = width; m_length = length; m_height = height; } private: int m_width; int m_length; int m_height; }; class StorageBox : public Box { public: StorageBox(int width, int length, int height, const string label&) : Box(width, length, height){ m_label = label; } private: string m_label; }; int main(){ const string aLabel = "aLabel"; StorageBox sb(1, 2, 3, aLabel); }</div>
具有多重继承的类的构造函数
如果类从多个基类派生,那么将按照派生类声明中列出的顺序调用基类构造函数:
#include <iostream> using namespace std; class BaseClass1 { public: BaseClass1() { cout << "BaseClass1 constructor." << endl; } }; class BaseClass2 { public: B