前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【C++】string类——模拟实现

【C++】string类——模拟实现

作者头像
_小羊_
发布2024-10-16 15:15:00
870
发布2024-10-16 15:15:00
举报
文章被收录于专栏:C++

前言

通过模拟实现string类的主要接口可以使我们对string类的理解更加透彻,深入理解内存管理,可以更好地理解字符串在内存中的存储方式,以及如何进行内存分配和释放,从而避免常见的内存泄漏和溢出问题,加深对面向对象编程理念的理解,比如封装、继承和多态。


💥1、string类主要函数接口

模拟实现string类,主要是实现string类的构造、拷贝构造、运算符重载、析构等。 为了防止与标准库中string类命名冲突,我们在空间域yjz中来模拟实现我们的string类。

代码语言:javascript
复制
namespace yjz
{
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;

		//无参构造
		string()
		{}

		//带参构造
		string(const char* str = "")//合二为一为缺省参数,空字符串表示'\0'
		{}

		//拷贝构造
		string(const string& str)
		{}

		//赋值重载
		string& operator=(const string& str)
		{}

		//析构
		~string()
		{}

		size_t size() const
		{}
		size_t capacity() const
		{}

		//清理数据
		void clear()
		{}

		//返回C格式
		const char* c_str() const
		{}

		char& operator[](size_t n)//可修改
		{}

		const char& operator[](size_t n) const//常量
		{}
		
		//迭代器
		iterator begin()
		{}
		iterator end()
		{}
		const_iterator begin() const
		{}
		const_iterator end() const
		{}

		void reserve(size_t n = 0);//扩容
		void push_back(char ch);//尾插
		void append(const char* str);//追加字符串
		string& operator+=(char ch);//尾插
		string& operator+=(const char* str);//追加字符串

		void insert(size_t pos, char ch);//插入字符
		void insert(size_t pos, const char* str);//插入字符串
		void erase(size_t pos, size_t len = npos);//删除

		size_t find(char ch, size_t pos = 0) const;//返回字符位置
		size_t find(const char* str, size_t pos = 0) const;//返回字符串位置
		string substr(size_t pos, size_t len = npos) const;//截取n个字符返回

	private:
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0;

		static const size_t npos;
	};

	//实现成全局函数,支持字符串和string对象比较
	bool operator<(const string& s1, const string& s2);
	bool operator<=(const string& s1, const string& s2);
	bool operator>(const string& s1, const string& s2);
	bool operator>=(const string& s1, const string& s2);
	bool operator==(const string& s1, const string& s2);
	bool operator!=(const string& s1, const string& s2);

	ostream& operator<<(ostream& out, const string& str);
	istream& operator>>(istream& in, string& str);
}

💥2、string类的模拟实现

💥2.1 构造和析构

  • 无参构造和带参构造可以合为一个默认构造,缺省参数不能给nullptr,可以给空字符串" ",用'\0'初始化
  • _capacity不包含'\0',每次开空间都要多开一个
  • 给被拷贝构造的对象新开一块空间,用strcpy将原字符串拷贝给新对象
代码语言:javascript
复制
无参构造
//string()
//	:_str(new char[1]{'\0'})
//	,_size(0)
//	,_capacity(0)
//{}

//带参构造
string(const char* str = "")//合二为一为缺省参数,空字符串表示'\0'
{
	_size = strlen(str);
	_capacity = _size;
	_str = new char[_size + 1];//多开一个存'\0'
	strcpy(_str, str);//strcpy把'\0'也拷贝
}

//拷贝构造
string(const string& str)
{
	_str = new char[str._capacity + 1];//多开一个存'\0'
	strcpy(_str, str._str);
	_size = str._size;
	_capacity = str._capacity;
}

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

size_t size() const
{
	return _size;
}

size_t capacity() const
{
	return _capacity;
}

void clear()
{
	_str[0] = '\0';
	_size = 0;
}

//返回C格式
const char* c_str() const
{
	return _str;
}

| 拷贝构造的现代写法:

上面实现的拷贝构造函数是我们自己申请一块新空间,再将数据拷贝过来,是比较传统的写法。 除了自己申请空间外还有一种办法也可以完成拷贝构造,就是借助构造函数+swap函数掠夺,间接完成拷贝构造。

代码语言:javascript
复制
void swap(string& tmp)
{
	std::swap(_str, tmp._str);
	std::swap(_size, tmp._size);
	std::swap(_capacity, tmp._capacity);
}

//拷贝构造(现代写法)
//string s2 = s1;
string(const string& str)
{
	string tmp(str._str);//构造
	swap(tmp);
}
  • 我们想用string类对象s1拷贝构造一个新对象s2,可以用构造函数构造一个临时对象tmp,然后再让s2通过swap函数掠夺tmp的所有数据,最后释放掉tmp
  • _str需要在声明的位置给缺省值nullptr

💥2.2 运算符重载

💥2.2.1 赋值重载
  • 编译器默认生成的赋值重载完成的是浅拷贝,还是和拷贝构造一样的问题,同一块空间会析构两次,所以需要我们自己实现赋值重载,完成深拷贝
  • 先释放掉旧空间,申请一块新空间往新空间内赋值,重新申请一块空间而不在原空间内直接赋值的原因是赋值的两个对象大小可能不一样,扩容比较麻烦
  • 还要考虑自己给自己赋值的情况,虽然我们不这么干,但是语法上是允许的
  • 参数和返回值我们都用引用,可以减少拷贝提高效率
代码语言:javascript
复制
string& operator=(const string& str)
{
	//防止自己给自己赋值
	if (this != &str)
	{
		delete[] _str;
		_str = new char[str._capacity + 1];
		strcpy(_str, str._str);
		_size = str._size;
		_capacity = str._capacity;
	}
	return *this;
}

| 赋值重载的现代写法:

  • 赋值重载和拷贝构造类似,所以赋值重载也可以用类似拷贝构造的现代写法
代码语言:javascript
复制
//赋值重载(现代写法)
//s2 = s1;
string& operator=(const string& str)
{
	//防止自己给自己赋值
	if (this != &str)
	{
		//string tmp(str._str);
		string tmp(str);//构造和拷贝构造都可
		swap(tmp);
	}
	return *this;
}
  • 我们想让s2指向一块和s1一样大小的空间,再把s1的内容赋值给s2,也是可以构造 / 拷贝构造一个临时对象tmp,让s2通过swap函数掠夺tmp的所有数据
  • tmp出作用域后自动析构,s2tmp交换了所以此时析构的是s2原来指向的空间,因为s2原来指向的空间本来就要析构所以一举两得

虽然上面的做法很绝,但是还有更绝的,我们直接一步做到位:

代码语言:javascript
复制
//赋值重载(现代写法)
string& operator=(string tmp)
{
	swap(tmp);
	return *this;
}
  • 不用传引用,直接传值,因为传值传参会调用拷贝构造

💥2.2.2 [ ]重载
代码语言:javascript
复制
char& operator[](size_t n)//可修改
{
	assert(n < _size);
	return _str[n];
}

const char& operator[](size_t n) const//常量
{
	assert(n < _size);
	return _str[n];
}

💥2.2.3 +=重载
代码语言:javascript
复制
string& string::operator+=(char ch)//尾插
{
	push_back(ch);
	return *this;
}

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

💥2.2.4 <<重载
  • 流插入、流提取重载放到全局,不需要定义为友元函数,因为没有访问成员变量,只访问的成员函数
代码语言:javascript
复制
ostream& operator<<(ostream& out, const string& str)
{
	for (auto ch : str)
	{
		out << ch;
	}
	return out;
}

💥2.2.5 >>重载
  • 从输入流中提取一个字符串,将序列存储在str中,该序列将被覆盖
  • istream提取操作使用空格作为分隔符
代码语言:javascript
复制
istream& operator>>(istream& in, string& str)
{
	char ch;
	in >> ch;
	while (ch != ' ' && ch != '\n')
	{
		str += ch;
		in >> ch;
	}
	return in;
}

