C++初阶-string类的增删的模拟实现

发布于:2025-05-11 ⋅ 阅读:(22) ⋅ 点赞:(0)

目录

1.string::reserve(size_t n)的模拟实现

2.string::push_back(char ch)的模拟实现

3.string::append(const char* str)的模拟实现

4.string& operator+=(char ch)和string& operator+=(const char* str)的模拟实现

5.string::insert(size_t pos,size_t n,char ch)的模拟实现

问题分析和最终版本

6.string::insert(size_t pos,const char* str)的模拟实现

7.string::npos的模拟实现

8.string::erase(size_t pos = 0,size_t len=npos)的模拟实现

问题分析和代码改进

9.总结



1.string::reserve(size_t n)的模拟实现

这个函数我们只实现扩容的版本,缩容的我们用得不多,所以这里就不实现了。

如何实现?

我们通过比较n与capacity来判断是否要扩容,若n>_capacity则我们先开辟一块新的空间(n+1)个char类型的空间(存储n+1个数据(因为不包含\0所以要+1)),然后再把内容拷贝到新空间上,最后释放原空间,并把_str指向新空间,_capacity置为n。则最终代码如下:

//.cpp
void string::reserve(size_t n)
{
	if (n > _capacity)
	{
		char* tmp = new char[n + 1];
		strcpy(tmp, _str);
		delete[] _str;
		_str = tmp;
		_capacity = n;
	}
}

我们通过该测试代码:

//.cpp
void test1()
{
	string s("Hello world");
	s.reserve(20);
}

调试发现结果:

地址是已经改变了的,并且把_capacity变化的代码是正确的。

2.string::push_back(char ch)的模拟实现

这个函数实现比较简单,先看一下_size是否等于_capacity或者判断是否_size+1>_capacity来判断是否需要库容,然后用二倍扩容的知识来进行扩容(之后的那些insert、append、+=都差不多),因为如果用1.5倍扩容还要涉及到其他的如四舍五入的知识,很麻烦。则代码如下:

//.cpp
void string::push_back(char ch)
{
	if (_size == _capacity)
	{
		//扩容
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}
	_str[_size] = ch;
	++_size;
	//注意!!!
	_str[_size] = '\0';
}

用以下的函数来测试:

//.cpp
void test1()
{
	string s("Hello world");
	s.push_back('a');
}

则调试发现:

则代码无误。

3.string::append(const char* str)的模拟实现

这个函数实现起来比之前复杂一些:

首先我们需要计算出str的长度,用strlen来计算;

其次,我们需要用_size+len是否大于_capacity来判断是否需要扩容;

如果需要扩容,我们需要把newCapacity置为2*_capacity;

但是如果_size+len还是大于newCapacity呢么我们就需要把newCapacity置为len+_size了,最后再扩容至newCapacity。

而且我们还不能在插入字符串时用strcat(_str,str)来插入,因为效率太低了,要从头开始找\0。我们用新的方式:strcpy(_str+_size,str);我们就从这个_str的_size位置即之后的字符串替换为str,这个方式比之前的方式难想到,故也需要很强的思维性!

最后不要忘记把_size+=len了!

那么我们最终代码如下:

//.cpp
void string::append(const char* str)
{
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		//扩容
		size_t newCapacity = 2 * _capacity;
		//不确定二倍扩容后的空间是否足够
		if (_size + len > 2 * _capacity)
		{
			newCapacity = _size + len;
		}
		reserve(newCapacity);
	}
	strcpy(_str + _size, str);
	_size += len;
}

用以下代码进行测试:

//.cpp
void test1()
{
	string s("Hello world");
	s.append(" Hello CSDN");
}

那么结果:

则代码无误。

4.string& operator+=(char ch)和string& operator+=(const char* str)的模拟实现

这两个函数实现都是前面两个函数实现的复用,所以就只展示代码,就不测试了。

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

5.string::insert(size_t pos,size_t n,char ch)的模拟实现

