string类底层是一个字符串指针
1、类结构定义
#include <iostream>
#include <cstring>
using namespace std;
class CMyString
{
private:
char* m_pDate;
public:
CMyString(const char* pDate = NULL); //普通构造函数,const:防止修改
CMyString(const CMyString& other); //拷贝构造函数,const:防止修改,&:省去调用复制构造函数提高效率,涉及深拷贝、浅拷贝
~CMyString(); //析构函数
CMyString& operator = (const CMyString& other); //重构赋值运算符,返回引用:为了连续赋值,const:防止修改,&:省去调用复制构造函数提高效率,涉及安全性
//CMyString& operator + (const CMyString& other);
//bool operator == (const CMyString& other);
int getLength();
void printString(){ cout<<m_pDate<<endl; } //用于测试
};
2、简单说明
正如代码中注释部分说明,
const 是为了防止函数内部修改;
& 是为了省去隐式调用拷贝构造函数,从而提高效率;
3、详细解说
以“重构赋值运算符”例,详细解说注意事项
(1)是否把返回值的类型声明为该类型的引用,并在函数结束前返回实例自身的引用(即*this)。
只有返回一个引用,才可以允许连续赋值。否则如果函数的返回值是void,应用该赋值运算符将不能做连续赋值。假设有3个CMyString的对象:str1、str2和str3,在程序中语句str1=str2=str3将不能通过编译 。若只是两个对象之间的赋值,返回值为void也可以达到效果。
(2)是否把传入的参数的类型声明为常量引用。
如果传入的参数不是引用而是实例,那么从形参到实参会调用一次复制构造函数。把参数声明为引用可以避免这样的无谓消耗,能提高代码的效率。同时,我们在赋值运算符函数内不会改变传入的实例的状态,因此应该为传入的引用参数加上const关键字。即省去调用复制构造函数,提高效率。
(3)是否释放实例自身已有的内存。
如果我们忘记在分配新内存之前释放自身已有的空间,程序将出现内存泄露。
(4)是否判断传入的参数和当前的实例(*this)是不是同一个实例。
避免自赋值,如果是同一个,则不进行赋值操作,直接返回。如果事先不判断就进行赋值,那么在释放实例自身的内存的时候就会导致严重的问题:当*this和传入的参数是同一个实例时,那么一旦释放了自身的内存,传入的参数的内存也同时被释放了,因此再也找不到需要赋值的内容了。即存在非法访问或者多次释放同一内存单元的风险。
(5)是否有申请内存失败的安全处理。
如果此时内存不足导致new char抛出异常,m_pData将是一个空指针,这样非常容易导致程序崩溃。先创建一个临时实例,再交换临时实例和原来的实例。把strTemp.m_pData和实例自身的m_pData做交换。由于strTemp是一个局部变量,但程序运行到 if 的外面时也就出了该变量的作用域,就会自动调用strTemp 的析构函数,把 strTemp.m_pData 所指向的内存释放掉。由于strTemp.m_pData指向的内存就是实例之前m_pData的内存,这就相当于自动调用析构函数释放实例的内存。即利用临时实例的生命周期自动释放原来实例内容。
4、类成员函数实现
(1)普通构造函数
参数为 const 防止修改
strlen计算字符串长度没有把'\0'算进去,所以要+1
CMyString::CMyString(const char* pDate)
{
if( pDate == NULL )
{
m_pDate = new char[1];
*m_pDate = '\0';
}
else
{
//strlen计算字符串长度没有吧'\0'算进去
m_pDate = new char[strlen(pDate)+1];
strcpy(m_pDate, pDate);
}
}
(2)拷贝构造函数
参数为 const 防止修改
参数加 & 省去调用赋值构造函数提高效率
(2.1)浅拷贝,也叫位拷贝
CMyString::CMyString( const CMyString& other ) //浅拷贝
{
//没有重新申请新空间,共用同一块内存空间
//隐患:非法访问,重复释放内存
m_pDate = other.m_pDate;
}
浅拷贝引发的错误
(2.2)深拷贝
CMyString::CMyString( const CMyString& other ) //深拷贝
{
//delete m_pDate;//既然也是属于构造函数的一类,初始为空,不必delete
if( other.m_pDate == NULL )
{
m_pDate = NULL;
}
else
{
m_pDate = new char[strlen(other.m_pDate)+1];
strcpy(m_pDate, other.m_pDate);
}
}
(3)析构函数
释放前判断,避免重复释放
CMyString::~CMyString()
{
if(m_pDate) //释放前判断,避免重复释放
{
delete m_pDate;
m_pDate = NULL;
}
}
(4)重载赋值运算符
返回引用 实现连续赋值
参数为 const 防止修改
参数加 & 省去调用赋值构造函数提高效率
(4.1)不安全实现
CMyString& CMyString::operator = ( const CMyString& other )
{
if( &other != this ) //避免自赋值
{
if( m_pDate ) //先判断再删除,避免重复操作
delete m_pDate;
m_pDate = new char[strlen(other.m_pDate)+1]; //如果申请失败,后面strcpy会不安全
strcpy(m_pDate, other.m_pDate);
}
return *this;
}
(4.2)安全实现
利用临时实例巧妙实现安全转移
CMyString& CMyString::operator = ( const CMyString& other )
{
if( &other != this ) //避免自赋值
{
CMyString tmpOther(other);
//让tmpOther跟this交换date
char *tmpDate = tmpOther.m_pDate;
tmpOther.m_pDate = m_pDate;
m_pDate = tmpDate;
//临时实例tmpOther退出if会自动调用析构函数,清除了原本m_pDate的内容
}
return *this;
}
5、输出实例
int main()
{
CMyString str("hello"); //等同于 const char* p = "hello"; CMyString str(p);
str.printString();
cout<<"拷贝构造函数"<<endl;
CMyString str1(str);
str1.printString();
cout<<"重载赋值操作符"<<endl;
CMyString str2("world");
str2.printString();
CMyString str3("Birthday");
str3.printString();
str1 = str2 = str3;
str1.printString();
str2.printString();
str3.printString();
cout<<str1.getLength()<<endl;
return 0;
}
输出样子:
6、参考
《后台开发》核心技术与应用实践
《剑指Offer》