C++之String类模拟实现(下)

片头

哈喽~小伙伴们,在上一篇中,我们讲解了C++的string类的相关函数,这一章中,我们将继续深入学习string类函数,准备好了吗?咱们开始咯~


五、对内容进行修改

⑤insert函数

在指定位置插入字符或者字符串

函数声明

		//insert函数
		void insert(size_t pos, char ch);//插入字符

函数定义

	//insert函数,插入字符
	void string::insert(size_t pos, char ch) {
		//严格控制pos的取值范围,避免越界
		assert(pos <= _size);

		//如果_capacity和_size相等,需要扩容
		if (_capacity == _size) {
			size_t newCapacity = _capacity == 0 ? 4 : 2 * _capacity;
			reserve(newCapacity);
		}

		//end从'\0'的位置开始
		size_t end = _size;
		while (end >= pos) {
			_str[end + 1] = _str[end];
			//最后一次: pos+1 = pos;
			--end;
			//每执行完一次,end向前挪动一位
		}
		_str[pos] = ch;//将pos位置放入ch字符
		++_size;	   //_size更新
	}

这里的end初始指向末尾_size,也就是'\0'的位置,将数据不断往后挪动,直到腾出pos位置。

测试一下:

但是如果这时,我们往下标为0的位置头插'D',发现编译器崩溃,为啥呢?

因为pos此时为0, 如果pos大于等于0,则进入循环,若pos小于0,就结束。end是size_t类型,就不会小于0。如果end减到-1,再转成无符号类型size_t,无符号整型-1是一个很大很大的数,必然会越界。所以,我们将end的类型修改为int。

int end = _size;

但是此时的pos仍然是size_t类型,在一个操作符两边的操作数,如果它们类型不一样,它们就会发生隐式类型转换,当有符号类型遇到无符号类型,有符号类型就会隐式类型转换成无符号类型。

解决方法1:将pos强转成int类型

        int end = _size;                 //end从'\0'的位置开始
		while (end >= (int)pos)          //将pos强转成int,避免类型不同
        {       
			_str[end + 1] = _str[end];   //最后一次:_str[pos+1] = _str[pos]
			--end;
		}
		_str[pos] = ch;
		++_size;	  
	}

解决方法2:end仍然是size_t类型,但是起始位置是_size+1

		size_t end = _size + 1;			//end的起始位置是_size的下一个位置
		while (end > pos)				//end>pos位置继续,end==pos就结束
		{
			_str[end] = _str[end - 1];	//最后一次: 下标为0的元素挪到下标为1的位置
			end--;
		}
		_str[pos] = ch;
		++_size;	

我们 初始指向'\0'的下一个位置,最终当end和pos值相等时跳出循环,完成插入与对成员变量的修改。


接下来,我们用insert函数实现插入字符串

函数声明

	void insert(size_t pos, const char* str);//插入字符串

函数定义

//方法1:	
    void string::insert(size_t pos, const char* str) {
        //严格控制pos的取值范围,避免越界
		assert(pos <= _size);

		int len = strlen(str);			//新字符串的长度(不包括'\0')
		if (_size + len > _capacity)	//检查是否需要扩容
		{
			reserve(_size + len);
		}
		int end = _size;				//end的起始位置_size
		while (end >= (int)pos)			//判断end是否满足循环条件
		{
			_str[end + len] = _str[end];
			end--;
		}
		memcpy(_str + pos, str, len);	//使用memcpy函数,拷贝len个字符,不包括'\0'
		_size += len;					//_size更新
	}

诶,为啥这里不能使用strcpy函数了呢?

因为strcpy函数会把插入字符串的**'\0'**一起拷贝过去,改变了原字符串的内容,这样就不行的。

中间的逻辑也可以这样进行修改,保证结果正确:

		int end = _size + len;				  //将end设定为_size+len
		while (end > pos + len - 1)	          //判断end是否满足循环条件
		{
			_str[end] = _str[end - len];
			end--;
		}
		memcpy(_str + pos, str, len);		 //使用memcpy函数,拷贝len个字符,不包括'\0'
		_size += len;						 //_size更新

测试一下:

实现了insert函数后,我们就可以在push_back函数和append函数复用它们

