构造函数是 OOP 中一类特殊的成员函数,用于创建对象并初始化成员变量,在 C++ 中,构造函数和类相同,没有返回值。
一般在以下情况下需要自定义构造函数:
- 自定义类的成员初始化方式
- 创建类的对象时调用函数
构造函数一般时
public
的,但也可以被声明为protecated
、private
等
默认构造函数#
默认构造函数没有任何提供任何参数,在类定义时可以不显式地定义,当不显式定义时,编译器会自动生成一个构造函数,一个默认构造函数的例子是:
class MyClass{
public:
MyClass(){
// do something..
}
}
关于默认构造函数的 “最棘手分析”#
因为 C++ 的分析程序更偏向于声明,因此在调用编辑器生成的默认构造函数并尝试使用括号的时候会出现警告:
class MyClass{};
int main(){
MyClass myClass();
// warning C4930
}
在这个例子中,MyClass myClass (); 不会像期望的那样调用 MyClass 的默认构造函数来创建 myClass 对象。相反,这行代码实际上被解析为一个函数声明,而不是一个对象声明。这里,myClass 被解释为一个函数,它不带参数并返回 MyClass 类型的对象。
这种现象称为 “最棘手的分析”(Most Vexing Parse),是 C++ 语言中一个著名的语法解析问题。这个问题源自 C++ 语法的复杂性,其中某些语法结构可能根据上下文被解释为多种不同的事物。在这种情况下,编译器优先选择将表达式解释为函数声明而非对象构造。
通过显式创建对象避免该问题:
int main() {
MyClass myClass;
}
参数化构造函数#
参数化构造函数带有多个参数的初始化:
class MyClass{
public:
int x, y;
MyClass(int a, int b): x(a), y(b){
// do something..
}
}
此处使用了成员初始化表达式列表,其表达式具体形式为:
identifier(argument)
成员初始化表达式列表由冒号后所有的表达式组成:
MyFunc(int a, int b, int c):
m_a(a), m_b(b), m_c(c){}
复制构造函数#
复制构造函数用于根据同类型的一个对象初始化另一个新的对象,通常出现在对象的复制场景中:
class MyClass{
public:
int x, y;
MyClass(const MyClass& mc):
x(mc.x), y(mc.y) {}
}
上面的简单例子的成员都是标量,这种简单情况下编译器就足以生成复制构造函数,无需自己定义,当复制构造函数需要实现特殊行为,例如分类新内存时,就需要自己实现复制构造函数。
在定义构造函数时,需要重载复制赋值运算符=
移动构造函数#
移动构造函数是 C++11 后的新特性,用于将资源从一个对象转移到另一个对象,通常在支持右值引用的场景下使用,它将现有对象数据的所有权移交给新变量,而不复制原始数据。 它采用 rvalue 引用作为其第一个参数,以后的任何参数都必须具有默认值。 移动构造函数在传递大型对象时可以显著提高程序的效率:
//todo 左值和右值
class MyClass{
public:
char *str;
MyClass(MyClass && obj): str(obj.str){
obj.str = nullptr; // 避免原对象析构时释放内存
}
}
委托构造函数#
委托构造函数仍然是 C++11 后引入的新特性,它允许一个构造函数在当前类中调用另一个构造函数,进一步解耦代码:
class MyClass{
public:
int x, y, z;
MyClass(int a, int b):x(a), y(b), z(0) {}
MyClass(int a, int b, int c): Point(a, b) {z = c;}
}
继承构造函数#
继承构造函数仍然时 C++11 后引入的概念,它运训从基类中继承构造函数,使用using
关键字实现:
class Base{
public:
Base(int x){};
};
class MyClass: public Base{
using Base::Base; // 继承Base的构造函数
}
显式构造函数和隐式构造函数#
通过explicit
关键字声明的构造函数只能用于直接初始化,它防止了构造函数在需要隐式转换时被意外调用:
class MyClass {
public:
explicit MyClass(int a) {}
};