string底层实现细节

一、部分代码展示

cpp 复制代码
#pragma once
#include<cstring>
#include<cassert>
#include<iostream>
using namespace std;
namespace bit
{
	class string
	{
	public:
		// 迭代器类指针
		// 范围for就是在编译时替换成迭代器遍历,*it返回给ch
		typedef char* iterator;
		typedef const char* const_iterator;
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		const_iterator begin() const
		{
			return _str;
		}
		const_iterator end() const
		{
			return _str + _size;
		}


		/*string()
			:_str(new char[1])
			,_size(0)
			,_capacity(0)
		{
			_str[0] = '\0';
		}*/
		string(const char* str = "")
			:_size(strlen(str))
		{
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		// 拷贝构造要深拷贝,防止指针指向同一个空间
		/*string(const string& s)
		{
			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}*/
		// 现代写法
		string(const string& s)
		{
			string tmp(s._str);
			swap(tmp);
		}


		/*string& operator=(const string& s)
		{
			char* tmp = new char[s._capacity + 1];
			strcpy(tmp, s._str);

			delete[] _str;
			_str = tmp;
			_size = s._size;
			_capacity = s._capacity;

			return *this;
		}*/
		/*string& operator=(const string& s)
		{
			string ss(s);
			swap(ss);

			return *this;
		}*/
		// 现代写法
		// 去掉引用就是传值传参,s是拷贝,修改不会改变外面的值
		string& operator=(string s)
		{
			swap(s);
			return *this;
		}

		size_t size() const
		{
			return _size;
		}

		size_t capacity() const
		{
			return _capacity;
		}

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

		char& operator[](size_t pos)
		{
			// 传统的数组,写是越界抽查,读不会检查出
			assert(pos < _size);
			return _str[pos];
		}

		const char& operator[](size_t pos) const
		{
			// 传统的数组,写是越界抽查,读不会检查出
			assert(pos < _size);
			return _str[pos];
		}

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

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

		void append(const char* str)
		{
			//size_t len = strlen(str);
			//if (_size + len > _capacity)
			//{
			//	reserve(_size + len);
			//}
			 strcpy会把str的\0也拷过去
			//strcpy(_str + _size, str);
			//_size += len;
			insert(_size, str);
		}

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

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

		void insert(size_t pos, char ch)
		{
			assert(pos <= _size); // 等于就是尾插
			if (_size == _capacity)
			{
				reserve(capacity == 0 ? 4 : 2 * _capacity);
			}
			//int end = _size; // 防止头插size_t减到负数变特别大的正数
			//while (end >= (int)pos)
			//{
			//	_str[end + 1] = _str[end];
			//	end--;
			//}
			int end = _size + 1;
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				end--;
			}

			_str[pos] = ch;
			_size++;
		}

		void insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			int end = _size + len;
			while (end - len >= pos)
			{
				_str[end] = _str[end - len];
				end--;
			}
			strcpy(_str + _size, str);
			_size += len;
		}

