目录
赋值运算符函数
赋值运算同样是一种很常见的运算,比如:
int x = 1, y = 2;
x = y;
自定义类型当然也需要这种运算,比如:
Point pt1(1, 2), pt2(3, 4);pt1 = pt2;//赋值操作
我们想上面的操作不符合构造函数(因为没有创建新的对象),也不符合拷贝构造的三种出现方式,所以是一个赋值操作。
在执行 pt1 = pt2; 该语句时, pt1 与 pt2 都存在,所以不存在对象的构造,这要与 Point pt2 =pt1; 语句区分开,这是不同的。
赋值运算符函数的形式
在上述例子中,当 = 作用于对象时,其实是把它当成一个函数来看待的。在执行 pt1 = pt2; 该语句时,需要调用的是赋值运算符函数。其形式如下:
类名& operator=(const 类名 &)
对Point类进行测试时,会发现我们不需要显式给出赋值运算符函数,就可以执行测试。这是因为如果类中没有显式定义赋值运算符函数时,编译器会自动提供一个赋值运算符函数。对于 Point 类而言,其实现如下:
Point & Point::operator=(const Point & rhs)
{
_ix = rhs._ix;
_iy = rhs._iy;
}
手动写出赋值运算符,再加上函数调用的提示语句。执行发现语句被输出,也就是说,当对象已经创建时,将另一个对象的内容复制给这个对象,会调用赋值运算符函数。
那么现在又产生了问题
首先,赋值号是一个双目运算符,如果把它视为一个函数,那么应该有两个参数。但是从赋值运算符函数的形式上看只接收了一个参数,为什么?
其次,赋值运算符函数返回类型是Point&,那么它的返回值是什么?
这也是上面代码报警告的原因。
这两个问题引出了一个重要的知识点——this指针

因为是指针常量不能指向其他对象,下面的方式是不允许的。

this指针
-
this指针的本质
this指针的本质是一个指针常量 Type* const pointer; 它储存了调用它的对象的地址,不可被修改。这样成员函数才知道自己修改的成员变量是哪个对象的。
this是一个隐藏的指针,可以在类的成员函数中使用,它可以用来指向调用对象。当一个对象的成员函数被调用时,编译器会隐式地传递该对象的地址作为 this 指针。
this指针指向本对象
-
this指针存在哪儿
编译器在生成程序时加入了获取对象首地址的相关代码,将获取的首地址存放在了寄存器中,这就是this指针。

对this取地址也是不允许的因为他是存储在寄存器中的。
-
this指针的生命周期
this 指针的生命周期开始于成员函数的执行开始。当一个非静态成员函数被调用时,this 指针被自动设置为指向调用该函数的对象实例。在成员函数执行期间,this 指针一直有效。它可以被用来访问调用对象的成员变量和成员函数。this指针的生命周期结束于成员函数的执行结束。当成员函数返回时,this指针的作用范围就结束了。
要注意,this指针的生命周期与它所指向的对象的生命周期虽然并不完全相同,但是是相关的。
this指针本身只在成员函数执行期间存在,但它指向的对象可能在成员函数执行前就已经存在,并且在成员函数执行后继续存在。
如果成员函数是通过一个已经销毁或未初始化的对象调用的,this指针将是悬挂的,它的使用将会是未定义行为。

理解以下问题:
-
对象调用函数时,是如何找到自己本对象的数据成员的? —— 通过this指针
-
this指针代表的是什么? —— 指向本对象
-
this指针在参数列表中的什么位置? —— 参数列表的第一位(默认自动加入)
-
this指针的形式是什么? —— 类名 * const this (指针常量)
Point & operator=(const Point & rhs){
this->_ix = rhs._ix;
this->_iy = rhs._iy;
cout << "Point & operator=(const Point &)" << endl;
return *this;
}
成员函数中可以加上this指针,展示本对象通过this指针找到本对象的数据成员。但是不要在参数列表中显式加上this指针,因为编译器一定会在参数列表的第一位加上this指针,如果显式再给一个,参数数量就不对了。
赋值运算符函数的定义
注意:如果对象的指针数据成员申请了堆空间,默认的赋值运算符函数就不够用了,以Computer类为例,默认的赋值运算符函数长这样
Computer & operator=(const Computer & rhs){
this->_brand = rhs._brand;
this->_price = rhs._price;
return *this;
}
这里的指针成员_brand是进行的浅拷贝,会造成问题