这个函数实现起来基本和之前一样,这里讲一下思路:

先判断pos是否小于等于_size,这个可以用assert来判断,也可以手动判断,因为不能越界,所以我们要加上一个判断;

然后判断是否要扩容,这个和之前一样,就不做多解释了;

然后我们要把第pos位置后的字符移动n个位置,方法是从后往前移动;

最后插入n个字符。

代码如下:

//.cpp
void string::insert(size_t pos, size_t n, char ch)
{
	assert(pos <= _size);
	if (_size + n > _capacity)
	{
		size_t newCapacity = _capacity * 2;
		if (_size + n > newCapacity)
		{
			newCapacity = _size + n;
		}
		reserve(newCapacity);
	}
	size_t end = _size;
	while (end >= pos)
	{
		_str[end + n] = _str[end];
		--end;
	}
	//插入n个字符
	for (size_t i = 0; i < n; i++)
	{
		_str[pos + i] = ch;
	}
	_size += n;
}

用以下代码进行测试:

//.cpp
void test1()
{
	string s("Hello");
	//尾部插入
	s.insert(5, 5, 'x');
	cout << s.c_str() << endl;
	//中间插入
	s.insert(2, 3, 'y');
	cout << s.c_str() << endl;
	//头部插入
	s.insert(0, 3, 'z');
	cout << s.c_str() << endl;
}

但是运行结果为:

也就是说头部插入有问题,原因是什么?

问题分析和最终版本

因为size_t end=_size;虽然end会--,但end是无符号整数,不会小于0所以end>=pos这个条件是一直都成立的,所以我们不能用这个来判断。

那我们如果把end改为int类型呢?

结果没有改变,为什么?

因为在C语言中两个操作数不一样的时候会把类型进行转换,通常是范围小转换成范围大的数。但是如果是无符号数与有符号数进行比较时,就会把有符号转换为无符号去比较。那我们把pos也改为int类型吗?

不行,我们要与string类的这个函数的参数保持一致,我们不能改变它的定义。

我们可以通过强制类型转换,如:end<=(int)pos,这个pos仍然为size_t类型。或者我们把end初始化为_size+n,循环改为while(end>(pos+n-1)){_str[end]=_str[end-n];--end;}我们也可以把循环的条件改为end>=(pos+n)这个需要自己画图去理解了,要么就因为开始的时候加了n,这个时候也要加n。

最终代码如下:

void string::insert(size_t pos, size_t n, char ch)
{
	assert(pos <= _size);
	if (_size + n > _capacity)
	{
		size_t newCapacity = _capacity * 2;
		if (_size + n > newCapacity)
		{
			newCapacity = _size + n;
		}
		reserve(newCapacity);
	}
	size_t end = _size + n;
	while (end >= (pos + n) )
	{
		_str[end] = _str[end - n];
		--end;
	}
	//插入n个字符
	for (size_t i = 0; i < n; i++)
	{
		_str[pos + i] = ch;
	}
	_size += n;
}

那么最终运行结果如下:

代码没有问题!

6.string::insert(size_t pos,const char* str)的模拟实现

这个函数前面和刚刚那个函数一样,后面的只要把字符串拷贝至pos位置即可,则代码如下:

//.cpp
void string::insert(size_t pos, const char* str)
{
	assert(pos <= _size);
	size_t n = strlen(str);
	if (n == 0)
	{
		return;
	}
	if (_size + n > _capacity)
	{
		size_t newCapacity = _capacity * 2;
		if (_size + n > newCapacity)
		{
			newCapacity = _size + n;
		}
		reserve(newCapacity);
	}
	size_t end = _size + n;
	while (end >= (pos + n))
	{
		_str[end] = _str[end - n];
		--end;
	}
	//插入n个字符
	for (size_t i = 0; i < n; i++)
	{
		_str[pos + i] = str[i];
	}
	_size += n;
}

测试函数如下:

void test1()
{
	string s("Hello");
	//尾部插入
	s.insert(5, " World");
	cout << s.c_str() << endl;
	//中间插入
	s.insert(5, " Beautiful");
	cout << s.c_str() << endl;
	//头部插入
	s.insert(0, "I Love ");
	cout << s.c_str() << endl;
}

运行结果如下:

7.string::npos的模拟实现

这个npos是一个静态的且不可以被修改的成员变量,而且它是size_t类型的,但是它的值为-1,而且我们是在.cpp里面定义npos的值,而不是在.h文件中定义,这个你可以试一下在.h文件中定义会有问题的!

那么我分为两个文件来讲解:

//.h
#define _CRT_SECURE_NO_WARNINGS 1
#include<string.h>
#include<assert.h>
#include<iostream>
using namespace std;
namespace td
{
	class string
	{
	public:
		string(const char* str = " ");
		const char* c_str() const
		{
			return _str;
		}
		~string();
		void reserve(size_t n);
		void push_back(char ch);
		void append(const char* str);
		string& operator+=(char ch);
		string& operator+=(const char* str);
		void insert(size_t pos, size_t n, char ch);
		void insert(size_t pos, const char* str);

	private:
		char* _str;
		size_t _size;
		size_t _capacity;
		const static size_t npos;
	};
	void test1();
}
//.cpp
#include"test.h"
namespace td
{
	const size_t string::npos = -1;
	string::string(const char* str)
	    :_size(strlen(str))
	{
	    _capacity = _size;
	    _str = new char[_size + 1];
	    strcpy(_str, str);
	}
	string::~string()
	{
		delete[] _str;
		_str = nullptr;
		_size = _capacity = 0;
	}
	void string::reserve(size_t n)
	{
		if (n > _capacity)
		{
			char* tmp = new char[n + 1];
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = n;
		}
	}
	void string::push_back(char ch)
	{
		if (_size == _capacity)
		{
			//扩容
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}
		_str[_size] = ch;
		++_size;
		//注意!!!
		_str[_size] = '\0';
	}
	void string::append(const char* str)
	{
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			//扩容
			size_t newCapacity = 2 * _capacity;
			//不确定二倍扩容后的空间是否足够
			if (_size + len > 2 * _capacity)
			{
				newCapacity = _size + len;
			}
			reserve(newCapacity);
		}
		strcpy(_str + _size, str);
		_size += len;
	}
	string& string::operator+=(char ch)
	{
		push_back(ch);
		return *this;
	}
	string& string::operator+=(const char* str)
	{
		append(str);
		return *this;
	}
	void string::insert(size_t pos, size_t n, char ch)
	{
		assert(pos <= _size);
		if (_size + n > _capacity)
		{
			size_t newCapacity = _capacity * 2;
			if (_size + n > newCapacity)
			{
				newCapacity = _size + n;
			}
			reserve(newCapacity);
		}
		size_t end = _size + n;
		while (end >= (pos + n) )
		{
			_str[end] = _str[end - n];
			--end;
		}
		//插入n个字符
		for (size_t i = 0; i < n; i++)
		{
			_str[pos + i] = ch;
		}
		_size += n;
	}
	void string::insert(size_t pos, const char* str)
	{
		assert(pos <= _size);
		size_t n = strlen(str);
		if (n == 0)
		{
			return;
		}
		if (_size + n > _capacity)
		{
			size_t newCapacity = _capacity * 2;
			if (_size + n > newCapacity)
			{
				newCapacity = _size + n;
			}
			reserve(newCapacity);
		}
		size_t end = _size + n;
		while (end >= (pos + n))
		{
			_str[end] = _str[end - n];
			--end;
		}
		//插入n个字符
		for (size_t i = 0; i < n; i++)
		{
			_str[pos + i] = str[i];
		}
		_size += n;
	}
	void test1()
	{
		string s("Hello");
		//尾部插入
		s.insert(5, " World");
		cout << s.c_str() << endl;
		//中间插入
		s.insert(5, " Beautiful");
		cout << s.c_str() << endl;
		//头部插入
		s.insert(0, "I Love ");
		cout << s.c_str() << endl;
	}
}