		void erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);
			if (len == npos || len >= _size - pos)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
		}

		void resize(size_t n, char c = '\0')
		{
			if (n <= _size)
			{
				_str[n] = '\0';
				_size = n;
			}
			else
			{
				reserve(n);
				for (size_t i = _size; i < n; i++)
					_str[i] = c;
				_str[n] = '\0';
				_size = n;
			}
		}

		/* 库里面的swap是三次拷贝一次析构,消耗大
		 编译器找函数的原则:
		 1.编译器指挥向上找
		 2.先在局部找,再在全局找,也可以在命名空间找
		 3.指定了命名空间也在命名空间找
		 string库里面也实现了两个参数的全局swap,目的就是为了区分算法库swap
		 有现成用现成,所以有3个swap也是调全局swap*/
		void swap(string& str)
		{
			std::swap(_str, str._str);
			std::swap(_size, str._size);
			std::swap(_capacity, str._capacity);
		}

		size_t find(char ch, size_t pos = 0) const
		{
			assert(pos < _size);
			for (size_t i = pos; i < _size; i++)
			{
				if (_str[i] == ch)
					return i;
			}
			return npos;
		}

		size_t find(const char* sub, size_t pos = 0) const
		{
			assert(pos < _size);
			const char* p = strstr(_str + pos, sub);
			if (p)
				return p - _str;
			else
				return pos;
		}

		string substr(size_t pos = 0, size_t len = npos)
		{
			string sub;
			if (len == npos || len >= _size - pos)
				for (size_t i = pos; i < _size; i++)
					sub += _str[i];
			else
				for (size_t i = pos; i < pos + len; i++)
					sub += _str[i];
			return sub;
		}

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

		~string()
		{
			delete[] _str;
			_size = 0;
			_capacity = 0;
		}
	private:
		char* _str;
		size_t _capacity;
		size_t _size;
	public:
		static const int npos;
	};
	const int string::npos = -1;

	void swap(string& x, string& y)
	{
		x.swap(y);
	}

	// 重载比较函数可以是全局的,这样第一个参数就可以不是string,更灵活
	bool operator==(const string& s1, const string& s2)
	{
		int ret = strcmp(s1.c_str(), s2.c_str());
		return ret == 0;
	}

	bool operator<(const string& s1, const string& s2)
	{
		int ret = strcmp(s1.c_str(), s2.c_str());
		return ret < 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 !(s1 == s2);
	}

	// 流提取的重载必须全局,但不一定友元
	// 友元的目的就是为了获取类的私有成员变量进行打印
	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 >> ch; // istream默认不会读空格和换行符
	//	ch = in.get(); // 相当于c语言里面的getchar()
	//	while (ch != ' ' && ch != '\n')
	//	{
	//		s += ch;
	//		ch = in.get();
	//	}
	//	return in;
	//}

	// 优化:缓冲区
	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char ch;
		ch = in.get();
		char buf[128];
		size_t i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buf[i++] = ch;
			if (i == 127)
			{
				buf[i] = '\0';
				s += buf;
				i = 0;
			}
			ch == in.get();
		}
		if (i != 0)
		{
			buf[i] = '\0';
			s += buf;
		}
		return in;
	}

	//istream& getline(istream& in, string& s)
	//{
	//	s.clear();
	//	char ch;
	//	// in >> ch; // istream默认不会读空格和换行符
	//	ch = in.get(); // 相当于c语言里面的getchar()
	//	while (ch != '\n')
	//	{
	//		s += ch;
	//		ch = in.get();
	//	}
	//	return in;
	//}

	// 优化
	istream& getline(istream& in, string& s)
	{
		s.clear();
		char ch;
		ch = in.get();
		char buf[128];
		size_t i = 0;
		while (ch != '\n')
		{
			buf[i++] = ch;
			if (i == 127)
			{
				buf[i] = '\0';
				s += buf;
				i = 0;
			}
			ch == in.get();
		}
		if (i != 0)
		{
			buf[i] = '\0';
			s += buf;
		}
		return in;
	}
}

二、细节

1、成员变量

要存储一个 string 对象需要有起始地址 _str,string 对象大小 _size,string 对象容量 _capacity

2、构造函数

(1)构造函数 string(const char* str = "")

cpp 复制代码
string(const char* str = "")
	:_size(strlen(str))
{
	_capacity = _size;
	_str = new char[_capacity + 1];
	strcpy(_str, str);
}

这个构造函数是深拷贝开空间

(2)拷贝构造 string(const string& s)

cpp 复制代码
/*string(const string& s)
{
	_str = new char[s._capacity + 1];
	strcpy(_str, s._str);
	_size = s._size;
	_capacity = s._capacity;
}*/
// 现代写法
string(const string& s)
{
	string tmp(s._str);
	swap(tmp);
}

拷贝构造的参数是不能改变的,所以用一个局部变量记录 s 信息再交换,最后除了函数 tmp 带着 *this 的信息销毁。一定要深拷贝,不然多个指针指向同一个空间,析构多次报错。

3、重载 = 运算符 string& operator=(string& s)

cpp 复制代码
/*string& operator=(const string& s)
{
	char* tmp = new char[s._capacity + 1];
	strcpy(tmp, s._str);

	delete[] _str;
	_str = tmp;
	_size = s._size;
	_capacity = s._capacity;

	return *this;
}*/
/*string& operator=(const string& s)
{
	string ss(s);
	swap(ss);

	return *this;
}*/
// 现代写法
// 去掉引用就是传值传参,s是拷贝,修改不会改变外面的值
string& operator=(string s)
{
	swap(s);
	return *this;
}

三种写法

第一种,自己深拷贝

第二种,在函数内部用拷贝构造 + 交换

第三种,去掉 const 和 & 在传参的过程中拷贝构造临时变量 s,函数内部直接交换

4、交换函数 void swap(string& str)

cpp 复制代码
void swap(string& str)
{
	std::swap(_str, str._str);
	std::swap(_size, str._size);
	std::swap(_capacity, str._capacity);
}

用于外部交换两个 string 对象,也用于内部构造对象