思考:
如果直接进行深拷贝,可行吗?

会有内存泄露,需要回收掉pc2._brand原本申请的空间
如果用delete回收掉pc2._brand原本申请的空间,再进行深拷贝,是否可行?—— 还要考虑自复制
如果没有自复制的判断,发现我们已经将原来的内容回收了,不可以进行访问了,就会出现下面这种情况,虽然编译器也不会报错。


先将原来指针指向的堆空间的内容回收,但是指针是存在的,然后利用这个指针重新申请可以空间用来存放copy的内容。
总结步骤—— 四步走(重点):
-
考虑自复制问题
-
回收左操作数原本申请的堆空间
-
深拷贝(以及其他的数据成员的复制)
-
返回*this
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#000000">Computer</span> <span style="color:#981a1a">&</span> <span style="color:#770088">operator</span><span style="color:#981a1a">=</span>(<span style="color:#770088">const</span> <span style="color:#000000">Computer</span> <span style="color:#981a1a">&</span> <span style="color:#000000">rhs</span>){
<span style="color:#770088">if</span>(<span style="color:#770088">this</span> <span style="color:#981a1a">!=</span> <span style="color:#981a1a">&</span><span style="color:#000000">rhs</span>){
<span style="color:#770088">delete</span> [] <span style="color:#000000">_brand</span>;
<span style="color:#000000">_brand</span> <span style="color:#981a1a">=</span> <span style="color:#770088">new</span> <span style="color:#008855">char</span>[<span style="color:#000000">strlen</span>(<span style="color:#000000">rhs</span>.<span style="color:#000000">_brand</span>)]();
<span style="color:#000000">strcpy</span>(<span style="color:#000000">_brand</span>,<span style="color:#000000">rhs</span>.<span style="color:#000000">_brand</span>);
<span style="color:#000000">_price</span> <span style="color:#981a1a">=</span> <span style="color:#000000">rhs</span>.<span style="color:#000000">_price</span>;
}
<span style="color:#770088">return</span> <span style="color:#981a1a">*</span><span style="color:#770088">this</span>;
}</span></span>
当没有return *this时,我们发现可以进行单次赋值

当我们在进行连续赋值时,是不可以的

但我们将上面的注释取消,就可以进行连续赋值,这是因为赋值运算是从右面开始的pt赋值给pt2,返回值为pt2的this指针指向pt2,然后再与pt3进行赋值运算。

赋值运算符函数的形式探究*
关于赋值运算符函数的形式探究也是面试中比较可能出现的问题,以下提出四个思考:
-
赋值运算符函数的返回必须是一个引用吗?
Computer operator=(const Computer & rhs)
{
……
return *this;
}
—— 符合拷贝构造的调用时机会造成一次多余的拷贝,增加不必要的开销
2. 赋值操作符函数的返回类型可以是void吗?
void operator=(const Computer & rhs)
{
……
}
—— 无法处理连续赋值
3. 赋值操作符函数的参数一定要是引用吗?
Computer & operator=(const Computer rhs)
{
……
return *this;
}
—— 会造成一次多余的拷贝,增加不必要的开销
注意:此时讨论的是赋值运算符函数的参数形式,前提是拷贝构造是正常的,如果拷贝构造没有加const就会出问题。
4. 赋值操作符函数的参数必须是一个const引用吗?
Computer & operator=(Computer & rhs)
{
……
return *this;
}
—— 无法避免在赋值运算符函数中修改右操作的内容,不合理

被折叠的 条评论
为什么被折叠?



