十、模拟实现 String

Ⅰ . 深浅拷贝

01 深浅拷贝问题

我们先来看一下 string 的构造和析构:

namespace yxt
{
	class string
	{
	public:
		string(const char* str)
		{
			// ...
		}

		~string()
		{
			// ...
		}
	private:
		char* _str;
	};
}

为了和原有的 string 进行区分,使用命名空间把它包起来

那么,构造函数可以像下面这样初始化嘛?

string(char* str)
	: _str(str) {}

当然是不行的,因为在我们初始化 string 时,通常是这么写的:

void test_string1() 
{
	string s1("hello world");
}

这是一个常量字符串,即使不是常量字符串,也是一个指针,是不能被修改的,那我们之后的增删改,怎么扩容呢?

就只能对堆上的空间扩容了,所以不能这么写

我们可以这样写:
 

string(const char* str)
	: _str(new char[strlen(str) + 1]) // 开strlen大小的空间
	{    
	    strcpy(_str, str);
	}

值得注意的是,这里需要 strlen(str) + 1,因为 strlen 算的是有效字符串的长度,不包含 \0 

下面实现析构:

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

我们来测试一下:

string.h

#pragma once
#include<iostream>
using namespace std;

namespace yxt
{
	class string
	{
	public:
		string(const char* str)
			: _str(new char[strlen(str) + 1]) // 开strlen大小的空间
		{
			strcpy(_str, str);
		}

		~string()
		{
			delete[] _str;
			_str = nullptr;
		}
	private:
		char* _str;
	};

	void test_string1()
	{
		string s1("hello world");
	}
}

test.cpp

#include"string.h"

int main()
{
	yxt::test_string1();

	return 0;
}

运行结果如下:编译通过

此时我们把测试用例修改一下,用 s1 拷贝构造 s2:

	void test_string1()
	{
		string s1("hello world");
		string s2(s1);
	}

运行结果如下:发生崩溃

解析:

默认生成的拷贝构造会按值拷贝(浅拷贝),将 s1 空间的每一个字节依次拷贝给 s2

通过调试我们会发现它们都指向了同一块空间

在出作用域后,会调用析构函数

后定义的先析构,此时 s2 先调用析构函数将空间释放掉了,但 s1所指的还是那块已经释放的空间

这样一来,s1 中的 _str 就成为了野指针,对野指针释放 直接报错

那么这么解决这样的问题呢?

我们 s2 拷贝构造你 s1,本意并不是想跟你指向一块空间!

我们的本意是想让 s2 有一块自己的空间,并且内容是 s1 里的 hello world

这里就涉及到了深浅拷贝的问题

02 深浅拷贝问题

我们举个最简单的例子 —— 拷贝就像是在抄作业!

浅拷贝:直接无脑照抄,连名字都不改。

            (直接把内存无脑指过去)

深拷贝:聪明地抄,抄的像是我自己写的一样。

            (开一块一样大的空间,再把数据拷贝下来,指向我自己开的空间)

  • 浅拷贝就是 原封不动 地把成员变量按字节依次拷贝过去。
  • 深拷贝就是进行深一个层次的拷贝,不是直接拷贝,而是 拷贝你指向的空间

03 拷贝构造的实现

我们之前实现日期类的时候,用自动生成的拷贝构造(浅拷贝)是可以的,

所以当时我们不用自己实现拷贝构造,让它默认生成就足够了。

但像 string 这样的类,它的拷贝构造我们不得不自己写

string 的拷贝构造:

/* s2(s1) */
string(const string& s)
	: _str(new char[strlen(s._str) + 1]) 
{
	strcpy(_str, s._str);
}

通过调试,发现内容一样,但地址不一样,是各自的空间

这就是深拷贝

04 赋值的深拷贝

现在有 s3,我们想把 s3 赋值给 s1:

void test_string1()
{
	string s1("hello world");
	string s2(s1);

	string s3("pig");
	s1 = s3;
}

 如果不自己实现赋值,将会和之前一样,因为浅拷贝问题而程序崩溃

所以我们需要自己实现 operator=,思路如下:

代码实现:

/* s1 = s3 */
string& operator=(const string& s)
{
	// 防止自己给自己赋值
	if (this != &s)
	{
		// 释放原有空间
		delete[] _str;
		// 开辟新空间
		_str = new char[strlen(s._str) + 1];
		// 赋值
		strcpy(_str, s._str);
	}
}