这个主要是为了你们了解一下每个函数的定义和声明,之后有些函数是要复用的,所以先总结一下。

const在最前面,static在中间,最后再是size_t类型,这个我们是一定要记得顺序的!

8.string::erase(size_t pos = 0,size_t len=npos)的模拟实现

首先,我们要设置缺省值在声明时,即在.h文件中要设置缺省值,而在.cpp文件中也就是在定义中我们不能加缺省值;

然后,如果pos+len大于_size,所以我们需要先把_str[pos]='\0'然后把_size=pos,最后return;

否则,我们就要设置一个end初始化为pos+len,然后再加上一个循环,循环条件是end<=_size,循环体内是_str[end-len]=_str[end];++end我们是从前往后赋值的,所以要这样写。

最后再把_size-=len即可。

所以最终代码如下:

//.cpp
void string::erase(size_t pos, size_t len)
{
	assert(pos < _size);
	if (pos + len >= _size)
	{
		//删完了
		_str[pos] = '\0';
		_size = pos;
		return;
	}
	size_t end = pos + len;
	while (end < _size)
	{
		_str[end - len] = _str[end];
		++end;
	}
	_size -= len;
	_str[_size] = '\0';
}

测试函数如下:

void test1()
{
	string s1("Hello world");
	string s2("Hello world");
	string s3("Hello world");
	string s4("Hello world");
	cout << s1.c_str() << endl;
	//删除从中间到最后的元素
	s1.erase(5);
	cout << s1.c_str() << endl;
	//删除从头到中间的元素
	s2.erase(0, 5);
	cout << s2.c_str() << endl;
	//删除从中间一个位置到另外一个中间的位置
	s3.erase(3, 5);
	cout << s3.c_str() << endl;
	//直接删完
	s4.erase();
	cout << s4.c_str() << endl;
}

那么这个代码运行结果就会报错,为什么?

问题分析和代码改进

如果len==npos 或 pos + len 远超 _size会直接截断字符串到 pos 位置,这个思路是不行的,所以我们要么就把前面的if语句改为如下形式:

len = std::min(len, _size - pos);

这样保证了我们不会越界了,也简化了我们的代码长度,或者我们把前面的if语句改为:

if ( len >= _size - pos)
{
	//删完了
	_str[pos] = '\0';
	_size = pos;
	return;
}

因为这样就不会有len为npos时再加pos而报错了,但是我们建议还是第一种方式来进行修改,因为第二种方式最后还可能会报错的,所以最终改变后的代码为:

void string::erase(size_t pos, size_t len) 
{
    if (pos >= _size) return;         // 检查 pos 是否合法
    len = std::min(len, _size - pos); // 限制 len 的范围

    size_t end = pos + len;
    while (end < _size) 
   {
        _str[end - len] = _str[end];
        ++end;
    }
    _size -= len;
    _str[_size] = '\0'; // 确保字符串以 \0 结尾
}

至于这个std::min函数就是查找两者之间的最小值,我们也可以手动判断。

则运行结果如下:

当然,我们也可以把循环改变成如下形式(和之前的insert方式一样):

void string::erase(size_t pos, size_t len) 
{
	if (pos >= _size) return;         // 检查 pos 是否合法
	len = std::min(len, _size - pos); // 限制 len 的范围
	size_t end = pos + len;
	strcpy(_str + pos, _str + pos + len);
	//while (end < _size) 
	//{
	//	_str[end - len] = _str[end];
	//	++end;
	//}
	_size -= len;
	_str[_size] = '\0'; // 确保字符串以 \0 结尾
}

这样运行起来还是可以的!

9.总结

这一节内容还是比较多的,而且理解起来比之前的提升了不只一个档次,下一讲的内容难度可能还会有过之而不及,所以说string类这个部分也是比较考验理解的。下一讲的内容快得话明天就可以发出来,喜欢的可以一键三连哦。下讲再见。