作者 | 梁唐
大家好,我是梁唐。
这是EasyC++系列的第71篇,来聊聊拷贝构造函数。
我们上一篇文章当中聊了面向对象中的一些坑,有的时候我们命名重载了构造函数和析构函数,但还是有问题。
这是因为在C++当中除了构造函数和析构函数之外,还有一些特殊的成员函数。这些成员函数是自动定义的,当我们没有意识到它们存在的时候,往往就会出现问题。
这些成员函数有:
编译器将会生成最后三个函数的定义——拷贝构造函数、赋值运算符和地址运算符。
比如当我们把一个对象赋值给另外一个对象的时候,编译器就会调用默认的赋值运算符,完成对象赋值工作。地址运算符返回调用对象的地址,也就是取地址的时候返回结果。一般默认返回this
指针,我们不需要过多操作。
另外C++11当中还提供了其他两个特殊的成员函数:移动构造函数和移动赋值运算符,这个我们在后面的文章讨论。
如果我们没有提供任何构造函数,C++将创建默认构造函数。比如我们定义了一个Time
类,编译器将会提供如下默认构造函数:
Time::Time() {}
编译器将会提供一个不接受任何参数,也不执行任何操作的构造函数。因为我们创建对象的时候总会调用构造函数:
Time t;
由于默认构造函数当中没有任何的操作,因此这样创建出来的对象t中的值是未知的。如果我们创建了构造函数,那么C++不会再提供默认构造函数。如果我们希望能够继续直接创建变量,则需要我们手动提供无参构造函数。
带参数的构造函数也可以是默认构造函数,只要所有的参数都有默认值:
Time(int hours=0, int minutes=0) {
_hours = hours;
_minutes = minutes;
}
为了防止歧义,这两者不能同时出现。
拷贝构造函数用于将一个对象复制到新创建的对象中。它用于初始化过程中,而不是常规的复制过程中。拷贝构造函数的原型通常如下:
Class_name(const Class_name &);
它接受一个指向类对象的常量引用作为参数。
对于拷贝构造函数我们只需要知道两点:何时调用和有何功能。
新建一个对象并且初始化的时候,拷贝构造函数都会被调用。大概有下面这么几种情况:
Time a;
Time b(a);
Time c = a;
Time d = Time(a);
Time *e = new Time(a);
其中中间的两种声明可能会使用拷贝构造函数直接创建c和d,也可能使用拷贝构造函数生成一个临时对象,然后将临时对象的内容赋给c和d,这取决于具体的实现。
最后一种声明使用a初始化一个匿名对象,并且将匿名对象的地址赋值给e指针。
每当程序生成了对象副本,都会使用拷贝构造函数。也就是说当函数按值传递对象或函数返回对象时,都会使用拷贝构造函数。
默认的拷贝构造函数逐个赋值非静态成员,复制的是成员的值。比如:
Time b = a;
等价于:
Time b;
b.hours = a.hours;
b.minutes = a.minutes;
只不过hours
和minutes
是私有成员,无法直接访问,因此通过不了编译。如果成员本身就是一个类对象,那么将会使用这个类对象的拷贝构造函数来复制。