库里面的swap是三次拷贝一次析构,消耗大
编译器找函数的原则:
1.编译器指挥向上找
2.先在局部找,再在全局找,也可以在命名空间找
3.指定了命名空间也在命名空间找
string库里面也实现了两个参数的全局swap,目的就是为了区分算法库swap
有现成用现成,所以有3个swap也是调全局swap

5、迭代器 iterator

cpp 复制代码
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
	return _str;
}
iterator end()
{
	return _str + _size;
}
const_iterator begin() const
{
	return _str;
}
const_iterator end() const
{
	return _str + _size;
}

string 的迭代器可以用指针

6、插入函数 void insert(size_t pos, char ch)

cpp 复制代码
void insert(size_t pos, char ch)
{
	assert(pos <= _size); // 等于就是尾插
	if (_size == _capacity)
	{
		reserve(capacity == 0 ? 4 : 2 * _capacity);
	}
	//int end = _size; // 防止头插size_t减到负数变特别大的正数
	//while (end >= (int)pos)
	//{
	//	_str[end + 1] = _str[end];
	//	end--;
	//}
	int end = _size + 1;
	while (end > pos)
	{
		_str[end] = _str[end - 1];
		end--;
	}

	_str[pos] = ch;
	_size++;
}

注意 size_t 类型减到负数会变成很大的值

7、比较运算符的重载

cpp 复制代码
bool operator==(const string& s1, const string& s2)
{
	int ret = strcmp(s1.c_str(), s2.c_str());
	return ret == 0;
}

bool operator<(const string& s1, const string& s2)
{
	int ret = strcmp(s1.c_str(), s2.c_str());
	return ret < 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);
}

重载比较函数可以是全局的,这样第一个参数就可以不是string,更灵活

8、流提取 ostream& operator<<(ostream& out, const string& s)

cpp 复制代码
ostream& operator<<(ostream& out, const string& s)
{
	for (auto ch : s)
		out << ch;
	return out;
}

流提取的重载必须全局,但不一定友元

友元的目的就是为了获取类的私有成员变量进行打印

9、流插入 istream& operator>>(istream& in, string& s)

cpp 复制代码
//istream& operator>>(istream& in, string& s)
//{
//	s.clear();
//	char ch;
//	// in >> ch; // istream默认不会读空格和换行符
//	ch = in.get(); // 相当于c语言里面的getchar()
//	while (ch != ' ' && ch != '\n')
//	{
//		s += ch;
//		ch = in.get();
//	}
//	return in;
//}

// 优化:缓冲区
istream& operator>>(istream& in, string& s)
{
	s.clear();
	char ch;
	ch = in.get();
	char buf[128];
	size_t i = 0;
	while (ch != ' ' && ch != '\n')
	{
		buf[i++] = ch;
		if (i == 127)
		{
			buf[i] = '\0';
			s += buf;
			i = 0;
		}
		ch == in.get();
	}
	if (i != 0)
	{
		buf[i] = '\0';
		s += buf;
	}
	return in;
}

istream默认不会读空格和换行符,所以用 istream 的函数 get() 来获取一个字符,包括空格换行

相关推荐
csdn_aspnet18 分钟前
c++ 给定欧氏平面中的一组线可以形成的三角形的数量
c++·算法·平面
Conmi·白小丑41 分钟前
Conmi的正确答案——Rider中引入WebView2包(C#)
开发语言·c#
SomeB1oody44 分钟前
【Rust自学】13.8. 迭代器 Pt.4:创建自定义迭代器
开发语言·后端·rust
半夏知半秋1 小时前
rust学习-函数的定义与使用
服务器·开发语言·后端·学习·rust
好好学习++1 小时前
【HF设计模式】06-命令模式
java·c++·设计模式·命令模式
王老师青少年编程1 小时前
gesp(C++五级)(12)洛谷:B4051:[GESP202409 五级] 小杨的武器
开发语言·数据结构·c++·算法·gesp·csp
kuan_li_lyg1 小时前
ROS2 与机器人视觉入门教程(ROS2 OpenCV)
开发语言·人工智能·opencv·计算机视觉·matlab·机器人·ros
ke_wu2 小时前
实现简单的TCP服务器
linux·服务器·c语言·网络·c++·网络协议·tcp/ip
斯内科2 小时前
C#使用WMI获取控制面板中安装的所有程序列表
开发语言·c#·wmi
轩源源2 小时前
数据结构——AVL树的实现
开发语言·数据结构·c++·avl树·函数模板·双旋·单旋