解析:

根据我们的实现思路,首先释放原有空间,然后开辟新的空间,

最后把 s3 的值赋值给 s1。为了防止自己给自己赋值,我们可以判断一下。

这时我们还要考虑一个难以发现的问题,如果 new 失败了怎么办?

程序会抛异常

失败了没问题,也不会走到 strcpy,但问题是我们已经把原有的空间释放掉了

走到析构那里二次释放可能会炸,所以我们得解决这个问题

我们可以试着把释放原有空间的步骤放到后面:

/* s1 = s3 */
string& operator=(const string& s)
{
	// 防止自己给自己赋值
	if (this != &s)
	{
		// 开辟新的空间
		char* tmp = new char[strlen(s._str) + 1];;
		// 赋值
		strcpy(tmp, s._str);
		// 释放原有空间
		delete[] _str;
		// 赋值
		_str = tmp;
	}
}

解析:

这样一来,就算是动态内存开辟失败了,我们也不用担心出问题了。

这是更标准的实现方式,我们先去开辟空间,放到临时变量 tmp 中,

tmp 没有翻车,再去释放原有的空间,最后再把 tmp 的值交付给 s1,

这是非常保险的,有效避免了空间没开成还把 s1 空间释放掉的事发生

Ⅱ . 模拟实现 string 库

01 正式开始实现 string

刚才我们为了方便讲解深浅拷贝的问题,有些地方所以没有写全,

是没有考虑增删查改的问题的,所以我们现在要增加一些成员:

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

02 成员函数 size 和 capacity

#include<iostream>
using namespace std;

namespace yxt
{
	class string
	{
	public:
        string(const char* str) 
		: _size(strlen(str)) 
		, _capacity(_size) 
    {
		_str = new char[_capacity + 1];     // 多开一个空间给\0
		strcpy(_str, str);
	}

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

		/* s2(s1) */
		string(const string& s)
		{
			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}

		/* s1 = s3 */
		string& operator=(const string& s)
		{
			// 防止自己给自己赋值
			if (this != &s)
			{
				// 开辟新的空间
				char* tmp = new char[strlen(s._str) + 1];;
				// 赋值
				strcpy(tmp, s._str);
				// 释放原有空间
				delete[] _str;
				// 赋值
				_str = tmp;

				_size = s._size;
				_capacity = s._capacity;
			}
			return *this;
		}
	private:
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0;
	};
}

为了减少 strlen 的次数,在初始化列表中只处理 _size 和 _capacity

03 c_str() 的实现

c_str() 返回的是C语言字符串的指针常量,是可读不写的。

c_str 的实现:

/* 返回C格式字符串:c_str */
const char* c_str() const 
{
	return _str;
}

const char*,因为是可读不可写的,所以我们需要用 const 修饰。

c_str 返回的是当前字符串的首字符地址,这里我们直接 return _str 即可实现。

我们来测试一下:

void test_string1() 
{
	string s1("hello world");
	string s2;
 
	cout << s1.c_str() << endl;
}

运行结果如下:

04 全缺省构造函数

我们还要考虑不带参的情况,比如下面的 s2:

void test_string1() 
{
	string s1("hello world");    // 带参
	string s2;                   // 不带参
}

不带参初始化:

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

这里我们开一个空间给 \0,既然都这么写了,我们不如直接在缺省值上动手脚:

string(const char* str = "")
	: _size(strlen(str)) 
	, _capacity(_size) 
{
	_str = new char[_capacity + 1];     // 多开一个空间给\0
	strcpy(_str, str);
}

一般的类都是提供全缺省的,值得注意的是,这里缺省值给的是 " "

有人看到指针 char* 就忍不住想给个空 nullptr:

string(const char* str = nullptr)

不能给!给了就崩。因为 strlen 是不会去检查空的,它是去找 \0 

void test_string2() 
{
	string s1("hello world");
	string s2;
 
	cout << s1.c_str() << endl;
	cout << s2.c_str() << endl;
}

也就相当于直接对这个字符串进行解引用了,这里的字符串又是空,所以会引发空指针问题。

所以我们这里给的是一个空的字符串 " ",常量字符串默认就带有 \0,这样就不会出问题:

string(const char* str = "")

05 size() 和 operator[] 的实现

size() 的实现:

size_t size() const 
{
	return _size;
}

operator[] 的实现:

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

	return _str[pos];
}

直接返回字符串对应下标位置的元素

因为返回的是一个字符,所以我们这里引用返回 char。

我们来测试一下,遍历整个字符串,这样既可以测试到 size() 也可以测试到 operator[] :

void test_string1() 
{
	string s1("hello world");
	string s2;
 
	for (size_t i = 0; i < s1.size(); i++) 
    {
		cout << s1[i] << " ";
	}
	cout << endl;
}

运行结果如下:

我们再来测试一下 operator[] 的 "写" 功能

void test_string1() 
{
	string s1("hello world");
	string s2;
 
	s1[0] = 'F';
	for (size_t i = 0; i < s1.size(); i++) 
    {
		cout << s1[i] << " ";
	}
	cout << endl;
}

普通对象可以调用,但是 const 对象呢?所以我们还要考虑一下 const 对象。

我们写一个 const 对象的重载版本:

const char& operator[](size_t pos) const
{
	assert(pos < _size);

	return _str[pos];
}

因为返回的是 pos 位置字符的 const 引用,所以可读但不可写。

Ⅲ . 实现迭代器(iterator)

01 再探迭代器

在上一章中,我们首次讲解迭代器,为了方便理解,我们当时解释其为像指针一样的类型。

实际上,有没有一种可能,它就是一种指针呢?

遗憾的是,迭代器并非指针,而是类模板。 只是它表现地像指针,模拟了指针的部分功能。

02 迭代器的实现

实际上迭代器的实现非常简单,它就是一个 char* 的指针罢了(但也不一定)。

后面我们讲解 list 的时候它又不是指针了,又是自定义类型了

实现迭代器的 begin() 和 end():

typedef char* iterator;
iterator begin()
{
	return _str;
}

iterator end()
{
	return _str + size();	// 返回最后一个数据的下一个位置
}

我们来测试一下:

void test_string2() 
{
	string s1("hello world");

	// 迭代器写
	string::iterator it = s1.begin();
	while (it != s1.end()) 
	{
		*it += 1;
		it++;
	}

	// 迭代器读
	it = s1.begin();   // 重置起点
	while (it != s1.end()) 
	{
		cout << *it << " ";
		it++;
	}
}
	

运行结果如下:

03 const 迭代器的实现

我们知道,const 迭代器就是可以读但是不可以写的迭代器。

const 迭代器:

typedef const char* const_iterator;
const_iterator begin() const
{
	return _str;
}

const_iterator end() const
{
	return _str + size();	// 返回最后一个数据的下一个位置
}

这里用 const 修饰,意味着解引用时可以读但不可以写。

04 思考迭代器

它的底层是连续地物理空间,给原生指针++解引用能正好贴合迭代器的行为,就能做到遍历。

但是对于链表和树型结构来说,迭代器的实现就没有这么简单了。

但是,强大的迭代器通过统一的封装,无论是树、链表还是数组……

它都能用统一的方式遍历,这就是迭代器的优势,也是它的强大之处。

05 再探范围 for

上一章讲 string 类对象的遍历时,我们讲的第三种方式就是范围 for,回忆一下 

我们上一章提到过,我们现在就来演示一下范围 for 的实现:

for (auto e : s1) 
{
	cout << e << " ";
}
cout << endl;

你会发现根本就不需要自己实现,你只要把迭代器实现好,范围 for 直接就可以用。

范围 for 的本质是由迭代器支持的,编译时范围 for 会被替换成迭代器。

这么一看,又是自动加加,又是自动判断结束的范围 for,好像也没那么回事儿。

注意:

它的替换是认 begin 和 end 的,我们可以试着把我们实现的迭代器 begin 的 b 改成大写 B 试试:

		typedef char* iterator;
		iterator Begin() {
			return _str;         
		}
		iterator end() {
			return _str + _size;  
		}
void test_string2() {
	string s1("hello world");
	string::iterator it = s1.Begin();
	while (it != s1.end()) {
		*it += 1;
		it++;
	}
	it = s1.Begin();   // 重置起点
	while (it != s1.end()) {
		cout << *it << " ";
		it++;
	}
	
    for (auto e : s1) {
		cout << e << " ";
	}
	cout << endl;
}

迭代器是可以正常用的,但是范围 for 就寄了。

