CPP学习之——按值传递对象(9.10)

一 概述

从前面几节我们了解了按值传递与按址传递的区别,按址传递可以修改原始变量的值,按值传递由于是传递的原始变量的副本,因此它不会修改原始变量的值。

二 概念

  • 假如仅仅是传递变量的话,采用指针或者引用这种按址传递方式的优势不明显,但是假如是传递较大对象的话,这种优势是相当明显的
  • 这是因为,按值传递在向函数传递一个对象时,会像传递变量那样建立一个该对象的拷贝,而从函数返回一个对象时,也要建立这个被返回的对象的拷贝
  • 这样假如该对象的数据非常多时,这种拷贝带来的内存开销是相当可观的。比如说该对象拥有1000多个double型成员变量,每个double型变量占据8个字节,1000个就要占据8000个字节,每次通过值传递的方式给函数传递该对象,都要在栈中复制该对象,占用8000个字节的栈内空间,而返回该对象,又要在栈中复制一次,这样就又要占用8000个字节的内存空间。我们知道栈的内存只有2M大小,8000个字节占用8k,那么斤斤传递该对象就占用栈内16k字节的空间。并且别的对象要访问该对象的8000个数据成员的时候,也要同样采取复制的方式,那么系统的开销也无法估算了。
  • 然后,按值传递所付出的开销远不止如此,由于在传递过程中需要复制对象,因此会默认调用复制构造函数,该函数的作用就是创建某个对象的临时副本。关于复制构造函数,将会在深入函数中做进一步讲解,这里你只需知道,只要在栈中创建临时拷贝都会自动调用复制构造函数即可。
  • 而当函数返回时,传递该对象时创建的该对象时创建的该对象的副本会被删除,这时候又会自动调用该对象的析构函数来释放内存。假设返回的任然是该对象,并且仍采用按值传递的方式,那么就又会调用复制构造函数建立一个该对象的临时副本,当该值被成功返回给调用程序后,然后再调用该对象的析构函数删除临时拷贝并释放内存
  • 我们看到复制构造函数和析构函数一连被执行了两次,这无疑会增加系统的开销。我们用一个实例来演示一下按值传递一个对象的复制与删除过程

三 代码及输出

3.1 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<iostream>
using namespace std;
class A {
public:
A() {cout << "执行构造函数,创建一个对象\n";}
A(A&) {cout << "执行复制构造函数,创建该对象的副本\n";}
~A() {cout << "执行析构函数,删除该对象\n";}
};
A func(A one)
{
return one;
}
int main()
{
A a;
func(a);
return 0;
}

3.2 输出结果

1
2
3
4
5
6
执行构造函数,创建一个对象
执行复制构造函数,创建该对象的副本
执行复制构造函数,创建该对象的副本
执行析构函数,删除该对象
执行析构函数,删除该对象
执行析构函数,删除该对象

3.3 说明

  • A a;该行定义了一个类A的对象a,创建一个对象会自动调用构造函数,由于构造函数中加入一条输出信息,所以我们看到输出"执行构造函数创建一个对象"
  • func函数:这时会自动调用复制构造函数创建对象a的一个副本,接下来将对象a按值传递到func函数中,所以我们看到输出了"执行复制构造函数创建该对象的副本",以证明复制构造函数被调用
  • 当我们将这个副本传递到func函数中,这样程序转到这一行来执行,func函数又将接收到的副本返回了,由于返回方式也是按值返回,所以又会调用复制构造函数,再次创建一个返回值one的副本,所以我们看到输出的第三行还是复制构造函数调用的信息
  • 由于func函数的返回值没有赋给任何对象,因此这个返回值的临时对象也就被丢弃了,这时自动调用析构函数来释放这个临时对象所占用的内存

3.4 总结

通过这个程序我们就可以观察到,将一个对象按值传递给一个函数,会调用两次复制构造函数和两次析构函数,这样系统的开销是很大的。