上面的流提取重载是存在问题的,我们期望的是读到空格或者换行就结束,但是对于字符而言是不需要分割的,没有分隔符的概念,所以提取字符会跳过空格和换行,因次不能用复用库中的流提取。

  • 为了能读到空格和换行字符,我们可以使用get
  • 调用clear清空原始数据,但不改变空间大小
代码语言:javascript
复制
istream& operator>>(istream& in, string& str)
{
	str.clear();
	char ch;
	//cin >> ch;
	ch = in.get();
	while (ch != ' ' && ch != '\n')
	{
		str += ch;
		//in >> ch;
		ch = in.get();
	}
	return in;
}

| >>重载优化

上面我们实现的>>重载虽然没什么绝对的问题,但是存在缺陷。如果输入的字符串太长,不断+=要扩容很多次,消耗还是比较大的。虽然可以提前扩容解决,但是到底扩容多大又是个问题。 为了减少拷贝,我们可以模拟一个缓冲区。先在栈上开一个大小适中的数组(理论上越大越好,实际几百比较合适),将读到的字符先放到数组中,等数组满了再拷贝到string类对象中,这样可以大大减少堆上空间的扩容次数。 虽然在栈上开了空间,但是在栈上开空间高效且消耗小,出作用域自动销毁。

代码语言:javascript
复制
istream& operator>>(istream& in, string& str)
{
	str.clear();
	char ch;
	const size_t N = 256;
	char buff[N];
	size_t i = 0;
	ch = in.get();
	while (ch != ' ' && ch != '\n')
	{
		buff[i++] = ch;
		if (i == N - 1)
		{
			buff[i] = '\0';
			str += buff;
			i = 0;
		}
		ch = in.get();
	}
	if (i > 0)
	{
		buff[i] = '\0';
		str += buff;
	}
	return in;
}
  • 模拟缓冲区,可以将扩容次数和空间消耗降到最小

💥2.2.6 关系运算符重载
  • 我们只需要写其中的两个,剩下的可以复用
代码语言:javascript
复制
bool operator<(const string& s1, const string& s2)
{
	return strcmp(s1.c_str(), s2.c_str()) < 0;
}

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);
}

bool operator==(const string& s1, const string& s2)
{
	return strcmp(s1.c_str(), s2.c_str()) == 0;
}

bool operator!=(const string& s1, const string& s2)
{
	return !(s1 == s2);
}
  • 库中写了类和类、类和字符串、字符串和类比较,我们只写了类和类比较但也能完成字符串和类比较是因为隐式类型转换(但是类型转换中间会构造一个临时对象,再用这个临时对象去拷贝构造,编译器优化为直接构造

💥2.3 迭代器

  • 普通迭代器
代码语言:javascript
复制
typedef char* iterator;
iterator begin()
{
	return _str;//指向第一个字符
}

iterator end()
{
	return _str + _size;//指向'\0'
}
  • 常量迭代器
代码语言:javascript
复制
typedef const char* const_iterator;
const_iterator begin() const
{
	return _str;//指向第一个字符
}

const_iterator end() const
{
	return _str + _size;//指向'\0'
}

💥2.4 扩容

  • 大了扩容,小了不变
  • 手动异地扩容
  • 多开一个空间用于存字符'\0'
  • 拷贝完原始数据后手动释放旧空间
代码语言:javascript
复制
void string::reserve(size_t n)//【异地扩容】
{
	if (n > _capacity)
	{
		char* tmp = new char[n + 1];//多开一个存'\0'
		strcpy(tmp, _str);
		delete[] _str;
		_str = tmp;
		tmp = nullptr;
		_capacity = n;//不包含'\0'
	}
}

💥2.5 插入

💥2.5.1 尾插
  • 如果空间不够,可以调用reserve来扩容
  • 尾插一个字符后,不要忘了++_size
  • 因为尾插的字符覆盖了字符'\0',所以我们还要在字符串末尾手动加上'\0'
代码语言:javascript
复制
void string::push_back(char ch)//尾插
{
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : 2 * _capacity);
	}
	_str[_size++] = ch;
	_str[_size] = '\0';//尾插完后不要忘了补上'\0'
}

