【C++】STL---string

string类

一、C语言中的字符串

C语言中,字符串是以 ‘\0’ 结尾的一些字符的集合,为了操作方便,C标准库中提供了一些 str 系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。

二、string类

  1. string 是表示字符串的字符串类
  2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
  3. string 在底层实际是:basic_string 模板类的别名,typedef basic_string<char, char_traits, allocator> string;
  4. 不能操作多字节或者变长字符的序列。
  5. 在使用 string 类时,必须包含 #include 头文件以及 using namespace std;

其中,string 类的许多接口可以点击链接-> string 查看。

在这里需要介绍一下迭代器,在 string 类中,迭代器其实就是原生指针,迭代器的使用如下:

代码语言:javascript
复制
		#include <iostream>
		#include <string>
		using namespace std;
	int main()
	{
		string s1(&#34;hello,world&#34;);
	
		// 迭代器的使用,iterator 就是迭代器,它需要指定作用域
		string::iterator it = s1.begin();
		while (it != s1.end())
		{
			cout &lt;&lt; *it &lt;&lt; &#39; &#39;;
			it++;
		}
		cout &lt;&lt; endl;
	
		return 0;
	}</code></pre></div></div><p>其中 <code>s1.begin();</code> 其实就是指向字符串开始的指针,<code>s1.end()</code> 就是指向 ‘\0’ 的指针。</p><h3 id="35s4i" name="%E4%B8%89%E3%80%81%E6%A8%A1%E6%8B%9F%E5%AE%9E%E7%8E%B0-string-%E7%B1%BB">三、模拟实现 string 类</h3><p>下面我们直接开始模拟实现 <strong>string</strong> 类的接口,在实现的过程中讲解用法,注意,我们只模拟比较常见和重要的接口。</p><p>我们先观察 string 类的声明部分,先预览一下我们需要实现哪些接口:</p><h4 id="4comq" name="0.-string-%E7%B1%BB%E7%9A%84%E5%A3%B0%E6%98%8E">0. string 类的声明</h4><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">		namespace Young
	{
		class String
		{
		public:
	
			// 迭代器
			typedef char* iterator;
			typedef const char* const_iterator;
			iterator begin();
			iterator end();
			const_iterator begin() const;
			const_iterator end() const;
	
	
			// 构造
			String(const char* str = &#34;&#34;)
				:_str(new char [strlen(str) + 1])
				,_size(strlen(str))
				,_capacity(_size)
			{
				strcpy(_str, str);
			}
	
	
			// 析构
			~String()
			{
				delete[] _str;
				_str = nullptr;
				_size = _capacity = 0;
			}
	
			// 交换
			void swap(String&amp; tmp)
			{
				::swap(_str, tmp._str);
				::swap(_size, tmp._size);
				::swap(_capacity, tmp._capacity);
			}
	
	
			// s2(s1)
			// 拷贝构造
			String(const String&amp; str)
				:_str(nullptr)
				,_size(0)
				,_capacity(0)
			{
				String tmp(str._str);
				swap(tmp);
			}
	
	
			// s2 = s1
			// 赋值运算符重载
			String&amp; operator=(const String&amp; str)
			{
				if (this != &amp;str)
				{
					String tmp(str._str);
					swap(tmp);
				}
	
				return *this;
			}
	
	
			// 申请空间 -- 不改变 _size
			void reserve(size_t n);
	
			// 将空间调整为 n -- 改变 _size
			void resize(size_t n, char c = &#39;\0&#39;);
	
	
			// 尾插字符
			void push_back(char c);
			String&amp; operator+=(char c);
	
	
			// 尾插字符串
			void append(const char* str);
			String&amp; operator+=(const char* str);
	
	
			// 清空字符串
			void clear();
	
	
			// 获取字符串长度
			size_t size() const;
	
	
			// 获取容量
			size_t capacity() const;
	
	
			// [] 重载   s[1]
			const char&amp; operator[](size_t index) const;
			char&amp; operator[](size_t index);
	
	
			// 比较符号运算符重载
			bool operator&gt;(const String&amp; s) const;
			bool operator==(const String&amp; s) const;
			bool operator&gt;=(const String&amp; s) const;
			bool operator&lt;(const String&amp; s) const;
			bool operator&lt;=(const String&amp; s) const;
			bool operator!=(const String&amp; s) const;
	
	
			// 返回它的字符串 -- 返回 char* 类型
			const char* c_str() const;
	
			// 判断是否为空字符串
			bool empty() const;
	
			// find -- 从pos位置开始查找字符/字符串
			size_t find(char ch, size_t pos = 0) const;
			size_t find(const char* str, size_t pos = 0) const;
	
	
			// 获得从 pos 位置开始到 len 的子字符串;如果 len 不给值,默认到结尾
			String substr(size_t pos, size_t len = npos) const;
	
			// 在 pos 位置插入插入字符 ch 或字符串 str
			String&amp; insert(size_t pos, char ch);
			String&amp; insert(size_t pos, const char* str);
	
			// 删除从 pos 位置开始 len 长度的字符串;如果 len 不给值就默认删到末尾 
			String&amp; erase(size_t pos, size_t len = npos);
	
			// 打印数据
			void Print();
	
		private:
			char* _str;
			size_t _size;
			size_t _capacity;
	
		public:
			const static size_t npos = -1;
		};
	
		// 流插入、流提取      cout &lt;&lt; s1;
		ostream&amp; operator&lt;&lt;(ostream&amp; out, const String&amp; s);
		istream&amp; operator&gt;&gt;(istream&amp; in, String&amp; s);
	}</code></pre></div></div><h4 id="f9rdl" name="1.-%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0">1. 构造函数</h4><p>构造函数往往是实现一个类首先需要考虑的,而 <strong>string</strong> 类的成员变量分别有 <code>char* _str; size_t _size; size_t _capacity;</code> ,虽然都是内置类型,但是 <strong>char</strong>* 类型需要我们手动申请空间,所以需要我们显式写构造函数:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">			// 构造函数
		String(const char* str = &#34;&#34;)
			:_str(new char [strlen(str) + 1])
			,_size(strlen(str))
			,_capacity(_size)
		{
			strcpy(_str, str);
		}</code></pre></div></div><p>我们在构造函数中给了缺省值 <code>&#34;&#34;</code> 即空字符串;我们在申请空间的时候往往是要申请比 <strong>str</strong> 多一个空间,因为需要存放 <code>&#39;\0&#39;</code>;最后我们使用 <strong>strcpy</strong> 函数将 <strong>str</strong> 拷贝到 <strong>_str</strong> 即可。</p><h4 id="aah55" name="2.-%E6%9E%90%E6%9E%84%E5%87%BD%E6%95%B0">2. 析构函数</h4><p>因为 <strong>string</strong> 类是我们手动申请空间的,所以要我们手动释放,析构函数如下:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">			// 析构函数
		~String()
		{
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 0;
		}</code></pre></div></div><p>要注意使用 <strong>delete</strong> 需要匹配使用。</p><h4 id="7j459" name="3.-%E6%8B%B7%E8%B4%9D%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0">3. 拷贝构造函数</h4><p>在这里的拷贝构造函数中,我们就需要回顾一下<strong>浅拷贝</strong>和<strong>深拷贝</strong>了,我们此前也在 类和对象(中篇) 了解过,现在来回顾一下。</p><ol class="ol-level-0"><li><strong>浅拷贝:</strong> 也称值拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。</li></ol><p>我们可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享。</p><ol class="ol-level-0"><li><strong>深拷贝:</strong> 如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。</li></ol><p>下面我们自己显式写一个 <strong>string</strong> 类的拷贝构造函数:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">			// String s2(s1);
		// 拷贝构造
		String(const String&amp; str)
			:_str(nullptr)
			,_size(0)
			,_capacity(0)
		{
			String tmp(str._str);
			swap(tmp);
		}</code></pre></div></div><p>注意,上面的拷贝构造中,假设是这样: <code>String s2(s1);</code> 则 <strong>_str</strong> 就是 <strong>s2</strong> 对象的,<strong>str</strong> 是 <strong>s1</strong> 对象的,我们的思路是首先将 <strong>_str</strong> 走初始化列表置空,<strong>_size</strong> 和 <strong>_capacity</strong> 置零,然后利用构造函数 <code>String tmp(str._str);</code> 实例化一个 <strong>tmp</strong> 对象,此时 <strong>tmp</strong> 相当于是 <strong>s1</strong> ,最后将 <strong>s2</strong> 和 <strong>tmp</strong> 对象中的资源交换,即完成了 <strong>s1</strong> 拷贝给 <strong>s2</strong>;而且出了作用域 <strong>tmp</strong> 还会自动调用析构函数析构。</p><p>注意以上的 <code>swap</code> 函数也是 <strong>string</strong> 类中的,所以也需要我们自己实现,实现如下:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">			// 交换
		void swap(String&amp; tmp)
		{
			std::swap(_str, tmp._str);
			std::swap(_size, tmp._size);
			std::swap(_capacity, tmp._capacity);
		}</code></pre></div></div><p>在实现中,我们再利用标准库中的 <strong>swap</strong> 函数帮助我们完成 string 类的<strong>swap</strong> 函数。</p><p>使用和结果如下图:</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:auto"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722949347225505291.png" /></div></div></div></figure><h4 id="eto9r" name="4.-%E8%B5%8B%E5%80%BC%E8%BF%90%E7%AE%97%E7%AC%A6%E9%87%8D%E8%BD%BD">4. 赋值运算符重载</h4><p>赋值运算符重载也和拷贝构造差不多,实现如下:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">			// s2 = s1
		// 赋值运算符重载
		String&amp; operator=(const String&amp; str)
		{
			if (this != &amp;str)
			{
				String tmp(str._str);
				swap(tmp);
			}

			return *this;
		}</code></pre></div></div><p>使用和结果如下:</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:auto"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722949347884849727.png" /></div></div></div></figure><h4 id="9ij0d" name="5.-%E8%BF%AD%E4%BB%A3%E5%99%A8">5. 迭代器</h4><p><strong>string</strong> 类的迭代器其实就是原生指针,声明在上面的 <strong>string</strong> 类声明中,下面我们直接实现:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">		// 迭代器
	Young::String::iterator Young::String::begin()
	{
		return _str;
	}
	
	Young::String::iterator Young::String::end()
	{
		return _str + _size;
	}
	
	Young::String::const_iterator Young::String::begin() const
	{
		return _str;
	}
	
	Young::String::const_iterator Young::String::end() const
	{
		return _str + _size;
	}</code></pre></div></div><p>由于我们是声明和定义分离写,所以在 <code>iterator/const_iterator</code> 和 <code>begin()/end()</code> 前都要指定我们的作用域;其中 <strong>iterator/const_iterator</strong> 分别是普通对象调用的迭代器和 <strong>const</strong> 对象调用的迭代器;<strong>begin()/end()</strong> 分别是指向字符串的头和尾的指针。</p><h4 id="2maas" name="6.-%E5%85%83%E7%B4%A0%E8%AE%BF%E9%97%AE%EF%BC%9A%5B%5D-%E9%87%8D%E8%BD%BD">6. 元素访问:[] 重载</h4><p>为了方便访问 <strong>string</strong>,我们可以重载 [] 可以直接访问下标,实现如下:</p><p>const对象:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">		const char&amp; Young::String::operator[](size_t index) const
	{
		assert(index &lt; _size);
	
		return _str[index];
	}</code></pre></div></div><p>普通对象:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">		char&amp; Young::String::operator[](size_t index)
	{
		assert(index &lt; _size);
	
		return _str[index];
	}</code></pre></div></div><h4 id="8kfei" name="7.-%E6%B5%81%E6%8F%92%E5%85%A5%E4%B8%8E%E6%B5%81%E6%8F%90%E5%8F%96%E9%87%8D%E8%BD%BD">7. 流插入与流提取重载</h4><p>在使用 <strong>string</strong> 的时候,为了方便查看字符串,我们可以重载流插入和流提取,方便打印查看字符串;在以前讲过,我们为了方便我们的使用以及体现流插入和提取的使用价值,我们要在类外面实现,防止 <strong>this</strong> 指针抢占第一个参数位置,实现如下:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">		// 流插入    cout &lt;&lt; s1;
	ostream&amp; Young::operator&lt;&lt;(ostream&amp; out, const String&amp; s)
	{
		for (size_t i = 0; i &lt; s.size(); i++)
		{
			out &lt;&lt; s[i];
		}
		return out;
	}</code></pre></div></div><p>流插入中我们只需要将每一个字符打印出来即可;</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">		// 流提取    cin &gt;&gt; s
	istream&amp; Young::operator&gt;&gt;(istream&amp; in, String&amp; s)
	{
		s.clear();
	
		char buff[129];
		size_t i = 0;
	
		char ch = in.get();
	
		while (ch != &#39; &#39; &amp;&amp; ch != &#39;\n&#39;)
		{
			buff[i++] = ch;
			if (i == 128)
			{
				buff[i] = &#39;\0&#39;;
				s += buff;
				i = 0;
			}
			ch = in.get();
		}
	
		if (i != 0)
		{
			buff[i] = &#39;\0&#39;;
			s += buff;
		}
	
		return in;
	}</code></pre></div></div><p>我们创建一个 <strong>buff</strong> 数组,存放输入的字符,当 <strong>buff</strong> 数组满了就一把插入到对象中,避免频繁开辟空间;因为流提取默认遇到 <code>&#39; &#39;</code> 或 <code>&#39;\0&#39;</code> 就结束,所以我们需要用 <strong>cin</strong> 的成员函数 <code>get()</code> 提取到 <code>&#39; &#39;</code> 或 <code>&#39;\0&#39;</code> ,方便我们判断结束条件。</p><h4 id="bkkrb" name="8.-%E4%B8%8E%E5%AE%B9%E9%87%8F%E7%9B%B8%E5%85%B3%E7%9A%84%E6%8E%A5%E5%8F%A3">8. 与容量相关的接口</h4><h5 id="dluqk" name="%EF%BC%881%EF%BC%89size">(1)size</h5><p>获取字符串的有效长度,实现:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">		size_t Young::String::size() const
	{
		return _size;
	}</code></pre></div></div><h5 id="7inqv" name="%EF%BC%882%EF%BC%89capacity">(2)capacity</h5><p>获取字符串的容量,实现:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">		size_t Young::String::capacity() const
	{
		return _capacity;
	}</code></pre></div></div><h5 id="5jagq" name="%EF%BC%883%EF%BC%89clear">(3)clear</h5><p>清空字符串的内容,实现:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">		void Young::String::clear()
	{
		_str[0] = &#39;\0&#39;;
		_size = 0;
	}</code></pre></div></div><p>清空字符串的内容并不是销毁空间,所以只需要在下标为 0 位置加上 <code>&#39;\0&#39;</code> 即可,并将长度置 0.</p><h5 id="dils6" name="%EF%BC%884%EF%BC%89empty">(4)empty</h5><p>判断字符串是否为空字符串,实现:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">		bool Young::String::empty() const
	{
		return _size == 0;
	}</code></pre></div></div><p>只需要判断 <strong>_size</strong> 是否为 0.</p><p>上面四个接口的使用如下:</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:auto"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722949348295052118.png" /></div></div></div></figure><h5 id="3fn1v" name="%EF%BC%885%EF%BC%89reserve">(5)reserve</h5><p>我们可以查看 <strong>reserve</strong> 接口的相关文档:</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:auto"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722949348609294326.png" /></div><div class="figure-desc">在这里插入图片描述</div></div></div></figure><p>其实就是申请 <strong>n</strong> 个空间,<strong>reserve</strong> 有保留的意思,就是有保留 <strong>n</strong> 个空间的意思,<strong>n</strong> 大于 <strong>_capacity</strong> 就改变空间,小于则不用改变;注意 <strong>reserve</strong> 不改变 <strong>_size</strong> 的值;其实现如下:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">		// 申请空间
	void Young::String::reserve(size_t n)
	{
		if (n &gt; _capacity)
		{
			char* tmp = new char[n + 1];
			strcpy(tmp, _str);
	
			delete[] _str;
			_str = tmp;
	
			_capacity = n;
		}
	}</code></pre></div></div><p>假设需要申请 <strong>n</strong> 个空间,就需要申请 <strong>n+1</strong> 空间,给 <code>&#39;\0&#39;</code> 预留一个空间;然后将原来字符串中的内容拷贝到新开辟的空间中,然后销毁原来的空间 <strong>_str</strong>,让原来的空间 <strong>_str</strong> 指向新的空间 <strong>tmp</strong>。</p><h5 id="5kjua" name="%EF%BC%886%EF%BC%89resize">(6)resize</h5><p><strong>reserve</strong> 和 <strong>resize</strong> 的区别就是 <strong>resize</strong> 是调整空间的大小,并可以初始化空间,<strong>resize</strong> 是可以改变 <strong>_size</strong> 的值的。</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">		// 调整空间+初始化
	void Young::String::resize(size_t n, char c)
	{
		// 如果 n 大于 _size,直接申请 n 个空间,然后从原来的尾部开始初始化 
		if (n &gt; _size)
		{
			reserve(n);
	
			for (size_t i = _size; i &lt; n; i++)
			{
				_str[i] = c;
			}
	
			_str[n] = &#39;\0&#39;;
			_size = n;
		}
	
		// 否则,删数据
		else
		{
			_str[n] = &#39;\0&#39;;
			_size = n;
		}
	}</code></pre></div></div><p>初始化的字符如果没有显式传,会使用我们在声明处给的缺省值 <code>&#39;\0</code>。</p><h4 id="c88fr" name="9.-%E4%BF%AE%E6%94%B9%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E7%9B%B8%E5%85%B3%E6%8E%A5%E5%8F%A3">9. 修改字符串的相关接口</h4><h5 id="3rjbj" name="%EF%BC%881%EF%BC%89push_back">(1)push_back</h5><p>尾插,在字符串尾部插入一个字符,我们先看原文档:</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:auto"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722949349168838582.png" /></div></div></div></figure><p>

实现如下:

代码语言:javascript
复制
		// 尾插字符
void Young::String::push_back(char c)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}

		_str[_size++] = c;
		_str[_size] = &#39;\0&#39;;
	}</code></pre></div></div><p>尾插之前需要判断容量是否已经满了,满了就要扩容;或者容量是 0,我们就默认开 4 个容量。</p><h5 id="15lle" name="%EF%BC%882%EF%BC%89append">(2)append</h5><p>追加字符串,我们先看文档:</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:auto"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722949349564811600.png" /></div></div></div></figure><p>文档中重载了许多接口,我们在这里只实现一个接口,就是尾插字符串,也就是上图中的第三个接口,实现如下:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">		// 尾插字符串
	void Young::String::append(const char* str)
	{
		int len = strlen(str);
	
		// 空间不够扩容
		if (_size + len &gt; _capacity)
		{
			reserve(_size + len);
		}
	
		strcpy(_str + _size, str);
		_size += len;
	}</code></pre></div></div><h5 id="edesm" name="%EF%BC%883%EF%BC%89+=-%E8%BF%90%E7%AE%97%E7%AC%A6%E9%87%8D%E8%BD%BD">(3)+= 运算符重载</h5><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:auto"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722949350160286350.png" /></div><div class="figure-desc">在这里插入图片描述</div></div></div></figure><p><strong>+=</strong> 运算符也是追加字符、字符串、string 对象,我们在这里实现追加字符和字符串,也就是尾插,其实现如下:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">		//尾插字符
	Young::String&amp; Young::String::operator+=(char c)
	{
		push_back(c);
	
		return *this;
	} </code></pre></div></div><p>有了之前实现的 <code>push_back</code> 和 <code>append</code> ,我们只需要复用它们就可以实现了;</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">		// 尾插字符串
	Young::String&amp; Young::String::operator+=(const char* str)
	{
		append(str);
	
		return *this;
	}</code></pre></div></div><p>以上四个接口的使用与流插入流提取的使用如下:</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:auto"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722949350493693090.png" /></div></div></div></figure><h5 id="1l6q7" name="%EF%BC%884%EF%BC%89insert">(4)insert</h5><p><strong>insert</strong> 是在 <strong>pos</strong> 位置插入字符 <strong>ch</strong> 或字符串 <strong>str</strong>,我们就实现插入字符或字符串的接口,实现如下:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">		// 插入字符
	Young::String&amp; Young::String::insert(size_t pos, char ch)
	{
		assert(pos &lt; _size);
	
		// 满了就扩容
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}
	
		// 挪动数据
		size_t end = _size + 1;
		while (end &gt; pos)
		{
			_str[end] = _str[end - 1];
			end--;
		}
	
		// 插入字符
		_str[pos] = ch;
		_size++;
	
		return *this;
	}</code></pre></div></div><p>插入字符串:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">		Young::String&amp; Young::String::insert(size_t pos, const char* str)
	{
		assert(pos &lt; _size);
	
		// 判断插入字符串的长度是否会满
		size_t len = strlen(str);
		if (_size + len &gt; _capacity)
		{
			reserve(_size + len);
		}
	
		// 挪动数据
		size_t end = _size + len;
		while (end &gt; pos)
		{
			_str[end] = _str[end - len];
			end--;
		}
	
		// 拷贝数据1.
		/*for (size_t i = pos; i &lt; len; i++)
		{
			_str[i] = str[i];
		}*/
	
		// 拷贝数据2.
		strncpy(_str + pos, str, len);
		_size += len;
	
		return *this;
	}</code></pre></div></div><h5 id="dn9qe" name="%EF%BC%885%EF%BC%89erase">(5)erase</h5><p><strong>erase</strong> 是删除从 <strong>pos</strong> 位置开始 <strong>len</strong> 长度的字符串;如果 <strong>len</strong> 不给值就默认删到末尾;</p><p>到末尾我们需要在声明处定义一个 <strong>npos</strong> 的静态无符号变量,将它定义为 <strong>-1</strong> ,因为是无符号,所以它是整型的最大值,我们在缺省值处给 <strong>npos</strong> ,即可取到末尾。注意,如果声明和定义分离写,缺省值只能给在声明处。</p><p>实现如下:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">		Young::String&amp; Young::String::erase(size_t pos, size_t len)
	{
		assert(pos &lt; _size);
		
		// 删到末尾
		if (len == npos || pos + len &gt; _size)
		{
			_str[pos] = &#39;\0&#39;;
			_size = pos;
		}
	
		// 删 len 长度
		else
		{
			strcpy(_str + pos, _str + pos + len);
			_size -= len;
		}
	
		return *this;
	}</code></pre></div></div><p>insert 和 erase 的使用如下图:</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:auto"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722949350899828684.png" /></div></div></div></figure><h4 id="73i07" name="10.-%E6%93%8D%E4%BD%9C%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%8E%A5%E5%8F%A3">10. 操作字符串的接口</h4><h5 id="5p8ii" name="%EF%BC%881%EF%BC%89c_str">(1)c_str</h5><p>返回它的字符串 - 返回 char* 类型,实现:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">		const char* Young::String::c_str() const
	{
		return _str;
	}</code></pre></div></div><h5 id="4kjtp" name="%EF%BC%882%EF%BC%89find">(2)find</h5><p><strong>find</strong> 是查找函数的接口,从 <strong>pos</strong> 位置开始查找字符/字符串,<strong>pos</strong> 不给值默认下标从 <strong>0</strong> 开始找,实现如下:</p><p>查找字符:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">		size_t Young::String::find(char ch, size_t pos) const
	{
		assert(pos &lt; _size);
	
		for (size_t i = pos; i &lt; _size; i++)
		{
			if (_str[i] == ch)
			{
				// 返回下标
				return i;
			}
		}
	
		return npos;
	}</code></pre></div></div><p>查找字符串:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">		size_t Young::String::find(const char* str, size_t pos) const
	{
		assert(pos &lt; _size);
		assert(str);
	
		const char* ret = strstr(_str + pos, str);
		if (ret == nullptr)
			return npos;
	
		// 下标相减,返回下标
		return ret - _str;
	}</code></pre></div></div><p><strong>strstr</strong> 是查找匹配字串的库函数,它的返回值是如果找到就返回匹配字串的开头,否则返回空。</p><h5 id="2j7nr" name="%EF%BC%883%EF%BC%89substr">(3)substr</h5><p><strong>substr</strong> 是获得从 <strong>pos</strong> 位置开始到 <strong>len</strong> 的子字符串;如果 <strong>len</strong> 不给值,默认到结尾,即 <strong>npos</strong>,实现如下:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">		Young::String Young::String::substr(size_t pos, size_t len) const
	{
		assert(pos &lt; _size);
	
		// 创建一个临时对象 tmp
		String tmp;
	
		// end 为取到的子串的结尾的下标
		size_t end = pos + len;
	
		// 取到末尾
		if (len == npos || end &gt; _size)
		{
			len = _size - pos;
			end = _size;
		}
	
		// 申请 len 的空间
		tmp.reserve(len);
	
		// 开始取子串
		for (size_t i = pos; i &lt; end; i++)
		{
			tmp += _str[i];
		}
	
		return tmp;
	}</code></pre></div></div><p>一般 <strong>find</strong> 和 <strong>substr</strong> 一起使用,它们的使用场景可以将一个网址分割成协议、域名、资源名,使用如下:</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:auto"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722949351430526770.png" /></div></div></div></figure><h4 id="4m5bh" name="11.-%E6%AF%94%E8%BE%83%E8%BF%90%E7%AE%97%E7%AC%A6%E9%87%8D%E8%BD%BD">11. 比较运算符重载</h4><p>我们也像以前一样,只需要实现 <code>&gt;</code> 和 <code>==</code> 运算符,其它的都复用这两个就可以了,实现如下:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0">		bool Young::String::operator&gt;(const String&amp; s) const
	{
		return strcmp(_str, s._str) &gt; 0;
	}
	
	bool Young::String::operator==(const String&amp; s) const
	{
		return strcmp(_str, s._str) == 0;
	}
	
	bool Young::String::operator&gt;=(const String&amp; s) const
	{
		return *this &gt; s || *this == s;
	}
	
	bool Young::String::operator&lt;(const String&amp; s) const
	{
		return !(*this &gt;= s);
	}
	
	bool Young::String::operator&lt;=(const String&amp; s) const
	{
		return !(*this &gt; s);
	}
	
	bool Young::String::operator!=(const String&amp; s) const
	{
		return !(*this == s);
	}</code></pre></div></div><p>使用和结果如下:</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:auto"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722949351839628565.png" /></div></div></div></figure>