string类的实现
string类底层是一个字符串指针
1、类结构定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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)浅拷贝,也叫位拷贝
1 2 3 4 5 6 | CMyString::CMyString( const CMyString& other ) //浅拷贝 { //没有重新申请新空间,共用同一块内存空间 //隐患:非法访问,重复释放内存 m_pDate = other.m_pDate; } |
浅拷贝引发的错误
(2.2)深拷贝
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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)析构函数
1 | 释放前判断,避免重复释放 |
1 2 3 4 5 6 7 8 | CMyString::~CMyString() { if (m_pDate) //释放前判断,避免重复释放 { delete m_pDate; m_pDate = NULL; } } |
(4)重载赋值运算符
返回引用 实现连续赋值
参数为 const 防止修改
参数加 & 省去调用赋值构造函数提高效率
(4.1)不安全实现
1 2 3 4 5 6 7 8 9 10 11 | 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)安全实现
利用临时实例巧妙实现安全转移
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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、输出实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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》
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· 展开说说关于C#中ORM框架的用法!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?