💥2.5.2 追加字符串
  • 追加字符串之前,我们要算一下追加后的字符串长度如果长度大于二倍的原空间,就按字符串长度扩;如果小于二倍的原空间,就按二倍扩容
  • 扩容完用strcpy拷贝追加的字符串时,要追加到_str + _size的位置
  • 追加完成后及时更新_size的值
代码语言:javascript
复制
void string::append(const char* str)//追加字符串
{
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
	}
	strcpy(_str + _size, str);
	_size += len;//更新_size
}

💥2.5.3 插入字符

这里有一个比较容易踩坑的点,像下面实现的函数,当我们在除了_size == 0位置插入字符外其他地方都是可以正常实现的,但是头插会陷入死循环。

代码语言:javascript
复制
void string::insert(size_t pos, char ch)//插入字符
{
	assert(pos <= _size);
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : 2 * _capacity);
	}
	size_t end = _size;
	while (end >= pos)
	{
		_str[end + 1] = _str[end--];
	}
	_str[pos] = ch;
	_str[_size + 1] = '\0';
}

头插会失败的原因是:我们定义的end无符号整型,所以end始终都是不小于0的,有同学可能会说把end改为int类型不就好了?但是就算用int定义end,这个函数还是会陷入死循环。

因为pos是无符号整型,posend比较时pos会使end转换为无符号整型,然后再参与比较。

关于算数转换更多详细内容请跳转阅读 —> C语言(操作符)2

要解决这个问题可以强制类型转换,但更建议像下面这样修改:

代码语言:javascript
复制
void string::insert(size_t pos, char ch)//插入字符
{
	assert(pos <= _size);
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : 2 * _capacity);
	}
	size_t end = _size + 1;
	while (end > pos)
	{
		_str[end--] = _str[end - 1];
	}
	_str[pos] = ch;
	_size++;
}

💥2.5.4 插入字符串
  • 插入字符串前也要先计算一下插入后字符串的总长度,如果长度大于二倍的原空间,就按字符串长度扩;如果小于二倍的原空间,就按二倍扩容
  • 写循环结束条件时要细心的画图确定,如果有空格字符也要挪动
  • 插入字符串和追加字符串不同,追加字符串可以调用strcpy函数,因为strcpy函数可以将'\0'也拷贝过来,而插入字符串不需要将'\0'插入进来,所以不适合使用strcpy,可以考虑循环插入
代码语言:javascript
复制
void string::insert(size_t pos, const char* str)//插入字符串
{
	assert(pos <= _size);
	size_t len = strlen(str);
	if (len + _size > _capacity)
	{
		reserve(len + _size > 2 * _capacity ? len + _size : 2 * _capacity);
	}
	size_t end = _size + len;
	while (end > pos + len - 1)
	{
		_str[end--] = _str[end - len];
	}
	for (size_t i = 0; i < len; i++)
	{
		_str[pos + i] = str[i];
	}
	_size += len;
}
  • 其实插入一个字符和插入一个字符串本质上是一样的,不同的只是一些值的差异和细节的处理

💥2.6 删除

  • string类里面声明函数时可以给一个缺省值npos,如果函数调用时只给一个实参则默认pos位置后面的内容全部删除
  • 函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省值
代码语言:javascript
复制
void erase(size_t pos, size_t len = npos);//删除
  • npos我们也只能在.h文件中声明(声明在string类private成员变量下,为静态常量),.cpp文件中定义npos为无符号整型,-1的补码为全1,经过算数转换后就成了整型最大值
代码语言:javascript
复制
const size_t string::npos = -1;
  • 擦除字符串值中从字符位置开始并跨越 len 字符的部分,如果内容太短或 len string::npos,则擦除直到字符串末尾的部分
  • 要分两种情况分别讨论len大于pos位置后面字符串的长度和小于后面字符串的长度
  • 删除后也要及时更新_size的值
