什么是复制构造函数?
只有单个形参, 而且该形参是对本类类型对象的引用( 常用const修饰), 这样的构造函数称为复制构造函数.
什么时候使用复制构造函数?
1. 根据另一个同类型的对象显示或隐式初始化一个对象.
2. 复制一个对象, 将它作为实参传给一个函数.
3. 从函数返回时复制一个对象.
4.初始化顺序容器中的元素.
5.根据元素初始化列表初始化数组元素.
1)对象的定义形式
c++支持两种初始化形式: 直接初始化和复制初始化. 复制初始化使用=符号, 而直接初始化将初始化式放在圆括号中.
当用于类类型对象时, 初始化的复制形式和直接形式有所不同: 直接初始化直接调用与实参匹配的构造函数, 复制初始化总是调用复制构造函数. 复制初始化 首先使用指定构造函数 创建一个临时对象, 然后用复制构造函数将那个临时对象复制到正在创建的对象.
例如:
string null_book = "9-999-99999-9"; 复制初始化
string dots(10, ‘.‘); 直接初始化
string empty_copy() = string(); 复制初始化
string empty_direct; 直接初始化
2) 形参与返回值
当形参为非引用类型时, 将复制实参的值. 以非引用类型作为返回值时, 将返回return语句中的值的副本.
3)初始化容器元素
复制构造函数可用于初始化顺序容器中的元素. 例如
vector<string> svec(15);
编译器首先使用string默认构造函数创建一个临时值来初始化svec, 然后使用复制构造函数将临时值复制到svec的每个元素.
4) 构造函数与数组元素
如果使用常规的花括号括注的数组初始化列表来提供显示元素的初始化, 将使用复制初始化来初始化每个元素.根据指定值创建适当类型的元素, 然后使用复制构造函数将该值复制到相应元素.
合成的复制构造函数
如果我们没有定义复制构造函数, 编译器就会为我们合成一个. 与合成的默认构造函数不同, 即使我们定义了其他构造函数, 也会合成复制构造函数. 合成复制构造函数的行为是, 执行逐个成员初始化, 将新对象初始化为原对象的副本.
例如:
class Example
{
private:
int num;
std::string str;
}
则合成复制构造函数如下:
Example :: Example(const Example& other) : num( other.num), str( other.str) {}
定义自己的复制构造函数
对于许多类而言, 合成复制构造函数只完成必要的工作. 只包含类类型成员或内置类型( 但不是指针类型) 成员的类, 无须显示地定义复制构造函数, 也可以复制.
然而, 有些类必须对复制对象时发生的事情加以控制.
这样的类经常有一个数据成员是指针, 或者有成员表示在构造函数中分配的其他资源. 而另一些类在创建新对象时必须做一些特定工作. 这两种情况下, 都必须定义复制构造函数.
禁止复制
有些类需要完全禁止复制. 例如, iostream 类就不允许复制 . 要做到禁止复制, 我们必须自己定义一个复制构造函数并且把它声明为private, 并且不对它进行定义.
因为如果我们不声明一个复制构造函数, 编译器会合成一个, 把复制构造函数声明为private并且不定义是防止友元和成员进行复制. 通过声明(但不定义) private 复制构造函数, 可以禁止任何复制类类型对象的尝试.
还有一点需要注意的是如果定义了复制构造函数就必须定义一个默认构造函数, 因为一旦我们定义了构造函数编译器就不会合成默认构造函数了.
最后用课本上的两个例题巩固一下.
1. 对下面的类编写一个复制构造函数复制所有成员. 复制pstring指向的对象而不是复制指针.
struct Noname { Noname() : pstring ( new std:: string), i(0), d(0) {} private: std::string *pstring; int i; double d; };
答:
Noname::Noname( const Noname& pg) { pstring = new std::string; *pstring = *(pg.pstring); i= pg.i; d= pg.d; }
2.哪个类可能需要定义一个复制构造函数?
(a) 包含四个float成员的Point3w 类.
(b) Matrix类, 其中, 实际矩阵在构造函数中动态分配, 在析构函数中删除.
(c) Payroll类, 在这个类中为每个对象提供唯一ID.
(d) Word类, 包含一个string和一个以行列位置为元素的vector.
答: (a)不需要, 因为该类中数据成员都是内置类型,没有指针成员, 使用编译器提供的合成复制构造函数即可.
(b) 需要, 涉及指针及内存的动态分配.
(c) 需要, 根据已存在的Payroll对象创建其副本时, 需要提供唯一的ID.
(d)不需要, 编译器会自动为其数据成员调用string和vector的复制构造函数.