【C++】STL之string模拟实现(附带讲解)

一、简单介绍

①【STL简介】

STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。

  • STL的六大组件
    在这里插入图片描述

②【string简介】

std::string 是 C++ 标准模板库 (STL) 中用于处理字符串的类(定义在 <string> 头文件中),它封装了字符序列的操作,提供丰富的成员函数,支持动态内存管理,比 C 风格字符数组更安全高效。

二、模拟实现

①【成员变量】

_str:字符数组的指针
_size:字符串的字符个数
_capacity:字符数组可容纳的字符个数(这里设定字符串最后一位为’\0’,故实际占用空间为_capacity+1)
npos:size_t类型的最大值,常表示作用至字符串最后一个字符,可参考https://legacy.cplusplus.com/reference/string/string/npos/

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

static const size_t npos = -1; // 无符号的-1非常大!

②【成员函数】

1. 构造函数

string:将根据字符长度申请空间,并拷贝形参至指针_str中,若不传参则默认形参为空字符串。

	string::string(const char* str = "")
		:_size(strlen(str))
	{
		_capacity = _size;
		_str = new char[_capacity + 1];
		memcpy(_str, str, _capacity + 1);
	}

_string_iterator:因为string底层可以用数组实现,这里作为练习就不配置迭代器类了,直接开辟数组空间,使用指针作为简易的迭代器。(迭代器这部分将在【STL之list模拟实现】中具体演示!)

2. 拷贝构造函数

