(超详细 + 逐个解读) C++的STL学习 4 : string类的模拟实现(具体实现)

上一次我们实现了string的基本功能, 为了加深对string容器的理解, 这次我们要实现他的很多基本功能, 虽说是实现, 但也不可能和库中的string完全相同, 只能说尽量向库中的容器靠拢.
那么话不多说, 我们直接开始吧. 可能会有点多, 耐心看完, 你一定有所收获~

一. 成员变量

上次string的简单实现只有一个char*, 这次的话, 会加入size和capacity

成员变量如下:

private:
	char* _ptr;
	size_t _size; //有效字符个数
	size_t _capacity; //可以存放的最大有效字符个数

注: 前四个函数因为上次实现的时候已经做过详细解释, 这里就不多做说明, 有兴趣的朋友可以去看看上一篇的简单实现, 链接奉上~
string容器的简单实现

二. 基本功能

1. 构造函数

构造函数, 没什么好说的, 想比之前唯一要注意的点就是:
别忘了给_size和_capacity初始化

代码如下:

	//全缺省的构造函数
	String(const char* str = "") 
		:_ptr(new char[strlen(str) + 1])
		,_size(strlen(str))
		,_capacity(_size)
	{
		strcpy(_ptr, str);
	}

2. 拷贝构造

直接现代写法奉上: 跟上面一样, 别忘了给新的成员变量初始化
还有就是这里要自定义一个交换函数, 内容就是交换三个成员变量

代码如下:


	//交换函数
	void Swap(String& str) {
		swap(_ptr, str._ptr);
		swap(_size, str._size);
		swap(_capacity, str._capacity);
	}

	//现代写法
	String(const String& str)
		:_ptr(nullptr)  //这里指针一定要初始化, 否则交换完释放的时候空间有问题
		, _size(0)
		, _capacity(0)
	{
		String tmp(str._ptr);
		Swap(tmp);
	}

3. 赋值运算符重载函数

也是一样, 直接现代写法奉上

	//现代写法
	String& operator=(String str) {
		Swap(str);
		return *this;
	}

4. 析构函数

析构就是释放资源, 别忘了给_size和_capacity赋0, 养成良好的习惯

	//析构
	~String() {
		if (_ptr) {
			delete[] _ptr;
			_ptr = nullptr;
			_size = _capacity = 0;
		}
	}

三. 插入删除相关

1. reserve增容函数

这里的reserve函数只实现增容的逻辑,
基本逻辑就是: 当n > _capacity时进行增容逻辑
增容的话大家也不陌生了, 无非就是开空间, 拷贝, 然后释放原有空间, 最后更新成员变量

这里开空间的时候直接开了n + 1个空间, 因为无法确定_capacity和n的具体关系, 为了保证空间够用, 直接新开n + 1个空间, 多开一个存放'\0'

	//增容, 不实现缩容
	void reserve(size_t n) {
		if (n > _capacity) {
			//开空间, 拷贝
			//这里无法保证_capacity和n的具体大小关系, 所以直接开够n个空间即可
			char* tmp = new char[n + 1];
			strcpy(tmp, _ptr);
			
			//释放空间, 改变指向, 更新容量
			delete[] _ptr;
			_ptr = tmp;
			_capacity = n;
		}
	}

2. 尾插字符 push_back

尾插一个字符: 首先, 只要是插入接口就必须要判断是否要增容
然后进行尾插操作, 最后一定不要忘了新增一个’\0’

这里增容之前我创建了一个newCap表示新的容量, 运用三目运算符给他赋值, 如果当前_capacity 为 0, 就像库中一样, 给一个初值15, 如果不是, 那么以2倍的规则增容

	//尾插字符
	void push_back(const char ch) {
		if (_size == _capacity) {
			//增容
			//如果是空的话, 给初值为15(模拟库中的string), 否则以2倍增容
			size_t newCap = _capacity == 0 ? 15 : 2 * _capacity;
			reserve(newCap);
		}
		//尾插
		_ptr[_size++] = ch;
		//最后一定不要忘了补一个'\0'~
		_ptr[_size] = '\0';
	}

