上次说的三种参数传递方式,对于一个数据来说没太大的差别,要是换做是一个具有n个数据成员的对象,那在内存中就需要建立n个副本,这种开销就大了去了,另外的原因如下:
1、对象的传递
对象作为参数传递一次,就需要调用复制构造函数,为对象在内存中创立一个副本,生命结束后,又需要调用析构函数销毁对象,例如:
#include <iostream>
using namespace std;
class A
{
public:
A(){cout<<"构造函数执行创立对象"<<endl;}
~A(){cout<<"析构函数执行销毁对象"<<endl;}
A(A&){cout<<"复制构造函数执行创立对象副本"<<endl;}
};
A fun(A a)
{
return a;
}
int main()
{
A a;
fun(a);
return 0;
}
输出结果:
分析:
1)当创建一个对象a时,调用复制构造函数A()
2)函数fun(a)把对象a作为参数传递给fun函数时,由于是按值传递,会调用复制构造函数创建一个副本
3)函数fun中把a对象a返回时又传递了一次,因此又调用复制构造函数创建第二个副本
4)由于以上加起来在内存中创建了3个同样的对象a,因此调用3次析构函数销毁对象。
2、按地址和引用传递对象
方法很简单,把主函数中fun()的参数改为“&a”,在fun函数定义的开头将函数的接收参数和返回参数都设为指针,即加“*”,即为按地址传递;
把fun函数定义的开头将函数的接收参数和返回参数都设为引用型,即加“&”,其他结构不变,即为按引用传递;
输出结果:
分析:
可见对于按地址传递和按引用传递的机制来说,对象本身的值并没有传递,因此没有调用复制构造函数,为系统节约了一大笔开销。
讨论:对于按地址传递,我们可以把接收参数、返回参数和指向参数的指针都设为const值,起到保护作用,即第10行代码替换为:
const A *const fun(A *const a)
这样保证了函数接受和返回的对象都是不可修改的,同时指向返回值的指针也是不可修改的。
3、指针和引用的区别
1)指针可以为空,引用不能为空
2)指针可以被赋值,引用只能被初始化
3)在堆中创建内存区域时必须用指针来指向它,但可以为该指针取一个外号
4)指针和引用可以混合使用,比如下面这个蛋疼的代码:
int *&p=new int
实际的指针就是&p,只不过p本身又是指针的一个外号而已。