C++对象的优化
大约 8 分钟
C++对象的优化
1、对象使用过程中背后调用了哪些方法?
了解构造和析构的调用,以及调用顺序
用临时对象生成新对象
class Test
{
public:
Test(int a = 10) :ma(a)
{ cout << "Test(int)" << endl; }
~Test()
{ cout << "~Test()" << endl; }
Test(const Test &t) :ma(t.ma)
{ cout << "Test(const Test&)" << endl; }
Test& operator=(const Test &t)
{
cout << "operator=" << endl;
ma = t.ma;
return *this;
}
private:
int ma;
};
int main()
{
Test t1;//调用构造函数
Test t2(t1);//调用拷贝构造函数
Test t3 = t1;//调用拷贝构造函数,因为t3还没有生成
//Test(20) 显示生成临时对象,是没有名字的,所以其生存周期:所在的语句
//语句结束,临时对象就析构了
/*
C++编译器对于对象构造的优化:用临时对象生成新对象的时候,临时对象
就不产生了,直接构造新对象就可以了
*/
Test t4 = Test(20);//和Test t4(20);没有区别的!
cout << "--------------" << endl;
return 0;
}
C++编译器对于对象构造的优化:
用临时对象给已存在对象赋值
int main()
{
Test t1;//调用构造函数
Test t2(t1);//调用拷贝构造函数
Test t3 = t1;//调用拷贝构造函数,因为t3还没有生成
//Test(20) 显示生成临时对象,是没有名字的,所以其生存周期:所在的语句
//语句结束,临时对象就析构了
/*
C++编译器对于对象构造的优化:用临时对象生成新对象的时候,临时对象
就不产生了,直接构造新对象就可以了
*/
Test t4 = Test(20);//和Test t4(20);没有区别的!
cout << "--------------" << endl;
//t4.operator=(t2)
t4 = t2;//调用赋值重载函数,因为t4原本已存在
//Test(30)显式生成临时对象
//t4原本已存在,所以不是构造,这个临时对象肯定要构造生成的
//临时对象生成后,给t4赋值
//出语句后,临时对象析构
//t4.operator=(const Test &t)
t4 = Test(30);
cout << "--------------" << endl;
return 0;
}
,所以不是构造,,给t4赋值出语句后,临时对象析构
显示生成临时对象和隐式生成临时对象
int main()
{
Test t1;
Test t2(t1);
Test t3 = t1;
Test t4 = Test(20);
cout << "--------------" << endl;
t4 = t2;//t4调用赋值重载函数
t4 = Test(30);
//显式生成临时对象,赋值给t4,出语句后,临时对象析构
t4 = (Test)30;
/*
把30强转成Test类型int->Test(int)
把其他类型转成类类型的时候,编译器就看这个类类型 有没有合适的构造函数
把整型转成Test,就看这个类类型有没有带int类型参数的构造函数 ,
有,就可以显式生成临时对象,然后赋值给t4
出语句后,临时对象析构
*/
//隐式生成临时对象,然后赋值给t4,出语句后,临时对象析构
t4 = 30;
//把整型转成Test,Test(30) int->Test(int) char*->Test(char*)
cout << "--------------" << endl;
return 0;
}
把转成的时候,编译器就看这个类类型 有没有合适的
临时对象的指针和引用
- 指针指向临时对象,出了语句,;
- 引用指向临时对象,出了语句,。引用相当于给临时对象起了一个名字。
int main()
{
Test t1;
Test t2(t1);
Test t3 = t1;
Test t4 = Test(20);
cout << "--------------" << endl;
t4 = t2;
t4 = Test(30);
t4 = (Test)30;
t4 = 30;
cout << "--------------" << endl;
Test* p = &Test(40);//指针指向临时对象,这个临时对象肯定是要生成的
//然后p指向这个临时对象的地址
//出语句后,临时对象析构
//此时p指向的是一个已经析构的临时对象,p相当于野指针了
const Test& ref = Test(50);//引用一个临时对象,这个临时对象也是要生成的
//出语句后,临时对象不析构,因为引用相当于是别名,临时对象出语句析构是因为没有名字
//用引用变量引用临时对象是安全的,临时对象就是有名字了,临时对象的生存周期就变成引用变量的
//生存周期了。引用变量是这个函数的局部变量,return完,这个临时对象才析构
cout << "--------------" << endl;
return 0;
}
示例二
class Test
{
public:
//因为a,b有默认值,所以构造有3种方式:
//Test() Test(10) Test(10, 10)
Test(int a = 5, int b = 5)//构造函数
:ma(a), mb(b)
{
cout << "Test(int, int)" << endl;
}
~Test()//析构函数
{
cout << "~Test()" << endl;
}
Test(const Test& src)//拷贝构造函数
:ma(src.ma), mb(src.mb)
{
cout << "Test(const Test&)" << endl;
}
void operator=(const Test& src)//赋值重载函数
{
ma = src.ma;
mb = src.mb;
cout << "operator=" << endl;
}
private:
int ma;
int mb;
};
//对象的构造顺序标识:1,2,3...14
Test t1(10, 10);//1.Test(int, int)
int main()
{
Test t2(20, 20);//3.Test(int, int)
Test t3 = t2;//4.Test(const Test&)
//第一次运行到它才初始化的,static Test t4(30, 30);
static Test t4 = Test(30, 30);//5.Test(int, int)
t2 = Test(40, 40);//6.Test(int, int) operator= 出语句调用 ~Test()
//(50,50)是逗号表达式,(表达式1,表达式2,表达式n)
//(50,50)的最后的结果是最后一个表达式n的结果 50
//(50, 50) = (Test)50; Test(int)
t2 = (Test)(50, 50);//7.Test(int,int) operator= 出语句调用~Test()
t2 = 60;//Test(int) 8.Test(int,int) operator=出语句调用~Test()
Test* p1 = new Test(70, 70);//9. Test(int,int) 要调用delete才析构对象
Test* p2 = new Test[2];//10. Test(int,int) Test(int,int) 要调用delete[]才析构对象
Test* p3 = &Test(80, 80);//11. Test(int,int) 出语句调用~Test()
const Test& p4 = Test(90, 90);//12. Test(int,int)
delete p1;//13.~Test()
delete[]p2;//14. ~Test() ~Test()
}
Test t5(100, 100);//2.Test(int, int)
t2 = (Test)(50, 50);
由于将(50,50)等价于50,所以最后调用Test(50)
注意
Test t5(100, 100);//2.Test(int, int)
在mian()
前,因为****析构顺序:
p1->p2(析构2次)->p4->t3->t2->t4->t5->t1
2、函数调用过程中对象被后续调用的方法太多
#include <iostream>
using namespace std;
class Test
{
public:
//有默认值,可以有2种构造方式:Test() Test(20)
Test(int data = 10) :ma(data)
{
cout << "Test(int)" << endl;
}
~Test()
{
cout << "~Test()" << endl;
}
Test(const Test& t) :ma(t.ma)
{
cout << "Test(const Test&)" << endl;
}
void operator=(const Test& t)
{
cout << "operator=" << endl;
ma = t.ma;
}
int getData()const { return ma; }
private:
int ma;
};
Test GetObject(Test t) //不能返回局部的或临时的对象的指针或引用
{
int val = t.getData();
Test tmp(val);
return tmp;
}
int main()
{
Test t1;//1、调用带整型参数的构造函数
Test t2;//2、调用带整型参数的构造函数
t2 = GetObject(t1);//函数调用,实参传递给形参,是初始化还是赋值?
//当然是初始化,对象初始化是调用构造函数,赋值是两个对象都存在 调用左边对象的=重载
//t1是已经构造好的Test对象,而形参是t是正在定义的Test对象
//3、调用Test(const Test&) 拿t1拷贝构造形参t
//4、调用Test(int)的构造,构造tmp对象 然后return tmp;tmp是不能直接给t2赋值的
//因为tmp和t2是两个不同函数栈帧上的对象,是不能直接进行赋值的 GetObject函数完成调用时
//tmp对象作为局部对象就析构了 ,为了把返回值带出来, 在return tmp;这里,首先要在main函数栈帧
//上构建一个临时对象,目的就是把tmp对象带出来,
//5、调用 Test(const Test&),tmp拷贝构造main函数栈帧上的临时对象
//6、出 GetObject作用域,tmp析构
//7、形参t对象析构
//8、operator =,把main函数刚才构建的临时对象赋值给t2,临时对象没名字,出了语句就要析构
//9、把main函数刚才构建的临时对象析构
//10、main函数结束,t2析构
//11、t1析构
return 0;
}
3、总结三条对象优化原则
函数参数传递过程中,对象优先按,这样可以!
函数返回对象的时候,应该优先返回一个,而
收返回值是对象的函数调用的时候,优先按的方式接收,
Test GetObject(Test& t) //引用传递
{
int val = t.getData();
//Test tmp(val);
//return tmp;
//返回临时对象
return Test(val);
}
int main()
{
Test t1;
Test t2 = GetObject(t1);
//t2 = GetObject(t1);
return 0;
}