产生临时对象的情况和性能优化

产生临时对象的情况和解决方案

1.以传值的方式给函数传递参数

执行以下案例:
传值时的赋值操作

#include <iostream>


using namespace std;

class CTempValue
{
public:
	int val1;
	int val2;

public:
	CTempValue(int v1 = 0, int v2 = 0);
	CTempValue(const CTempValue& t) : val1(t.val1), val2(t.val2)
	{
		cout << "调用了拷贝构造函数" << endl;
	}
	virtual ~CTempValue()
	{
		cout << "调用了析构函数" << endl;
	}

public:
	int Add(CTempValue tobj);
};

CTempValue::CTempValue(int v1, int v2) : val1(v1), val2(v2)
{
	cout << "调用了构造函数!" << endl;
	cout << "val1=" << v1 << endl;
	cout << "val2=" << v2 << endl;
}

int CTempValue::Add(CTempValue tobj)
{
	int tmp = tobj.val1 + tobj.val2;
	tobj.val1 = 1000;
	return tmp;
}


int main(void)
{
	CTempValue tm(10, 20);
	int Sun = tm.Add(tm);
	cout << "Sun = " << Sun << endl;
	cout << "tm.val1 = " << tm.val1 << endl;
}

运行结果:
在这里插入图片描述
结果中,调用了拷贝构造函数,其调用原因是:调用Add成员函数时把对象tm传递给了Add成员函数,此时,系统会调用拷贝构造函数创建一个新的副本tobj(成员函数Add的形参),用于在函数体Add内使用,因为这是一个副本,所以val1 = 1000的值并不会影响外界tm对象的val1值.
所以不难看出,"int CTempValue::Add(CTempValue tobj)"这行代码中的形参tobj是一个局部对象(局部变量),从程序功能的角度来讲,函数体内需要临时使用它一下,来完成一个程序的功能,求和运算,但他确实又是一个局部变量,只能在Add函数体内使用,所以从严格意义上讲,它又不能称为一个临时对象,因为真正的临时对象往往指的是真实存在,但又感觉不到的对象.
此过程中,代码生成tobj对象,调用了CTempValue类的拷贝构造函数,有了复制的动作,所以会影响到程序执行效率.
代码优化方式:
在定义和实现中,把对象修改为引用即可,修改如下.

类 CTempValue 的 Add 成员函数的声明代码为:

	int Add(CTempValue& tobj);

类 CTempValue 的 Add 成员函数的声明实现为:

int CTempValue::Add(CTempValue& tobj)
{
	int tmp = tobj.val1 + tobj.val2;
	tobj.val1 = 1000;  //此时的修改会对外界产生影响了
	return tmp;
}

运行结果:
在这里插入图片描述
结果可以发现,少了一次调用拷贝构造函数和析构函数,提示了效率,而且引用修改val1影响了函数外部val1的值.

2.类型转换生成的临时对象/隐式类型转换以保证函数调用成功

下文的案例中的临时对象是真实存在的,但从程序代码的角度不能直接看到它.
接着上面案例:
在main主函数中写这两行

	CTempValue sum;
	sum = 1000;     //这里产生了一个临时对象

运行结果:
在这里插入图片描述
第一行代码" CTempValue sum;" 执行的是:

调用了构造函数!
val1=0
val2=0

而" sum = 1000;"产生的结果是:

调用了构造函数!
val1=1000
val2=0
调用了析构函数

从这4行运行结果可以看出,系统在执行这行语句时,调用了一次CTempValue类的构造函数和析构函数,这说明系统肯定产生了一个对象,但这个对象在哪里,通过代码完全看不到,所以这个对象是一个真正的临时对象.
产生原因:
把1000赋值给sum,sum本身是一个CempValue类型的对象,1000是一个int类型的数字,这里编译器帮助我们以1000位参数调用了CTempValue的构造函数创建了一个临时对象,因为CTempValue构造函数的两个参数都有默认值,所以这里的数字顶替了第一个参数,而第二个参数系统就用了默认值,所以从1000是可以成功创建出CtempValue对象的.
为了方便进一步观察,往CTempValue类中添加public修饰的赋值运算符的定义代码:

	CTempValue& operator = (const CTempValue& tmpv)
	{
		val1 = tmpv.val1;
		val2 = tmpv.val2;
		cout << "调用了拷贝赋值运算符!" << endl;
		return  *this;
	}

