(1)是否把返回值的类型声明为该类的引用,并在函数结束的时侯返回示例自身的引用(即*this).只有返回一个引用,才可以允许连续赋值。
(2)是否把参数的类型声明为常量引用。如果传入的参数不是引用而是实例,那么从形参到实参会调用一次构造函数。把参数声明为引用时可以避免这样无谓的消耗,能提高代码的效率。
(3)是否释放实例自身已有的内存。如果忘记释放,会导致内存的泄漏。
(4)是否判断传入的参数和当前的实例是不是同一个实例。如果是同一个,则不用进行赋值,直接返回。如果不进行判断时,会导致比较严重的问题就是,赋值的内容被删除了。
下面给出一个MyString类的实现:
#include<iostream>
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
using namespace std;
class MyString{
public:
MyString(char *pdata = NULL);
MyString(const MyString& str);
MyString& operator=(const MyString& str);
~MyString(void);
void Print();
private:
char *m_pdata;
};
//构造函数
MyString::MyString(char *pdata)
{
if(pdata == NULL){
m_pdata = new char[1];
m_pdata[0] = '\0';
}else{
int len = strlen(pdata);
m_pdata = new char[len + 1];
strcpy(m_pdata,pdata);
}
}
//拷贝构造函数
MyString::MyString(const MyString& str)
{
int length = strlen(str.m_pdata);
m_pdata = new char[length + 1];
strcpy(m_pdata,str.m_pdata);
}
//析构函数
MyString::~MyString()
{
delete []m_pdata;
}
//赋值运算
MyString& MyString::operator = (const MyString& str)
{
if(this == &str){
return *this;
}
delete []m_pdata;
m_pdata = NULL;
m_pdata = new char[strlen(str.m_pdata) + 1];
strcpy(m_pdata,str.m_pdata);
return *this;
}
void MyString::Print()
{
printf("%s", m_pdata);
}
void Test1()
{
printf("Test1 begins:\n");
char text[] = "Hello world";
MyString str1(text);
MyString str2;
str2 = str1;
printf("The expected result is: %s.\n", text);
printf("The actual result is: ");
str2.Print();
printf(".\n");
}
void Test2()
{
printf("Test2 begins:\n");
char text[] = "Hello world";
MyString str1(text);
str1 = str1;
printf("The expected result is: %s.\n", text);
printf("The actual result is: ");
str1.Print();
printf(".\n");
}
void Test3()
{
printf("Test3 begins:\n");
char text[] = "Hello world";
MyString str1(text);
MyString str2, str3;
str3 = str2 = str1;
printf("The expected result is: %s.\n", text);
printf("The actual result is: ");
str2.Print();
printf(".\n");
printf("The expected result is: %s.\n", text);
printf("The actual result is: ");
str3.Print();
printf(".\n");
}
int main(int argc,char** argv)
{
Test1();
Test2();
Test3();
return 0;
}
我们先来看测试程序的执行结果:
根据测试的程序来看,赋值运算符函数基本没有什么问题了。
上述对于赋值运算符函数的实现只适用于初级的程序员,要想和别人不一样,我们就需要考虑下面的问题:
在前面的函数中,我们在分配内存之前先用delete释放了实例m_pdata的内存。如果此时内存不足导致new char抛出异常,m_pdata将是一个空指针,这样非常容易导致程序崩溃。也就是说一旦在赋值运算符内部抛出了一个异常,实例将不再保持原有的有效状态,这就违背了异常安全性的原则。
想要解决这个问题我们有两种办法:(1)一个简单的办法就是我们先用new 分配新内容再用delete释放已有的内容。这样只在分配i内容成功之后再释放原来的内容,当分配失败时,我们能确保原来的实例还存在。
(2)第二个更好的办法就是,先创建一个临时实例,再交换临时实例和原来的实例。
下面我们给出第二种办法的代码:
MyString& MyString::operator = (const MyString& str)
{
if(this != &str){
MyString strtemp(str);
char *temp = strtemp.m_pdata;
strtemp.m_pdata = m_pdata;
m_pdata = temp;
}
return *this;
}
局部的对象strtemp在函数执行完就要调用析构函数,利用析构函数释放了原来实例的内容。。。