之前讲过,我们可以把一个对象赋值给一个类型与之相同的变量。编译器将生成必要的代码把“源”对象各属性的值分别赋值给“目标”对象的对应成员。这种赋值行为称之为逐位赋值(bitwise copy)。这种行为在绝大多数数场合都没有问题,但如果某些成员变量时指针的话,那么问题来了:对象成员进行逐位复制的结果是你将拥有两个一模一样的实例,而这两个副本里的同名指针会指向相同的地址。
事实上,对于任何一个类,如果没有用户自定义的赋值运算符函数,系统会自动地为其生成一个默认的赋值运算符函数,以完成数据成员之间的复制。例如下面的程序段:
X & X::operator=(const X &source)
{
//类对象成员之间赋值语句
}

一旦类X的两个对象ob1和ob2已创建,就可以用ob1=ob2进行赋值了。通常情况下,默认的赋值运算符函数就可以完成赋值任务,但在某些特殊情况下。例如,类中有一种指针类的形式,如果使用默认的赋值运算符函数就会产生指针悬挂的错误。此时,就必须显式地定义一个赋值运算符重载函数,使参与赋值的两个对象有各自的存储空间,以解决这个问题。
要是程序员在当初进行对象“复制”时能够精确地表明应该复制些什么和如何复制,那就理想了。
分析下面几行代码:
MyClass obj1;
MyClass obj2;
obj2 = obj1;
解决方案:
1、重载赋值操作符
---MyClass &operator = (const MyClass &rhs);
因为这里使用的参数是一个引用,所以编译器在传递输入参数时就不会再为它创建另外一个副本(否则可能导致无限递归)。又因为这里只需要读取这个输入参数,而不用改变它的值,所以我们用const把那个引用声明为一个常量确保万无一失。返回一个引用,该引用指向一个MyClass类的对象。
#include <iostream>
#include <string>
using namespace std;
class MyClass
{
public:
MyClass(int *p);
~MyClass();
MyClass &operator = (const MyClass &rhs); //重载赋值符
void print();
private:
int *ptr;
};
MyClass::MyClass(int *p)
{
ptr = p;
}
MyClass::~MyClass()
{
delete ptr;
}
MyClass &MyClass::operator = (const MyClass &rhs)
{
if(this != &rhs)
{
delete ptr;
ptr = new int;
*ptr = *rhs.ptr;
}
else
{
cout << "赋值号两边为同个对象,不做处理!\n";
}
return *this;
}
void MyClass::print()
{
cout << *ptr << endl;
}
int main()
{
MyClass obj1(new int(1));
MyClass obj2(new int(2));
obj1.print();
obj2.print();
obj2 = obj1;
obj1.print();
obj2.print();
return 0;
}
但是,只对赋值符进行重载还不能完美地解决问题,正如刚才所说,C++的发明者把解决方案弄得有点复杂。
有个问题:为啥在VC上能编译,但运行时会程序中止。
以下代码通过构造一个副本构造器
#include <iostream>
#include <string>
using namespace std;
class MyClass
{
public:
MyClass(int *p);
MyClass(const MyClass &rhs); //副本构造器
~MyClass();
MyClass &operator = (const MyClass &rhs); //重载赋值符
void print();
private:
int *ptr;
};
MyClass::MyClass(int *p)+
{
cout << "进入主构造器\n";
ptr = p;
cout << "离开主构造器\n";
}
MyClass::MyClass(const MyClass &rhs)
{
cout << "进入副本构造器\n";
*this = rhs; //这里的赋值已经是重载之后的赋值符
cout << "离开副本构造器\n";
}
MyClass::~MyClass()
{
cout << "进入析构器\n";
delete ptr;
cout << "离开析构器\n";
}
MyClass &MyClass::operator = (const MyClass &rhs)
{
cout << "进入赋值语句重载\n";
if(this != &rhs)
{
delete ptr;
ptr = new int;
*ptr = *rhs.ptr;
}
else
{
cout << "赋值号两边为同个对象,不做处理!\n";
}
cout << "离开赋值语句重载\n";
return *this;
}
void MyClass::print()
{
cout << *ptr << endl;
}
int main()
{
MyClass obj1(new int(1));
MyClass obj2(new int(2));
obj2 = obj1;
obj1.print();
obj2.print();
cout << "-----------------------\n";
MyClass obj3(new int(3));
MyClass obj4 = obj3;
obj3.print();
obj4.print();
cout << "-----------------------\n";
MyClass obj5(new int(5));
obj5 = obj5;
obj5.print();
return 0;
}
重新写的一个例子:
#include <iostream>
#include <string>
using namespace std;
class Internet
{
public:
Internet(char *name, char *url); //定义构造函数
Internet(Internet &temp); //拷贝构造函数
~Internet();
Internet& operator = (Internet &temp); //赋值运算符重载
public:
char *name;
char *url;
};
Internet::Internet(char *name, char *url)
{
this -> name = new char[strlen(name)+1];
this -> url = new char[strlen(url)+1];
if(name)
{
strcpy(this -> name, name);
}
if(url)
{
strcpy(this -> name, name);
}
}
Internet::Internet(Internet &temp)
{
this -> name = new char[strlen(temp.name) + 1];
this -> url = new char[strlen(temp.url) + 1];
if(name)
{
strcpy(this -> name, temp.name);
}
if(url)
{
strcpy(this -> name, temp.url);
}
}
Internet::~Internet()
{
delete[] name;
delete[] url;
}
Internet& Internet::operator = (Internet &temp)
{
delete[] this -> name;
delete[] this -> url;
this -> name = new char[strlen(temp.name) + 1];
this -> url = new char[strlen(temp.url) + 1];
if(this -> name)
{
strcpy(this -> name, temp.name);
}
if(this -> url)
{
strcpy(this -> url, temp.url);
}
return *this;
}
int main()
{
Internet a("Education", "www.edu.cn"); //创建对象
Internet b = a; //b对象还不存在,所以调用拷贝构造函数,进行构造处理
cout << b.name << endl << b.url << endl;
Internet c("Tsinghua", "www.tsinghua.edu.cn"); //b对象已经存在,所以系统选择赋值运算符重
//载函数处理
b = c;
cout << b.name << endl << b.url << endl;
return 0;
}
注意:在进行赋值运算符"="重载时,赋值运算符只能重载为成员运算符,不能重载为友元运算符函数。此外,赋值运算符重载后不能被继承。