3. 尾插字符串append

尾插字符串, 首先也要考虑增容, 这里先获取要插入的字符串长度len
_size + len > _capacity, 进行增容. 由于无法确定要插入的字符串到底有多长, 所以这里直接以_size + len作为参数进行增容
然后进行尾插: 直接用strcpy把字符串拷贝进来即可, 这里直接把’\0’也拷贝进来, 所以不用手动添加, 不过想手动添加也没什么问题
最后别忘了更新成员变量_size

	//尾插字符串 append
	void append(const char* str) {
		//获取字符串长度
		size_t len = strlen(str);
		//判断增容
		if (_size + len > _capacity) {
			//直接增容到_size + len, 因为无法确定_size + len有多大
			reserve(_size + len);
		}

		//尾插字符串, 这里把str的'\0'也拷贝过来了
		strcpy(_ptr + _size, str);
		_size += len;
	}

4. +=运算符重载函数

+=运算符其实才是我们最常用的尾插方法
上面已经完整的写出了尾插的接口, 那么+=只需要复用上面的代码即可
这里写了三个重载函数, 分别是+=一个字符, +=一个字符串, +=一个String对象

能复用的代码尽量复用, 既能方便我们代码的书写, 也方面后期的代码维护

	//+=运算符重载函数
	//+= char ch
	String& operator+=(const char ch) {
		push_back(ch);
		return *this;
	}

	//+= char* str
	String& operator+= (const char* str) {
		append(str);
		return *this;
	}

	//+= String
	String& operator+=(const String& str) {
		append(str._ptr);
		return *this;
	}

接下来我们测试一下上面的代码是否正确, 测试代码如下:

void test() {
	String s = "abcde";
	String s2("xyz");
	s2 = s;
	
	s.clear();
	s.push_back('1');
	s.push_back('2');
	s.push_back('3');

	s.append("abc");

	s2 += '0';
	s2 += "QAQ";
	s2 += s;
}

调试窗口如下:
在这里插入图片描述

这里建议大家把测试代码拿去自己调试一下, 因为每一行都涉及变化, 截图过于繁琐, 故不在此展示

5. 在任意位置插入 insert

  1. 插入一个字符
    首先这里使用了一个断言, 保证代码运行下去时位置pos一定是小于有效字符_size的
    插入依然是要考虑增容, 和上面大同小异
    这里的核心就是移动元素了, 插入的话要从后往前移动元素, 不然数据会被覆盖 移动的写法有很多, 可以的话最好自己画一个图演示一下, 这是最清晰的
    最后就是插入和增加’\0’

下面给出一个图解:
在这里插入图片描述

	//任意位置插入
	void insert(size_t pos, const char& ch) {
		assert(pos < _size);

		//判断增容 
		if (_size == _capacity) {
			size_t newCap = _capacity == 0 ? 15 : 2 * _capacity;
			reserve(newCap);
		}
		//移动元素 --> 从后向前, 不然会覆盖
		size_t end = _size;
		while (end > pos) {
			_ptr[end] = _ptr[end - 1];
			--end;
		}

		//插入新字符
		_ptr[pos] = ch;
		//添加新的结束标志
		_ptr[++_size] = '\0';
	}

下面演示一个经常遇到的错误写法:

			while (end >= pos) {
			_ptr[end + 1] = _ptr[end];
			--end;
			}

当pos = 0时, 会无限循环, 因为end是size_t类型, 无符号, 永远大于等于0

  1. 插入一个字符串
    与上面基本一致, 增容的逻辑和上面的append一致, 这里不再多说
    最大的不同就是元素的移动了, 下面会给出图解
    最后插入字符串不用strcpy是因为strcpy会把最后的’\0’拷贝进来, 所以选择使用memset memset是字节拷贝, 最后一个参数是字节大小

