【C++】string类 (模拟实现详解 下)

时间:2024-10-29 20:16:06

       

         我们接着上一篇【C++】string类 (模拟实现详解 上)-****博客继续对string模拟实现。从这篇内容开始,string相关函数的实现就要声明和定义分离了。

1.reserve、push_back和append

string.hstring类里进行函数的声明。

void reserve(size_t n); //扩容
void push_back(const char x);//尾插字符
void append(const char* str);//尾插字符串

string.cpp中进行函数的实现。这个文件要包含include "string.h",同样用命名空间。

n小于_capacity的情况都默认为空间不变,n大于_capacity扩容。

#include "string.h"
namespace lyj //命名空间
{
	void string::reserve(size_t n)
	{
		if (n > _capacity)
		{
			char* temp = new char[n + 1];//多开一个,留给'\0'
			strcpy(temp, _str);//把_str里的数据拷贝到新空间
			delete[] _str; //释放旧空间
			_str = temp; //让_str指向新空间
            _capacity = n; //更新_capacity的值
		}
	}
}

reserve实现好了,push_back可以直接复用reserve的扩容。还是在命名空间里实现。

void string::push_back(const char x)//尾插字符
{
	if (_size == _capacity)//两者相等时空间不够
	{
		//扩容,2倍扩,如果_capacity为0,就直接给4
		string::reserve(_capacity == 0 ? 4 : _capacity * 2);
	}
	_str[_size] = x;//插入字符
	++_size;//更新_size的值 
	_str[_size] = '\0';//末尾一定是'\0'
}

新插入的x会把原来末尾的\0覆盖,所以我们要在新末尾加上\0。

append也是需要复用reserve。push_back是2倍扩容,append我们就要分情况决定扩容大小了。

void string::append(const char* str)//尾插字符串
{
	size_t len = strlen(str);//计算要插入的字符串的长度
	if (_size + len > _capacity)//如果空间不够,扩容
	{
		//如果插入的字符串特别长,2倍扩就会扩容频繁,所以在这里判断一下
		reserve(_size + len > _capacity * 2 ? _size + len : _capacity * 2);
	}
	strcpy(_str + _size, str);//从原字符串的'\0'出开始拷贝新的str
	_size += len; //更新_size的大小
}

如果插入的字符串特别长,2倍扩就会扩容频繁,所以如果原字符串长度加上插入的新字符串长度比_capacity的两倍要,我们就不以2倍扩容,直接扩到原字符串长度加上插入的新字符串长度那么大,反之,还是以2倍扩。

test.cpp中测试一下。

void test2()
{
	string s1("hello world");
	s1.push_back('x');
	s1.append("yyyyyyyyyyyyyyyyyyy");
	cout << s1.c_str() << endl;
}
int main()
{
	lyj::test2(); //指定命名空间调用函数

	return 0;
}

2.operator+=

operator+=有两种形式,+=字符+=字符串

string.hstring类里进行函数的声明。

string& operator+=(char ch);//+=字符
string& operator+=(const char* str);//+=字符串

string.cpp中进行函数的实现。

 前面push_back实现好了之后,operator+=字符就可以直接复用他。

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

同样的,append实现好之后,operator+=字符串就可以直接复用他。

string& string::operator+=(const char* str)//+=字符串
{
	append(str);
	return *this;
}

 在test.cpp中测试一下。

void test3()
{
	string s1("hello world");
	s1 += '$';
	s1 += '\n';
	s1 += "hello ****";
	cout << s1.c_str() << endl;
}
int main()
{
	lyj::test3(); //指定命名空间调用函数

	return 0;
}

3.insert

insert我们也实现两个形式,插入字符和插入字符串。 

string.hstring类里进行函数的声明。

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

3.1 插入字符

string.cpp中进行函数的实现。

假设我们现在要在2位置插入x

执行_str[_size+1]==_str[_size] 。

然后--_size。 

然后再执行_str[_size+1]==_str[_size] 。

在while循环里重复执行,当_size等于pos时。

 

所以循环退出的条件是_size<pos。然后把_size+1的字符换成插入的x。

 我们再考虑特殊情况,当_size为0,此时_size+1为1。上面的代码可行。但是我们不可以直接用_size遍历,这样就直接改变_size的值,我们用size来遍历。代码如下。

void string::insert(size_t pos, char ch)//插入字符
{
	assert(pos >= 0 && pos <= _size);
	int size = _size; //用size遍历,不用_size
	if (_size = _capacity)//空间不够开空间
	{
		string::reserve(_capacity == 0 ? 4 : _capacity * 2);
	}
	while (size >= pos)
	{
		_str[size + 1] = _str[size];
		--size;
	}
	_str[size + 1] = ch;
	++_size;
}

但是我们会发现,当size=0时,代码运行崩溃了。因为C语言里,两个数比较的时候会自动发生类型提升。所以这里的int类型的size被提升成了和pos一样的类型,导致代码死循环。所以这里要做一个类型转换。