string:string的拷贝函数可以直接写,也可以在拷贝构造函数中调用构造函数构造tmp类,this再与之交换成员变量,函数调用完毕后析构的是原this的空间内存。(同理,再利用拷贝构造函数也可以实现赋值重载运算符

基础写法:手动配置空间。

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

进阶写法:利用构造函数来配置空间。

	void string::swap(string& tmp) // 自定义string类的swap成员函数
	{
		std::swap(_capacity, tmp._capacity);
		std::swap(_size, tmp._size);
		std::swap(_str, tmp._str);
	}

	string::string(const string& str) // 现代C++的拷贝构造函数写法,利用构造函数省去手动深拷>>贝,执行效率区别不大
	{
		string tmp(str._str); // 调用构造函数
		swap(tmp);
		std::cout << "拷贝构造函数" << std::endl;
	}

_string_iterator:使用指针作为简易的迭代器,若想学习移步至【STL之list模拟实现】。

3. 析构函数

string:释放指针_str的空间并置为nullptr,_size与_capacity清零。

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

_string_iterator:使用指针作为简易的迭代器,若想学习移步至【STL之list模拟实现】。

③【迭代器】

1. 读写迭代器(iterator)

iterator:迭代器对应的数据可读写。

	typedef char* iterator;
	
	string::iterator string::begin()
	{
		return _str;
	}
	string::iterator string::end()
	{
		return _str + _size;
	}

2. 只读迭代器(const_iterator)

const_iterator:迭代器对应的数据只读,不可被修改。(使用指针作为简易的迭代器,故可将只读迭代器视作const char*,在【STL之list模拟实现】中会演示正规做法!)

	typedef const char* const_iterator;

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

④【运算符重载】

1. 赋值运算符重载(operator=)

operator=:上面在拷贝构造函数部分中提到可以利用拷贝构造函数+自定义的swap方式提高代码书写效率,这里展示三种方法!

基础写法:手动配置空间。

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

进阶写法:利用拷贝构造函数来配置空间。(成员函数swap参照上方拷贝构造函数

	string& string::operator=(const string& str) // 现代C++写法(函数内调用拷贝构造,再swap掠夺"资本")
	{
		if (this != &str)
		{
			string tmp(str);
			swap(tmp);
		}
		return *this;
	}

进阶写法Pro版:利用拷贝构造函数来配置空间,传参调用拷贝构造函数,无需手动调用拷贝构造函数,更简洁。(成员函数swap参照上方拷贝构造函数的进阶写法

	string& string::operator=(string tmp) // 现代C++写法(传参调用拷贝构造得到tmp,在函数内用swap掠夺"资本")
	{
		swap(tmp);
		std::cout << "=" << std::endl;
		return *this;
	}

2. 复合赋值运算符(operator+=)

operator+=:直接调用append成员函数。(涉及空间扩容)

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

3. 下标访问运算符(operator[])

oeprator[]:下标访问字符,含读写、只读两种版本。

	char& string::operator[](size_t i) // 读写版本
	{
		assert(i < _size);
		return _str[i];
	}
	
	const char& string::operator[](size_t i) const // 只读版本
	{
		assert(i < _size);
		return _str[i];
	}

4. 输入输出运算符(cin>>与cout<<)

cout<<:由于语法限制,需为全局函数,因为类的成员函数第一个参数默认为this指针

	std::ostream& operator<<(std::ostream& out, const string& s)
	{
		for (auto ch : s) // 这里的end()与_size有关,故可代替for (size_t i = 0; i < _size; i++)
		{
			out << ch;
		}
		return out;
	}

cin>>:由于语法限制,需为全局函数,因为类的成员函数第一个参数默认为this指针

	std::istream& operator>>(std::istream& in, string& str)
	{
		str.clear();

		char buff[128];
		size_t i = 0;
		char ch = in.get();
		while (ch != '\n' && ch != ' ') // 截断条件
		{
			buff[i++] = ch;
			if (i == 127)
			{
				buff[i] = '\0';
				str += buff;
				i = 0;
			}
			ch = in.get();
		}
		if (i > 0) // 处理未被流提取的字符
		{
			buff[i] = '\0';
			str += buff;
		}
		return in;
	}

⑤【功能函数】

1. getline

getline:与输入运算符重载相似,仅修改了delim截断条件

	std::istream& getline(std::istream& in, string& str, char delim = '\n')
	{
		str.clear();

		char buff[128];
		size_t i = 0;
		char ch = in.get();
		while (ch != delim) // 截断条件
		{
			buff[i++] = ch;
			if (i == 127)
			{
				buff[i] = '\0';
				str += buff;
				i = 0;
			}
			ch = in.get();
		}
		if (i > 0) // 处理未被流提取的字符
		{
			buff[i] = '\0';
			str += buff;
		}
		return in;
	}

2. swap

swap:已在拷贝构造函数的进阶写法中介绍过。

	void string::swap(string& tmp) // 自定义string类的swap成员函数
	{
		std::swap(_capacity, tmp._capacity);
		std::swap(_size, tmp._size);
		std::swap(_str, tmp._str);
	}

3. c_str

c_str:以C语言的字符数组指针类型作为返回,内容为const只读。

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

4. size

size:返回string的字符个数。

	size_t string::size() const
	{
		return _size;
	}

5. reserve

reserve:为字符数组扩容至n。

	void string::reserve(size_t n)
	{
		if (n > _capacity)
		{
			char* tmp = new char[n + 1];
			memcpy(tmp, _str, _size + 1);
			delete[] _str;
			_str = tmp;
			_capacity = n;
			//std::cout << "执行扩容成功 :> " << _capacity << std::endl;
		}
	}

6. push_back

push_back:尾插字符。

	void string::push_back(char ch)
	{
		if (_capacity == _size)
		{
			size_t new_capacity = _capacity == 0 ? 4 : 2 * _capacity; // 扩至两倍
			reserve(new_capacity);
		}
		_str[_size++] = ch;
		_str[_size] = '\0';
	}

7. pop_back

pop_back:尾删字符。

	void string::pop_back()
	{
		assert(_size > 0);
		_str[_size] = '\0';
		--_size;
	}

8. append

append:在尾部插入C语言中的字符串。

	string& string::append(const char* str)
	{
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			size_t new_capacity = 2 * _capacity >= _size + len ? 2 * _capacity : _size + len;
			reserve(new_capacity);
		}
		memcpy(_str + _size, str, len + 1);
		_size += len;
		return *this;
	}

9. insert

insert:在pos处插入字符,原字符串pos及pos后的字符向后移动一格。

	string& string::insert(size_t pos, char ch)
	{
		assert(pos <= _size);
		if (_capacity == _size)
		{
			size_t new_capacity = _capacity == 0 ? 4 : 2 * _capacity;
			reserve(new_capacity);
		}
		size_t end = _size + 1;
		while (end > pos)
		{
			_str[end] = _str[end - 1];
			--end;
		}
		_str[pos] = ch;
		++_size;
		return *this;
	}

10. erase

erase:从pos处删除len个字符(包括pos处),若len超出范围删至末尾,若未传入len参数也删至末尾npos在成员变量中有解释!)。

	string& string::erase(size_t pos, size_t len = npos)
	{
		assert(pos < _size);
		if (len == npos || len >= _size - pos) // 后面全删的情况
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else
		{
			size_t pos2 = pos + len;
			memmove(_str + pos, _str + pos2, _size + 1 - pos2);
			_size -= len;
		}
		return *this;
	}

11. find

find:从pos处开始查找字符or字符串的下标,pos默认为0,若未查询则返回npos

	size_t string::find(char ch, size_t pos = 0) const // 查找字符为ch的下标
	{
		for (size_t i = pos; i < _size; i++)
		{
			if (_str[i] == ch)
				return i;
		}
		return npos;
	}
	
	size_t string::find(const char* str, size_t pos = 0) const // 查找字符串为*str的下标
	{
		const char* p = strstr(_str + pos, str);
		if (p == nullptr)
			return npos;
		else
			return p - _str;
	}

12. substr

substr:截取字符or字符串,若无传递len参数则截取至字符串末尾

	string string::substr(size_t pos, size_t len = npos) const
	{
		assert(pos < _size);
		if (len == npos || len > _size - pos) // 需修改len的情况,确保len个数正确
			len = _size - pos;
		string tmp;
		tmp._size = len;
		tmp.reserve(len);
		memcpy(tmp._str, _str + pos, len);
		return tmp;
	}

13. clear

clear:清空字符串,代码复用方面在输入重载运算符>>全局函数getline中使用过。

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

三、整体代码

gitee源码https://gitee.com/zj-xiaoxiong/cpp/tree/master/2025/08_18_string

①【string.h】

#pragma once
#include <iostream>
#include <string.h>
#include <assert.h>

namespace wyx
{
	class string
	{
	public:
		// 构造函数、拷贝构造函数、析构函数
		string(const char* str = "");
		string(const string& str);
		~string();

		// 迭代器
		typedef char* iterator;
		typedef const char* const_iterator;
		iterator begin();
		iterator end();
		const_iterator begin() const;
		const_iterator end() const;

		// 运算符重载
		char& operator[](size_t i);
		const char& operator[](size_t i) const;
		string& operator+=(char ch);
		string& operator+= (const char* str);
		bool operator<(const string& str) const;
		bool operator==(const string& str) const;
		bool operator<=(const string& str) const;
		bool operator>(const string& str) const;
		bool operator>=(const string& str) const;
		//string& operator=(const string& str);
		string& operator=(string tmp);

		// string功能
		const char* c_str() const;
		size_t size() const;
		void reserve(size_t n);
		void push_back(char ch);
		void pop_back();
		string& append(const char* str);
		string& insert(size_t pos, char ch);
		string& insert(size_t pos, const char* str);
		string& 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;
		void clear();
		void Print();
		void swap(string& tmp);
	private:
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0;

		static const size_t npos = -1; // 无符号的-1非常大!
	};
	std::ostream& operator<<(std::ostream& out, const string& s);
	std::istream& operator>>(std::istream& in, string& s);
	std::istream& getline(std::istream& in, string& str, char delim = '\n');
	void swap(string& s1, string& s2);
}

②【string.cpp】

#include "string.h"

namespace wyx
{
	// 构造函数、拷贝构造函数、析构函数
	string::string(const char* str)
		:_size(strlen(str))
	{
		_capacity = _size;
		_str = new char[_capacity + 1];
		memcpy(_str, str, _capacity + 1);
	}
	/*string::string(const string& str)
		:_size(str._size)
		, _capacity(str._capacity)
	{
		_str = new char[_capacity + 1];
		memcpy(_str, str._str, _capacity + 1);
	}*/
	string::string(const string& str) // 现代C++的拷贝构造函数写法,利用构造函数省去手动深拷贝,执行效率区别不大
	{
		string tmp(str._str); // 调用构造函数
		swap(tmp);
		std::cout << "拷贝构造函数" << std::endl;
	}
	string::~string()
	{
		delete[] _str;
		_str = nullptr;
		_size = _capacity = 0;
	}

	// 迭代器
	string::iterator string::begin()
	{
		return _str;
	}
	string::iterator string::end()
	{
		return _str + _size;
	}
	string::const_iterator string::begin() const
	{
		return _str;
	}
	string::const_iterator string::end() const
	{
		return _str + _size;
	}

	// 运算符重载
	char& string::operator[](size_t i)
	{
		assert(i < _size);
		return _str[i];
	}
	const char& string::operator[](size_t i) const
	{
		assert(i < _size);
		return _str[i];
	}
	string& string::operator+=(char ch)
	{
		push_back(ch);
		return *this;
	}
	string& string::operator+=(const char* str)
	{
		append(str);
		return *this;
	}
	bool string::operator<(const string& str) const
	{
		size_t i = 0, j = 0;
		while (i < _size && j < str._size)
		{
			if (_str[i] < str._str[j])
				return true;
			else if (_str[i] > str._str[j])
				return false;
			else
				++i, ++j;
		}
		return _size < str._size;

	}
	bool string::operator==(const string& str) const
	{
		size_t i = 0, j = 0;
		while (i < _size && j < str._size)
		{
			if (_str[i] != str._str[j])
				return false;
		}
		return _size == str._size;
	}
	bool string::operator<=(const string& str) const
	{
		return *this < str || *this == str;
	}
	bool string::operator>(const string& str) const
	{
		return !(*this <= str);
	}
	bool string::operator>=(const string& str) const
	{
		return !(*this < str);
	}
	/*string& string::operator=(const string& str)
	{
		if (this != &str)
		{
			_capacity = str._capacity;
			_size = str._size;
			char* tmp = new char[_capacity + 1];
			memcpy(tmp, str._str, _size + 1);
			delete[] _str;
			_str = tmp;
		}
		return *this;
	}*/
	/*string& string::operator=(const string& str) // 现代C++写法(函数内调用拷贝构造,再swap掠夺"资本")
	{
		if (this != &str)
		{
			string tmp(str);
			swap(tmp);
		}
		return *this;
	}*/
	string& string::operator=(string tmp) // 现代C++写法(传参调用拷贝构造得到tmp,在函数内用swap掠夺"资本")
	{
		swap(tmp);
		std::cout << "=" << std::endl;
		return *this;
	}

	// string功能
	const char* string::c_str() const
	{
		return _str;
	}
	size_t string::size() const
	{
		return _size;
	}
	void string::reserve(size_t n)
	{
		if (n > _capacity)
		{
			char* tmp = new char[n + 1];
			memcpy(tmp, _str, _size + 1);
			delete[] _str;
			_str = tmp;
			_capacity = n;
			//std::cout << "执行扩容成功 :> " << _capacity << std::endl;
		}
	}
	void string::push_back(char ch)
	{
		if (_capacity == _size)
		{
			size_t new_capacity = _capacity == 0 ? 4 : 2 * _capacity; // 扩至两倍
			reserve(new_capacity);
		}
		_str[_size++] = ch;
		_str[_size] = '\0';
	}
	void string::pop_back()
	{
		assert(_size > 0);
		_str[_size] = '\0';
		--_size;
	}
	string& string::append(const char* str)
	{
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			size_t new_capacity = 2 * _capacity >= _size + len ? 2 * _capacity : _size + len;
			reserve(new_capacity);
		}
		memcpy(_str + _size, str, len + 1);
		_size += len;
		return *this;
	}
	string& string::insert(size_t pos, char ch)
	{
		assert(pos <= _size);
		if (_capacity == _size)
		{
			size_t new_capacity = _capacity == 0 ? 4 : 2 * _capacity;
			reserve(new_capacity);
		}
		size_t end = _size + 1;
		while (end > pos)
		{
			_str[end] = _str[end - 1];
			--end;
		}
		_str[pos] = ch;
		++_size;
		return *this;
	}
	string& string::insert(size_t pos, const char* str)
	{
		assert(pos <= _size);
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			size_t new_capacity = 2 * _capacity >= _size + len ? 2 * _capacity : _size + len;
			reserve(new_capacity);
		}
		size_t end = _size + len;
		while (end > pos - 1 + len)
		{
			_str[end] = _str[end - len];
			--end;
		}
		memcpy(_str + pos, str, len);
		_size += len;
		return *this;
	}
	string& string::erase(size_t pos, size_t len)
	{
		assert(pos < _size);
		if (len == npos || len >= _size - pos) // 后面全删的情况
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else
		{
			size_t pos2 = pos + len;
			memmove(_str + pos, _str + pos2, _size + 1 - pos2);
			_size -= len;
		}
		return *this;
	}
	size_t string::find(char ch, size_t pos) const
	{
		for (size_t i = pos; i < _size; i++)
		{
			if (_str[i] == ch)
				return i;
		}
		return npos;
	}
	size_t string::find(const char* str, size_t pos) const
	{
		const char* p = strstr(_str + pos, str);
		if (p == nullptr)
			return npos;
		else
			return p - _str;
	}
	string string::substr(size_t pos, size_t len) const
	{
		assert(pos < _size);
		if (len == npos || len > _size - pos) // 需修改len的情况,确保len个数正确
			len = _size - pos;
		string tmp;
		tmp._size = len;
		tmp.reserve(len);
		memcpy(tmp._str, _str + pos, len);
		return tmp;
	}
	void string::clear()
	{
		_size = 0;
		_str[0] = '\0';
	}
	void string::Print()
	{
		std::cout << "size=" << _size << " capacity=" << _capacity << " p=" << this << std::endl;
	}
	void string::swap(string& tmp)
	{
		std::swap(_capacity, tmp._capacity);
		std::swap(_size, tmp._size);
		std::swap(_str, tmp._str);
	}

	// 非成员函数
	std::istream& getline(std::istream& in, string& str, char delim)
	{
		str.clear();

		char buff[128];
		size_t i = 0;
		char ch = in.get();
		while (ch != delim) // 截断条件
		{
			buff[i++] = ch;
			if (i == 127)
			{
				buff[i] = '\0';
				str += buff;
				i = 0;
			}
			ch = in.get();
		}
		if (i > 0) // 处理未被流提取的字符
		{
			buff[i] = '\0';
			str += buff;
		}
		return in;
	}
	void swap(string& s1, string& s2)
	{
		s1.swap(s2);
	}

	// 非成员重载运算符
	std::ostream& operator<<(std::ostream& out, const string& s)
	{
		for (auto ch : s) // 这里的end()与_size有关,故可代替for (size_t i = 0; i < _size; i++)
		{
			out << ch;
		}
		return out;
	}
	std::istream& operator>>(std::istream& in, string& str)
	{
		str.clear();

		char buff[128];
		size_t i = 0;
		char ch = in.get();
		while (ch != '\n' && ch != ' ') // 截断条件
		{
			buff[i++] = ch;
			if (i == 127)
			{
				buff[i] = '\0';
				str += buff;
				i = 0;
			}
			ch = in.get();
		}
		if (i > 0) // 处理未被流提取的字符
		{
			buff[i] = '\0';
			str += buff;
		}
		return in;
	}
}