代码语言:javascript
复制
void string::erase(size_t pos, size_t len)//删除
{
	assert(pos < _size);
	if (len >= _size - pos)//pos后面全删
	{
		_str[pos] = '\0';
		_size = pos;
	}
	else//pos后面不全删
	{
		for (size_t i = pos; i < _size - len + 1; i++)
		{
			_str[i] = _str[i + len];
		}
		_size -= len;
	}
}

💥2.7 查找

💥2.7.1 查找字符
  • 当只传一个实参时,默认从头开始往后查找
  • 当指定 pos 时,搜索仅包括位置 pos 处或位置后的字符,而忽略任何可能出现的包含 pos 之前字符的情况
代码语言:javascript
复制
size_t string::find(char ch, size_t pos)//返回字符位置
{
	for (size_t i = pos; i < _size; i++)
	{
		if (_str[i] == ch)
		{
			return i;
		}
	}
	return npos;//没找到则返回npos
}

💥2.7.2 查找字符串
  • 当只传一个实参时,默认从头开始往后查找
  • 可以调用strstr来完成,注意第一个参数不是_str,而是_str + pos
  • 如果没找到则返回npos,找到了则返回字符串第一个字符的下标,可以用ptr - _str两指针相减来获得下标
代码语言:javascript
复制
size_t string::find(const char* str, size_t pos) const//返回字符串位置
{
	assert(pos < _size);
	const char* ptr = strstr(_str + pos, str);
	if (nullptr == ptr)
	{
		return npos;
	}
	else
	{
		return ptr - _str;
	}
}

💥2.7.3 返回子串
  • 只传一个实参时默认返回pos位置后面的整个子串
  • sub += _str[pos + i];是尾插一个字符,这一步骤不能写sub[i] = _str[pos + i];因为虽然运算符重载的operator[]可以返回指定位置的值,但是此时sub只是开了len个长度的空间,没有元素,_size为0,assert(pos < _size)会报错
代码语言:javascript
复制
string string::substr(size_t pos, size_t len) const//截取n个字符返回
{
	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;
}

💥3、string类模拟实现完整代码

string.h:

代码语言:javascript
复制
#pragma once

#include <iostream>
#include <assert.h>
using namespace std;

namespace yjz
{
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;

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