给出图解:
在这里插入图片描述

	void insert(size_t pos, const char* str) {
		assert(pos <= _size);
		int len = strlen(str);
		
		//增容
		if (_size + len > _capacity) {
			reserve(_size + len);
		}

		//移动元素  
		size_t end = _size + len;
		// end: [pos + len, _size + len]  '\0'也一起移动到末尾了
		while (end >= pos + len) {
			_ptr[end] = _ptr[end - len];
			--end;
		}

		//插入字符串
		memcpy(_ptr + pos, str, len * sizeof(char));

		_size += len;
	}

6. 删除erase

这里删除接口就不写那么多了, 直接一个接口搞定
要注意的是: 删除时移动元素是从前向后移动, 不然会数据会被覆盖
至于元素的移动, 跟上面插入大同小异, 画个图即可…写法很多, 下面是我的一种

代码如下:

	//删除
	void erase(size_t pos, size_t len) {
		//移动元素 ---> 从前向后移动, 不然会覆盖
		size_t start = pos + len;
		while (start <= _size) {
			_ptr[start - len] = _ptr[start];
			start++;
		}
		//更新_size
		_size -= len;
	}

四. 迭代器相关(重点)

迭代器: 访问容器元素的一种机制
类似于指针
操作: 移动, 解引用, 判断
迭代器实现: char*

	typedef char* iterator;
	typedef const char* const_iterator;

1. begin()

	//begin: 第一个元素的位置
	iterator begin() {
		return _ptr;
	}

		const_iterator begin() const {
		return _ptr;
	}

2. end()

	//end: 最后一个元素的下一个位置
	iterator end() {
		return _ptr + _size;
	}
		
	const_iterator end() const {
		return _ptr + _size;
	}

3. operator[]

	//[]重载
	//可读可写
	char& operator[](size_t pos) {
		assert(pos < _size);
		return _ptr[pos];
	}

	//只读
	const char& operator[](size_t pos) const {
		assert(pos < _size);
		return _ptr[pos];
	}

有了这些我们就可以进行遍历了

下面给出3种遍历方式的代码:

void test4() {
	String s = "12345";
//1. []
	for (int i = 0; i < s.size(); i++) {
		cout << s[i] << " ";
	}
	cout << endl;

//2. 迭代器
	String::iterator it = s.begin();
	while (it != s.end()) {
		cout << *it << " ";
		it++;
	}
	cout << endl;

//范围for
	for (const auto& ch : s) {
		cout << ch << " ";
	}
	cout << endl;
}

运行结果:
在这里插入图片描述

五. 逻辑运算符

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

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

	bool operator>(const String& s) {
		if (strcmp(_ptr, s.c_str()) > 0) 
			return true;
		return false;
	}

	bool operator>=(const String& s) {
		return (*this > s) || (*this == s);
	}

	bool operator==(const String& s) {
		if (strcmp(_ptr, s.c_str()) == 0)
			return true;
		return false;
	}

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

测试代码:

void test5() {
	String s = "123";
	String s2 = "123";
	String s3 = "02";
	String s4 = "9";

	cout << (s == s2) << endl;
	cout << (s != s2) << endl;
	cout << (s > s3) << endl;
	cout << (s <= s4) << endl;
	cout << (s >= s2) << endl;
	cout << (s4 < s2) << endl;

}

运行结果:
在这里插入图片描述

六. 其他操作

1. 获取size

const成员函数, 普通成员和const成员都能调用

	//获取size
	int size() const {
		return _size;
	}

2. 获取capacity

	//获取capacity
	size_t capacity() const {
		return _capacity;
	}

3. 判空

	//判空
	bool empty() const {
		if (_size == 0)
			return true;
		return false;
	}

4. 清空字符串

注意只影响size, 不影响capacity

	void clear() {
		erase(0, strlen(_ptr));
		_size = 0;
	}

