#include<iostream>
#include<string.h>
class String
{
public:
String(const char*str=NULL);
String(const String&another);
~String();
String& operator=(const String &str);
private:
char* m_pdata;
};
String::String(const char*str)
{
if (str==NULL)
{
m_pdata=new char[1];
m_pdata[0]='\0';
}
else
{
m_pdata=new char[strlen(str)]+1;
strcpy(m_pdata,str);
}
}
String::String(const String& another)
{
m_pdata=new char[strlen(another.m_pdata)+1];
strcpy(m_pdata,another.m_pdata);
}
String & String::operator=(const String &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;
}
String::~String()
{
delete[]m_pdata;
}
赋值运算符的实现需要注意以下几点:
- 连续赋值。把返回值声明为该类型的引用,并在函数结束前返回实例自身的引用*this。
- 避免无谓消耗。参数类型声明为常量引用,如果传入的不是引用而是实例,那么从形参到实参会调用一次复制构造函数。声明为引用避免这样的无谓消耗,常量保证传入的实例不会被改变。
- 避免内存泄漏。如果忘记在分配新内存前释放自身已有空间,程序将出现内存泄露。
- 判断是否是自身。判断传入的实例和自身是否是同一个实例,如果是,那就直接返回该实例,否则在释放实例自身已有空间后就无法再找到需要赋值的内容了。
考虑异常安全的解法:
- 如果内存不足,导致new char抛出异常,m_pdata将是一个空指针,容易导致程序崩溃,也就是说一旦在赋值运算符函数内部抛出一个异常,String的实例被修改,不再保持有效状态,这就违背了异常安全性原则。
- 有两种方法:第一种是先用new分配新内容再delete释放已有内容。这样只有new成功后再释放原来的内容,也就是说失败时能保证String的实例不会被修改。更好的方法是先创建一个临时实例,再交换临时实例和原来的实例。
String& String::operator=(const String&str)
{
if(this!=&str)
{
String strTemp(str);
char* pTemp=strTemp.m_pdata;
strTemp.m_pdata=m_pdata;
m_pdata=pTemp;
}
return *this;
}
该代码中,在String的构造函数里用new分配内存。如果由于内存不足抛出异常,还没有修改原来实例的状态,因此实例的状态还是有效的,保证了异常安全性。