		//带参构造
		string(const char* str = "")//合二为一为缺省参数,空字符串表示'\0'
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_size + 1];//多开一个存'\0'
			strcpy(_str, str);//strcpy把'\0'也拷贝
		}

		//拷贝构造(传统写法) 1
		//string(const string& str)
		//{
		//	_str = new char[str._capacity + 1];//多开一个存'\0'
		//	strcpy(_str, str._str);
		//	_size = str._size;
		//	_capacity = str._capacity;
		//}

		void swap(string& tmp)
		{
			std::swap(_str, tmp._str);
			std::swap(_size, tmp._size);
			std::swap(_capacity, tmp._capacity);
		}

		//拷贝构造(现代写法)
		string(const string& str)
		{
			string tmp(str._str);//构造
			swap(tmp);
		}

		赋值重载(传统写法) 1
		//string& operator=(const string& str)
		//{
		//	//防止自己给自己赋值
		//	if (this != &str)
		//	{
		//		delete[] _str;
		//		_str = new char[str._capacity + 1];
		//		strcpy(_str, str._str);
		//		_size = str._size;
		//		_capacity = str._capacity;
		//	}
		//	return *this;
		//}

		赋值重载(现代写法) 2
		//string& operator=(const string& str)
		//{
		//	防止自己给自己赋值
		//	if (this != &str)
		//	{
		//		string tmp(str._str);
		//		string tmp(str);//构造和拷贝构造都可
		//		swap(tmp);
		//	}
		//	return *this;
		//}

		//赋值重载(现代写法)
		string& operator=(string tmp)
		{
			swap(tmp);
			return *this;
		}

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

		size_t size() const
		{
			return _size;
		}

		size_t capacity() const
		{
			return _capacity;
		}

		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}

		//返回C格式
		const char* c_str() const
		{
			return _str;
		}

		char& operator[](size_t n)//可修改
		{
			assert(n < _size);
			return _str[n];
		}

		const char& operator[](size_t n) const//常量
		{
			assert(n < _size);
			return _str[n];
		}

		//迭代器
		iterator begin()
		{
			return _str;//指向第一个字符
		}
		iterator end()
		{
			return _str + _size;//指向'\0'
		}
		const_iterator begin() const
		{
			return _str;//指向第一个字符
		}
		const_iterator end() const
		{
			return _str + _size;//指向'\0'
		}

		void reserve(size_t n = 0);//扩容
		void push_back(char ch);//尾插
		void append(const char* str);//追加字符串
		string& operator+=(char ch);//尾插
		string& operator+=(const char* str);//追加字符串

		void insert(size_t pos, char ch);//插入字符
		void insert(size_t pos, const char* str);//插入字符串
		void erase(size_t pos, size_t len = npos);//删除

		size_t find(char ch, size_t pos = 0) const;//返回字符位置
		size_t find(const char* str, size_t pos = 0) const;//返回字符串位置
		string substr(size_t pos, size_t len = npos) const;//截取n个字符返回

	private:
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0;

		static const size_t npos;
	};

	//实现成全局函数,支持字符串和string对象比较
	bool operator<(const string& s1, const string& s2);
	bool operator<=(const string& s1, const string& s2);
	bool operator>(const string& s1, const string& s2);
	bool operator>=(const string& s1, const string& s2);
	bool operator==(const string& s1, const string& s2);
	bool operator!=(const string& s1, const string& s2);

	ostream& operator<<(ostream& out, const string& str);
	istream& operator>>(istream& in, string& str);
}

string.cpp:

代码语言:javascript
复制
#define  _CRT_SECURE_NO_WARNINGS

#include "string.h"

namespace yjz
{
	const size_t string::npos = -1;

	void string::reserve(size_t n)//【异地扩容】
	{
		if (n > _capacity)
		{
			char* tmp = new char[n + 1];//多开一个存'\0'
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = n;//不包含'\0'
		}
	}

	void string::push_back(char ch)//尾插
	{
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : 2 * _capacity);
		}
		_str[_size++] = ch;
		_str[_size] = '\0';//尾插完后不要忘了补上'\0'
	}

	void string::append(const char* str)//追加字符串
	{
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
		}
		strcpy(_str + _size, str);
		_size += len;//更新_size
	}

	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, char ch)//插入字符
	{
		assert(pos <= _size);
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : 2 * _capacity);
		}
		size_t end = _size + 1;
		while (end > pos)
		{
			_str[end--] = _str[end - 1];
		}
		_str[pos] = ch;
		_size++;
	}

	void string::insert(size_t pos, const char* str)//插入字符串
	{
		assert(pos <= size());
		int length = strlen(str);
		if (size() + length > capacity())
		{
			reserve(size() + length > 2 * capacity() ? size() + length : 2 * capacity());
		}
		for (size_t i = size(); i >= pos; i--)
		{
			_str[i + length] = _str[i];
		}
		for (size_t i = 0; i < length; i++)
		{
			_str[i + pos] = *(str + i);
		}
		_size += length;
	}

	void string::erase(size_t pos, size_t len)//删除
	{
		assert(pos < _size);
		if (len >= _size - pos)//pos后面全删
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else//pos后面不全删
		{
			for (size_t i = pos; i < _size - len + 1; i++)
			{
				_str[i] = _str[i + len];
			}
			_size -= len;
		}
	}

	size_t string::find(char ch, size_t pos) const//返回字符位置
	{
		assert(pos < _size);
		for (size_t i = pos; i < _size; i++)
		{
			if (_str[i] == ch)
			{
				return i;
			}
		}
		return npos;//没找到则返回npos
	}

	size_t string::find(const char* str, size_t pos) const//返回字符串位置
	{
		assert(pos < _size);
		const char* ptr = strstr(_str + pos, str);
		if (nullptr == ptr)
		{
			return npos;
		}
		else
		{
			return ptr - _str;
		}
	}

	string string::substr(size_t pos, size_t len) const//截取n个字符返回
	{
		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;
	}

	bool operator<(const string& s1, const string& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) < 0;
	}

	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);
	}

	bool operator==(const string& s1, const string& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) == 0;
	}

	bool operator!=(const string& s1, const string& s2)
	{
		return !(s1 == s2);
	}

	ostream& operator<<(ostream& out, const string& str)
	{
		for (auto ch : str)
		{
			out << ch;
		}
		return out;
	}

	//istream& operator>>(istream& in, string& str)
	//{
	//	str.clear();
	//	char ch;
	//	//cin >> ch;
	//	ch = in.get();
	//	while (ch != ' ' && ch != '\n')
	//	{
	//		str += ch;
	//		//in >> ch;
	//		ch = in.get();
	//	}
	//	return in;
	//}

	istream& operator>>(istream& in, string& str)
	{
		str.clear();
		char ch;
		const size_t N = 256;
		char buff[N];
		size_t i = 0;
		ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == N - 1)
			{
				buff[i] = '\0';
				str += buff;
				i = 0;
			}
			ch = in.get();
		}
		if (i > 0)
		{
			buff[i] = '\0';
			str += buff;
		}
		return in;
	}
}