5. 返回C风格字符串

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

6. 查找find函数

这里要加一个静态成员变量 npos, 值为-1, 表示没找到
	static const size_t npos;

静态成员要在类外初始化

	//查找字符
	size_t find(const char& ch, size_t pos = 0) {
		for (int i = pos; i < _size; i++) {
			if (_ptr[i] == ch) {
				//找到了
				return i;
			}
		}
		//没找到
		return npos;
	}

//查找字符串
	size_t find(const char* str, size_t pos = 0) {
		char* start = strstr(_ptr + pos, str);
		if (start) {
			//找到了, 返回偏移量
			return start - _ptr;
		}
		//没找到
		return npos;
	}

7. 输入输出运算符重载

输入输出要声明成友元函数, 为了访问类中的私有成员
不定义为成员函数是因为写出来的顺序与我们的习惯相反(之前文章也有说到)
最后选择友元函数为最优解

类中声明:

	friend ostream& operator<<(ostream& _cout, const String& str);
	friend istream& operator>>(istream& _cin, String& str);

类外定义:

ostream& operator<<(ostream& _cout, const String& str) {
	//_cout << str._ptr;
	for (const auto& ch : str) {
		cout << ch;
	}
	return _cout;
}

istream& operator>>(istream& _cin, String& str) {
	char ch;
	str.clear();//先清空
	while ((ch = getchar()) != EOF) {
		if (ch == '\n')
			break;

		str += ch;
	}
	return _cin;
}

测试代码:

void test2() {
	string s = "abc";
	cout << s << endl;
	cin >> s;
	cout << s << endl;
}

结果如下:
在这里插入图片描述
到这里string的实现就完成了…

下面给出完整的代码:

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
#include<assert.h>

using namespace std;


class String {
public:
	//全缺省的构造函数
	String(const char* str = "") 
		:_ptr(new char[strlen(str) + 1])
		,_size(strlen(str))
		,_capacity(_size)
	{
		strcpy(_ptr, str);
	}


	//交换函数
	void Swap(String& str) {
		swap(_ptr, str._ptr);
		swap(_size, str._size);
		swap(_capacity, str._capacity);
	}

	//拷贝构造
	//传统写法
	//String(const String& str)
	//	:_ptr(new char[strlen(str._ptr) + 1])
	//	,_size(str._size)
	//	,_capacity(str._capacity)
	//{
	//	//拷贝
	//	strcpy(_ptr, str._ptr);
	//}

	//现代写法
	String(const String& str)
		:_ptr(nullptr)  //这里指针一定要初始化, 否则交换完释放的时候空间有问题
		, _size(0)
		, _capacity(0)
	{
		String tmp(str._ptr);
		Swap(tmp);
	}

	//赋值
	//传统写法
	//String& operator=(const String& str) {
	//	if (this != &str) {
	//		//开空间, 拷贝, 释放原有空间
	//		char* tmp = new char[strlen(str._ptr) + 1];
	//		strcpy(tmp, str._ptr);
	//		delete[] _ptr;

	//		//指向新的空间, 并更新size和cap
	//		_ptr = tmp;
	//		_size = str._size;
	//		_capacity = str._capacity;
	//	}
	//	return *this;
	//}


	//现代写法
	String& operator=(String str) {
		Swap(str);
		return *this;
	}


	//析构
	~String() {
		if (_ptr) {
			delete[] _ptr;
			_ptr = nullptr;
			_size = _capacity = 0;
		}
	}


	//增容, 不实现缩容
	void reserve(size_t n) {
		if (n > _capacity) {
			//开空间, 拷贝
			//这里无法保证_capacity和n的具体大小关系, 所以直接开够n个空间即可
			char* tmp = new char[n + 1];
			//strcpy(tmp, _ptr);
			//strcpy 遇到'\0'就会停止, 可能出现bug, 这里直接循环拷贝
			for (int i = 0; i < _size; i++) {
				tmp[i] = _ptr[i];
			}

			//释放空间, 改变指向, 更新容量
			delete[] _ptr;
			_ptr = tmp;
			_capacity = n;
		}
	}


