• Index

赋值运算符和 this

Reads: 1

赋值运算符

基础示例

#include <iostream> // std::cout std::endl

class test
{
public:
    test(void) = default;
    test(const test &);
    void operator=(const test &);
};

int main(void)
{
    test obj1;
    test obj2;
    obj2 = obj1;
    return 0;
}

test::test(const test &)
{
    std::cout << "调用复制构造函数" << std::endl;
}

void test::operator=(const test &)
{
    std::cout << "调用复制赋值运算符" << std::endl;
}

输出结果:

调用复制赋值运算符

基础讲解

上面代码中重载了赋值运算符=,从输出结果可以看出,代码中的=调用的是重载的赋值运算符=。不过,就算去掉重载的=,同样也是不会调用复制构造函数。

复制构造函数和移动构造函数只能在初始化的时候调用的。上面代码中obj2 = obj1的时候,两个对象都已经初始化创建完毕。对象创建完后的=赋值操作都是调用复制赋值运算符移动赋值运算符

基础拓展

在前面讲解默认和删除的时候讲解过,当类不声明定义复制赋值运算符移动赋值运算符时就会有默认复制赋值运算符默认移动赋值运算符

  1. 在没有写出复制赋值运算符移动赋值运算符的情况下,就会存在默认复制赋值运算符和默认移动赋值运算符,对应的就是所有成员变量逐个复制和逐个移动。
  2. 在写出复制赋值运算符函数但没有写出移动赋值运算符函数的情况下,当初始值是左值时,将调用复制赋值运算符函数;当初始值是右值时,也是调用复制赋值运算符函数。
  3. 在写出移动赋值运算符函数但没有写出复制赋值运算符函数的情况下,当初始值是左值时,编译报错;当初始值是右值时,将调用移动赋值运算符函数。
  4. 在写出复制赋值运算符函数移动赋值运算符函数的情况下,当初始值是左值时,将调用复制赋值运算符函数;当初始值是右值时,将调用移动赋值运算符函数。

可以发现,复制/移动赋值运算符的情况跟复制/移动构造函数的情况类似。

this

按照惯例,如果重载运算符=用于复制或者转移时,一般返回对象自身的引用。这样就可以使用连续的等于号来赋值,假设上面代码中obj2 = obj1;,当它赋值完后就会返回obj2的引用。那么,再假设我加一条语句:

auto obj3 = obj2 = obj1;

这样obj1的值就会复制给obj2,然后返回obj2自身的引用,接着使用这个引用,就可以将obj2的值复制给obj3。所以上面代码可以进一步修改。

那么,如何使operator=()函数返回对象自身?这时候可以借助关键字this,一般叫做this指针。在类中,this指针代表着对象的内存地址,而且它是const限制的,因此它保存的地址是不能改变的。这样说明不好理解,先看看下面例子:

基础示例 1

#include <iostream>

class test
{
public:
    // 用于输出对象的内存地址
    // 每个对象创建的时候, this就会代表对象的内存地址
    // 所以不同的对象的输出结果都不同
    void print_obj_address(void) const
    {
        std::cout << "当前对象的地址是:" << this << std::endl;
    }
};

int main(void)
{
    test test1;
    test1.print_obj_address();
    std::cout << "test1的内存地址是:" << &test1 << std::endl;

    test test2;
    test2.print_obj_address();
    std::cout << "test2的内存地址是:" << &test2 << std::endl;

    return 0;
}

基础讲解 1

运行输出后就可以看到前两个的地址是相同的,后两个的地址也是相同的,而test1test2的内存地址是不同的。所以每个对象里面的this指针就是对象自身的内存地址,在类中使用this指针用来统一代表不同的对象的内存地址,这样就可以愉快的写代码了。(或者作为初学者的你可能理解this指针有点困难,只要多写代码,就能很快理解this指针,加油吧)。

基础示例 2

那么我们既然知道了this指针,现在就可以马上改写重载运算符=

#include <iostream> // std::cout std::endl

class test
{
public:
    test & operator=(const test &);
};

int main(void)
{
    test obj1;
    test obj2;
    test obj3;

    // 只要operator=函数返回自身引用
    // 对象就可以使用连续使用等于号赋值
    obj3 = obj2 = obj1;
    return 0;
}

test & test::operator=(const test &)
{
    return *this;
}

基础讲解 2

只要operator=()函数返回自身引用,那么对象就可以使用连续使用等于号赋值,否则只能像下面代码这样拆开:

obj2 = obj1;
obj3 = obj2;

巩固练习

我们的simple_vector只显式地写了复制构造函数而没有显示地重载赋值运算符,那么现在,如果simple_vector的对象创建后再赋值的话,它将会调用默认复制赋值运算符,那么复制的情况就跟默认复制构造函数一样,会出现指针直接赋值。为了防止复制或转移出错,simple_vector必须明确写出复制赋值运算符函数移动赋值运算符函数

simple_vector完成以下成员函数:

// 复制赋值
simple_vector & operator=(const simple_vector &x);

// 转移赋值
simple_vector & operator=(simple_vector &&x);

当完成了这个赋值运算符重载之后,simple_vector就已经算是完整的类了,这个设计也就大功告成了。


Comments

Make a comment

  • Index

WARNING: You are using an old browser that does not support HTML5. Please choose a modern browser (Chrome / Microsoft Edge / Firefox / Sarafi) to get a good experience.