C++string类(现代写法、传统写法以及模拟实现)

为什么学习string

C语言中的字符串

C语言中,字符串是以‘\0’结尾的一些字符的集合,为了操作方便,C标准库提供了一些str系列的函数,但是这些库函数是与字符串分开的,不符合OOP思想,而且底层空间需要用户自己管理,稍不留神就会发生越界访问。

string

string是一个类,对于底层空间自己管理,不会发生越界访问。string使用起来方便、快捷、简单。

string的常用接口

string类对象的构造

函数名称 功能说明
string() (重点) 构造空的string类对象,即空字符串
string(const char* s) (重点) 用C-string来构造string类对象
string(size_t n, char c) string类对象中包含n个字符c
string(const string&s) (重点) 拷贝构造函数

string对象的容量操作

函数名称 功能说明
size(重点) 返回字符串有效字符长度
length 返回字符串有效字符长度
capacity 返回空间总大小
empty (重点) 检测字符串释放为空串,是返回true,否则返回false
clear (重点) 清空有效字符
reserve (重点) 为字符串预留空间
resize (重点) 将有效字符的个数该成n个,多出的空间用字符c填充

注意

1、size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。

2、clear只是将string中有效字符清空,不会改变底层空间大小。

3、resize(size_t n)与resize(size_t n,char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。

4、reserve(size_t res_arg=0):为string预留出空间,不改变有效元素的个数,当reserve的参数小于string的底层空间总大小时,reserve不会改变容量大小。

string类对象的访问与遍历操作

函数名称 功能说明
operator[] (重 点) 返回pos位置的字符,const string类对象调用
begin+ end begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭 代器
rbegin + rend begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭 代器
范围for C++11支持更简洁的范围for的新遍历方式

string类对象的修改操作

函数名称 功能说明
push_back 在字符串后尾插字符c
append 在字符串后追加一个字符串
operator+= (重点) 在字符串后追加字符串str
c_str(重点) 返回C格式字符串
find + npos(重点) 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置
rfind 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
substr 在str中从pos位置开始,截取n个字符,然后将其返回

注意

对string的操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。这样可以有效的避免开辟多次重新开辟空间,拷贝数据时消耗的时间。

string类非成员函数

函数 功能说明
operator+ 尽量少用,因为传值返回,导致深拷贝效率低
operator>> (重点) 输入运算符重载
operator<< (重点) 输出运算符重载
getline (重点) 获取一行字符串

简洁版string类的模拟实现

下面我们来实现一个简单的string要求能够正确的实现内存管理,要求实现string类的构造、拷贝构造、赋值运算符重载以及析构函数。

需要注意的时,string类中包含了指针,存在深浅拷贝的问题,有关深浅拷贝的文章参考我另外一篇博客:C++入门–构造函数、拷贝构造函数、析构函数

string类的传统写法

class string{
     
public:
	string(const char *str="")
	{
     
		if(str==nullptr)
		{
     
			assert(false);
			return;
		}
		_str=new char[strlen(str)+1];
		strcpy(_str,str._str);
	}
	
	string(const string& s)
	:_str(new char[strlen(s._str)+1])
	{
     	
		strcpy(_str,s._str);
	}
	
	string& operator=(const string& s)
	{
     
		if(this!=&s)	//判断是否自己给自己赋值
		{
     
			delete[] _str;	//删除_str原有的空间
			_str=new char[strlen(s._str)+1];
			strcpy(_str,s._str);
		}
		return *this;
	}
	
	~string()
	{
     
		delete[] _str;
		_str=nullptr;
	}
	
private:
	char *_str;
	
}

string类的现代写法

class string{
     
public:
	string(const char* str=“”)
	{
     
		if(str==nullptr)
		{
     
			str="";
		}
		_str=new char[strlen(str)+1];
		strcpy(_str,str);
	}
	
	string(const string& s)
	:_str(nullptr);
	{
     
		string tempStr(s._str);//通过构造函数去生成临时变量,然后让_str和临时变量的_str交换,然后出函数作用域后临时变量析构,析构nullptr合法。
		swap(tempStr._str,_str);
	}
	
	string& operator=(string s)	//传值生成临时变量,
	{
     
		swap(s._str,_str);
		return *this;
	}
	
	~string()
	{
     
		delete[] _str;
		_str=nullptr;
	}
	
private:
	char *_str;
}

string类的模拟实现

要实现string类的模拟实现,我们来对string类分解成一个一个的小模块。

string类的定义、私有成员

namespace Mystring{
     
	class string{
     
	public:
	
		void swap(string& s){
     
			std::swap(_str,s._str);
			std::swap(_size,s._size);
			std::swap(_capacity,s._capacity);
		}		
		//构造函数
		string(const char* str){
     
			_size=strlen(str);
			_capacity=_size;
			_str=new char[_capacity+1];
			strcpy(_str,str);
		}
		//拷贝构造
		string(const string& s)
			:_str(nullptr),_size(0),_capacity(0)
		{
     
			string temp(s._str);//构造一个临时对象,然后和临时对象交换,出作用域临时对象析构。
			swap(temp);
		}
		//赋值运算符重载使用现代写法
		string& operator(string s){
     
			swap(s);
			return *this;
		}
		//析构函数
		~string(){
     
			delete[] _str;
			_str=nullptr;
		}
	private:
		char *_str;
		size_t _size;
		size_t _capacity;
	public:
		static const size_t npos;
	}
	//标记结尾
	const size_t string::npos=-1;
}

string类的迭代器、重载[]运算符

namespace Mystring{
     
	class string{
     
	public:
		typedef char* iterator;
		typedef const char* const_iterator;
		
		iterator begin(){
     
			return _str;
		}
		iterator end(){
     
			return _str+_size;
		}
		const_iterator begin() const{
     
			return _str;
		}
		const_iterator end() const{
     
			return _str+_size;
		}
		char& operator[](size_t i){
     
			assert(i<_size);
			return _str[i];
		}
		
		const char& operator[](size_t i) const{
     
			assert(i<_size);
			return _str[i];
		}
		
	private:
		char *_str;
		size_t _size;
		size_t _capacity;
	public:
		static const size_t npos;
	}
	//标记结尾
	const size_t string::npos=-1;
}

string类的reserve、resize、insert函数实现

namespace Mystring{
     
	class string{
     
	public:
		//If n is greater than the current string capacity, the function causes the container to increase its capacity to n characters (or greater).当n>_capacity时,将_capacity的大小扩大为n
		void reserve(size_t n){
     
			if(n>_capacity){
     
				char *temp=new char[n+1]; //还有一个标志位'\0';所以要多申请一个空间
				strcpy(temp,_str);
				delete[] _str;//释放原来的空间
				_str=temp;
				_capacity=n;
			}
		}
		//放n<_size时,将_size改为n,当n>_size时,将索引为[_size,n-1]区间字符改为ch
		void resize(size_t n,char ch='\0'){
     
			if(n<_size){
     
				_str[n]='\0';
				_size=n;
			}else{
     
				if(n>_capacity){
     //当n>_capacity时需要扩容
					reserve(n);
				}
				for(size_t i=_size;i<n;++i){
     
					_str[i]=ch;
				}
				_str[n]='\0';
				_size=n;
			}		
		}
		
		//插入一个字符
		void insert(size_t pos,char ch){
     
			assert(pos<=_size);//当pos==_size时即插入到最后
			if(pos==_capacity){
     
				size_t newcapacity = _capacity == 0 ? 8 : _capacity * 2;//当字符串为空时,给默认长度为8,当不为空时,_capacity扩大为原来的2倍
				reserve(newcapacity);
			}
			size_t end=_size+1;
			while(end>pos){
     
				_str[end]=_str[end-1];
				--end;
			}
			_str[pos]=ch;
			++size;
		}
		
		void insert(size_t pos,const char* str){
     
			assert(pos<=_size);//当pos==_size时即插入到最后
			size_t len = strlen(str);
			if (len + _size > _capacity) {
     
				reserve(len+_size);
			}
			size_t end = len + _size;
			while (end > pos + len) {
     
				_str[end] = _str[end - len];
				--end;
			}
			_str[end] = _str[end - len];
			strncpy(_str+pos,str,len);
			_size += len;
		}
	private:
		char *_str;
		size_t _size;
		size_t _capacity;
	public:
		static const size_t npos;
	}
	//标记结尾
	const size_t string::npos=-1;
}

string类的erase、find函数实现

namespace Mystring{
     
	class string{
     
	public:
	//从pos下标开始删除长度为len的字符串
	void erase(size_t pos,size_t len=npos) {
     
		assert(pos<_size);
		if(len==npos||pos+len>=_size){
     
			_str[pos]='\0';
			_size=pos;
		}else{
     
			strcpy(__str+pos,_str+pos+len);
			_size-=len;
		}
	}
	//查找字符
	size_t find(char ch,size_t pos=0) {
     
		for (size_t i = pos; i < _size; ++i) {
     
			if (_str[i] == ch) {
     
				return i;
			}
		}
		return npos;
	}		
	//查找字符串
	size_t find(const char* sub,size_t pos=0) {
     
		const char* ret = strstr(_str+pos,sub);
		if (ret == nullptr) {
     
			return npos;
		}
		else {
     
			return ret - _str;
		}
	}
	private:
		char *_str;
		size_t _size;
		size_t _capacity;
	public:
		static const size_t npos;
	}
	//标记结尾
	const size_t string::npos=-1;
}		
				

string类的operator+=、push_back、append、c_str、size函数实现

namespace Mystring{
     
	class string{
     
	public:
		void push_back(char ch) {
     
			insert(_size,ch);
		}
		void append(const char* str) {
     
			insert(_size, str);
		}

		string& operator+=(char ch)
		{
     
			push_back(ch);
			return *this;
		}
		string& operator+=(const char* str)
		{
     
			append(str);
			return *this;
		}

		string& operator+=(const string& s)
		{
     
			append(s._str);
			return *this;
		}

		const char* c_str() const {
     
			return _str;
		}

		size_t size() const {
     
			return _size;
		}
	private:
		char *_str;
		size_t _size;
		size_t _capacity;
	public:
		static const size_t npos;
	}
	//标记结尾
	const size_t string::npos=-1;
}		
				

string类的非成员函数实现

	//注意:operator<<不一定是类的友元函数
	ostream& operator<<(ostream& out,const string& s){
     
		for(size_t i=0;i<s.size();++i){
     
			out<<s[i];
		}
		return out;
	}

	istream& operator>>(istream& in, string& s) {
     
		s.reserve(0);
		char ch;
		while (1) {
     
			in.get(ch);
			if (ch == '\n' || ch == ' ') break;
			else s += ch;
		}
		return in;
	}
	
	bool operator<(const string& s1,const string& s2) {
     
		size_t i1 = 0, i2 = 0;
		while (i1 < s1.size() && i2 < s2.size()) {
     
			if (s1[i1] < s2[i2]) {
     
				return true;
			}
			else if (s1[i1] > s2[i2]) {
     
				return false;
			}
			++i1, ++i2;
		}
		return s1.size() < s2.size();
	}

	bool operator==(const string& s1, const string& s2) {
     
		if (s1.size() != s2.size()) return false;
		size_t i = 0;;
		while (i < s1.size()) {
     
			if (s1[i] != s2[i]) {
     
				return false;
			}
			++i;
		}
		return true;
	}
	
	bool operator!=(const string& s1, const string& s2) {
     
		return !(s1==s2);
	}
	
	bool operator<=(const string& s1, const string& s2) {
     
		return s1==s2&&s1<s2;
	}
	
	bool operator>(const string& s1, const string& s2) {
     
		return !(s1<=s2);
	}
	
	bool operator>=(const string& s1, const string& s2) {
     
		return !(s1<s2);
	}

你可能感兴趣的:(C/C++,c++)