C++复制构造函数

C++复制构造函数

图:广州天河CBD

Guderian出品

本文整理自《北京大学公开课:C++面向对象程序设计》

基本概念

  • 只有一个参数,即对同类对象的引用
  • 形如X::X(X &)X::X(const X &),二者选一,后者能以常量对象作为参数
  • 如果没有定义复制构造函数,那么编译器生成默认的复制构造函数。默认的复制构造函数完成复制功能

构造函数与复制构造函数的异同

  • 构造函数(或称无参构造函数)不一定存在,你不写构造函数,编译器就只会帮你生成一个什么也不做的无参构造函数
  • 复制构造函数一定存在,你不写复制构造函数,编译器也会帮你写好一个具有复制功能的复制构造函数
1
2
3
4
5
6
7
8
class Complex
{
private:
douebl real, imag;
};

Complex c1; //调用缺省的构造函数
Complex c2(c1); //调用缺省的复制构造函数,将c2初始化成和c1一样
  • 如果定义了自己的复制构造函数,则默认的复制构造函数不存在
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Complex
{
public:
double real, imag;
Complex() {}
Complex(const Complex &c)
{
real = c.real;
imag = c.imag;
cout << "Copy constructor called";
}
};

Complex c1;
Complex c2(c1);
//调用自己定义过的复制构造函数,输出Copy constructor called

特别强调1

注意:不允许有形如X::X(X)的复制构造函数,参数一定是引用,不能是对象

1
2
3
4
5
6
7
8
class CSample
{
CSample(CSample c)
{
//错,不允许出现这样的复制构造函数
//这样写的话,下面的内容要么变成了构造函数,要么语法是错误的
}
};

复制构造函数起作用的三种情况

(1)当用一个对象去初始化同类的另一个对象时

1
2
Complex c2(c1);
Complex c2 = c1; //初始化语句,非赋值语句,与上面写法是等价的

(2)当某函数有一个参数是类A的对象时

那么该函数被调用时,类A的复制构造函数将被调用。C++规则:形参是实参的拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class A
{
public:
A() {};
A(A &a)
{
cout << "Copy constructor called" << endl;
}
};

void Func(A a1) {}

int main()
{
A a2;
Func(a2);

retrun 0;
}
//输出:
Copy constructor called

(3)当函数的返回值是类A的对象时

那么在函数返回时,A的复制构造函数被调用,作用是初始化作为返回值的类A对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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 b(4);
return b;
}

int main()
{
cout << Func().v << endl;
//此处Func()的返回值没有名字,是一个复制构造函数初始化的对象

return 0;
}

//输出:
Copy constructor called
4

特别强调2

注意:对象间赋值并不导致复制构造函数被调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class CMyClass
{
public:
int n;
CMyClass() {};
CMyClass(CMyClass &c) { n = 2 * c.n; }
//不推荐复制构造函数这样写,此处仅用于凸显调用复制构造函数与对象间赋值的区别
};

int main()
{
CMyClass c1, c2;
c1.n = 5;
c2 = c1; //这是赋值语句
CMyClass c3(c1); //调用复制构造函数,c3并没有变得跟c1一样

cout << c2.n << ", ";
cout << c3.n << endl;

return 0;
}

//输出:
5, 10

定义函数时常量引用参数的使用

1
2
3
4
void fun(CMyClass obj)
{
cout << "fun" << endl;
}
  • 这样的函数,调用时生成形参会引发复制构造函数调用,复制一遍时间开销比较大
  • 所以可以考虑使用CMyClass & 引用类型作为参数,本质上与实参是一样的
  • 如果希望确保实参的值在函数中不应被改变,那么可以加上const关键字,这样的话如果你定义的函数中出现了改变实参的值得语句。编译器就会报错

我自己写的程序难道我自己不记得我没有改过参数吗?的确,你现在记得,明天也记得,但以后再改你的程序,就未必记得了。从代码规范的角度来讲,加上const关键字是必要的

1
2
3
4
void fun(const CMyClass &obj)
{
//如此一来,函数中任何试图改变obj值得语句都将是非法
}

思考:为什么要自己写复制构造函数?

想通这个问题是高难度操作,作者水平有限,无法清晰地回答,仅提供参考链接。

前置知识:

  • 深拷贝、浅拷贝
  • 传值、传地址、传引用

参考链接:


参考并整理自《C++面向对象设计》郭炜,北京大学,感谢原作者!编辑时有删改

本文标题:C++复制构造函数

文章作者:G-SS-Hacker

发布时间:2019年12月04日 - 20:55:34

最后更新:2019年12月07日 - 10:50:40

原始链接:https://G-SS-Hacker.github.io/C-复制构造函数/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。