//尾插一个字符
	void string::push_back(char ch) {
		insert(_size, ch);	//在_size位置,插入一个字符ch
	}

//尾插一个字符串
	void string::append(const char* str) {
		insert(_size, str);//在_size位置,插入一个字符串
	}

⑥erase函数

函数声明

        //erase函数
		void erase(size_t pos = 0 , size_t len = npos);

从pos位置开始,删除len个字符(pos默认为0,len默认为npos)

这里的pos不能等于_size,因为我们不能删去'\0',npos为整数-1,这里我们需要自己在类中定义。

public:
    static const int npos;

在外部进行初始化

const int string::npos = -1;

函数定义

//erase函数
	void string::erase(size_t pos, size_t len = npos) {
		assert(pos < _size);				 //严格控制pos的有效区间

		//len大于后面字符个数时,有多少删多少
		if (len == npos || len >= _size - pos) 
		{
			_str[pos] = '\0';				//将pos的位置改为'\0'
			_size = pos;					//有效元素的个数为pos个
		}
		else
		{
		//len小于后面的字符个数
			strcpy(_str + pos, _str + pos + len);//将后面的字符拷贝到pos位置
			_size -= len;						 //有效字符个数更新
		}
	}

运行一下:


⑦find查找字符或字符串

函数声明

		//find函数
		size_t find(char ch, size_t pos = 0);			//查找字符
		size_t find(const char* sub, size_t pos = 0);	//查找字符串

函数定义

//从pos位置开始,查找字符
	size_t string::find(char ch, size_t pos) {
		for (int i = pos; i < _size; i++) {
			if (_str[i] == ch) {
				return i;
			}
		}
		return npos;
	}

//从pos位置开始,查找字符串
	size_t string::find(const char* sub, size_t pos) {
		char* p = strstr(_str + pos, sub);
		return p - _str;//返回的就是下标
	}

**(1)查找字符:**我们给了缺省值npos,如果不赋值,默认从起始位置开始找,遍历数组;若找到目标字符,则返回下标;若找不到,则返回npos

**(2)查找目标字符串:**我们调用字符串函数strstr,如果找到返回目标字符串起始位置的指针;若没找到,则返回空指针。我们再加一个判断条件,如果不为空,指针相减即为目标字符串起始位置的下标(第一个元素的下标);若为空,则找不到,返回npos。

因此,我们还可以把查找字符串函数再改进一下:

//从pos位置开始,查找字符串
	size_t string::find(const char* sub, size_t pos) {
		//p指针表示找到目标字符串的地址
		char* p = strstr(_str + pos, sub);

		//如果p指针存在,直接返回目标字符串的下标
		if (p) {
			return p - _str;
		}
		else {
		//如果p指针不存在,返回npos
			return npos;
		}
	}

运行一下


⑧substr函数

substr函数用于从字符串中提取一个子字符串

函数声明

		//substr函数
		string substr(size_t pos = 0, size_t len = npos);

函数定义

//substr函数

//定义方法1:
	string string::substr(size_t pos, size_t len) {
		string sub;
		if (len == npos || len >= _size - pos) {
			for (size_t i = pos; i < _size; i++) {
				sub += _str[i];
			}
		}
		else {
			for (size_t i = pos; i < pos + len; i++) {
				sub += _str[i];
			}
		}
		return sub;
	}

我们直接在新的sub字符串尾插字符

还有另外一种定义方法:

//substr函数

//定义方法2:
	string string::substr(size_t pos, size_t len) {
		if (len == npos || len >= _size - pos) {
		string  sub(_str + pos);
			return sub;
		}
		else {
			string sub;
			sub.reserve(len);//至少开len个字符的空间
			for (int i = 0; i < len; i++) //执行拷贝的次数
			{
				sub += _str[pos + i];//从pos位置开始,拷贝len个字符
			}
			return sub;
		}
	}

⑨swap函数

函数声明

		//swap函数
		void swap(string& s);

函数定义

//swap函数
	void string::swap(string& s) {
		//使用std库里面的swap函数
		std::swap(_str, s._str);		
		std::swap(_capacity, s._capacity);
		std::swap(_size, s._size);
	}

