第36课 经典问题(上)----重载赋值操作符

什么时候需要重载赋值操作符?
编译器是否提供默认的赋值操作符?


编译器为每个类默认重载了赋值操作符
默认的赋值操作符仅完成浅拷贝
当需要进行深度拷贝时必须重载赋值操作符
赋值操作符与拷贝构造函数有相同的存在意义

 

#include <iostream>

using namespace std;

class Test
{
private:
    int *m_pointer;
public:
    Test()
    {
        m_pointer = NULL;
    }
    Test(int i)
    {
        m_pointer = new int(i);
    }
    void print()
    {
        cout << "m_pointer=" << hex << m_pointer << endl;
    }
    ~Test()
    {
        delete m_pointer;
    }

};

int main()
{
    Test t1 = 1;
    Test t2;

    t2 = t1;

    t1.print();
    t2.print();

    return 0;

}

 

编译时可以通过,运行时程序发生崩溃。

原因:t2 = t1;

程序在崩溃之前,指向了相同的堆空间,打印完之后,main函数即将结束,将对象t1和t2销毁,将触发析构函数的调用。两次删除同一个堆空间,程序肯定要崩溃,此时必须进行深拷贝。

 

只要一个类中它有成员指向了外部的资源,此时必须进行深拷贝,于是必须要重载赋值操作符,在有必要的情况下自定义拷贝构造函数。赋值操作符和拷贝构造函数具有同等重要的意义

 

#include <iostream>

using namespace std;

class Test
{
private:
    int *m_pointer;
public:
    Test()
    {
        m_pointer = NULL;
    }
    Test(int i)
    {
        m_pointer = new int(i);
    }
    Test(const Test& obj)  //定义
    {
        m_pointer = new int(*obj.m_pointer);
    }

    Test& operator = (const Test& obj)  //赋值操作符的重载,1.返回值必须是一个引用,就是为了连续赋值。2.参数必须是一个const的引用类型。
                                   //3.赋值操作不是自赋值,就是自己赋给自己。4.必须将当前的对象返回。
    {
        if(this != &obj)
        {
            delete m_pointer;  //先将当前对象中的m_pointer指针所指的内存空间删除。下面会重新生成。
            m_pointer = new int(*obj.m_pointer);
        }

        return *this;
    }

    void print()
    {
        cout << "m_pointer=" << hex << m_pointer << endl;
    }
    ~Test()
    {
        delete m_pointer;
    }

};

int main()
{
    Test t1 = 1;
    Test t2;

    t2 = t1;  //编译器会去看,在当前的Test类中有没有重载赋值操作符。如果发现重载赋值操作符,就使用所定义的实现了。

    t1.print();
    t2.print();

    return 0;

}

 

(1)要熟练记住,重载赋值操作符的那4点注意事项。只要记住了这4点,以后工作中如果遇到重载赋值操作符,基本上就不会出现bug。

(2)t2 = t1,这个地方它会不会调用拷贝构造函数,即深拷贝。答案是不会的,为什么?

因为这个地方是赋值,不是初始化。赋值的时候是不会触发拷贝构造函数的调用。这点在前面已经说过。如果是下面的这种形式:

Test t2 (t1);等价于Test t2 = t1(这是初始化),此时会触发拷贝构造函数的调用。要注意:构造函数只有在定义对象或初始化对象的时候调用,这与赋值操作符是完全不同的,不要混淆。

(3)假设在程序中,没有重载赋值操作符,t2 = t1;只会调用默认的拷贝构造函数,实现浅拷贝的工作。结果就是两个对象的指针指向了同一片内存空间,程序运行时崩溃。

(4)在程序中,如果将t2 = t2,会出现什么情况。

编译可以通过,运行也没有问题,但是没有任何的意义。没有意义,为什么这种情况还存在,就是为了兼容C语言。

在C语言中,这样是合法的:

int i = 0;

i = i

C++为了兼容C语言,不得已也必须支持这种写法。因此我们在重载赋值操作符的时候,必须处理自赋值的情况。如何处理呢?此处是通过地址判断的。

数组类的优化

#ifndef _INTARRAY_H_
#define _INTARRAY_H_

class IntArray
{
private:
    int m_length;
    int* m_pointer;

    IntArray(int len);
    IntArray(const IntArray& obj);//拷贝构造函数定义为私有的,它在外部是无法调用的。实际上,使用了二阶构造之后,拷贝构造函数就不起作用了。
                                  //不允许拷贝构造,但是允许赋值,因此赋值操作符的重载还是有必要的。
    bool construct();
public:
    static IntArray* NewInstance(int length);
    int length();
    bool get(int index, int& value);
    bool set(int index ,int value);
    int& operator [](int index);
    IntArray& operator = (const IntArray& obj);
    IntArray& self();
    ~IntArray();
};

#endif
#include "IntArray.h"

IntArray::IntArray(int len)
{
    m_length = len;
}

bool IntArray::construct()
{
    bool ret = true;

    m_pointer = new int[m_length];

    if( m_pointer )
    {
        for(int i=0; i<m_length; i++)
        {
            m_pointer[i] = 0;
        }
    }
    else
    {
        ret = false;
    }

    return ret;
}

IntArray* IntArray::NewInstance(int length)
{
    IntArray* ret = new IntArray(length);

    if( !(ret && ret->construct()) )
    {
        delete ret;
        ret = 0;
    }

    return ret;
}

int IntArray::length()
{
    return m_length;
}

bool IntArray::get(int index, int& value)
{
    bool ret = (0 <= index) && (index < length());

    if( ret )
    {
        value = m_pointer[index];
    }

    return ret;
}

bool IntArray::set(int index, int value)
{
    bool ret = (0 <= index) && (index < length());

    if( ret )
    {
        m_pointer[index] = value;
    }

    return ret;
}
int& IntArray::operator [](int index)
{
    return m_pointer[index];
}
IntArray& IntArray::operator =(const IntArray& obj)
{
    if(this != &obj)
    {
       int* pointer = new int[obj.m_length];

       for(int i=0; i<obj.m_length; i++)
       {
            pointer[i] = obj.m_pointer[i];
       }
       m_length = obj.m_length;
       delete[] m_pointer;
       m_pointer = pointer;
    }

    return *this;
}
IntArray& IntArray::self()
{
    return *this; //返回this指针指代的当前对象即可。
}
IntArray::~IntArray()
{
    delete[]m_pointer;
}
#include <iostream>
#include <string>
#include "IntArray.h"

using namespace std;

int main()
{
    IntArray* a = IntArray::NewInstance(5);
    IntArray* b = IntArray::NewInstance(10);

    if( a && b )
    {
        IntArray& array = a->self();
        IntArray& brray = b->self();

        cout << "array.length() = " << array.length() << endl;
        cout << "brray.length() = " << brray.length() << endl;

        array = brray;

        cout << "array.length() = " << array.length() << endl;
        cout << "brray.length() = " << brray.length() << endl;
    }

    delete a;
    delete b;

    return 0;
}

一般性原则:重载赋值操作符,必然需要实现深拷贝。反过来,如果要实现深拷贝,就必须提供赋值操作符的自定义实现以及自定义拷贝构造函数。

编译器默认提供的函数

 补充:

1. new int[]是创建一个int型数组,数组大小是在[]中指定
int *p = new int[3];//申请一个动态整形数组,数组的长度为[]中的值
2. new int()是创建一个int型数,并且用()括号中的数据进行初始化,例如:
int *p = new int(10); //p指向一个值为10的int数。

posted @ 2019-11-25 22:02  一代枭雄  阅读(292)  评论(0编辑  收藏  举报