对三,王炸:

赋值的本质,是将变量传递给一个匿名临时变量,之后再传递给另一个变量。


  •  匿名临时对象:
#include <iostream>
using namespace std; class A {
public:
A() {
cout << "构造函数:" << this << endl;
}
A(const A &a) {
cout << "拷贝构造函数:" << this << endl;
}
~A() {
cout << "析构函数:" << this << endl;
}
}; A f() {
A a;
return a;
}
int main() {
A a = f();
return ;
}

首先要知道赋值的时候回调用拷贝构造函数,初始化的时候调用构造函数:

执行 return a; 产生了匿名临时对象 F903,在给a(外面的)的赋值之前,销毁局部对象F803,F903赋值给了外面的a,程序执行结束时销毁。

C/C++——赋值理解(匿名临时对象)-LMLPHP

A()用来创建匿名对象,理论上这也是应该调用拷贝构造函数的,但事实上,编译器会对此进行优化,变成A a;

C/C++——赋值理解(匿名临时对象)-LMLPHP

 那么怎样能避免产生临时对象呢?

直接赋值是不可避免的,但是作为函数返回值时是可以避免的:

当返回类型为引用是,在内存中不产生返回值的副本(注意:不能返回局部变量的引用,局部变量会在函数之前销毁。)

对于这个例子,左边的return ,实际上是调用拷贝构造函数把该对象的值拷入临时对象,再进行return,
return完毕临时对象即刻销毁。对于一般变量亦是如此,只是不调用构造函数。

C/C++——赋值理解(匿名临时对象)-LMLPHP

注意:
  • 不要返回函数内部new分配的内存的引用:

正常写法,无内存泄漏。

C/C++——赋值理解(匿名临时对象)-LMLPHP

被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,显然new生成的这块内存将无法释放。

(和上面说的不矛盾,这里直接把*s返回了,不再需要额外的一个临时变量了,但是外面的string s; 已经在栈中分配内存了。)

C/C++——赋值理解(匿名临时对象)-LMLPHP

想要释放掉这片内存,只能这样:

C/C++——赋值理解(匿名临时对象)-LMLPHP

所以说,并不是说返回函数内存new分配的内存的引用或指针是非法的,只是说如果要返回,必须要十分注意,因为很有可能造成内存泄露。
所以一般不提倡返回函数内存new分配的内存的引用或指针。

  •  强制类型转化:

C++属于强类型语言,只要类型不一样,就不能赋值。

但是这里是可以赋值当场打脸,也是因为出现了一个匿名临时对象作为隐式转换的过渡桥梁。

C/C++——赋值理解(匿名临时对象)-LMLPHP    C/C++——赋值理解(匿名临时对象)-LMLPHP

注意第二张图:

这里并不是&b开辟了新的空间,引用的就是转换的临时变量

(int &b = a;是报错的)可见临时变量的常量性,const才能引用。

下面这样的隐式转换看似好像是错的。

C/C++——赋值理解(匿名临时对象)-LMLPHP

#include <iostream>
using namespace std; class A { public:
int x;
A(int num) {
cout << "构造函数:" << this << endl;
x = num;
}
A(const A &a) {
cout << "拷贝构造函数:" << this << endl;
}
~A() {
cout << "析构函数:" << this << endl;
}
}; int main() {
A a = ;
cout << a.x << endl;
return ;
}

两个不同类型能否进行赋值操作,在于能否找到一个中间桥梁,这里的赋值寻找到了构造函数A(int num){};(重载 ‘=’ 运算符也是可以实现的) 所以可以成功,并且a成功实例化。

在构造函数前加上explicit关键字,则禁止 类似这样 不应该允许的经过转换构造函数进行的隐式转换的发生。

加上一个operator int(),反过来也是可以实现的:

C/C++——赋值理解(匿名临时对象)-LMLPHP


05-11 20:14