拷贝构造函数
通过学习构造函数,初学者已经了解到可以通过编写带参数的构造函数完成对象属性的初始化。除此之外,我们可能还希望能根据已定义好的类对象完成新对象的属性初始化,就像定义了int a = 3后,能够定义新变量int b = a,用a初始化同类型的新变量b。对于类来说,就是希望能够定义一个以类对象作为参数的构造函数,这就是本节要介绍的拷贝构造函数。
拷贝构造函数是使用类对象的引用作为参数的构造函数,它能够将参数的属性值拷贝给新的对象,完成新对象的初始化。拷贝构造函数的定义形式如下所示:
class 类名
{
public:
构造函数名称(类名 &变量名)
{
函数体
}
…
};
下面定义一个带有拷贝构造函数的汽车类Car,代码如下所示:
class Car{
public:
Car(string con_carname, int con_seats) //带参数的构造函数
{
m_strCarName = con_carname;
m_nSeats = con_seats;
}
Car(Car &con_refcar) //拷贝构造函数
{
m_strCarName = con_refcar.m_strCarName;
m_nSeats = con_refcar.m_nSeats;
}
private:
string m_strCarName;
int m_nSeats;
};
从上述的代码可以看出,Car类有两个构造函数,Car(string con_carname, int con_seats)是大家熟悉的带参数的构造函数,Car(Car &con_refcar)是拷贝构造函数,参数是类对象引用。拷贝构造函数是构造函数的重载,拷贝构造函数中通过已有对象为新对象的数据成员提供了初值。
通常在三种情况下会自动调用拷贝构造函数,下面分别进行介绍。
1、使用一个对象初始化另一个对象
下面的案例中,使用拷贝构造函数完成了对象的创建和初始化,如例1所示。
例1
1 #include <iostream>
2 #include <string>
3 using namespace std;
4
5 class Car
6 {
7 public:
8 Car(string con_carname, int con_seats); //带参数的构造函数
9 Car(Car &refcar); //拷贝构造函数
10 ~Car(); //析构函数
11 private:
12 string m_strCarName;
13 int m_nSeats;
14 };
15
16 Car::Car(string con_carname, int con_seats) //定义带参数的构造函数
17 {
18 cout << "Car constructor with params!" << endl;
19 m_strCarName = con_carname;
20 m_nSeats = con_seats;
21 }
22
23 Car::Car(Car &con_refcar) //定义拷贝构造函数
24 {
25 cout << "Car cp constructor!" << endl;
26 m_strCarName = con_refcar.m_strCarName;
27 m_nSeats = con_refcar.m_nSeats;
28 }
29 Car::~Car() //定义析构函数
30 {
31 static int i = 0;
32 cout << "destructor is called!" << endl;
33 if (i == 1)
34 system("pause");
35 i++;
36 }
37 int main()
38 {
39 Car mynewcar("my first car", 4); //定义类对象
40 Car myseccar(mynewcar); //调用拷贝构造函数定义类对象
41 return 0;
42 }
运行结果如图1所示。
图1 例1运行结果
例1中Car中的成员函数都是在类中声明,类外定义,其中带参数的构造函数在第16-21行定义,拷贝构造函数在第23-28行定义。运行结果显示,main()函数第39行调用带参数的构造函数创建对象mynewcar,第40行通过拷贝构造函数根据已创建好的对象初始化了新对象myseccar,对象生命期结束时调用了析构函数。
2、对象作为实参传递给函数参数
当函数的实参为对象时,函数调用拷贝构造函数将实参传递给形参,如例2所示。
例2
1 #include <iostream>
2 #include <string>
3 using namespace std;
4
5 class Car
6 {
7 public:
8 Car(string con_carname, int con_seats); //带参数的构造函数
9 Car(Car &refcar); //拷贝构造函数
10 ~Car(); //析构函数
11 void print(); //打印属性值的函数
12 private:
13 string m_strCarName;
14 int m_nSeats;
15 };
16
17 Car::Car(string con_carname, int con_seats) //定义带参数的构造函数
18 {
19 cout << "Car constructor with params!" << endl;
20 m_strCarName = con_carname;
21 m_nSeats = con_seats;
22 }
23
24 Car::Car(Car &con_refcar) //定义拷贝构造函数
25 {
26 cout << "Car cp constructor!" << endl;
27 m_strCarName = con_refcar.m_strCarName;
28 m_nSeats = con_refcar.m_nSeats;
29 }
30 Car::~Car() //定义析构函数
31 {
32 static int i = 0;
33 cout << "destructor is called!" << endl;
34 if (i == 1)
35 system("pause");
36 i++;
37 }
38 void Car::print() //定义pirnt()函数,显示属性值
39 {
40 cout << "carname: " << m_strCarName << ", "
41 << "seats: " << m_nSeats << endl;
42 }
43 //定义普通函数,显示某对象属性值,参数为类对象
44 void print_carinfo(Car carinfo)
45 {
46 carinfo.print();
47 }
48 int main()
49 {
50 Car mynewcar("my first car", 4); //定义类对象
51 //调用print_carinfo()函数显示mynewcar对象属性值,参数为对象mynewcar
52 print_carinfo(mynewcar);
53 return 0;
54 }
运行结果如图2所示。
图2 例2运行结果
从例2的运行结果看出,当参数为类对象时,会调用拷贝构造函数将实参传递给形参。代码第52行调用了print_carinfo()函数,该函数以mynewcar为实参向形参传递数据,此时调用拷贝构造函数。
3、函数返回值为类对象,创建临时对象作为返回值
当函数返回值为类对象时,将调用拷贝构造函数将返回值复制到临时对象中,用于数据传出。接下来通过一个案例来学习在这种应用场景下,拷贝构造函数的调用过程,如例3所示。
例3
1 #include <iostream>
2 #include <string>
3 using namespace std;
4
5 class Car
6 {
7 public:
8 Car();
9 Car(string con_carname, int con_seats); //带参数的构造函数
10 Car(Car &refcar); //拷贝构造函数
11 ~Car(); //析构函数
12 Car get_carinfo(); //获取对象信息的函数
13 void print();
14 private:
15 string m_strCarName;
16 int m_nSeats;
17 };
18 Car::Car() //定义无参数的构造函数
19 {
20 cout << "constructor!" << endl;
21 }
22 Car::Car(string con_carname, int con_seats) //定义带参数的构造函数
23 {
24 cout << "Car constructor with params!" << endl;
25 m_strCarName = con_carname;
26 m_nSeats = con_seats;
27 }
28
29 Car::Car(Car &con_refcar) //定义拷贝构造函数
30 {
31 cout << "Car cp constructor!" << endl;
32 m_strCarName = con_refcar.m_strCarName;
33 m_nSeats = con_refcar.m_nSeats;
34 }
35 Car::~Car() //定义析构函数
36 {
37 static int i = 0;
38 cout << "destructor is called!" << endl;
39 if (i == 3)
40 system("pause");
41 i++;
42 }
43 Car Car::get_carinfo() //定义获取对象信息的函数
44 {
45 Car tmp(m_strCarName, m_nSeats);
46
47 return tmp; //返回值为类对象
48 }
49 void Car::print() //打印对象属性值
50 {
51 cout << "carname: " << m_strCarName << ", "
52 << "seats: " << m_nSeats << endl;
53 }
54 int main()
55 {
56 Car mynewcar("my first car", 4); //通过带参数的构造函数定义类对象
57 Car ret; //通过无参数的构造函数定义类对象
58 //调用成员函数get_carinfo(),类对象接收返回值
59 ret = mynewcar.get_carinfo();
60 ret.print(); //显示属性值
61
62 return 0;
63 }
运行结果如图3所示。
图3 例3运行结果
通过例3的运行结果来分析程序执行流程。main()函数第56行通过带参数的构造函数初始化了mynewcar对象,第57行调用无参构造函数创建对象ret。第59行用ret接收get_carinfo()函数的返回值。在get_carinfo()函数中首先调用带参数的构造函数创建对象tmp,当函数返回时调用拷贝构造函数将tmp返回值复制给临时对象,然后tmp对象的生命期即将结束,通过析构函数回收资源,临时对象复制给ret后也被析构。接下来调用print()函数显示ret中的属性值,程序结束前将ret和mynewcar对象析构。
图4 调用拷贝构造函数返回函数值
至此,我们已经学习了拷贝构造函数的基本定义以及它的应用场景。如果用户没有自己定义拷贝构造函数,那么编译系统会自动提供一个默认的拷贝构造函数。默认的拷贝构造函数会将一个对象的全部数据成员赋值给另一个对象的对应数据成员。C++中把这种只对对象的数据成员进行简单赋值的操作称为“浅拷贝”,除了“浅拷贝”之外,是否还有与之对应的“深拷贝”呢,下一节将详细介绍。