string类的实现

时间:2021-04-07
本文章向大家介绍string类的实现,主要包括string类的实现使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

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)普通构造函数

参数为 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;
}

 

4、详细说明

以“重构赋值运算符”例,详细解说注意事项

(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的内存,这就相当于自动调用析构函数释放实例的内存。即利用临时实例的生命周期自动释放原来实例内容

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》

原文地址:https://www.cnblogs.com/Christal-R/p/14629145.html