	//尾插字符
	void push_back(const char ch) {
		if (_size == _capacity) {
			//增容
			//如果是空的话, 给初值为15(模拟库中的string), 否则以2倍增容
			size_t newCap = _capacity == 0 ? 15 : 2 * _capacity;
			reserve(newCap);
		}
		//尾插
		_ptr[_size++] = ch;
		//最后一定不要忘了补一个'\0'~
		_ptr[_size] = '\0';
	}

	//尾插字符串 append
	void append(const char* str) {
		//获取字符串长度
		size_t len = strlen(str);
		//判断增容
		if (_size + len > _capacity) {
			//直接增容到_size + len, 因为无法确定_size + len有多大
			reserve(_size + len);
		}

		//尾插字符串, 这里把str的'\0'也拷贝过来了
		strcpy(_ptr + _size, str);
		_size += len;
	}

	//+=运算符重载函数
	//+= char ch
	String& operator+=(const char ch) {
		push_back(ch);
		return *this;
	}

	//+= char* str
	String& operator+= (const char* str) {
		append(str);
		return *this;
	}

	//+= String
	String& operator+=(const String& str) {
		append(str._ptr);
		return *this;
	}


	//任意位置插入
	void insert(size_t pos, const char& ch) {
		assert(pos < _size);

		//判断增容 
		if (_size == _capacity) {
			size_t newCap = _capacity == 0 ? 15 : 2 * _capacity;
			reserve(newCap);
		}
		//移动元素 --> 从后向前, 不然会覆盖
		size_t end = _size;
		while (end > pos) {
			_ptr[end] = _ptr[end - 1];
			--end;
		}

		/*
		常见的错误写法:
			当pos = 0时,无限循环, 因为end是size_t类型, 无符号, 永远大于等于0
			while (end >= pos) {
			_ptr[end + 1] = _ptr[end];
			--end;
			}
		*/

		//插入新字符
		_ptr[pos] = ch;
		//添加新的结束标志
		_ptr[++_size] = '\0';
	}

	void insert(size_t pos, const char* str) {
		assert(pos <= _size);
		int len = strlen(str);
		
		//增容
		if (_size + len > _capacity) {
			reserve(_size + len);
		}

		//移动元素  
		size_t end = _size + len;
		// end: [pos + len, _size + len]  '\0'也一起移动到末尾了
		while (end >= pos + len) {
			_ptr[end] = _ptr[end - len];
			--end;
		}

		//插入字符串
		memcpy(_ptr + pos, str, len * sizeof(char));

		_size += len;
	}


	//删除
	void erase(size_t pos, size_t len) {
		//移动元素 ---> 从前向后移动, 不然会覆盖
		size_t start = pos + len;
		while (start <= _size) {
			_ptr[start - len] = _ptr[start];
			start++;
		}
		//更新_size
		_size -= len;
	}


	//获取size
	int size() const {
		return _size;
	}

	//获取capacity
	size_t capacity() const {
		return _capacity;
	}

	//判空
	bool empty() const {
		if (_size == 0)
			return true;
		return false;
	}


	//[]重载
	//可读可写
	char& operator[](size_t pos) {
		assert(pos < _size);
		return _ptr[pos];
	}

	//只读
	const char& operator[](size_t pos) const {
		assert(pos < _size);
		return _ptr[pos];
	}


	//迭代器: 访问容器元素的一种机制
	//类似于指针
	//操作: 移动, 解引用, 判断
	//迭代器实现: char*
	typedef char* iterator;
	typedef const char* const_iterator;

	//begin: 第一个元素的位置
	iterator begin() {
		return _ptr;
	}

	//end: 最后一个元素的下一个位置
	iterator end() {
		return _ptr + _size;
	}

