对于许多重要的实用类来说,仅有默认的赋值运算符函数是不够的,还需要用户根据实际需要自己对赋值运算符进行重载,已解决遇到的问题。指针悬挂就是这方面的一个典型问题。
1、指针悬挂问题
在某些特殊情况下,如类中有指针类型时,使用默认的赋值运算符函数会产生错误。例如,
例 1:关于浅层赋值的例子。
#include<iostream>
#include<string.h>
using namespace std;
class STRING{
private:
char *ptr;
public:
STRING(char *s){ //构造函数
cout<<"Constructor called."<<endl;
ptr=new char[strlen(s)+1];
strcpy(ptr,s);
}
~STRING(){
cout<<"Destructor called.---"<<ptr<<endl;
delete ptr;
}
};
int main(){
STRING p1("book");
STRING p2("jeep");
p2=p1;
return 0;
}
执行结果:
(1)程序开始运行,创建对象 p1 和 p2 ,分别调用构造函数,通过运算符 new 分别从内存中动态分配一块空间,字符指针 ptr 指向内存空间,这时两个动态空间中的字符串分别为 “book”和“jeep”。
(2)执行语句“p2=p1;” 时,因为没有用户自定义的赋值运算符函数,于是调用默认的赋值运算符函数,使两个对象 p1 和 p2 的指针 ptr 都指向 new 开辟的同一个空间,这个动态空间中字符串为“book”。
(3)主程序结束,对象逐个撤销。先撤销对象 p2,第 1 次调用析构函数,尽管这时 p1 的指针 ptr 存在,但是其所指向的空间却无法访问了,出现了所谓的 “指针悬挂”,输出出现异常。由于第 2 次执行析构函数中语句“ delete ptr; ”时,企图释放同一空间,从而导致了对同一内存空间的两次释放,这必然引起运行错误。
执行 p2=p1 之前:
执行 p2=p1 之后:
撤销对象 p2 后:
2、用深层复制解决指针悬挂问题
为了解决浅层复制出现的错误,必须显式地定义一个自己的赋值运算符,使之不但赋值数据成员,而且为对象 p1 和 p2 分配了各自的内存空间,这就是深层复制。
例 2 是在例 1 的基础上,增加了一个自定义的赋值运算符重载函数。
例 2:关于深层复制的例子。
#include<iostream>
#include<string.h>
using namespace std;
class STRING{
private:
char *ptr;
public:
STRING(char *s){ //构造函数
cout<<"Constructor called."<<endl;
ptr=new char[strlen(s)+1];
strcpy(ptr,s);
}
~STRING(){
cout<<"Destructor called.---"<<ptr<<endl;
delete ptr;
}
STRING &operator=(const STRING &); //声明赋值运算符重载函数 属于成员运算符重载函数
//STRING &operator属于使用引用返回函数值,返回函数的值类型为 STRING
//const STRING & 属于使用常引用作为函数参数 学习笔记30
};
STRING &STRING::operator=(const STRING &s){ //定义赋值运算符重载函数
if(this==&s) return *this; //这里的 &s 表示 s 的地址
delete ptr;
ptr=new char[strlen(s.ptr)+1];
strcpy(ptr,s.ptr);
return *this;
}
int main(){
STRING p1("book");
STRING p2("jeep");
p2=p1;
return 0;
}
执行结果:
(1)创建对象 p1 和 p2,分别调用构造函数,通过运算符 new 分别从内存中动态分配一块空间,字符指针 ptr 指向内存空间,这两个动态空间中的字符串分别为“book”和“jeep”。
(2)执行语句“p2=p1”时,调用自定义的赋值运算符重载函数,释放掉了 p2 指针 ptr 所指的旧区域, 又按照新长度分配新的内存空间给 p2,再把对象 p1 的数据成员赋给 p2 的对应的数据成员中。
(3)主程序结束,对象逐个撤销。
执行 p2=p1 之前:
执行 p2=p1 之后:
撤销对象 p2 后:
注意:
类的赋值运算符 “=” 只能重载为成员函数,而不能把它重载为友元函数,因为如果重载为友元函数:
friend STRING &operator=(STRING &p2,STRING &p1);
表达式 p1=“book” 将被解释为:
operator=(p1,"book");
这没有问题。但是对于表达式 “book”=p1 将被解释为:
operator=("book",p1);
即 C++ 编译器首先将 “book” 转换成一个隐藏的 string 对象,然后使用对象 p2 引用该隐藏对象,编译器并不认为这个表达式是错误的,从而将导致赋值语句上的混乱。因此双目赋值运算符应重载为成员函数的形式,而不能重载为友元函数的形式。