因为它是按迭代器固定的名称去替换的,begin 和 end,

如果你自己实现迭代器时没有按固定的规范去实现,

比如 begin 取名为 start,那范围 for 就不支持了。

Ⅳ . string 的增删查改

01 reserve() 的实现

我们先实现一下 reserve 增容:

		/*  增容 reserve */
		void reserve(size_t new_capacity)
		{
			if (new_capacity > _capacity)
			{
				char* tmp = new char[new_capacity + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				_capacity = new_capacity;
			}
		}

这里可以检查一下是否真的需要增容,万一接收的 new_capacity 比 _capacity 小,就不动。

 02 push_back() 的实现

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

首先检查是否需要增容,如果需要就调用我们上面实现的 reserve 函数,

参数传递可以用三目操作符,防止容量是0的情况,0乘任何数都是0从而引发问题的情况。

然后在 \0 处插入要追加的字符 append_ch,然后 _size++ 并手动添加一个新的 \0 即可。

我们来测试一下:

	void test_string3() 
	{
		string s1("hello world");
		cout << s1.c_str() << endl;

		s1.push_back('!');
		cout << s1.c_str() << endl;

		s1.push_back('A');
		cout << s1.c_str() << endl;
	}

运行结果如下:

03 append() 的实现

		/* 字符串追加 append */
		void append(const char* str)
		{
			size_t len = strlen(str);
			if (len + _size > _capacity)
			{
				reserve(len + _size > _capacity * 2 ? len + _size : _capacity * 2);
			}
			strcpy(_str + _size, str);
			_size += len;
		}

append 是追加字符串的,首先我们把要追加的字符串长度计算出来,

然后看容量够不够,不够我们就交给 reserve 去扩容,扩 _size + len,够用就行。

这里我们甚至都不需要用 strcat,因为它的位置我们很清楚,不就在 _str + _size 后面插入吗。

用 strcat 还需要遍历找到原来位置的 \0,太麻烦了。

04 operator+= 的实现

字符和字符串都可以用 += 去操作

所以我们需要两个重载版本,一个是字符的,一个是字符串的。

我们不需要自己实现了,直接复用 push_back 和 append 就好了

operator+=:

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

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

测试一下看看:

	void test_string4() 
	{
		string s1("hello world");
		cout << s1.c_str() << endl;

		s1 += '!';
		cout << s1.c_str() << endl;

		s1 += "this is new data";
		cout << s1.c_str() << endl;
	}

运行结果如下:

05 insert() 的实现

字符:

		/* insert */
		void insert(size_t pos, char ch)
		{
			assert(pos <= _size);

			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}
			size_t 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 (len == 0)
		return;

	if (len + _size > _capacity)
	{
		reserve(len + _size > _capacity * 2 ? len + _size : _capacity * 2);
	}

	size_t end = _size + len;
	while (end > pos + len - 1)
	{
		_str[end] = _str[end - len];
		--end;
	}

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

测试一下:

	void test_string5() 
	{
		string s1("hello world");
		cout << s1.c_str() << endl;

		s1.insert(0, 'X');
		cout << s1.c_str() << endl;

		s1.insert(0, "hahahaha");
		cout << s1.c_str() << endl;
	}

运行结果如下:

insert 都实现了,那 push_back 和 append 直接复用就可以了

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

			insert(_size, ch);
		}

		/* 字符串追加 append */
		void append(const char* str)
		{
			//size_t len = strlen(str);
			//if (len + _size > _capacity)
			//{
			//	reserve(len + _size > _capacity * 2 ? len + _size : _capacity * 2);
			//}
			//strcpy(_str + _size, str);
			//_size += len;

			insert(_size, str);
		}

测试一下 push_back 和 append,和复用它们两实现的 operator+= 有没有问题:

void test_string3() 
{
	string s1("hello world");
	cout << s1.c_str() << endl;

	s1.push_back('!');
	cout << s1.c_str() << endl;

	s1.push_back('A');
	cout << s1.c_str() << endl;
}

void test_string4() 
{
	string s1("hello world");
	cout << s1.c_str() << endl;

	s1 += '!';
	cout << s1.c_str() << endl;

	s1 += "this is new data";
	cout << s1.c_str() << endl;
}

运行结果如下:

06 resize() 的实现

我们为了扩容,先实现了 reverse,现在我们再顺便实现一下 resize。