加完后运行的结果:

调用了构造函数!
val1=0
val2=0
调用了构造函数!
val1=1000
val2=0
调用了拷贝赋值运算符!
调用了析构函数
调用了析构函数

多了一句"调用了拷贝赋值运算符!".
"sum = 1000"这行代码做的内容:
(1)用1000这个数字创建了一个类型为CTempValue的临时对象.
(2)调用拷贝赋值运算符,把这个临时对象里面的各个成员值赋给了sum对象.
(3)销毁这个刚刚创建的CTempValue临时对象.

因为产生了临时对象.所以起代码优化的方式如下:

//只需要把这两行代码写成一行即可
CTempValue sum = 1000;	

运行结果:

调用了构造函数!
val1=1000
val2=0
调用了析构函数

这次运行系统没有生成临时对象
原因讨论:
针对"CTempValue sum = 1000;“这行代码,这里的”="是定义是初始化,不是赋值,这行代码的工作过程可以这样理解:在定义了sum对象,系统就为sum对象创建了预留空间,然后用1000调用构造函数来构造临时对象的时候,这种构造是为sum对象创建的预留空间里进行的,所以并没有真的产生临时对象.

3.函数返回对象的时候

同案例2,临时对象雀氏存在,但从程序代码的校对又无法直接看到它.
继续刚才的案例添加.在当前文件中添加一个普通的全局函数:

CTempValue Double(CTempValue& ts)
{
	CTempValue tmpm;   //这里会消耗一次构造和一次析构函数的调用
	tmpm.val1 = ts.val1 * 2;
	tmpm.val2 = ts.val2 * 2;
	return tmpm;
}

main主函数中添加一下测试代码

	CTempValue ts1(10, 20);
	Double(ts1); //这里方便区分效果,先不接受函数Double返回的结果

执行结果:
在这里插入图片描述
(三个析构分别是,Double中的tmpm,临时对象的和main中ts1)
可以设置断点,观察每一步所调用的函数
return tmpm;这行代码执行后,结果会多出一下两行输出结果:

调用了拷贝构造函数
调用了析构函数

通过这两行可以看出,这里肯定生成了一个临时对象.
那么这个临时对象是产生的原因是什么呢?
(1)“调用了拷贝构造函数”,认为是系统生成了一个临时CTempValue对象导致,同时,系统还把tmpm对象信息赋值给临时对象了,因为tmpm对象的生命周期马上结束,在销毁之前,系统要把tmpm的信息复制到临时对象中去.
(2)“调用了析构函数”,认为是Double函数里面的tmpm对象销毁时系统调用了CTempValue类的析构函数所致.(当前函数的生命周期结束,系统自动释放的)

继续跟程序运行,当程序流程从Double函数返回,可以看到程序有输出了一行结果信息:

调用了析构函数

这里的析构其实就是临时对象的释放.

刚刚的代码便于区分,没有接收Double函数的返回结果,现在修改一下main中的代码,用te3来接Double函数返回的值:

	CTempValue ts1(10, 20);
	CTempValue ts3 = Double(ts1);

执行结果:

在这里插入图片描述
虽然这次也调用了三次析构,但是跟上次则不同
(三个析构分别是,Double中的tmpm,main中ts1和main中的ts3)
通过断点也可以看出来,return tmpm;这行代码执行结束,流程同样是从Double返回,结果跟上次少了一个析构函数:

调用了拷贝构造函数
调用了析构函数

原因就是这里函数Double返回的临时对象实际是被直接构造到ts3里面去了,就相当于临时对象被ts3接管了或者说ts3就是这个临时对象(这是编译器内部的优化手段和措施)

也可以使用右值引用来接,因为临时对象就是一种右值;

	CTempValue ts1(10, 20);
	CTempValue&& ts3 = Double(ts1);

这样执行的结果也是一样的.