运行一下


六、重载函数

①operator=赋值函数

函数声明

		//operator=赋值函数
		string& operator=(const string& s);

函数定义

//operator=赋值函数
	string& string::operator=(const string& s) {
		char* temp = new char[s._capacity + 1];		//多开一个空间,存放'\0'
		strcpy(temp, s._str);						//数据拷贝
		delete[] _str;								//释放原来的空间
			
		_str = temp;								//将指针指向新地址
		_size = s._size;							//_size更新
		_capacity = s._capacity;					//_capacity更新

		return *this;								//返回*this
	}

运行一下

但是如果是自己赋值给自己,那么就不需要释放原来的旧空间。因此,我们需要将代码改进

//operator=赋值函数
	string& string::operator=(const string& s) {
		if (this != &s) //如果不是自身赋值
		{
			char* temp = new char[s._capacity + 1];		//多开一个空间,存放'\0'
			strcpy(temp, s._str);						//数据拷贝
			delete[] _str;								//释放原来的空间

			_str = temp;								//将指针指向新地址
			_size = s._size;							//_size更新
			_capacity = s._capacity;					//_capacity更新
		}

		return *this;								//返回*this
	}
}

开辟一块新空间,将原内容拷贝到新空间中并释放原来的空间,然后更改指针指针指向和成员变量,最后返回*this

当然了,我们还可以将代码改进一下:

// s1 = s3
string& string::operator=(const string& s) {
		if (this != &s) //如果不是自身赋值
		{
			
			string temp(s._str);//调用构造函数,用s字符串去构造temp字符串
			swap(temp);			//将temp字符串和this进行交换
		}
		return *this;			//返回*this
	}

这个写法简单来说, 本来是s3赋值给s1,赋值完毕后,应该释放原来的s1空间。

这里,我们先调用构造函数,实参s3传递给形参s,用s._str构造string类型的temp对象,temp和s开了一样大的空间,s里面的值拷贝给temp,最后是temp和this进行交换,temp的空间给了s1,s1把自己不用的空间给了temp,最后temp调用析构函数,完成对s1原来空间的释放。

就比如:我点单了,外卖员送餐到我家了,我对他还提了一个要求:帮我把垃圾带走。外卖员说,好吧好吧。

还可以进一步优化

	// s1 = s3
	string& string::operator=(string temp) {
			swap(temp);			//将temp字符串和this进行交换
		return *this;			//返回*this
	}


    //swap函数
	void string::swap(string& s) {
		//使用std库里面的swap函数
		std::swap(_str, s._str);		
		std::swap(_capacity, s._capacity);
		std::swap(_size, s._size);
	}

为什么不能传递引用&,参数为string& temp呢?

因为传递了引用&,temp是s3的别名,看似是temp和this交换,实质上是s3和s1进行交换,s3只是赋值给s1,s3不能被修改。就像a = b,把b的值赋给a,但是b不能改变。

所以,我们需要用s3调用拷贝构造(string temp = s3;),构造出一个临时的string类型的对象temp,方便和s1进行交换。参数是传值,传值传参或者传值返回用的都是拷贝后的临时变量,对于自定义类型需要调用拷贝构造拷贝临时变量。

直接用temp和this进行交换,s1的空间换给temp,temp是个局部对象,出了作用域,调用析构函数。


②operator==等几个比较函数

函数声明

		//比较函数
		bool operator<(const string& s)const;
		bool operator<=(const string& s)const;
		bool operator>(const string& s)const;
		bool operator>=(const string& s)const;
		bool operator==(const string& s)const;
		bool operator!=(const string& s)const;

函数定义

   //比较2个字符串元素的ASCII码值
     bool string::operator<(const string& s)const 
    {
		return strcmp(_str, s._str) < 0;
	}

     bool string::operator==(const string& s)const 
    {
		return strcmp(_str, s._str) == 0;
	}

我们可以先写出2个简单的函数,剩下的情况可以复用上面的函数

其余的函数如下:

//比较函数
	 bool string::operator<=(const string& s)const 
    {
		return *this < s || *this == s;
	}

	 bool string::operator>(const string& s)const 
    {
		return !(*this <= s);
	}

	 bool string::operator>=(const string& s)const 
    {
		return !(*this < s);
	}

	 bool string::operator!=(const string& s)const 
    {
		return !(*this == s);
	}

