【C++初阶】模拟实现string的常见操作

这篇具有很好参考价值的文章主要介绍了【C++初阶】模拟实现string的常见操作。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

👦个人主页:@Weraphael
✍🏻作者简介:目前学习C++和算法
✈️专栏:C++航路
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注✨


一、准备工作

为了方便管理代码,分两个文件来写:

  • Test.cpp - 测试代码逻辑
  • string.h - 模拟实现string
    【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

二、string的结构

我们知道,string是一个管理字符数组的类,底层其实就是一个支持动态增长的字符数组,就像数据结构学的动态顺序表。

namespace wj
{
	class string
	{
	public:

	private:
		char* _str; // 指向动态开辟数组
		size_t _size; // 字符个数(不包含'\0')
		size_t _capacity; // 容量(不包含'\0')
	};
}

string类的成员变量有三个

  1. 字符指针_str指向开辟的动态数组
  2. _size标识有效数据个数(不包含'\0'),
  3. _capacity记录容量的大小(一般也不包含'\0'

还需要注意的是:我们新命名了命名空间域wj,就是避免和库中的string产生冲突。

三、模拟实现常见初始化操作

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

3.1 用C字符串构造

namespace wj
{
	class string
	{
	public:
		// 用C字符串构造
		string(const char* s)
			:_str(new char[strlen(s) + 1]) // +1是为'\0'
			, _size(strlen(s)) 
			, _capacity(_size)
		{
			// 拷贝数据
			memcpy(_str, s, _size + 1);
			// memcpy以拷贝字节个数
			// +1是为了拷贝'\0'
		}

	private:
		char* _str; // 动态字符数组
		size_t _size; // 字符个数
		size_t _capacity; // 容量
	};
}

以上代码有个易错点:要注意初始化列表的顺序,是按照成员变量的顺序来赋值的!

3.2 c_str

为了验证代码的正确性,需要打印出结果。由于自己模拟实现的string,还没有实现重载流插入<< ,所以不能直接打印string对象,而流插入<<是可以打印内置类型的。因此string是有提供转为内置类型的接口c_str

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

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

为什么会在函数后加个const?在往期博客我们讲过:只要成员函数内部不修改成员变量,都应该加上const

接下来来测试代码:

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

3.3 无参构造

无参构造的结果就是空字符,默认是有'\0'

string()
	:_str(new char[1])
	, _size(0)
	,_capacity(_size)
{
	_str[0] = '\0';
}

【测试结果】

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

3.4 拷贝构造

自定义类型的拷贝必须先调用拷贝构造函数。但如果不手动编写,编译器会默认生成一个浅拷贝/值拷贝的拷贝构造函数,即将所有成员变量逐一拷贝到新对象中(浅拷贝),这种拷贝方式对于内置类型或者全是自定义类型的成员变量来说是没有问题的。但是,如果类中有动态分配内存的指针变量,则需要手动编写深拷贝的拷贝构造函数。

// string s1("hello world");
// string s2(s1); // 拷贝构造
// 这里的str是s1的别名,其实就是s1

string(const string& str)
{
	// 1. 开一个和s1一样大的空间
	_str = new char[str._capacity + 1];
	// 2. 将s1的数据拷贝给s2
	memcpy(_str, str._str, str._size + 1);
	_size = str._size;
	_capacity = str._capacity;
}

注意:以上代码其实隐藏了一个this指针,这个this指针其实就是s2,因此以上代码本质就是:

string(const string& str)
{
	this->_str = new char[str._capacity + 1];
	memcpy(this->_str, str._str, str._size + 1);
	this->_size = str._size;
	this->_capacity = str._capacity;
}

但是高手是不会把this写出来的

【测试结果】

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

四、析构函数

由于成员变量含有动态内存开辟的空间,因此要手动写出析构函数

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

五、模拟实现常见遍历操作

5.1 下标访问[] + size接口

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

size_t size() const 
{
	return _size;
}

// 可读可写
// 可以引用返回:对象不在此函数域(在堆上),出了此作用域不会被销毁
char& operator[](size_t pos)
{
	// 断言,防止越界
	assert(pos >= 0 && pos < _size); 
	return _str[pos];
}

// 可读不可写
const char& operator[](size_t pos) const 
{
	assert(pos >= 0 && pos < _size);
	return _str[pos];
}

【测试结果】

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

5.2 迭代器

  • 迭代器iterator本质上就是一个容器的内嵌类型(内置类型),可以是内部类(自定义类型)、也可以是typedef的类型。而string的迭代器本质就是一个char*的指针,因此直接typedef即可。
  • 但是要注意:typedef要写在public段中,因为迭代器本身就是要给类外用的。
// 可读可写
typedef char* iterator;

iterator begin() 
{
	// 指向第一个字符
	return _str;
}
iterator end() 
{
	// 指向最后一个有效字符的下一个位置
	return _str + _size;
}

// 可读不可写
typedef const char* const_iterator;

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

【测试结果】

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

在往期博客我们将过,范围for的底层就是迭代器,因此也可以使用范围for:

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

六、尾插

6.1 push_back - 尾插一个字符 + reserve - 扩容

要尾插字符之前,首先需要考虑当前有效字符个数_size是否大于当前容量_capacity,如果大于,则需要扩容。因此,string库里同样也提供了扩容操作:

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

注意:reserve一般不会缩容

void reserve(size_t n)
{
	if (n > _capacity)
	{
		// 开一块新的空间
		char* tmp = new char[n + 1];
		// 拷贝数据到新的空间
		memcpy(tmp, _str, _size + 1);
		// 释放旧空间然后指向新空间
		delete[] _str;
		_str = tmp;
		_capacity = n;
	}
}

void push_back(char ch)
{
	// 可能存在扩容
	if (_size == _capacity)
	{
		// 假设默认2倍扩容
		// 如果是空串,扩了2倍容量还是0,因此要考虑容量为0的情况
		reserve(_capacity == 0 ? 4 : _capacity * 2); 
	}
	// 插入
	_str[_size] = ch;
	_size++;
	// 别忘了在末尾加'\0'
	_str[_size] = '\0';
}

【测试结果】

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

6.2 append - 尾插字符串

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

string& append(const char* s)
{
	// 可能存在扩容
	size_t n = strlen(s);
	if (_size + n > _capacity)
	{
		// 至少扩容_size+n
		reserve(_size + n);
	}
	memcpy(_str + _size, s, n + 1);
	_size += n;

	return *this;
}

【测试结果】

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

6.3 运算符重载+= – push_back和append升级版

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

它既可以尾插一个字符,还可以尾插字符串。因此,直接复用push_backappend即可

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

// +=字符
string& operator+=(char c)
{
	push_back(c);
	return *this;
}

【测试结果】

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

七、插入insert

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

string的插入接口设计有点冗余,我们直接实现最常见的即可

  • . pos位置插入n个字符

【思路】

  1. 首先要判断下标的合法性。
  2. 其次还要判断插入的字符加上原有的字符是否超过当前容量,超过就扩容。
  3. 然后就是挪动数据和插入数据。注意挪动数据一定要从最后一个字符'\0'开始挪动n次;不能从pos位置开始挪,否则后面的内容就被覆盖了。以下是动图展示
    【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言
// pos - 下标
// n - 插入的字符个数
// x - 插入的字符
void insert(size_t pos, size_t n, char x) 
{
	// 判断下标pos的合法性
	assert(pos >= 0 && pos <= _size);

	// 可能存在扩容
	if (_size + n > _capacity)
	{
		reserve(_size + n);
	}

	// 挪动数据
	// 从'\0'位置上开始挪动
	size_t end = _size;
	while (end >= pos)
	{
		// 一个字符挪动n次
		_str[end + n] = _str[end];
		end--;
	}
	
	// 插入数据
	for (size_t i = 0; i < n; i++)
	{
		_str[pos + i] = x;
	}
	_size += n;
}

通过思路分析,不难可以写出以上代码。但是以上代码有一个bug,当头插时,程序就崩溃了(如下图)

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

通过走读代码我们发现,posend的类型是都是无符号类型size_t。因此end最后自减到-1,由于类型是size_t,而无符号的-1是一个相当大的数,循环条件成立就会一直死循环下去。

因此这里有两种方法:

第一种:将posend的类型全部改为int

void insert(int pos, size_t n, char x) 
{
	// 判断下标pos的合法性
	assert(pos >= 0 && pos <= _size);

	// 可能存在扩容
	if (_size + n > _capacity)
	{
		reserve(_size + n);
	}

	// 挪动数据
	int end = _size;
	while (end >= pos)
	{
		_str[end + n] = _str[end];
		end--;
	}
	// 插入数据
	for (int i = 0; i < n; i++)
	{
		_str[pos + i] = x;
	}
	_size += n;
}

以上这种方法虽然可以,但是和库里提供的类型是有所差别的,因此还是有些不好。

第二种:既然size_t类型的end自减到-1就会死循环,那么加个end != -1不就完事了。恰好,string库里提供了公共静态成员常量npos,这个常量使用值-1定义。

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

所以,最终代码如下:

namespace wj
{
	class string
	{
	public:
		void insert(size_t pos, size_t n, char x) 
		{
			// 判断下标pos的合法性
			assert(pos >= 0 && pos <= _size);

			// 可能存在扩容
			if (_size + n > _capacity)
			{
				reserve(_size + n);
			}

			// 挪动数据
			size_t end = _size;
			while (end != npos && end >= pos)
			{
				// 挪动n次
				_str[end + n] = _str[end];
				end--;
			}
			// 插入数据
			for (size_t i = 0; i < n; i++)
			{
				_str[pos + i] = x;
			}
			_size += n;
		}
		
	private:
		char* _str; // 动态字符数组
		size_t _size; // 字符个数
		size_t _capacity; // 容量

		static const size_t npos;
	};
	const size_t string::npos = -1;
}

需要注意的是:静态成员需要在类外定义

【测试结果】

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

  • pos位置插入字符串

思路和以上类似,这里就不过多赘述了

void insert(size_t pos, const char* str)
{
	// 判断下标pos的合法性
	assert(pos <= _size);
	
	// 可能存在扩容
	size_t length = strlen(str);
	if (_size + length > _capacity)
	{
		reserve(_size + length);
	}
	// 挪动数据
	size_t end = _size;
	while (end >= pos && end != npos)
	{
		_str[end + length] = _str[end];
		end--;
	}
	// 插入数据
	for (size_t i = 0; i < length; i++)
	{
		_str[pos + i] = str[i];
	}
	_size += length;
}

【测试结果】

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

八、删除操作erase

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

【思路】

  • 首先要检查下标的合法性
  • 删除要分情况讨论:
    第一种:当前下标往后的字符全都需要删除
    【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言
    第二种:删除的字符是符合范围内的
    【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言
string& erase(size_t pos = 0, size_t len = npos)
{
	// 检查下标合法性
	assert(pos >= 0 && pos < _size);
	
	// pos位置(包括pos)后面全部删除的情况
	if (len == npos || pos + len >= _size)
	{
		_str[pos] = '\0';
		_size = pos;
	}
	// 删一部分的情况 
	else
	{
		size_t end = pos + len;
		while (end <= _size)
		{
			_str[pos] = _str[end];
			pos++;
			end++;
		}
		_size -= len;
	}
	return *this;
}

【测试结果】

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

九、查找操作find

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

  • 查找字符
// 从下标pos开始查找字符,如果实参不传第二个参数,默认从下标为0开始查找
size_t find(char x, size_t pos = 0)  const
{
	// 检查查找下标的合法性
	assert(pos >= 0 && pos < _size);
	// 遍历查找即可
	for (size_t i = pos; i < _size; i++)
	{
		if (_str[i] == x)
		{
			// 找到返回下标
			return i;
		}
	}
	// 没找到默认返回npos,其实就是-1
	return npos;
}

【测试结果】

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

  • 查找字符串

查找子串可以有很多方法,最简便就是使用C语言中的strstr函数

size_t find(const char* str, size_t pos = 0) const
{
	// 检查查找下标的合法性
	assert(pos >= 0 && pos < _size);

	//strstr(const char * str1, const char * str2) - 从str1中查找str2
	const char* ptr = strstr(_str + pos, str);
	// 如果ptr为空说明没找到
	if (ptr == nullptr)
	{
		// 找不到返回npos
		return npos;
	}
	// 否则找到了
	else
	{
		return ptr - _str;
	}
}

【测试结果】

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

十、截取substr

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

// pos - 截取的下标
// len - 截取的长度
// npos - 截取的最大长度
string substr(size_t pos = 0, size_t len = npos) const
{
	// 判断下标的合法性
	assert(pos >= 0 && pos < _size);

	// 分两种情况,
	// 1. 可能需要截到尾
	// 2. 可能需要截取一部分
	
	size_t n = len;
	// 截取到尾的情况
	if (len == npos || pos + len >= _size)
	{
		// 左闭右开
		n = _size - pos;
	}
	// 截取一部分情况
	else
	{
		string tmp;
		tmp.reserve(n);
		for (size_t i = pos; i < pos + n; i++)
		{
			// 将截取的字符全部放去tmp
			tmp += _str[i];
		}
    }
	return tmp;
}

十一、改变字符串的有效个数resize

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

三种情况:

  • n小于size,相当于删除数据,保留n个字符
  • n等于size,则保留原数据
  • n大于size,则会扩容,同时后面会补充n - size个字符(不指定默认是'\0'
void resize(size_t n, char ch = '\0')
{
	// 相当于删除数据,只保留前n个字符
	if (n < _size) 
	{
		_size = n;
		_str[_size] = '\0';
	}
	
	else // 否则相当于插入数据
	{
	  	// 可能有扩容情况,n比_capacity大才会扩容,n=_capacity什么也不发生
		reserve(n);
		for (size_t i = _size; i < n; i++) // 填数据
		{
			_str[i] = ch;
		}
		_size = n;
		_str[_size] = '\0';
	}
}

十二、流插入<<

成员函数默认第一个形参都是对象的地址,也就是隐藏的this指针。由于cout抢占了对象的第一个位置,因此不能当做成员函数,就只能写在类外。这里和以往实现日期类不同的是不需要在类中定义成友元(没有访问类的私有成员)

// 注意:必须引用返回,不然会报错。因为ostream这个类做了一个防拷贝
ostream& operator<<(ostream& out, const string& s)
{
	for (auto ch : s)
	{
		out << ch;
	}
	return out;
}

总结流插入和c_str的差别

  • c_str返回的是C形式的字符串,遇到'\0'就不打印
  • 流插入打印是不管'\0',有多少字符就打印多少字符

十三、流提取>>

istream& operator>>(istream& in, string& s)
{
	char ch = in.get();

	while (ch != ' ' && ch != '\n')
	{
		s += ch;// += 自己就会扩容
		ch = in.get();
	}
	return in;
}

注意:不能用in(本质上就是cin)直接读取,因为当我们输入完字符后,都是以换行结束,但in默认读取到空格或者换行就不会往下读了(只有getline可以)。istream类里有get接口可以读取空格和换行

但以上代码还是不够完善,当多次对一个对象输入的情况,要对之前形成一次覆盖,可以对比库里的string

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

自己实现的上一次输入的数据没有清理干净。因此要清理上次的内容

istream& operator>>(istream& in, string& s)
{
	// 清理
	s.clear();
	
	char ch = in.get();

	while (ch != ' ' && ch != '\n')
	{
		s += ch;// += 自己就会扩容
		ch = in.get();
	}
	return in;
}

【测试结果】

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

还有一个问题,当一开始输入连续空格或者换行时,可以对比自己实现的和库里的:

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

因此要过滤前面的空格或者换行

istream& operator>>(istream& in, string& s)
{
	// 清理
	s.clear();
	
	char ch = in.get();
	// 处理前缓冲区前面的空格或者换行
	while (ch == ' ' || ch == '\n')
	{
		ch = in.get();
	}

	while (ch != ' ' && ch != '\n')
	{
		s += ch;// += 自己就会扩容
		ch = in.get();
	}
	return in;
}

【测试结果】

【C++初阶】模拟实现string的常见操作,C++,c++,c语言,笔记,学习,visualstudio,开发语言

但是以上代码的性能不够好,当输入好几个字符时,由于+=就会不断进行扩容,就会有损耗。

优化思路:提前开128个空间的字符数组,减少扩容的消耗

istream &operator>>(istream &in, string &s)
{
    s.clear();

    char ch = in.get();

    // 处理前缓冲区前面的空格或者换行
    while (ch == ' ' || ch == '\n')
    {
        ch = in.get();
    }

	// 开128个空间的字符数组
    char buff[128];
    int i = 0;

    while (ch != ' ' && ch != '\n')
    {
        buff[i++] = ch;
        // 当i为127,就要给'\0'预留一个空间
        if (i == 127)
        {
            buff[i] = '\0';
            s += buff;
			
            i = 0;
        }
        ch = in.get();
    }
    // 处理循环结束后如果i不为0,
    if (i)
    {
        buff[i] = '\0';
        s += buff;
    }
    return in;
}

十四、清空操作clear

直接将第一个字符改成'\0'即可

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

十五、比较操作

15.1 <

// s1 < s2
bool operator<(const string& s) const
{	
	// 以短的字符串为基础来比较
	int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);

	// 如果ret == 0
	// 说明以短的字符串为基础比较的字符对应位置都是相等
	// 则长的字符串大
    if (ret == 0)
         return _size < s._size;
         
    // 否则
    // ret < 0说明s1 < s2为真
    // ret>0,则s1>s2为假
    else // ret != 0
         return ret < 0;
}

15.2 ==

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

当写完<==,剩下的代码就可以复用了。

15.3 <=

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

15.4 >

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

15.5 >=

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

15.6 !=

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

十六、交换操作swap

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

十七、赋值运算符重载=

默认不写是以值的方式逐字节拷贝(浅拷贝/值拷贝),因此内置类型成员变量是直接赋值的,而自定义类型成员会去调用它的默认构造函数。但要注意动态开辟的成员变量。如果不写深拷贝,两个对象会同时指向动态开辟的空间,就会导致析构两次的问题。

法一:传统写法:

// s1 = s2
// s1是隐藏的this指针

// 方法:
// 首先开一个和s2同样大的空间并且把s2的数据拷贝
// 然后再释放掉s1指向的空间,最后再让s1指向新拷贝的那个空间
string& operator=(const string& s)
{
	if (this != &s)
	{
		// 首先开一个和s2同样大的空间并且把s2的数据拷贝
		char* tmp = new char[s._capacity + 1];
		memcpy(tmp, s._str, s._size + 1);
		// 然后再释放掉s1指向的空间
		delete[] _str;
		// 最后再让s1指向新拷贝的那个空间
		_str = tmp;

		_size = s._size;
		_capacity = s._capacity;
	}
	return *this;
}

法二:现代写法

// 方法:
// 用s2拷贝构造tmp的对象,然后再让tmp和s1交换
string& operator=(const string& s)
{
	if (this != &s)
	{
		string tmp(s);
		swap(tmp);
	}
	return *this;
}

法二延伸:文章来源地址https://www.toymoban.com/news/detail-654062.html

//s1 = s2
// 传参时,s2调用拷贝构造给tmp(深拷贝),然后再和s1交换
// 交换的是深拷贝的对象

string& operator=(string tmp)
{
	swap(tmp);
	return *this;
}

十八、源码string.h

#pragma once
#include <assert.h>

namespace wj
{
	class string
	{
	public:
	
		string(const char* str)
			:_str(new char[strlen(str) + 1])
			, _size(strlen(str)) 
			, _capacity(_size) 
		{
			memcpy(_str, str, _size + 1);
		}

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

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

		string()
			:_str(new char[1])
			, _size(0)
			, _capacity(0)
		{
			_str[0] = '\0';
		}

		size_t size() const 
		{
			return _size;
		}

		char& operator[](size_t pos)
		{
			assert(pos < _size); 
			return _str[pos];
		}

		const char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}
		
		typedef char* iterator;
		
		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		typedef const char* const_iterator;

		const_iterator begin() const
		{
			return _str;
		}

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

		void reserve(size_t n) 
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				memcpy(tmp, _str, _size + 1);
				delete[] _str;
				_str = tmp;
				_capacity = n;
			}
		}

		void push_back(char ch)
		{
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2); 
				_str[_size] = ch;
				_size++;
				_str[_size] = '\0';
			}
		}

		void append(const char* s)
		{
			size_t length = strlen(s);
			if (_size + length > _capacity)
			{
				reserve(_size + length);
			}
			memcpy(_str + _size, s, length + 1);
			_size += length;
		}

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

		string& operator+=(const char* s)
		{
			append(s);
			return *this;
		}
		
		string& insert(size_t pos, int n, char x) // 函数重载
		{
			assert(pos <= _size);

			if (_size + n > _capacity)
			{
				reserve(_size + n);
			}

			size_t end = _size;

			while (end >= pos && end != npos)
			{
				_str[end + n] = _str[end];
				end--;
			}
			for (size_t i = 0; i < n; i++)
			{
				_str[pos + i] = x;
			}
			_size += n;
			
			return *this;
		}

		string& insert(size_t pos, const char* str)
		{
			assert(pos <= _size);

			size_t length = strlen(str);
			if (_size + length > _capacity)
			{
				reserve(_size + length);
			}

			size_t end = _size;
			while (end >= pos && end != npos)
			{
				_str[end + length] = _str[end];
				end--;
			}

			for (size_t i = 0; i < length; i++)
			{
				_str[pos + i] = str[i];
			}
			_size += length;

			return *this;
		}


		string& erase(size_t pos, size_t len = npos)
		{
			assert(pos <= _size);

			if (len == npos || len + pos >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				size_t end = pos + len;
				while (end <= _size)
				{
					_str[pos] = _str[end];
					pos++;
					end++;
				}
				_size -= len;
			}
			return *this;
		}

		size_t find(char x, size_t pos = 0) 
		{
			assert(pos < _size);

			for (size_t i = pos; i < _size; i++)
			{
				if (_str[i] == x)
				{
					return i;
				}
			}
			return npos;
		}

		size_t find(const char* str, size_t pos = 0) 
		{
			assert(pos < _size);
			if (ptr == nullptr)
			{
				return npos;
			}
			else
			{
				return ptr - _str;
			}
		}

		string substr(size_t pos = 0, size_t len = npos) const
		{
			assert(pos < _size);

			size_t n = len;
			if (len == npos || pos + len > _size)
			{
				n = _size - pos;
			}

			string tmp;
			tmp.reserve(n);
			for (size_t i = pos; i < pos + n; i++)
			{
				tmp += _str[i];
			}
			return tmp;
		}

		string(const string& s)
		{
			// 深拷贝
			_str = new char[s._capacity + 1];
			memcpy(_str, s._str, s.size() + 1);
			_size = s._size;
			_capacity = s._capacity;
		}
		
		void resize(size_t n, char ch = '\0')
		{
			if (n < _size)
			{
				_size = n;
				_str[_size] = '\0';
			}
			else 
			{
				reserve(n);
				for (size_t i = _size; i < n; i++) 
				{
					_str[i] = ch;
				}
				_size = n;
				_str[_size] = '\0';
			}
		}

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

		bool operator<(const string& s) const
		{

			int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);
			return ret == 0 ? _size < s._size : ret < 0;
		}

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

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

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

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

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

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

		// 法一(传统写法):

		/*string& operator=(const string& s)
		{
			if (this != &s)
			{
				char* tmp = new char[s._capacity + 1];
				memcpy(tmp, s._str, s._size + 1);
				delete[] _str;
				_str = tmp;

				_size = s._size;
				_capacity = s._capacity;
			}
			return *this;
		}*/

		// 法二(现代写法):
		/*string& operator=(const string& s)
		{
			if (this != &s)
			{
				string tmp(s);
				swap(tmp);
			}
			return *this;
		}*/

		// 法二的延伸
		
		//s1 = s2
		// 传参时,s2调用拷贝构造给tmp(深拷贝),然后再和s1交换
		/*
		string& operator=(string tmp)
		{
			swap(tmp);

			return *this;
		}
		*/
		
	private:
		char* _str;
		size_t _size;
		size_t _capacity;

		static size_t npos;
	};
	size_t string::npos = -1;

	ostream& operator<<(ostream& out, const string& s)
	{
		for (auto ch : s)
		{
			out << ch;
		}
		return out;
	}
	
	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		
		char ch = in.get();
		
		while (ch == ' ' || ch == '\n')
		{
			ch = in.get();
		}

		while (ch != ' ' && ch != '\n')
		{
			s += ch;
			ch = in.get();
		}
		return in;
	}


	// 优化版
	/*
    istream &operator>>(istream &in, string &s)
    {
        s.clear();

        char ch = in.get();

        // 处理前缓冲区前面的空格或者换行
        while (ch == ' ' || ch == '\n')
        {
            ch = in.get();
        }

        char buff[128];
        int i = 0;

        while (ch != ' ' && ch != '\n')
        {
            buff[i] = ch;
            // 当i为127,就要给'\0'预留一个空间
            if (i == 127)
            {
                buff[i] = '\0';
                s += buff;

                i = 0;
            }
            ch = in.get();
        }
        // 处理循环结束后如果i不为0,
        if (i)
        {
            buff[i] = '\0';
            s += buff;
        }
        return in;
    }
    */
}

