NIUHE

日々私たちが过ごしている日常というのは、実は奇迹の连続なのかもしれんな

C++关于对象传值与传引用的总结

传值VS传引用

C++在传递对象类型的参数时候有两种情况:

  • 如果是引用就相当于直接把这个对象本身传递过去了
  • 若是传值则需在进入函数之前通过拷贝构造函数建立一个临时对象(如果没有自己写拷贝构造函数则是浅拷贝),然后再离开函数之后这个临时对象会自动销毁(调用了析构函数

深拷贝VS浅拷贝

浅拷贝即直接复制对象里的字段 ##### 深拷贝是如果该字段不是指针则直接复制,若是指针则新申请一块内存空间复制指针指向的内容

不要在拷贝构造函数里修改源对象

这点我觉得尤其重要。 之所以这么说是因为拷贝构造函数的调用不是那么可控的,不像普通函数一样,只要你不主动调用就不会执行。拷贝构造函数很可能在你不知道的地方会被调用,比如上述函数传值。若是在调用拷贝构造函数的时候改变了源对象很可能会造成意想不到的结果,除非你考虑得很周全,确认所以会调用拷贝构造函数的地方都需要改变源对象。 所以推荐写拷贝构造函数的时候加上const

1
Object(const Object& obj);

拷贝构造函数的调用

拷贝构造函数不是所有对象的赋值都会调用,只有再创建对象的时候赋值才会调用!!!

比如:

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

A(A& a){
cout << "Copy" << endl;
}
};

int main()
{
A a; // A!
A b = a; // Copy
A c; // A!
c = a; // 不输出
return 0;
}

上述代码输出

1
2
3
A!
Copy
A!

可见只会在A b = a这句话的时候调用拷贝构造函数,而下面的c = 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
30
31
32
33
34
class A
{
public:
A(){
cout << "A!" << endl;
}

A(A& a){
cout << "Copy" << endl;
}
};

class B
{
public:
B(){
cout << "B!" << endl;
}
B(const A& a){
cout << "B(A)!" << endl;
this->a = a;
}

A a;
};

int main()
{
A a; // A!
B b1; // A! B!
b1.a = a; //
B b2(a); // A! B(A)!
return 0;
}

程序输出如下:

1
2
3
4
5
A!
A!
B!
A!
B(A)!

我们来分析一下: 执行A a调用构造函数输出A!

继续执行B b1先调用A的构造函数再调用B的构造函数输出A! B!

继续执行b1.a = a给b1里的a赋值,这句话没有输出,也就是说没有调用A的拷贝构造函数,这是一个浅拷贝

继续执行B b2(a),调用B的有参构造函数并在构造函数里给a赋值,输出A! B(A)!,没有输出Copy,说明也没有调用A的拷贝构造函数,也是一个浅拷贝

那如果A中有复杂的指针类型字段,我想执行深拷贝该怎么办呢?

我只想到一个不是很聪明的解决办法: 可以用先通过拷贝构造函数构造一个A对象(深拷贝),再把它的值赋给B里的A对象(浅拷贝),具体代码如下:

1
2
3
4
5
6
7
8
9
10
int main()
{
A a; // A!

A* t = new A(a); // Copy
B b; // A! B!
b.a = *t;

return 0;
}

输出:

1
2
3
4
A!
Copy
A!
B!

这样做可以使b.a达到深拷贝的目的,即b.aa里的指针指向不同的内存地址,而地址上存储的值是一样的。之所以用new一个指针而不是直接创建变量是因为new出的空间可以存活整个程序运行期间存活,而直接创建变量的话可能会因为函数返回而消失。这里的t只是一个中介,之后也不要对其进行操作,会影响到b.a里的指针字段指向的值。

Powered by Hexo and Theme by Hacker
© 2019 NIUHE