构造函数constructor 与析构函数destructor(四)

拷贝构造函数:拷贝构造函数就是在用一个类对象来创建另外一个类对象时被调用的构造函数,如果我们没有显示的提供拷贝构造函数,编译器会隐式的提供一个默认拷贝构造函数。

拷贝构造函数的定义是X(const X& ){}

1 class Test{
2    int m_i;
3 public:
4     Test(int i):m_i(i){}
5      Test(const Test& vt):m_i(vt.m_i){}//拷贝构造函数
int getI()const {return m_i;}
6 7 };
1 int main(){
2 
3    Test t(12);
4     Test t2(t);//调用拷贝构造函数来初始化t2对象
5     Test t3=t;//等价于t3(t),也是调用拷贝构造函数
6     return 07 }

记住拷贝构造函数的只有一个参数,并且这个参数是类类型的const引用,参数不能是普通的值传递,必须是引用。原因有二

一:如果参数是const Test vt,那么实参在传递给形参的时候,还是会给形参创建对象,分配内存。这时把实参传递给形参,仍然是是用类对象初始化类对象,还是会调用拷贝构造函数,就会形成递归调用。

二:如果传递引用,实际上是传递的一个地址,不会创建新的对象,而拷贝时也只是拷贝的一个地址大小的空间,会增加程序的效率。

当函数的形参是类的对象,调用函数时,进行形参与实参结合时使用。这时要在内存新建立一个局部对象,并把实参拷贝到新的对象中。理所当然也调用拷贝构造函数。

1 void fun(Test vt){
2 
3     cout<<vt.,getI()<<endl;
4 
5 }

这个函数的参数是类的对象,会调用拷贝构造函数的

1 int main(){
2 
3     Test t(12);
4     Test t2 = t;//调用一次拷贝构造函数
5     Test t3(t);//调用一次拷贝构造函数
6     fun(t);//调用拷贝构造函数
7     return 0;
8 }

fun的参数变成对象的引用时,就不会再调用拷贝构造函数了。

返回值为Const 引用 以及类对象的区别

当函数的返回值是类对象,函数执行完成,返回到调用者时也会调用拷贝构造函数。理由也是要建立一个临时对象中,从函数返回的对象来初始化产生的临时对象。

 1 //test.h
 2 #ifndef TEST_H
 3 #define TEST_H
 4 class Test
 5 {
 6     int m_i;
 7 public:
 8     Test(int i=0);
 9     Test(const Test& vt);
10     ~Test();
11     int getI()const;
12 };
13 #endif //TEST_H
14 
15 
16 //test.cpp
17 #include "Test.h"
18 #include<iostream>
19 using std::cout;
20 using std::endl;
21 
22 Test::Test(int i) :m_i(i)
23 {
24     cout << "default constructor" << endl;
25 }
26 
27 Test::Test(const Test& vt):m_i(vt.m_i){
28     cout << "copy constructor" << endl;
29 }
30 
31 Test::~Test()
32 {
33 }
34 
35 int Test::getI()const{
36     return m_i;
37 }
38 
39 //demo.cpp
40 
41 #include<iostream>
42 #include"Test.h"
43 using std::endl;
44 using std::cout;
45 void fun1(Test& vt){
46 
47     cout << "m_i=" << vt.getI() << endl;
48 }
49 void fun2(Test vt){
50     cout << "m_i=" << vt.getI() << endl;
51 }
52 
53 Test fun3(const Test& vt){
54     return vt;
55 }
56 int main(){
57 
58     Test t;
59     fun3(t);//因为fun3函数的参数是类的引用,因此不用产生临时对象。但是返回值类型为类对象会产生临时对象,
60            // 所以要调用一个拷贝构造函数,把从函数返回的值,拷贝到生成的临时对象中
61     return 0;
62 }

我们还可以在析构函数中添加代码,来测试临时对象时什么时候释放的。

在析构函数~Test()中添加打印代码,main函数如下所示:

1 int main(){
2 
3     Test t;
4     fun3(t);//因为fun3函数的参数是类的引用,因此不用产生临时对象。但是返回值类型为类对象会产生临时对象,
5            // 所以要调用一个拷贝构造函数,把从函数返回的值,拷贝到生成的临时对象中
6     cout << "........." << endl;
7     return 0;
8 }

我们可以看到,临时对象产生后,如果没有别的对象接受,马上就销毁。如果有别的对象接受,再复制完成后销毁。但是如果mia函数中添加一句如下的代码

1 int main(){
2 
3     Test t;
4     Test t2=fun3(t);//因为fun3函数的参数是类的引用,因此不用产生临时对象。但是返回值类型为类对象会产生临时对象,
5            // 所以要调用一个拷贝构造函数,把从函数返回的值,拷贝到生成的临时对象中
6     cout << "........." << endl;
7     return 0;
8 }

此时临时对象被Test t2接受,就没有马上释放,而是当t2的生命周期完时释放。

现在增加一个函数fun4,它的返回值类型和传递的参数类型都是类的引用

1 const Test& fun4(const Test& vt){
2      return vt;
3 }

然后把main的代码修改成如下的;

1 int main(){
2 
3     Test t;//创建t对象,调用默认构造函数
4     const Test& t1=fun4(t);//传参,和返回值都没有产生临时对象。
5     cout << "........." << endl;
6     return 0;
7 }

运行结果

我们可以看到,当返回值为类的引用时,并不会产生临时对象。这是一个非常好的改变程序效率的方法。

所以从上面的一系列代码中,我们可以看到,传参和返回值类型会影响程序的效率,因为传参类型是值类型的话,会产生临时对象,临时对象的产生,释放,拷贝,都会产生消耗,从而导致程序效率降低。

原文地址:https://www.cnblogs.com/cplinux/p/5618785.html