这里再提一下 reverse 和 resize 的区别:

resize 分给初始值和不给初始值的情况,所以有两种:

但是我们上面讲构造函数的时候说过,我们可以使用全缺省的方式,这样就可以二合一了。

resize 实现的难点是要考虑种种情况,我们来举个例子分析一下:

如果欲增容量比 _size 小的情况:

我们直接加 \0 就可以了

如果预增容量比 _size 大的情况:

resize 是开空间 + 初始化,开空间的工作我们就可以交给已经实现好的 reserve,

然后再写 resize 的初始化的功能,我们这里可以使用 memset 函数。

		/* resize */
		void resize(size_t new_capacity, char ch = '\0') 
		{
			// 如果欲增容量比_size小
			if (new_capacity <= _size) 
			{
				_str[new_capacity] = '\0';      // 拿斜杠零去截断
				_size = new_capacity;           // 更新大小
			}
			// 欲增容量比_size大
			else 
			{
				if (new_capacity > _capacity) 
				{
					reserve(new_capacity);
				}
				// 起始位置,初始化字符,初始化个数
				memset(_str + _size, ch, new_capacity - _size);
				_size = _capacity;
				_str[_size] = '\0';
			}
		}

07 find() 的实现

查找字符:

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

遍历整个字符串,找到了目标字符 ch 就返回对应的下标。

如果遍历完整个字符串都没找到,就返回 npos

这个 npos 我们可以在成员变量中定义:

	private:
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0;
		static const size_t npos = -1;

查找字符串:

		size_t find(const char* str, size_t pos)
		{
			assert(pos < _size);

			const char* ptr = strstr(_str + pos, str);
			if (ptr == nullptr)
			{
				return npos;
			}
			else
				return ptr - _str;
		}

这里我们可以用 strstr 去找子串,如果找到了,返回的是子串首次出现的地址。如果没找到,返回的是空。所以我们这里可以做判断,如果是 nullptr 就返回 npos。如果找到了,就返回对应下标,子串地址 - 开头,就是下标了。

08 erase() 的实现

		/* erase */
		void erase(size_t pos, size_t len)
		{
			assert(pos < _size);

			if (pos + len >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				for (size_t i = pos + len; i <= _size; i++)
				{
					_str[i - len] = _str[i];
				}
				_size -= len;
			}
		}

测试一下:

	void test_string6() 
	{
		string s1("hello world");
		cout << s1.c_str() << endl;

		s1.erase(5, 2);   // 从第五个位置开始,删两个字符
		cout << s1.c_str() << endl;

		s1.erase(5, 20);  // 从第五个位置开始,删完
		cout << s1.c_str() << endl;
	}

运行结果如下:

 

Ⅴ . 传统写法和现代写法

01 拷贝构造的传统写法

对于深拷贝,传统写法就是本本分分分地去完成深拷贝。

我们刚才实现的方式,用的就是传统写法:

		/* s2(s1) */
		string(const string& s)
		{
			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}

这就是传统写法

02 拷贝构造的现代写法

现在我们来介绍一种现代写法,它和传统写法本质工作是一样的,即完成深拷贝。

现代写法的方式不是本本分分地去按着 Step 一步步干活,而是 "投机取巧" 地去完成深拷贝。

看代码:

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

		string(const string& s)
			:_str(nullptr)
		{
			string tmp(s._str);
			swap(tmp);
		}

现代写法的本质就是复用了构造函数。

我们为什么要在初始化列表中,给 _str 个空指针:

string(const string& s)
	: _str(nullptr)

我们可以设想一下,如果我们不对他进行处理,那么它的默认指向会是个随机值。

tmp 是一个局部对象,我们把 s2 原来的指针和 tmp 交换了,那么 tmp 就成了个随机值了。

tmp 出了作用域要调用析构函数,对随机值指向的空间进行释放,怎么释放?

不是你自己的 new / malloc 出来的,你还硬要对它释放,就可能会引发崩溃。

但是 delete / free 一个空,是不会报错的,因为会进行一个检查。

所以是可以 delete 一个空的,我们这里初始化列表中把 nullptr 给 _str,

是为了交换完之后, nullptr 能交到 tmp 手中,这样 tmp 出了作用域调用析构函数就不会翻车了

03 赋值重载的现代写法

传统写法:

/* s1 = s3 */
string& operator=(const string& s)
{
	// 防止自己给自己赋值
	if (this != &s)
	{
		// 开辟新的空间
		char* tmp = new char[strlen(s._str) + 1];;
		// 赋值
		strcpy(tmp, s._str);
		// 释放原有空间
		delete[] _str;
		// 赋值
		_str = tmp;

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

现代写法:

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

不用引用传参,利用了拷贝构造。

04 总结

现代写法在 string 中体现的优势还不够大,因为好像和传统写法差不多。

 但是到后面我们实现 vector、list 的时候,你会发现现代写法的优势真的是太大了。

现代写法写起来会更简单些,比如如果是个链表,传统写法就不是 strcpy 这么简单的了,

你还要一个一个结点拷贝过去,但是现代写法只需要调用 swap 交换一下就可以了。

现代写法更加简洁,只是在 string 这里优势体现的不明显罢了,我们后面可以慢慢体会。
 

Ⅵ . operator 运算符重载

01 operator<

看代码:

	/* s1 < s2 */
	bool operator<(const string& s1, const string& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) < 0;
	}

02 operator==

看代码:

	/* s1 == s2 */
	bool operator==(const string& s1, const string& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) == 0;
	}

03 剩下的直接复用

/* s1 <= s2 */
bool operator<=(const string& s1, const string& s2)
{
	return s1 < s2 || s1 == s2;
}

/* s1 > s2 */
bool operator>(const string& s1, const string& s2)
{
	return !(s1 <= s2);
}

/* s1 >= s2 */
bool operator>=(const string& s1, const string& s2)
{
	return s1 > s2 || s1 == s2;
}

/* s1 != s2 */
bool operator!=(const string& s1, const string& s2)
{
	return !(s1 == s2);
}

Ⅶ . 流插入和流提取

01 operator<< 的实现

	// cout << s1  →  operator<<(cout, s1)
	ostream& operator<<(ostream& out, const string& s)
	{
		for (auto ch : s)
		{
			out << ch;
		}
		return out;
	}

 02 operator>> 的实现

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

		const int N = 1024;
		char buffer[N];
		int i = 0;

		char ch;
		ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			buffer[i++] = ch;
			if (i == N - 1)
			{
				buffer[i] = '\0';
				s += buffer;
				i = 0;
			}

			ch = in.get();
		}

		if (i > 0)
		{
			buffer[i] = '\0';
			s += buffer;
		}
		return in;
	}

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

Ⅷ . 完整代码

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;

namespace yxt
{
	class string
	{
	public:
		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(const char* str = "")
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

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

		size_t size() const
		{
			return _size;
		}

		size_t capacity() const
		{
			return _capacity;
		}

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

			return _str[pos];
		}

		const char& operator[](size_t pos) const
		{
			assert(pos < _size);

			return _str[pos];
		}

		/* s2(s1) */
		string(const string& s)
		{
			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}

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

		string(const string& s)
			:_str(nullptr)
		{
			string tmp(s._str);
			swap(tmp);
		}

		/* s1 = s3 */
		string& operator=(const string& s)
		{
			// 防止自己给自己赋值
			if (this != &s)
			{
				// 开辟新的空间
				char* tmp = new char[strlen(s._str) + 1];;
				// 赋值
				strcpy(tmp, s._str);
				// 释放原有空间
				delete[] _str;
				// 赋值
				_str = tmp;

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

		/*  增容 reserve */
		void reserve(size_t new_capacity)
		{
			if (new_capacity > _capacity)
			{
				char* tmp = new char[new_capacity + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				_capacity = new_capacity;
			}
		}

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

			insert(_size, ch);
		}

		/* 字符串追加 append */
		void append(const char* str)
		{
			//size_t len = strlen(str);
			//if (len + _size > _capacity)
			//{
			//	reserve(len + _size > _capacity * 2 ? len + _size : _capacity * 2);
			//}
			//strcpy(_str + _size, str);
			//_size += len;

			insert(_size, str);
		}

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

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

		/* insert */
		void insert(size_t pos, char ch)
		{
			assert(pos <= _size);

			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}
			size_t 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 (len == 0)
				return;

			if (len + _size > _capacity)
			{
				reserve(len + _size > _capacity * 2 ? len + _size : _capacity * 2);
			}

			size_t end = _size + len;
			while (end > pos + len - 1)
			{
				_str[end] = _str[end - len];
				--end;
			}

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

		/* resize */
		void resize(size_t new_capacity, char ch = '\0') 
		{
			// 如果欲增容量比_size小
			if (new_capacity <= _size) 
			{
				_str[new_capacity] = '\0';      // 拿斜杠零去截断
				_size = new_capacity;           // 更新大小
			}
			// 欲增容量比_size大
			else 
			{
				if (new_capacity > _capacity) 
				{
					reserve(new_capacity);
				}
				// 起始位置,初始化字符,初始化个数
				memset(_str + _size, ch, new_capacity - _size);
				_size = _capacity;
				_str[_size] = '\0';
			}
		}

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

		size_t find(const char* str, size_t pos)
		{
			assert(pos < _size);

			const char* ptr = strstr(_str + pos, str);
			if (ptr == nullptr)
			{
				return npos;
			}
			else
				return ptr - _str;
		}

		/* erase */
		void erase(size_t pos, size_t len)
		{
			assert(pos < _size);

			if (pos + len >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				for (size_t i = pos + len; i <= _size; i++)
				{
					_str[i - len] = _str[i];
				}
				_size -= len;
			}
		}

		/* 返回C格式字符串:c_str */
		const char* c_str() const 
		{
			return _str;
		}

		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}
	private:
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0;
		static const size_t npos = -1;
	};

