一、前提了解
1、临时对象被创建的情况
- 以值的形式给函数传参(值传递):
- 参数类型是类类型时,函数调用时,这时会调用拷贝构造函数来创建一个对象,分配内存空间,利用实参的值给这个对象初始化,生成的对象可以称为实参的一个副本,那么所有在函数里的操作都是针对于这个副本的,不会影响到原参数,函数结束后,这个临时对象就会被撤销(析构函数)
class TestTemp {
public:
int data;
int id;
private:
static int count;
public:
TestTemp(int a = 0);
TestTemp(TestTemp& t) {
data = t.data;
id = count++;
cout << "拷贝构造函数" << "data= " << data << " id= " << id<< endl << endl;;
}
virtual ~TestTemp() {
cout << "析构函数" << "id= "<<id<<endl << endl;;
}
TestTemp& operator=(const TestTemp& t) {
cout << "赋值运算符重载" << endl << endl;;
data = t.data;
return *this;
}
int getObj(TestTemp t);
};
int TestTemp::count = 0;
TestTemp::TestTemp(int a){
this->data = a;
this->id = count++;
cout << "构造函数: " << "data= " << data << " id= " << id << endl<<endl;
}
int TestTemp::getObj(TestTemp t) {
int temp = t.data;
t.data = 10000;
return temp;
}
void test_06() {
TestTemp tt(10);
cout << "值传递产生临时对象测试: " << endl<<endl;
cout << "tt.getObji = " << tt.getObj(tt) << endl;
cout << "tt.data= " << tt.data << endl << endl;
}
1.tt调用构造函数
2.getObj的参数 t 调用拷贝构造函数,相当于 TestTemp t = tt;给t分配内存空间并用tt 初始化
3. t 的地址和 tt 是不一样 ,只是一个临时对象,对 tt不会造成影响;函数完毕,就会调用析构函数并释放内存
- 类型转换:
- 一般是会先定义一个对象,然后给这个对象赋值;
- 此时如果赋值的对象类型不正确,就会把这个对象当作参数传给构造函数的形参(但类型要和构造函数参数类型匹配),构造函数就会生成一个临时对象,把这个临时对象赋值给tt1,结束后就会调用析构函数
void test_06() {
cout << "类型转换产生临时对象测试: " << endl << endl;
TestTemp tt1;
tt1 = 20;
//TestTemp tt1 = 30;
//这种形式,就会直接构造,而不是赋值
}
1.tt1 调用构造函数
2. 20 类型不正确,调用构造函数,把20作为参数传进去,生成一个临时对象
3.类型正确后,调用拷贝构造函数给 tt1 赋值 ,复制完毕,临时对象就会析构释放内存
- 函数返回一个对象: 函数需要返回一个对象时,函数会在栈中创建一个临时对象又叫匿名对象(类对象还会调用拷贝构造函数)来存储函数的返回值
//类中增加一个函数
friend TestTemp fun(TestTemp& t);
//类外定义该函数
TestTemp fun(TestTemp& t) {
cout << "fun: " << endl;
t.data = t.data * 4;
cout << "fun t.data change" << endl;
return t;
}
void test_06() {
cout << "函数返回产生临时对象测试: " << endl << endl;
TestTemp tt2(40);
TestTemp tt3;
tt3 = fun(tt2);
cout<<"tt2.data= "<<tt2.data<<endl;
}
1.tt2调用构造函数,tt3调用构造函数
2.fun函数返回的是TestTemp类型的对象,相当于 TestTemp 临时 = t,此时调用拷贝构造函数(t是一个对象)
3.调用赋值运算符函数给 tt3 赋值 ,完毕后,临时对象析构释放内存
2.拷贝构造函数的参数必须是该类的引用类型
如果拷贝构造函数的参数不是一个该类的引用类型,而是诸如const T 或 T 这样的类型,那么在我们调用拷贝构造函数时,会采用传值的方式将实参的值传给形参的值,这种方式又会调用拷贝构造函数,从而造成无穷的递归调用拷贝构造函数
3.复制对象和移动对象
- 复制对象: 将B的内容复制给A
- 先创建对象A 即开辟空间
- 复制B的内容
- 上述可能会用到拷贝构造函数和赋值运算符函数
- 移动对象:
- 一个对象B被创建了有了内存空间,但B并不具有这片空间的使用权,B会将内容直接交给A来接管即移动到A【交接完成,可以把B指向内存的指针置空】
- 上述的B可以理解为 将被销毁/临时 对象,是右值
二、左值和右值
1.右值
右值:不能用取地址 & 运算符获得对象的内存地址,表达式结束后就不再存在的临时对象;位于等号右边
可以分为将亡值和纯右值:
将亡值xvalue: 与右值引用相关的表达式类型,该表达式是要被移动的对象
- 右值引用: 对一个右值的引用 (别名/绑定),一因为右值本身不具有名字,那么只能通过引用来关联它们
- Type &&引用名 = 右值表达式
- 右值引用的用途: 完成对一个将亡值的语义转移过程,使我们在复制具有大块内存空间时,可以直接使用原对象已经分配好的内存空间,进而省去重新分配内存空间的过程
纯右值: 临时值——函数返回的临时变量、字面量值、lambda表达式
为什么右值不能被取地址:
- 对于临时对象,它可以存储于寄存器,所以没办法用 取地址 运算符
- 对于常量,它可能被编码到机器指令的“立即数”中,所以没办法用 取地址 运算符
std::move:
- 并不能移动任何东西,唯一的功能将一个左值强制转化为右值引用,继而可以通过右值引用使用该值,以用于移动语义
- 实现上,std::move 等同于 强制类型转换 static_cast<T&&> (leftValue)
- 可以避免大量内存分配的高昂代价
2.左值
左值: 能用取地址&运算符获得对象的内存地址,表达式结束后该对象依然存在
- 左值可以出现在等号左边或右边
- 所有的具名变量或对象都是左值
- 左值引用: 给左值起别名,Type& 引用名 = 左值表达式,
3.总结
左值看地址,右值看内容
拷贝构造还是构造,赋值针对于一个已有的对象