C++重载赋值操作符

赋值操作符“=”可以用来将一个对象拷贝给另一个已经存在的对象。如果我们重新定义了一种新的数据类型,比如说复数类,那么我们就需要重载一下赋值操作符,使之能够满足我们的赋值需求。

当然,拷贝构造函数也有同样的功能。拷贝构造函数可以将一个对象拷贝给另一个新建的对象。如果我们没有在类中显式定义拷贝构造函数,也没有重载赋值操作符,那么系统会为我们的类提供一个默认的拷贝构造函数和一个赋值操作符。前面在介绍类的相关知识时已经提到,系统为我们提供的默认拷贝构造函数只是将源对象中的数据一一拷贝给目标对象,而系统为类提供的赋值操作符也是这样的一种功能。

【例 1】
complex c1(4.3, -5.8);
complex c2;
c2 = c1;
cout<<c1<<endl;
cout<<c2<<endl;
执行结果为:

4.3 + -5.8 i
4.3 + -5.8 i

利用前面我们定义的 complex 类,我们先定义了两个 complex 类的对象 c1 和 c2,c1 对象通过带参构造函数初始化,之后用 c1 来初始化 c2,最后输出这两个复数类对象。

注意在 complex 类中,我们并未定义拷贝构造函数,也没有重载赋值操作符,但是例 1 中c2 = c1;并未有语法错误,根据程序运行结果可以得知,该赋值操作成功地完成了。这是因为,系统默认为类提供了一个拷贝构造函数和一个赋值操作符,而数据一对一的拷贝也满足我们复数类的需求了。

在前面介绍拷贝构造函数时我们提到过,系统提供的默认拷贝构造函数有一定的缺陷,例如当类中的成员变量包含指针的时候,会导致一些意想不到的程序漏洞,此时就需要重新定义一个拷贝构造函数。在相同的情况下,系统提供的赋值操作符也无法满足我们的需求,必须要进行重载。

在前面介绍拷贝构造函数一节中,我们已经详细分析了系统提供的默认拷贝构造函数遇到指针成员变量时带来的风险,直接使用系统默认提供的赋值操作符同样会有此种风险,在此我们将不再重新分析这一问题,而只是将前面的示例再次拿过来,并且在程序中补上赋值操作符重载函数。

【例 2】
#include<iostream>
using namespace std;

class Array
{
public:
    Array(){length = 0; num = NULL;};
    Array(int * A, int n);
    Array(Array & a);
    Array & operator= (const Array & a);
    void setnum(int value, int index);
    int * getaddress();
    void display();
    int getlength(){return length;}
private:
    int length;
    int * num;
};

Array::Array(Array & a)
{
    if(a.num != NULL)
    {
        length = a.length;
        num = new int[length];
        for(int i=0; i<length; i++)
            num[i] = a.num[i];
    }
    else
    {
        length = 0;
        num = 0;
    }   
}

//重载赋值操作符
Array & Array::operator= (const Array & a)
{
    if( this != &a )
    {
        delete[] num;
        if(a.num != NULL)
        {
            length = a.length;
            num = new int[length];
            for(int i=0; i<length; i++)
                num[i] = a.num[i];
        }
        else
        {
            length = 0;
            num = 0;
        }           
    }
    return *this;
}

Array::Array(int *A, int n)
{
    num = new int[n];
    length = n;
    for(int i=0; i<n; i++)
        num[i] = A[i];
}

void Array::setnum(int value, int index)
{
    if(index < length)
        num[index] = value;
    else
        cout<<"index out of range!"<<endl;
}

void Array::display()
{
    for(int i=0; i<length; i++)
        cout<<num[i]<<" ";
    cout<<endl;
}

int * Array::getaddress()
{
    return num;
}

int main()
{
    int A[5] = {1,2,3,4,5};
    Array arr1(A, 5);
    arr1.display();
    Array arr2(arr1);
    arr2.display();
    arr2.setnum(8,2);
    arr1.display();
    arr2.display();
    cout<<arr1.getaddress()<<" "<<arr2.getaddress()<<endl;
    arr1 = arr2;
    arr1.display();
    arr2.display();
    arr2.setnum(9,3);
    arr1.display();
    arr2.display();
    cout<<arr1.getaddress()<<" "<<arr2.getaddress()<<endl;
    return 0;
}
在这个例子中,我们以类成员函数的形式重载了赋值操作符,因为前面已经介绍过该程序了,下面就直接来看主函数。

主函数中,前半部分和之前介绍拷贝构造函数时是相同的,这个我们也忽略过去。直接从arr1 = arr2;语句开始看起。这个语句就会调用类中的操作符重载函数,我们可以将这一语句理解为:
arr1.operator=(arr2);
该语句会执行赋值操作符重载函数中的函数体。在该函数体中,我们为 arr1 重新开辟了一个内存空间,因此就可以规避 arr1 和 arr2 中的 num 指向同一块存储区域的风险。如此一来,使用系统默认提供的赋值操作符所带来的风险就可以避免了。在这之后的语句中,我们还修改了 arr2 中的数据,并没有影响到 arr1,可见确实将风险给化解了。

当然,如果类中并没有包含需要动态分配内存的指针成员变量时,我们使用系统提供的默认拷贝构造函数和赋值操作符也就可以了,无需再多此一举地重新定义和重载一遍。