C++中的构造函数可以分为5类:默认构造函数、普通构造函数、拷贝构造函数、转换构造函数、移动构造函数。

默认构造函数

未提供显式初始值时,用来创建对象的构造函数。以Student类为例,默认构造函数的原型为

Student();//没有参数
Student(int num=0;int age=0);//所有参数均有默认值

何时生成默认的构造函数

当一个类没有构造函数时,如果满足以下四个条件其中之一,则编译器会为该类自动生成一个默认的构造函数:

  1. 该类含有一个类类型(非内置类型)的成员变量,且该类型含有默认构造函数。
  2. 该类继承自含有默认构造函数的基类。
  3. 该类继承或声明了虚函数。
  4. 该类含有虚基类。

普通构造函数

C++用于构建类的新对象时需要调用的函数

Student(int num,int age);//有参数

拷贝构造函数

何时生成默认的拷贝构造函数

当一个类没有拷贝构造函数时,如果满足以下四个条件其中之一,则编译器会为该类自动生成一个默认的拷贝构造函数:

  1. 该类含有一个类类型(非内置类型)的成员变量,且该类型含有拷贝构造函数。
  2. 该类继承自含有拷贝构造函数的基类。
  3. 该类继承或声明了虚函数。
  4. 该类含有虚基类。

需要注意的是,默认的拷贝构造函数实现的是浅拷贝

拷贝构造函数调用的时机

拷贝构造函数在以下三种情况下会被调用

1).当用一个对象去初始化同类的另一个对象时,会引发复制构造函数被调用。例如,下面的两条语句都会引发复制构造函数的调用,用以初始化 c2。

Complex c2(c1);
Complex c2 = c1;

这两条语句是等价的。

注意,第二条语句是初始化语句,不是赋值语句。赋值语句的等号左边是一个早已有定义的变量,赋值语句不会引发复制构造函数的调用。例如:

Complex c1, c2;
c1=c2;

这条语句不会引发复制构造函数的调用,因为 c1 早已生成,已经初始化过了。

2).如果函数 F 的参数是类 A 的对象,那么当 F 被调用时,类 A 的复制构造函数将被调用。换句话说,作为形参的类A的对象,是用复制构造函数初始化的,而且调用复制构造函数时的参数,就是调用函数时所给的实参。

#include<iostream>
using namespace std;
class A{
public:
    A(){};
    A(A & a){
        cout<<"Copy constructor called"<<endl;
    }
};
void Func(A a){ }
int main(){
    A a;
    Func(a);
    return 0;
}

程序的输出结果为:

Copy constructor called

这是因为 Func 函数的形参 a 在初始化时调用了复制构造函数。

前面说过,函数的形参的值等于函数调用时对应的实参,现在可以知道这不一定是正确的。如果形参是一个对象,那么形参的值是否等于实参,取决于该对象所属的类的复制构造函数是如何实现的。例如上面的例子,Func 函数的形参 a 的值在进入函数时是随机的,未必等于实参,因为复制构造函数没有做复制的工作。

以对象作为函数的形参,在函数被调用时,生成的形参要用复制构造函数初始化,这会带来时间上的开销。如果用对象的引用而不是对象作为形参,就没有这个问题了。但是以引用作为形参有一定的风险,因为这种情况下如果形参的值发生改变,实参的值也会跟着改变。

如果要确保实参的值不会改变,又希望避免复制构造函数带来的开销,解决办法就是将形参声明为对象的 const 引用。例如:

void Function(const Complex & c)
{
    ...
}

3). 如果函数的返冋值是类 A 的对象,则函数返冋时,类 A 的复制构造函数被调用。换言之,作为函数返回值的对象是用复制构造函数初始化 的,而调用复制构造函数时的实参,就是 return 语句所返回的对象。例如下面的程序:

#include<iostream>
using namespace std;
class A {
public:
    int v;
    A(int n) { v = n; };
    A(const A & a) {
        v = a.v;
        cout << "Copy constructor called" << endl;
    }
};
A Func() {
    A a(4);
    return a;
}
int main() {
    cout << Func().v << endl;
    return 0;
}

程序的输出结果是:

Copy constructor called
4

转换构造函数

一个构造函数接收一个不同于其类类型的形参,可以视为将其形参转换成类的一个对象。像这样的构造函数称为转换构造函数。在 C++ string 类中可以找到使用转换构造函数的实用示例。string 类提供一个将 C 字符串转换为 string 的转换构造函数

class string
{
    //仅显示转换构造函数
    public:
        string(char *);//形参时其他类型变量,且只有一个形参
};

移动构造函数

所谓移动语义,指的就是以移动而非深拷贝的方式初始化含有指针成员的类对象。简单的理解,移动语义指的就是将其他对象(通常是临时对象)拥有的内存资源“移为已用”。事实上,对于程序执行过程中产生的临时对象,往往只用于传递数据(没有其它的用处),并且会很快会被销毁。因此在使用临时对象初始化新对象时,我们可以将其包含的指针成员指向的内存资源直接移给新对象所有,无需再新拷贝一份,这大大提高了初始化的执行效率。

#include <cstring>

using namespace std;

class Str{
    public:
        char *str;
        Str(Str &&s)//移动构造函数
        {
        }
        ~Str()
        {
        }
};

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