代码优化方式:
改造一下Double函数(只是当前函数使用此方法,否则只能产生临时方法去返回).

CTempValue Double(CTempValue& ts)
{
	//CTempValue tmpm;   //这里会消耗一次构造和一次析构函数的调用
	//tmpm.val1 = ts.val1 * 2;
	//tmpm.val2 = ts.val2 * 2;
	//return tmpm;

	return CTempValue(ts.val1 * 2, ts.val2 * 2);
}

运行结果:
在这里插入图片描述
省去了一次"拷贝构造函数"和"析构函数";

实验1 类的定义、对象数组的使用 1. 定义一个学生类(Student), 属性有 1)非静态属性String studentNumber 2)非静态属性String studentName 3)非静态属性int markForMaths 4)非静态属性int markForEnglish 5)非静态属性int markForScience 方法有: 1)构造方法Student(String number, String name) 2)构造方法Student() 3)String getNumber() 4)String getName() 5)void enterMarks(int markForMaths, int markForEnglish, int markForScience) 6)int getMathsMark() 7)int getEnglishMark() 8)int getScienceMark() 9)double calculateAverage() 10)String toString() 返回学生信息,包括学号、姓名、数学成绩、英语成绩、科学成绩、平均成绩。 注意:为了保证calculateAverage返回double类型,需要把三个分数的和除以3.0,而不是3. 另外,分数的初始值是什么?如果每个分数初始值为0,会造成混淆,分数为0表示还没有输入分数,还是分数确实为0?有更好的初始值吗? 编写Student类,并且编写一个StudentTest类,对Student类进行测试。 StudentTest类运行效果如下: 请输入学生学号:2011211301 请输入学生姓名:王晓 请输入学生三门课成绩(数学,英语,科学):88,79,90 学生信息如下: 学号:2011211301 姓名:王晓 数学成绩:88 英语成绩:79 科学成绩:90 平均成绩:85.66666666666667 2.定义一个StudentList类用来存储Student对象 属性有 1)Student[] list; //list存储学生对象 2)int total; //学生总人数 方法有: 1)StudentList(int length) //length是数组长度 2)boolean add(Student stu) //增加stu到数组中,成功,返回true,否则false 3)boolean remove(int no) //删除第no个数组元素,删除成功,返回true,否则false 4)boolean remove(Student number) //删除学号为number的学生,删除成功,返回true,否则false 5)boolean isEmpty() //判断数组是否为空,若是,返回true,否则false 6)Student getItem(int no) //返回第no个学生 7)Student getItem(Student number) //返回学号为number的学生,若该生不存在,返回null。 8) int getTotal() 返回学生总人数 编写StudentList类,并且编写一个StudentListTest类,对StudentList类进行测试。 StudentListTest类运行效果: 菜单如下,请输入 1~8代表您要执行的操作: 1. 增加1个学生 2. 根据学号删除学生 3. 根据位置删除学生 4. 判断是否为空 5.根据位置返回学生 6.根据学号返回学生 7. 输出全部学生信息 8.退出程序 请输入您的操作:1 请输入学生信息: 学号:2011211301 姓名:王晓 数学成绩:88 英语成绩:79 科学成绩:90 ---目前有1个学生,信息为---: 学号:2011211301 姓名:王晓 数学成绩:88 英语成绩:79 科学成绩:90 平均成绩:85.66666666666667 请输入您的操作:1 学号:2011211311 姓名:李辉 数学成绩:80 英语成绩:79 科学成绩:93 ---目前有2个学生,信息为---: 学号:2011211301 姓名:王晓 数学成绩:88 英语成绩:79 科学成绩:90 平均成绩:85.66666666666667 姓名:李辉 数学成绩:80 英语成绩:79 科学成绩:93 平均成绩:84.0 请输入您的操作:5 请输入学生位置:10 对不起,没有对应的学生 请输入您的操作:5 请输入学生位置:2 学生信息如下: 姓名:李辉 数学成绩:80 英语成绩:79 科学成绩:93 平均成绩:84.0 请输入您的操作:3 请输入要删除第几个学生:2 删除成功 ---目前有1个学生,信息为:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值