③流插入和流提取

注意:流插入和流提取不能写在string类里面。比如:cout<<d1,这样只能传递2个参数,如果放到了成员函数,就会多一个this指针,这个参数就不方便传递。因此,我们把它写在类外面。

函数声明

	//流插入和流提取
	istream& operator>>(istream& is, string& str);
	ostream& operator<<(ostream& os, const string& str);

函数定义

//流提取
	ostream& operator<<(ostream& os, const string& str) {
		for (size_t i = 0; i < str.size(); i++) {
			os << str[i];
		}
		return os;
	}

我们不需要将上述函数设置为友元,因为我们没有访问成员变量,是通过迭代器来实现的

那么,怎么实现流插入呢?

首先,我们需要获取一个一个字符

//流输入
//不完整代码(错误代码)
	istream& operator>>(istream& is, string& str) {
		char ch;
		is >> ch;

		while (ch != ' ' && ch != '\n')
			//默认遇到空格或者换行,读取结束
		{
			str += ch;//将读取到的字符尾插到str字符串后面
		}
		return is;
	}

但是这样就会出现一个问题:每次都是读取的是同一个字符,不会读取到下一个字符。

比如:我键盘输入字符串"abcdefg",结果is只读取到第一个字母'a'。因此,我们必须在while循环里面再进行读取。

//流输入
//改进版本(仍然不完善)
	istream& operator>>(istream& is, string& str) {
		char ch;
		is >> ch;

		while (ch != ' ' && ch != '\n')
			//默认遇到空格或者换行,读取结束
		{
			str += ch;
			cin >> ch;//继续读取下一个字符
		}
		return is;
	}

但是又有新的问题出现了:此时的C++中的is和cin一样,把空格或者换行当做分割符,会自动过滤掉换行或者空格,ch永远都不可能是空格或换行,就会陷入死循环。

怎么办呢?因为cin是istream类型的对象,我们可以用C++库里面提供的get()函数

//流输入
//仍然不完善
	istream& operator>>(istream& is, string& str) {
		char ch = is.get();

		while (ch != ' ' && ch != '\n')
			//默认遇到空格或者换行,读取结束
		{
			str += ch;
			ch = is.get();//继续读取下一个字符
		}
		return is;
	}

get()函数可以读取到空格或者换行

现在我们可以运行一下:

清空原字符串的内容,我们需要定义clear成员函数

//字符串清理

//函数声明
		void clear();
//函数定义
        void string::clear() {
		_str[0] = '\0';	//将原字符串的下标为0的位置为'\0'
		_size = 0;		//有效元素个数为0个
	}

//流输入
	istream& operator>>(istream& is, string& str) {
		str.clear();	//调用clear函数,清空原来的字符串的内容
		char ch = is.get();

		while (ch != ' ' && ch != '\n')
			//默认遇到空格或者换行,读取结束
		{
			str += ch;
			ch = is.get();//继续读取下一个字符
		}
		return is;
	}

当前流输入函数是可以的,但是当我们输入大量的字符串,就要频繁调用operator+=函数,不断扩容,效率低下,有什么更好的方法呢?

有的小伙伴可能会说:我可以写一个reserve函数,预留空间。但是,reserve函数里面应该开多大的空间呢?

比如:reserve(100),开了可以存放100个元素的空间。但是我如果只存放2、3个字符呢?会造成空间大量的浪费。有没有什么办法能解决这个问题?

有!我们可以定义一个buff数组,元素类型为char,能存放128个元素

		char buff[128]; //定义一个buff数组,元素类型为char
		int i = 0;		//元素的起始下标为0

每次获取到的字符不会直接调用operator+=函数,而是把它存放到buff数组中

	while (ch != ' ' && ch != '\n')
			//默认遇到空格或者换行,读取结束
		{
			//str += ch;
			buff[i++] = ch;//把读取到的字符存放到buff数组里面
			ch = is.get(); //继续读取下一个字符
		}