void string::insert(size_t pos, char ch)//插入字符
{
	assert(pos >= 0 && pos <= _size);
	int size = _size; //用size遍历,不用_size
	if (_size = _capacity)//空间不够开空间
	{
		string::reserve(_capacity == 0 ? 4 : _capacity * 2);
	}
	while (size >= (int)pos) //类型转换
	{
		_str[size + 1] = _str[size];
		--size;
	}
	_str[size + 1] = ch;
	++_size;
}

 在test.cpp中测试一下。

void test4()
{
	string s;
	string s1("hello world");
	s1.insert(0, 'x');
	cout << s1.c_str() << endl;
}
int main()
{
	lyj::test4(); //指定命名空间调用函数
	
	return 0;
}

3.2 插入字符串

string.cpp中进行函数的实现。

假设我们现在要在3位置插入8个x。

我们要在位置3往后留出len个长度。

 让_str[size+len]=_str[size]。

然后size--; size+len也就前移了。

再执行 _str[size+len]=_str[size];

一直循环,到size等于pos。

 执行 _str[size+len]=_str[size];

然后size--;

此时数据的移动就完成了。我们把8个x插进去。_str+pos位置开始插,插len个。

void string::insert(size_t pos, const char* str)//插入字符串
{
	assert(pos >= 0 && pos <= _size);
	size_t len = strlen(str);//提前计算str的长度
	int size = _size; //用size遍历,不用_size
	if (_size + len > _capacity)//空间不够开空间
	{
		//如果str太长,不按2倍扩
		reserve(_size + len > _capacity * 2 ? _size + len : _capacity * 2);
	}
	while (size >= (int)pos)//要类型转换
	{
		_str[size + len] = _str[size];
		--size;
	}
	//把str插进去,起始位置是_str+pos
	//这里不用strcpy,因为strcpy会把\0也拷贝过去。
	memcpy(_str + pos, str, len);//用memcpy
    _size += len;//更新_size的值
}

这里不用strcpy,因为strcpy会把\0也拷贝过去,我们选择用memcpy,拷贝len个字节,就是str的内容大小。

 在test.cpp中测试一下。

void test4()
{
	string s;
	string s1("hello world");
	s1.insert(3, "xxxxxxxx");
	cout << s1.c_str() << endl;
}

头插、尾插、中间插入都是没问题的 。

4.erase

把pos位置开始的len个字符删了。删除数据的时候,参数列表第二个参数要给一个缺省值npos,这个npos要自己定义,在string类里。

private:
	char* _str;
	size_t _size;
	size_t _capacity;
	static const size_t npos = -1;//这里可以直接初始化,只有整形可以

string.hstring类里进行函数的声明。

void erase(size_t pos, size_t len = npos);//删除

string.cpp中进行函数的实现。

分两种情况讨论。

(1)len比pos后面的剩余的字符数大。

所以我们只需要把pos位置改为\0。此时_size=pos. 

(2) len比pos后面的剩余的字符数小。

 

这样就可以了,第一个\0后面的东西根本不用管。此时_size大小为_size-len。 

void string::erase(size_t pos, size_t len)//删除
{
	assert(_size > 0);//删除数据前提是有数据
	assert(pos >= 0 && pos < _size);
	if (len >= _size - pos)//情况1
	{
		_str[pos] = '\0';
		_size = pos;
	}
	else //情况2
	{
		while (pos + len <= _size)
		{
			_str[pos] = _str[pos + len];
			pos++;
		}
		_size -= len;
	}
}

test.cpp中测试一下。

void test5()
{
	string s;
	string s1("hello world");
	s1.erase(0, 1);//头删
	cout << s1.c_str() << endl;

	s1.erase(5, 3);//中间删
	cout << s1.c_str() << endl;

	s1.erase(3);//不传第二个参数
	cout << s1.c_str() << endl;
}

结果没问题。

5.find

string.hstring类里进行函数的声明。

size_t find(char ch, size_t pos = 0);
size_t find(const char* str, size_t pos = 0);

 在string.cpp中进行函数的实现。

size_t string::find(char ch, size_t pos)
{
	assert(pos < _size);
	size_t n = pos;
	while (_str[n] != ch)
	{
		n++;
		if (n == _size)
		{
			return npos;
		}
	}
	return n;
}
size_t string::find(const char* str, size_t pos = 0)
{
	assert(pos < _size);
	const char* ptr = strstr(_str + pos, str);
	if (ptr == nullptr)
	{
		return npos;
	}
	else
	{
		return ptr - _str;//返回下标
	}
}

6.substr

string.hstring类里进行函数的声明。

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

string.cpp中进行函数的实现。

string string::substr(size_t pos, size_t len)
{
	assert(pos < _size);
	if (len > _size - pos)
	{
		len = _size - pos;
	}
	string sub;
	sub.reserve(len);
	for (size_t i = 0; i < len; i++)
	{
		sub += _str[pos + i];
	}
	return sub;
}

 在test.cpp中测试一下。和find一起测试。

void test6()
{
	string s("test.cpp.zip");
	size_t pos = s.find('.');
	string suffix = s.substr(pos);
	cout << suffix.c_str() << endl;
}

string类的模拟实现就说这么多。模拟实现目的是帮助我们更好的理解string。

本篇就到这里,拜拜~