1.函数传值
先声明一下函数传值是有两次传递的,先把值转换成需要的类型,再把值传递拷贝给函数内部使用,具体一点比如funcl(3),先把3构造成A对象,在把这个对象拷贝到函数里面
#include <iostream>
using namespace std;
class A
{
public:
//构造函数
A(int a = 0):_a(a)
{
cout << "A(int a)" << endl;
}
//拷贝构造函数
A(const A& aa) :_a(aa._a)
{
cout << "A(const A& aa)" << endl;
}
//赋值运算符重载
A& operator = (const A& aa)
{
cout << "A& operator = (const A& aa)" << '\n';
if (this != &aa)//判断是不是自赋值,虽然这里判断看起来没有什么用,但是养成习惯还是有必要的
{
_a = aa._a;
}
return *this;
}
~A(){}
private:
int _a;
};
void funcl( A aa)
{
}
int main()
{
A aa1 = 1;//这里注意一点,没有初始化的=和已经实例化的=调用的构造函数不同
funcl(aa1);//调用拷贝构造函数,要在一个表达式中才可以优化
funcl(3);//优化成调用一次构造函数,正常流程是先把3构造成A类,再调用拷贝构造传入实参
funcl(A(3));//优化成调用一次构造函数,正常流程也是先把3构造成A类,再调用拷贝构造传入实参
return 0;
}
2.传引用传参
为什么在传参的时候最好使用【传引用传参】,原因就是在于可以减少拷贝,提高程序运行效率
通过调式可以发现,funcl_1(aa2)无论是【构造】还是【拷贝构造】,都不会去调用,这是为什么呢?
原因就在于这里使用的是引用接收,那么形参部分的aa
就是aa1
的别名,无需构造产生一个新的对象,也不用去拷贝产生一个,直接用形参部分这个就可以了,现在知道引用传参的好处了吧
void funcl_1(const A& aa)
{
;
}
A aa2 = 1;
funcl_1(aa2);//直接引用形参,没有额外构造花费
funcl_1(3);//调用构造函数,临时对象要使用const,否则就会权限放大
funcl_1(A(3));//调用构造函数
3.传值返回
如果没有接收返回值那么就只有一次拷贝,如果返回值有接收(指的是没有实例的初始化,已经实例化的会调用赋值重载)还是调用一次拷贝构造,这是因为拷贝+拷贝,编译器会优化成一次拷贝
如果已经被实例化,例如下面的d,会调用赋值重载,拷贝和赋值重载并不能被优化成一次调用,所以尽量还是用初始化的方式接受返回值,以便编译器可以自动优化
A funcl_2()
{
A aa;
return aa;
}
funcl_2();//return返回时调用拷贝构造函数,析构分别在函数内一次,在函数外一次,如果没有接受返回值则立即析构
A c = funcl_2();//接受返回值,按理来说会产生第二次拷贝,但是编译器会优化成一次拷贝构造,
A d;
d = funcl_2();//d已经被实例化,所以会调用赋值重载,且不会和前面的拷贝构造一起优化
4.传引用返回
返回引用跟传入引用相似,因为是返回的一个对象的引用,所以不需要调用拷贝构造,直接返回临时对象,但是这个临时对象在返回引用的时候会自动销毁,以至于传引用其实相当于什么都没有传递,所以要判断是否是临时对象的引用返回
A& funcl_3()
{
A aa(0);
return aa;
}
funcl_3();//没有调用拷贝构造
A f = funcl_3();//这里虽然调用了一次拷贝构造,但是是对临时对象的拷贝,没有用,所以内部数据还是随机值,这里用值返回可以解决
5.匿名对象返回
匿名对象那肯定是一个临时对象了,所以这里用的是返回值,而不是引用,
A funcl_4()
{
return A();
}
funcl_4();//构造+拷贝构造优化成了直接构造
A aa4 = funcl_4();//构造+拷贝构造+拷贝构造优化成了直接构造,这里析构的顺序是按照栈的顺序来析构的
6.完整代码
#include <iostream>
using namespace std;
class A
{
public:
//构造函数
A(int a = 0):_a(a)
{
cout << "A(int a)" << endl;
}
//拷贝构造函数
A(const A& aa) :_a(aa._a)
{
cout << "A(const A& aa)" << endl;
}
//赋值运算符重载
A& operator = (const A& aa)
{
cout << "A& operator = (const A& aa)" << '\n';
if (this != &aa)//判断是不是自赋值,虽然这里判断看起来没有什么用,但是养成习惯还是有必要的
{
_a = aa._a;
}
return *this;
}
~A(){}
private:
int _a;
};
void funcl( A aa)
{
;
}
void funcl_1(const A& aa)
{
;
}
A funcl_2()
{
A aa;
return aa;
}
A& funcl_3()
{
A aa(0);
return aa;
}
//返回匿名对象
A funcl_4()
{
return A();
}
int main()
{
//先声明函数传值是有两次传递的,先把值转换成需要的类型,再把值传递拷贝给函数内部
//传值
A aa1 = 1;//这里注意一点,没有初始化的=和已经实例化的=调用的构造函数不同
funcl(aa1);//调用拷贝构造函数,要在一个表达式中才可以优化
funcl(3);//优化成调用一次构造函数,正常流程是先把3构造成A类,再调用拷贝构造传入实参
funcl(A(3));//优化成调用一次构造函数,正常流程也是先把3构造成A类,再调用拷贝构造传入实参
//传引用
A aa2 = 1;
funcl_1(aa2);//直接引用形参,没有额外构造花费
funcl_1(3);//调用构造函数,临时对象要使用const,否则就会权限放大
funcl_1(A(3));//调用构造函数
//在为函数传递参数的时候,尽量使用引用传参,可以减少拷贝的工作,而且有的编译器还不自带优化
//传值返回
funcl_2();//return返回时调用拷贝构造函数,析构分别在函数内一次,在函数外一次,如果没有接受返回值则立即析构
A c = funcl_2();//接受返回值,按理来说会产生第二次拷贝,但是编译器会优化成一次拷贝构造,
A d;
d = funcl_2();//d已经被实例化,所以会调用赋值重载,且不会和前面的拷贝构造一起优化
//传引用返回
funcl_3();//没有调用拷贝构造
A f = funcl_3();//这里虽然调用了一次拷贝构造,但是是对临时对象的拷贝,没有用,所以内部数据还是随机值,这里用值返回可以解决
//匿名对象返回
funcl_4();//构造+拷贝构造优化成了直接构造
A aa4 = funcl_4();//构造+拷贝构造+拷贝构造优化成了直接构造,这里析构的顺序是按照栈的顺序来析构的
return 0;
}
7.总结
- 拷贝+拷贝=拷贝
- 尽量使用const + &传参,减少拷贝的同时防止权限放大
- 接收返回值对象,尽量拷贝构造方式接收,不要赋值接收【会干扰编译器优化】
- 函数中返回对象时,尽量返回匿名对象【可以增加编译器优化】