	const_iterator begin() const {
		return _ptr;
	}

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


	//resize(n, ch) 
	//		1. n <= size: 只需要修改size
	//		2. size < n <= capacity: 赋值ch  -->  [size, capacity) , 修改size
	//		3. n > capacity:  增容, 赋值, 修改size
	void resize(size_t n, char ch = '\0') {
		if (n > _capacity) {
			//增容
			reserve(n);
		}
		if (n > _size) {
			//赋值
			//默认要赋值'\0', 所以不能用strcpy, 只能逐个赋值或者用memset (字节拷贝)
			memset(_ptr + _size, ch, n - _size);
		}

		//修改size
		_size = n;
		_ptr[_size] = '\0';
	}


	//查找
	size_t find(const char& ch, size_t pos = 0) {
		for (int i = pos; i < _size; i++) {
			if (_ptr[i] == ch) {
				//找到了
				return i;
			}
		}
		//没找到
		return npos;
	}

	size_t find(const char* str, size_t pos = 0) {
		char* start = strstr(_ptr + pos, str);
		if (start) {
			//找到了, 返回偏移量
			return start - _ptr;
		}
		//没找到
		return npos;
	}

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

	void clear() {
		erase(0, strlen(_ptr));
		_size = 0;
	}


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

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

	bool operator>(const String& s) {
		if (strcmp(_ptr, s.c_str()) > 0) 
			return true;
		return false;
	}

	bool operator>=(const String& s) {
		return (*this > s) || (*this == s);
	}

	bool operator==(const String& s) {
		if (strcmp(_ptr, s.c_str()) == 0)
			return true;
		return false;
	}

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


	friend ostream& operator<<(ostream& _cout, const String& str);
	friend istream& operator>>(istream& _cin, String& str);

private:
	char* _ptr;
	size_t _size; //有效字符个数
	size_t _capacity; //可以存放的最大有效字符个数
	static const size_t npos;
};
const size_t String::npos = -1;


ostream& operator<<(ostream& _cout, const String& str) {
	//_cout << str._ptr;
	for (const auto& ch : str) {
		cout << ch;
	}
	return _cout;
}

istream& operator>>(istream& _cin, String& str) {
	char ch;
	str.clear();//先清空
	while ((ch = getchar()) != EOF) {
		if (ch == '\n')
			break;

		str += ch;
	}
	return _cin;
}


void test() {
	String s = "abcde";
	String s2("xyz");
	s2 = s;
	
	s.clear();
	s.push_back('1');
	s.push_back('2');
	s.push_back('3');

	s.append("abc");

	s2 += '0';
	s2 += "QAQ";
	s2 += s;
}

void test2() {
	string s = "abc";
	cout << s << endl;
	cin >> s;
	cout << s << endl;
}


void test3() {
	String s = "0123456789";
	s.insert(7, 'a');
	s.insert(3, "xyz");
	s.erase(2, 1);

	size_t pos = s.find('4', 0);
	pos = s.find("xyz", 0);
	pos = s.find("aaaaa", 0);
}


void test4() {
	String s = "12345";

	for (int i = 0; i < s.size(); i++) {
		cout << s[i] << " ";
	}
	cout << endl;

	String::iterator it = s.begin();
	while (it != s.end()) {
		cout << *it << " ";
		it++;
	}
	cout << endl;

	for (const auto& ch : s) {
		cout << ch << " ";
	}
	cout << endl;
}


void test5() {
	String s = "123";
	String s2 = "123";
	String s3 = "02";
	String s4 = "9";

	cout << (s == s2) << endl;
	cout << (s != s2) << endl;
	cout << (s > s3) << endl;
	cout << (s <= s4) << endl;
	cout << (s >= s2) << endl;
	cout << (s4 < s2) << endl;

}


int main() {
	//test();
	test2();
	//test3();
	//test4();
	//test5();

	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

殇&璃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值