总结上一篇文章的三个点:
1.实参到形参传递的初始化的过程,我们使用引用;
(const:防止实参被修改;形参可以引用常量,常引用)
2.返回的时候,返回一个正在定义的对象,而不是一个已经定义好的对象;
3.函数调用结束接收一个对象时,用正在定义的对象接收,而不是已经存在的对象。
原理:
用初始化的方式去定义一个对象,而不是改变一个已经存在的对象;
返回一个正在定义的对象,而不是已经存在的对象;
函数参数使用引用,而不是用值接收。
实现一个test类演示这三个过程:
class test{
public:
test(){ cout << "test()" << endl; }
~test(){ cout << "~test()" << endl; }
test(const test &src){ cout << "test(const test &src)" << endl;}
void operator=(const test &src){cout << "operator=(const test &src)" << endl;}
};
//这里定义个全局函数,用来演示上述三点的效率:
test fun(test src)
{
cout << "fun()" << endl;
return;
}
一、实参到形参传递的过程,使用引用接收和使用对象接收的区别:
//1.使用对象接收
void fun(test src)
{
cout << "fun()" << endl;
}
int main()
{
test a;
fun(a);
return 0;
}
执行结果:
test()
test(const test &src)
fun()
~test()
~test()
请按任意键继续. . .
//2.使用引用接收实参值
void fun(test &src)
{
cout << "fun()" << endl;
}
int main()
{
test a;
fun(a);
return 0;
}
执行结果:
test()
fun()
~test()
请按任意键继续. . .
分析:少了拷贝构造函数和临时对象的析构函数;
二、返回一个对象时,返回正在定义的对象而不是已经存在的对象:
//1.返回一个已经存在的对象
test fun(test &src)
{
cout << "fun()" << endl;
test b = src;
return b;
}
int main()
{
test a;
a = fun(a);
return 0;
}
执行结果:
test()
fun()
test(const test &src)
test(const test &src) //用返回对象b 拷贝构造主调函数上的临时内存
~test()
operator=(const test &src) //主调函数上临时内存上的对象赋值给已经存在的对象a
~test()
~test()
请按任意键继续. . .
//由于是自定义对象,所以执行函数前,就给主调函数的栈帧上开辟好了,所以返回值到主调函数的过程实际上是拷贝构造的过程;
// 2.返回一个正在定义的对象:
test fun(test &src)
{
cout << "fun()" << endl;
return test(src);
}
int main()
{
test a;
a = fun(a);
return 0;
}
执行结果:
test()
fun()
test(const test &src)
operator=(const test &src) //主调函数上临时内存上的对象赋值给已经存在的对象a
~test()
~test()
请按任意键继续. . .
分析:
由于返回值是自定义,所以返回值不会通过寄存器带回,
所以在函数调用前会在主调函数的栈帧上开辟一块内存,来接收这个返回值;
我们返回一个正在定义的对象,实际上这个临时对象不会产生
而是用生成临时对象的方式去构造主调函数内存上的这个内存,
而这个内存地址在函数压栈的时候,会告诉被调函数。
三、用一个正在定义的对象接收函数返回值,而不是已经存在的对象接收:
//1.用已经存在的对象接收返回值
test fun(test &src)
{
cout << "fun()" << endl;
return test();
}
int main()
{
test a;
test c;
c= fun(a);
return 0;
}
执行结果:
test()
test()
fun()
test()
operator=(const test &src)
~test()
~test()
~test()
请按任意键继续. . .
//2.用一个正在定义的对象来接收
test fun(test &src)
{
cout << "fun()" << endl;
return test();
}
int main()
{
test a;
test c = fun(a);
//!!!!这里实际上fun(&c , a) 传了两个参数,一个是c的地址,一个是a对象
return 0;
}
执行结果:
test()
fun()
test()
~test()
~test()
请按任意键继续. . .
分析:
此时主调函数上的临时内存也不会产生了,
他会在 用 生成retrun test(src)的这个临时对象的方式去调用构造函数对象c
当然这个retrun test(src) 临时对象也不会产生。
为什么我们在函数调用时要见到函数的声明或者定义呢:
1.实参和形参的类型和个数是否匹配;
2.函数调用点返回值的类型和声明或定义时的返回值类型是否一直;
3.根据返回值类型确定返回值的带回方式。
>4字节 :一个寄存器带回
>4 <8 :两个寄存器带回
>8 :通过在主调函数产生临时量的方式带回,压栈的时候会将这块内存的地址告诉被调函数
然后通过ebp指针偏移访问到这块临时内存。
实际上,只要是返回我们 自定义 的类型的值,都要产生临时量!!!!
不论返回值多大,都要产生临时量。
总结:
1.我们应该尽量避免临时对象的生成,更多的是用临时对象生成方式直接去构造我们的目标对象,提高效率。
2.返回一个正在定义的对象用这个临时对象,接收返回值用一个正在定义的对象接收,这样这个临时对象不会产生,主调函数上的临时内存也不会产生。
用临时对象构造同类型的正在定义的新对象时,这个临时对象不产生
将创建对象改变成“初始化”的过程,而不是“赋值“的过程;