	/* s1 < s2 */
	bool operator<(const string& s1, const string& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) < 0;
	}

	/* s1 == s2 */
	bool operator==(const string& s1, const string& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) == 0;
	}

	/* s1 <= s2 */
	bool operator<=(const string& s1, const string& s2)
	{
		return s1 < s2 || s1 == s2;
	}

	/* s1 > s2 */
	bool operator>(const string& s1, const string& s2)
	{
		return !(s1 <= s2);
	}

	/* s1 >= s2 */
	bool operator>=(const string& s1, const string& s2)
	{
		return s1 > s2 || s1 == s2;
	}

	/* s1 != s2 */
	bool operator!=(const string& s1, const string& s2)
	{
		return !(s1 == s2);
	}

	// cout << s1  →  operator<<(cout, s1)
	ostream& operator<<(ostream& out, const string& s)
	{
		for (auto ch : s)
		{
			out << ch;
		}
		return out;
	}

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

		const int N = 1024;
		char buffer[N];
		int i = 0;

		char ch;
		ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			buffer[i++] = ch;
			if (i == N - 1)
			{
				buffer[i] = '\0';
				s += buffer;
				i = 0;
			}

			ch = in.get();
		}

		if (i > 0)
		{
			buffer[i] = '\0';
			s += buffer;
		}
		return in;
	}

	void test_string1() 
	{
		string s1("hello world");
		string s2;

		s1[0] = 'F';
		for (size_t i = 0; i < s1.size(); i++) 
		{
			cout << s1[i] << " ";
		}
		cout << endl;
	}

	void test_string2() 
	{
		string s1("hello world");

		// 迭代器写
		string::iterator it = s1.begin();
		while (it != s1.end()) 
		{
			*it += 1;
			it++;
		}

		// 迭代器读
		it = s1.begin();   // 重置起点
		while (it != s1.end()) 
		{
			cout << *it << " ";
			it++;
		}
	}

	void test_string3() 
	{
		string s1("hello world");
		cout << s1.c_str() << endl;

		s1.push_back('!');
		cout << s1.c_str() << endl;

		s1.push_back('A');
		cout << s1.c_str() << endl;
	}

	void test_string4() 
	{
		string s1("hello world");
		cout << s1.c_str() << endl;

		s1 += '!';
		cout << s1.c_str() << endl;

		s1 += "this is new data";
		cout << s1.c_str() << endl;
	}

	void test_string5() 
	{
		string s1("hello world");
		cout << s1.c_str() << endl;

		s1.insert(0, 'X');
		cout << s1.c_str() << endl;

		s1.insert(0, "hahahaha");
		cout << s1.c_str() << endl;
	}

	void test_string6() 
	{
		string s1("hello world");
		cout << s1.c_str() << endl;

		s1.erase(5, 2);   // 从第五个位置开始,删两个字符
		cout << s1.c_str() << endl;

		s1.erase(5, 20);  // 从第五个位置开始,删完
		cout << s1.c_str() << endl;
	}
}

  • 16
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值