test.cpp:

代码语言:javascript
复制
#define  _CRT_SECURE_NO_WARNINGS

#include "string.h"

namespace yjz
{
	void test_string1()
	{
		string s1;
		string s2("hello world");
		cout << s1.c_str() << endl;
		cout << s2.c_str() << endl;
		for (size_t i = 0; i < s2.size(); i++)
		{
			cout << s2[i] << " ";
		}
		cout << endl;

		string s3("hello world");
		string::iterator it = s3.begin();
		while (it != s3.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}

	void test_string2()
	{
		string s1("hello world");
		s1.push_back('#');
		cout << s1.c_str() << endl;
		s1.append("Are you ok?");
		cout << s1.c_str() << endl;
		s1 += '%';
		cout << s1.c_str() << endl;
		s1 += "Are you ok?";
		cout << s1.c_str() << endl;
		s1.insert(1, '&');
		s1.insert(0, '*');
		cout << s1.c_str() << endl;
		s1.insert(6, "Are you ok?");
		cout << s1.c_str() << endl;
		s1.erase(1, 5);
		cout << s1.c_str() << endl;
	}

	void test_string3()
	{
		string s("hello world");
		size_t pos = s.find('l');
		string suffix = s.substr(pos, 6);
		cout << suffix.c_str() << endl;
		string s1("hello world");
		string s2 = s1;
		cout << s2.c_str() << endl;
		string s3("Are you ok?");
		s1 = s3;
		cout << s1.c_str() << endl;
	}

	void test_string4()
	{
		string s1("hello world");
		string s2;
		s2 = s1;
		cout << s1 << endl;
		cout << s2 << endl;
	}
}

int main()
{
	yjz::test_string4();
	return 0;
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-08-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 💥1、string类主要函数接口
  • 💥2、string类的模拟实现
    • 💥2.1 构造和析构
      • 💥2.2 运算符重载
        • 💥2.2.1 赋值重载
        • 💥2.2.2 [ ]重载
        • 💥2.2.3 +=重载
        • 💥2.2.4 <<重载
        • 💥2.2.5 >>重载
        • 💥2.2.6 关系运算符重载
      • 💥2.3 迭代器
        • 💥2.4 扩容
          • 💥2.5 插入
            • 💥2.5.1 尾插
            • 💥2.5.2 追加字符串
            • 💥2.5.3 插入字符
            • 💥2.5.4 插入字符串
          • 💥2.6 删除
            • 💥2.7 查找
              • 💥2.7.1 查找字符
              • 💥2.7.2 查找字符串
              • 💥2.7.3 返回子串
          • 💥3、string类模拟实现完整代码
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档