到了这里,关于【C++初阶】模拟实现string的常见操作的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++初阶之一篇文章让你掌握string类(模拟实现)

    模拟实现 std::string 是一个有挑战性的练习,它可以带来多方面的收益,尤其对于学习 C++ 和深入了解字符串操作以及动态内存管理的机制。以下是模拟实现 std::string 的一些好处和重要意义: 学习 C++ 内存管理 :std::string 是一个动态分配内存的容器,模拟实现需要手动处理内

    2024年02月15日
    浏览(30)
  • 【C++初阶】list的常见使用操作

    👦个人主页:@Weraphael ✍🏻作者简介:目前学习C++和算法 ✈️专栏:C++航路 🐋 希望大家多多支持,咱一起进步!😁 如果文章对你有帮助的话 欢迎 评论💬 点赞👍🏻 收藏 📂 加关注✨ 功能:将数据进行链式存储。 链表( list )是一种物理存储单元上 非连续的存储结构 ,

    2024年02月11日
    浏览(26)
  • 【C++】手撕string(string的模拟实现)

    手撕string目录: 一、 Member functions 1.1 constructor 1.2  Copy constructor(代码重构:传统写法和现代写法) 1.3 operator=(代码重构:现代写法超级牛逼) 1.4 destructor 二、Other member functions 2.1 Iterators(在string类中,迭代器基本上就是指针) 2.1.1 begin() end() 2.1.2  范围for的底层

    2024年02月08日
    浏览(35)
  • 【C++】模拟实现string

      目录 🌞专栏导读 🌛定义string类  🌛构造函数 🌛拷贝构造函数 🌛赋值函数 🌛析构函数  🌛[]操作符重载  🌛c_str、size、capacity函数  🌛比较运算符重载   🌛resize与reserve函数 🌛push_back、append函数  🌛insert函数  🌛erase函数 🌛find函数  🌛swap函数 🌛clean函数  🌛

    2024年02月14日
    浏览(33)
  • 【C++】string模拟实现

    个人主页🍖:在肯德基吃麻辣烫 本文带你进入string的模拟实现,对于string,是我们深入学习STL的必要途径。 我在模拟实现string时,成员变量如下: 1.1 无参构造(默认构造) 构造时不进行任何初始化,则默认为空字符串 比如: bit::sring s1; 1.2 普通构造 思路: 1.先新申请一块空

    2024年02月16日
    浏览(35)
  • C++——string模拟实现

    前言:上篇文章我们对string类及其常用的接口方法的使用进行了分享,这篇文章将着重进行对这些常用的接口方法的内部细节进行分享和模拟实现。 目录 一.基础框架 二.遍历字符串 1.[]运算符重载 2.迭代器 3.范围for 三.常用方法 1.增加 2.删除 3.调整 4.交换 5.查找 6.截取 7.比较

    2024年03月12日
    浏览(28)
  • C++初阶-vector类的模拟实现

    C++ STL中的vector就类似于C语言当中的数组,但是vector又拥有很多数组没有的接口,使用起来更加方便。 相比于STL中的string,vector可以定义不同的数据类型。 迭代器的本质可以暂时看作是指针,模拟实现vector,需要定义三个指针:指向起始位置_start,指向最后一个有效元素的下

    2024年02月04日
    浏览(145)
  • 【C++初阶】第十篇:list模拟实现

    我们经常说list在底层实现时就是一个链表,更准确来说,list实际上是一个带头双向循环链表。 因此,我们若要实现list,则首先需要实现一个结点类。而一个结点需要存储的信息有:数据、前一个结点的地址、后一个结点的地址,于是该结点类的成员变量也就出来了(数据、

    2023年04月17日
    浏览(30)
  • C++模拟实现string类

    在C语言中,字符串是以’\\0’结尾的字符的集合,为了操作方便,C标准库中已经提供了一些str系列的库函 数,但是这些库函数与字符串是分离的,不符合面向对象编程的思想,而且底层空间需要用户自己管理,很可能会造成越界访问。 C++中对于string的定义为:typedef basic_s

    2024年02月13日
    浏览(61)
  • [C++]string及其模拟实现

    目录 string及其模拟实现::                                        1.构造函数                                        2.拷贝构造函数                                        3.赋值运算符重载                                        4.析构函数        

    2024年02月07日
    浏览(60)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包