③【Test.cpp】

#include "string.h"
using namespace std;

namespace wyx
{
	void test_01()
	{
		string str("123qwe");
		cout << str[5] << endl;
		for (auto& ch : str)
		{
			ch++;
			cout << ch << ' ';
		}
		cout << endl;
		cout << str.c_str() << endl;
		cout << typeid(wyx::string::iterator).name() << endl;
		cout << typeid(std::string::iterator).name() << endl;
	}

	void test_02()
	{
		string s1("qwer1234");
		cout << s1.size() << endl;
		cout << s1.c_str() << endl;
		s1.push_back('x');
		cout << s1.c_str() << endl;
		s1.append(" hello!xiaoxiong!");
		cout << s1.c_str() << endl;
		s1 += ' ';
		s1 += "123";
		cout << s1.c_str() << endl;
	}

	void test_03()
	{
		string s = "qwer";
		cout << s << endl;
		s += '\0';
		s += 'x';
		cout << s << endl;
		s += "\0 123";
		cout << s << endl;
		s += "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy";
		cout << s << endl;
	}

	void test_04()
	{
		string s = "qwer";
		cout << s << endl;
		s.insert(0, "xxxxxxxxxxxxxxxxxxxxx");
		cout << s << endl;
	}

	void test_05()
	{
		string s = "0123qwer";
		s.erase(0, 3);
		cout << s << endl;

		string s2 = "qwer12345";
		cout << s2 << endl;
		s2.push_back('x');
		s2.push_back('\0');
		s2.push_back('x');
		cout << s2 << endl;
		cout << s2.erase(4, 3) << endl;
		string s3 = "12";
		s3.pop_back();
		cout << s3 << endl;
	}

	void test_06()
	{
		string s = "0123456789";
		cout << s.find("3456789") << endl;
		cout << s.substr(0, 3) << endl;
	}

	void test_07()
	{
		string s = "012345";
		string s2 = "012347";
		cout << (s <= s2) << endl;
		cout << (s > s2) << endl;
	}

	void test_08()
	{
		string s, s2;
		cin >> s >> s2;
		cout << s << ' ' << s2 << endl;
		s.Print(); s2.Print();
		/*string s3;
		getline(cin, s3);
		cout << s3;*/
		s = s2;
		cout << s << ' ' << s2 << endl;
		s.Print(); s2.Print();
	}

	void test_09()
	{
		string s("12345");
		string s2("45678");
		std::swap(s, s2); // 需要一次拷贝构造,两次赋值拷贝(每个赋值拷贝函数会调用一次拷贝构造)
		cout << "//////////////" << endl;
		swap(s, s2); // 直接交换变量值,无拷贝构造与赋值拷贝
	}
}

int main()
{
	//wyx::test_01();
	//wyx::test_02();
	//wyx::test_03();
	//wyx::test_04();
	//wyx::test_05();
	//wyx::test_06();
	//wyx::test_07();
	//wyx::test_08();
	wyx::test_09();
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值