每读取完一个字符,下标i更新,因为buff数组只能存放128个元素,下标范围在0~127,有效元素的下标范围是0~126,最后一个下标127是留给'\0'的

while (ch != ' ' && ch != '\n')
			//默认遇到空格或者换行,读取结束
		{
			buff[i++] = ch;//把读取到的字符存放到buff数组里面

			//如果此时i为127,说明下标为0~126的位置已经有元素了
			if (i == 127) 
			{
				buff[i] = '\0'; //最后一个位置存放'\0'
				str += buff;	//此时的str就是一次性加上127个字符
                                //一次性把空间开好,避免频繁扩容
                i = 0;			//再次把i置为0
			}

			ch = is.get(); //继续读取下一个字符
		}

此时,如果遇到空格或者换行,不会进入循环,我们需要单独判断。

//此时,遇到空格或者换行,跳出循环
		if (i != 0) 
		{
			buff[i] = '\0';//将当前i的位置置为'\0'
			str += buff;   //将str更新
		}

完整代码如下:

//流插入

//字符串清理
	void string::clear() {
		_str[0] = '\0';
		_size = 0;
	}

	istream& operator>>(istream& is, string& str) {
		str.clear();
		char ch;
		ch = is.get();
		char buff[128];	//缓冲数组
		size_t i = 0;
		while (ch != ' ' && ch != '\n') //读字符读不到空格
		{
			buff[i++] = ch;
			if (i == 127) {
				buff[127] = '\0';
				str += buff;
				i = 0;
			}
			//s += ch;
			ch = is.get();
		}
		if (i > 0) {
			buff[i] = '\0';
			str += buff;
		}
		return is;
	}

输入是对内容的覆盖,所以我们首先实现一个clear函数来清空字符串,原理很简单:

//字符串清理
	void string::clear() {
		_str[0] = '\0';
		_size = 0;
	}

只需要修改_size并设置'\0'即可

注意,我们cin输入是读不到空格和换行符的。

其中,ch = is.get();从输入流中读取单个字符

我们这里创造了一个缓冲数组char buff[128];,来简单进行讲解一下:

这个数组功能就是存储字符的内容,如果数组满了,则直接进行+=将整个数组内容尾插到字符串中,并更新索引,其主要特点就是,避免了我们自己为字符串s不断扩容降低了效率,并且可能会造成空间的浪费

与cin不同的是,getline可以读取空格,所以我们只需要修改循环条件即可

istream& getline(istream& in, string& s) {
		s.clear();
		char ch;
		ch = in.get();
		char buff[128];
		size_t i = 0;
		while (ch != '\0') {
			buff[i++] = ch;
			if (i == 127) {
				buff[127] = '\0';
				s += buff;
				i = 0;
			}
			//s += ch;
			ch = in.get();
		}
		if (i > 0) {
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}

七、完整代码

string.h文件

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
#include<string.h>
#include<assert.h>

namespace bit
{
	class string
	{
	public:
		typedef char* iterator;//将char*重命名为iterator
		typedef const char* const_iterator;

		//非const版本的iterator
		iterator begin();	   //提供iterator begin()函数
		iterator end();		   //提供iterator end()函数

		//const版本的iterator
		const_iterator begin() const;	
		const_iterator end() const;

		//string();//无参构造
		string(const char* str = "");	//全缺省的构造函数
		~string();						//析构函数

		
		string(const string& s);		//拷贝构造函数

		const char* c_str() const;		//c_str函数
		size_t size() const;			//size()函数
		size_t capacity() const;		//capacity()函数

		//非const版本
		char& operator[](size_t pos);	//operator[]函数
		//const版本
		const char& operator[](size_t pos)const;

		//预留空间
		void reserve(size_t n);
		//尾插一个字符
		void push_back(char ch);
		//尾插一个字符串
		void append(const char* str);

		//operator+=函数可以构成重载,函数名相同,参数不同
		string& operator+=(char ch);
		string& operator +=(const char* str);

		//insert函数
		void insert(size_t pos, char ch);//插入字符
		void insert(size_t pos, const char* str);//插入字符串

		//erase函数
		void erase(size_t pos = 0, size_t len = npos);

		//find函数
		size_t find(char ch, size_t pos = 0);			//查找字符
		size_t find(const char* sub, size_t pos = 0);	//查找字符串

		//substr函数
		string substr(size_t pos = 0, size_t len = npos);

		//operator=赋值函数
		string& operator=(const string& s);

		//swap函数
		void swap(string& s1);

		//比较函数
		bool operator<(const string& s)const;
		bool operator<=(const string& s)const;
		bool operator>(const string& s)const;
		bool operator>=(const string& s)const;
		bool operator==(const string& s)const;
		bool operator!=(const string& s)const;

		//字符串清理
		void clear();

	private:
		char* _str;
		size_t _size;
		size_t _capacity;

		//静态成员变量npos在类里面声明
		const static size_t npos;
	};

	//流插入和流提取
	istream& operator>>(istream& is, string& str);
	ostream& operator<<(ostream& os, const string& str);

	//getline函数模拟流输入
	istream& getline(istream& in, string& s);
}

string.cpp文件

#include"string.h"
namespace bit {
	//静态成员变量npos在类外定义
	const size_t string::npos = -1;


	//指定类域,在类里面声明,在类外面定义

	//begin()函数返回的是第一个元素
	string::iterator string::begin() {
		return _str;
	}
	//end()函数返回的是最后一个元素的下一个位置
	string::iterator string::end() {
		return _str + _size;
	}

	string::const_iterator string::begin() const {
		return _str;
	}
	string::const_iterator string::end() const {
		return _str + _size;
	}


	//无参构造
//string::string() {
//	//_str = new char('\0');
//	_str = new char[1]{'\0'};
//	_size = 0;
//	_capacity = 0;
//}	

	//构造函数
	string::string(const char* str)
		//将_size放在初始化列表里面
		:_size(strlen(str))
	{
		//_str和_capacity放到函数体里面
		_str = (new char[_size + 1]);
		_capacity = _size;
		strcpy(_str, str);
	}

	//析构函数
	string::~string() {
		delete[] _str;
		_str = nullptr;
		_size = _capacity = 0;
	}

	//拷贝构造函数
	string::string(const string& s) {
		_str = new char[s._capacity + 1];	//多开1个空间,存放'\0'
		strcpy(_str, s._str);				//拷贝数据
		_capacity = s._capacity;			//设置容量
		_size = s._size;					//设置有效数据的个数
	}


	//c_str函数
	const char* string::c_str() const {
		return _str;
	}

	//size()函数
	size_t string::size() const {
		return _size;
	}

	//capacity()函数
	size_t string::capacity() const {
		return _capacity;
	}

	//operator[]函数
	char& string::operator[](size_t pos) {
		assert(pos < _size);//严格控制pos的有效区间
		return _str[pos];
	}
	//const版本
	const char& string::operator[](size_t pos)const {
		assert(pos < _size);
		return _str[pos];
	}


	void string::reserve(size_t n) {
		//如果n大于当前的_capacity,需要扩容
		if (n > _capacity) {
			//开n+1个空间,多开1个空间预留给'\0'
			//'\0'是不包括在_capacity里面的
			char* temp = new char[n + 1];
			strcpy(temp, _str);				//拷贝数据
			delete[] _str;					//释放旧空间

			_str = temp;					//将新的地址赋给_str
			_capacity = n;					//_capacity为n,代表n个有效数据
		}
	}

	//尾插一个字符
	void string::push_back(char ch) {
		如果_capacity == _size,说明空间为0或者空间满了,需要扩容
		//if (_capacity == _size) {
		//	size_t newCapacity = _capacity == 0 ? 4 : 2 * _capacity;
		//	reserve(newCapacity);
		//}
		//_str[_size] = ch;			//新的字符ch插入到原来存放'\0'的位置
		//_str[_size + 1] = '\0';		//'\0'就存放到下一个位置
		//_size++;					//_size更新

		insert(_size, ch);	//在_size位置,插入一个字符ch
	}

	//尾插一个字符串
	void string::append(const char* str) {
		获取str新字符串的长度
		//size_t len = strlen(str);

		如果_size+len大于原有的capacity,扩容
		//if (_size + len > _capacity) {
		//	reserve(_size + len);
		//}
		strcat(_str, str);//在原来的基础上,尾插str新字符串
		//strcpy(_str+_size, str);//自定义起始位置,从'\0'的位置开始
		//_size += len;			//_size更新

		insert(_size, str);//在_size位置,插入一个字符串
	}

	string& string::operator+=(char ch) {
		push_back(ch);//调用push_back函数
		return *this;
	}
	string& string::operator +=(const char* str) {
		append(str);//调用append函数
		return *this;
	}

	//insert函数
	void string::insert(size_t pos, char ch) {
		//严格控制pos的取值范围,避免越界
		assert(pos <= _size);

		//如果_capacity和_size相等,需要扩容
		if (_capacity == _size) {
			size_t newCapacity = _capacity == 0 ? 4 : 2 * _capacity;
			reserve(newCapacity);
		}

		end从'\0'的位置开始
		//int end = _size;
		将pos强转成int,避免类型不同
		//while (end >= (int)pos) {
		//	_str[end + 1] = _str[end];
		//	//最后一次: pos+1 = pos;
		//	--end;
		//	//每执行完一次,end向前挪动一位
		//}

		size_t end = _size + 1;			//end的起始位置是_size的下一个位置
		while (end > pos)				//end>pos位置继续,end==pos就结束
		{
			_str[end] = _str[end - 1];	//最后一次: 下标为0的元素挪到下标为1的位置
			end--;
		}
		_str[pos] = ch;//将pos位置放入ch字符
		++_size;	   //_size更新
	}
	void string::insert(size_t pos, const char* str) {
		//严格控制pos的取值范围,避免越界
		assert(pos <= _size);

		int len = strlen(str);				//新字符串的长度(不包括'\0')
		if (_size + len > _capacity)		//检查是否需要扩容
		{
			reserve(_size + len);
		}
		//int end = _size;					//end的起始位置_size
		//while (end >= (int)pos)				//判断end是否满足循环条件
		//{
		//	_str[end + len] = _str[end];
		//	end--;
		//}

		int end = _size + len;				  //将end设定为_size+len
		while (end > pos + len - 1)	      //判断end是否满足循环条件
		{
			_str[end] = _str[end - len];
			end--;
		}

		memcpy(_str + pos, str, len);		//使用memcpy函数,拷贝len个字符,不包括'\0'
		_size += len;						//_size更新
	}

	//erase函数
	void string::erase(size_t pos, size_t len) {
		assert(pos < _size);				 //严格控制pos的有效区间

		//len大于后面字符个数时,有多少删多少
		if (len == npos || len >= _size - pos) 
		{
			_str[pos] = '\0';				//将pos的位置改为'\0'
			_size = pos;					//有效元素的个数为pos个
		}
		else
		{
		//len小于后面的字符个数
			strcpy(_str + pos, _str + pos + len);//将后面的字符拷贝到pos位置
			_size -= len;						 //有效字符个数更新
		}
	}

	//find函数

	//从pos位置开始,查找字符
	size_t string::find(char ch, size_t pos) {
		for (int i = pos; i < _size; i++) {
			if (_str[i] == ch) {
				return i;
			}
		}
		return npos;
	}

	//从pos位置开始,查找字符串
	size_t string::find(const char* sub, size_t pos) {
		//char* p = strstr(_str + pos, sub);
		//return p - _str;//返回的就是下标

		//p指针表示找到目标字符串的地址
		char* p = strstr(_str + pos, sub);

		//如果p指针存在,直接返回目标字符串的下标
		if (p) {
			return p - _str;
		}
		else {
		//如果p指针不存在,返回npos
			return npos;
		}
	}

	//substr函数
	string string::substr(size_t pos, size_t len) {
		string sub;
		if (len == npos || len >= _size - pos) {
			for (size_t i = pos; i < _size; i++) {
				sub += _str[i];
			}
		}
		else {
			for (size_t i = pos; i < pos + len; i++) {
				sub += _str[i];
			}
		}
		return sub;
	}

	//substr函数
	//string string::substr(size_t pos, size_t len) {
	//	if (len == npos || len >= _size - pos) {
	//	string  sub(_str + pos);
	//		return sub;
	//	}
	//	else {
	//		string sub;
	//		sub.reserve(len);//至少开len个字符的空间
	//		for (int i = 0; i < len; i++) //执行拷贝的次数
	//		{
	//			sub += _str[pos + i];//从pos位置开始,拷贝len个字符
	//		}
	//		return sub;
	//	}
	//}

	//operator=赋值函数
	string& string::operator=(const string& s) {
		if (this != &s) //如果不是自身赋值
		{
			char* temp = new char[s._capacity + 1];		//多开一个空间,存放'\0'
			strcpy(temp, s._str);						//数据拷贝
			delete[] _str;								//释放原来的空间

			_str = temp;								//将指针指向新地址
			_size = s._size;							//_size更新
			_capacity = s._capacity;					//_capacity更新
		}

		return *this;								//返回*this
	}

	//swap函数
	void string::swap(string& s) {
		//使用std库里面的swap函数
		std::swap(_str, s._str);		
		std::swap(_capacity, s._capacity);
		std::swap(_size, s._size);
	}

	//比较函数
	bool string::operator<(const string& s)const {
		return strcmp(_str, s._str) < 0;
	}
	bool string::operator<=(const string& s)const {
		return *this < s || *this == s;
	}
	bool string::operator>(const string& s)const {
		return !(*this <= s);
	}
	bool string::operator>=(const string& s)const {
		return !(*this < s);
	}
	bool string::operator==(const string& s)const {
		return strcmp(_str, s._str) == 0;
	}
	bool string::operator!=(const string& s)const {
		return !(*this == s);
	}

	//字符串清理
	void string::clear() {
		_str[0] = '\0';
		_size = 0;
	}

	//流插入
	istream& operator>>(istream& is, string& str) {
		str.clear();
		char ch;
		ch = is.get();
		char buff[128];	//缓冲数组
		size_t i = 0;
		while (ch != ' ' && ch != '\n') //读字符读不到空格
		{
			buff[i++] = ch;
			if (i == 127) {
				buff[127] = '\0';
				str += buff;
				i = 0;
			}
			//s += ch;
			ch = is.get();
		}
		if (i > 0) {
			buff[i] = '\0';
			str += buff;
		}
		return is;
	}

	//流提取
	ostream& operator<<(ostream& os, const string& str) {
		for (size_t i = 0; i < str.size(); i++) {
			os << str[i];
		}
		return os;
	}

	istream& getline(istream& in, string& s) {
		s.clear();
		char ch;
		ch = in.get();
		char buff[128];
		size_t i = 0;
		while (ch != '\0') {
			buff[i++] = ch;
			if (i == 127) {
				buff[127] = '\0';
				s += buff;
				i = 0;
			}
			//s += ch;
			ch = in.get();
		}
		if (i > 0) {
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}

}

片尾

今天,在上一篇的基础上,我们深入学习了string类函数的模拟实现,希望看完这篇文章能对友友们有所帮助!!!

点赞收藏加关注!!!

谢谢大家!!!

相关推荐
wjs20245 分钟前
MongoDB 更新集合名
开发语言
monkey_meng9 分钟前
【遵守孤儿规则的External trait pattern】
开发语言·后端·rust
一只小小汤圆13 分钟前
opencascade源码学习之BRepOffsetAPI包 -BRepOffsetAPI_DraftAngle
c++·学习·opencascade
legend_jz33 分钟前
【Linux】线程控制
linux·服务器·开发语言·c++·笔记·学习·学习方法
嘿BRE43 分钟前
【C++】几个基本容器的模拟实现(string,vector,list,stack,queue,priority_queue)
c++
tangliang_cn1 小时前
java入门 自定义springboot starter
java·开发语言·spring boot
程序猿阿伟1 小时前
《智能指针频繁创建销毁:程序性能的“隐形杀手”》
java·开发语言·前端
新知图书1 小时前
Rust编程与项目实战-模块std::thread(之一)
开发语言·后端·rust
威威猫的栗子1 小时前
Python Turtle召唤童年:喜羊羊与灰太狼之懒羊羊绘画
开发语言·python
力透键背1 小时前
display: none和visibility: hidden的区别
开发语言·前端·javascript