[C++随笔录] string模拟实现

放在前面:
我们实现string类, 并不是跟源码一模一样(并不是造一个新的轮子), 只是了解每个接口的实现过程 ⇒ 我们以后也用的放心(比如时间复杂度, 空间复杂度等等)

基本结构

private:
	size_t _size; // 实际长度
	size_t _capacity; // 空间
	char* _str;

习惯把不可能为负数的值的类型 定义为 size_t

天选之子

构造函数

  1. 考虑到 无参调用和有参调用 && 只有一个参数 ⇒ 我们可以采用 全缺省的形式
  2. 传参类型应该为 常量字符串 ⇒ const char* ⇐ 一般用于初始化, 咋们给的值都是常量
  3. 缺省值初始化为 ""(空字符串) ⇐ 常量字符串默认就会有 \0, 即 “” (空字符串) 里面就是一个 \0
  4. _size 和 _capacity 的大小不包括 \0 ⇒ 所以, 我们初始化长度的时候, 用 strlen(str)
  5. _str要先开空间

👇👇👇

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

注意:

  1. 初始化的顺序是按照 声明的顺序来的 ⇒ 我们尽量保持 初始化和声明的顺序一致, 要不然就会出现问题
  2. 由于 _size 和 _capacity不包括 \0 的长度 ⇒ 我们_str开空间的时候要多开一个, 给 \0

🗨️为啥要用 memcpy函数? 为啥不用strcpy函数呢?

  • 1. memcpy函数 和 strcpy函数的 区别 : memcpy函数是 逐个字节进行copy的, 而strcpy是 遇到 \0就停止 copy
    2 我们标准库中 string类的输出是按照 _size 来的. 即遇到下面的情况, strcpy 和 strcpy的区别就体现出来了👇👇👇
	// 字符串如下:
	// hello 
	// world!
	// 来做以下几组实验

	// 1. 库里的string
	std::string str1 = "hello";
	str1 += "\0";
	str1 += "world!";
	cout << str1 << endl;

	// 2. 用strcpy来实现
	// ...
	//..

	// 3. 用memcpy来实现
	// ...
	// ...

*****
1. helloworld!
2. hello
3. helloworld!
*****
  1. memcpy默认是不会copy \0, 所以memcpy函数里面的长度 传的是 _capacity+1

析构函数

~string()
{
	delete[] _str; // 清理动态申请空间
	// 置零(置空)
	_str = nullptr;
	_size = _capacity = 0;
}

拷贝构造函数

  1. 我们不写构造函数, 系统自动生成的是一种 浅拷贝 ⇒ 对有动态资源申请的对象来说, 会对同一块空间析构两次
  2. 我们写的是 深拷贝 ⇒ 找一块新的空间给 this->_str, 然后将 s的内容 copy过去, 更新 _capacity 和 _size
String(const string& s)
{
	_str = new char[s._capacity + 1];
	memcpy(_str, s._str, s._capacity + 1);
	_capacity = s._capacity;
	_size = s._size;
}

空间

size()函数

size_t size()const
{
	return _size;
}

capacity()函数

size_t capacity()const
{
	return _capacity;
}

clear()函数

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

clear()函数 并不是用来清理空间的, 而是让空间置空(置零)

empty()函数

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

reverse()函数

void reverse(size_t n)
{
	assert(n >= 0);

	// 扩容逻辑 -- 一般我们不进行缩容
	if(n > _capacity)
	{
		char* tem = new char[n+1];
		memcpy(tem._str, _str, _capacity+1);
		delete[] _str;
		_str = tem;
		_capacity = n;
	}	
}

resize()函数

void resize(size_t n, char ch = '\0')
{
	assert(n >= 0);
	
	if(n <= _size)
	{
		_size = n;
		_str[_size] = '\0';
	}
	else
	{
		reverse(n);
		for(int i = _size; i < _size+n; i++)
		{
			_str[i] = ch;
		}
		
		_size = _size + n;
		_str[_size] = '\0';
	}
}

迭代器

迭代器是属于类的 ⇐ 我们声明迭代器的时候要声明类域
👇👇👇

std::string str = "hello world";
iterator it = str.begin();

*****
error C2955: “std::iterator”: 使用 类 模板 需要 模板 参数列表
*****

但要在 string类里面,定义一种类型, 有两种方式:

  1. typedef 一个变量
  2. 定义一个内部类 (内部类一般都是自定义类型)

而我们这里iterator其实就是数组_str各个下标对应的地址, 是一种 内置类型 ⇒ 所以, 我们采用typedef的方式来实现 iterator

iterator

typedef char* iterator;

begin()函数

iterator begin()
{
	return _str;
}

end()函数

iterator end()
{
	return _str + _size;
}

const_iterator

typedef const char* const_iterator;

begin()函数

const_iterator begin()const
{
	return _str;
}

end()函数

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

push_back()函数

尾插一个字符的操作:

  1. 是否需要扩容 ⇐ _size == _capacity

  2. 扩容逻辑:

    1. _capacity == 0 ⇒ 传个 4 过去扩容
    2. _capacity > 0 ⇒ 2倍扩容
  3. _size++, _str[_size] = ‘\0’;

void push_back(const char ch)
{
	// 是否扩容
	if (_size == _capacity)
	{
		size_t newcapacity = (_capacity == 0 ? 4 : _capacity * 2);
		reverse(newcapacity);
	}

	_str[_size] = ch;
	_size++;
	_str[_size] = '\0';

}

append()函数

尾插一个字符串的操作:

  1. 是否需要扩容

  2. 扩容逻辑:
    1. _size + len <= _capacity — — 不需要扩容
    2. _size + len > _capacity — — 扩容(_size + len)

  3. _size = _size + len, _str[_size] = ‘\0’;

void append(const char* ch)
{
	size_t len = strlen(ch);

	// 是否扩容
	if (len + _size > _capacity)
	{
		reverse(len + _size);
	}

	for (size_t i = 0; i < len; i++)
	{
		_str[_size + i] = ch[i];
	}
	_size += len;
	_str[_size] = '\0';

}

operator+=

复用 push_back() 和 append()

void operator+=(const char ch)
{
	push_back(ch);

}

void operator+=(const char* ch)
{
	append(ch);

}

insert()函数

在 下标为pos的位置插入n个字符:

  1. 是否需要扩容

  2. 扩容逻辑:

    1. _size + n <= _capacity — — 不需要扩容
    2. _size + n > _capacity — — 扩容(_size + n)
  3. 挪动数据

  4. _size = _size + n, _str[_size] = ‘\0’;

void insert(size_t pos, const char* ch)
{
	assert(pos >= 0);

	// 是否需要扩容
	size_t len = strlen(ch);
	if (_size + len > _capacity)
	{
		reverse(_size + pos);

	}

	// 挪动数据
	size_t end = _size;
	// 挪动数据时, 下标不能小于0(即不能等于 -1)
	while (end >= pos && end != _nops)
	{
		_str[end + len] = _str[end];
		end--;
	}

	// 插入数据
	for (size_t i = 0; i < len; i++)
	{
		_str[pos + i] = ch[i];
	}

	_size = _size + len;
}
  • 对了, 这里的 _nops是我么定义的一个静态成员变量
// 类里面的声明
public:
	static size_t _nops;

// 类外面的初始化
size_t muyu::string::_nops = -1; // 这里的muyu是我定义的一个命名空间域

🗨️为啥要定义一个nops? 为啥要初始化为 -1?

  • 前面, 我们有说过: 不可能为负数的, 我们定义成 size_t (无符号整数)
    如果 下标减到 -1 — ---- 由于是 size_t, 变量是不会比 -1 小的
    那么 size_t 类型如何区分开 -1 呢?
    size_t i = -1; ⇒ i 等于 2 ^ 32 -1;
    那么 下标 不等于 nops不就行了~~
    还有就是, 插入函数 和 删除函数中 字符串的长度如果不写, 就是nops

erase()函数

void erase(size_t pos, size_t n = _nops)
{
	assert(pos >= 0);

	// 是否把pos位置后面全部删除
	if (n == _nops || pos + n >= _size)
	{
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		for (size_t i = pos; i < pos + n; i++)
		{
			_str[i] = _str[i + n];
		}
		_size = _size - n;
	}
}

find()函数

size_t find(size_t pos = 0, const char ch )
{
	assert(pos < _size);

	for (int i = pos; i < _size; i++)
	{
		if (_str[i] == ch)
		{
			return i;
		}
	}

	return _nops;
}

size_t find(size_t pos = 0, const char* ch )
{
	assert(pos < _size);
	
	// 如果找到返回地址, 否则返回nullptr
	const char* res = strstr(_str, ch);

	if (res)
	{
		return res - _str;
	}
	else
	{
		return _nops;
	}

}

swap()函数

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

operator[]函数

//不是&, 那就返回的是常量临时拷贝
char& operator[](size_t n)
{
	assert(n <= _size);

	return _str[n];
}

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

	return _str[n];
}

operator= 函数

//string& operator=(const string& s)
//{
//	// 传统写法 -- 找一块空间, 把s的内容搞过去, 然后和*this交换
//	// 1. 找空间, 移内容;  2. 释放this的空间

//	string tem;
//	tem.reverse(s._capacity + 1);
//	memcpy(tem._str, s._str, s._capacity + 1);
//	tem._size = s._size;

//	swap(tem);

//	return *this;

//}

string& operator=(string s)
{
	swap(s);

	return *this;
}

比较

bool operator==(const string& s)
{
	// 如果_size都不相等, 那么何谈相等
	return _size == s._size &&
		memcmp(_str, s._str, _size) == 0;
}

bool operator>(const string& s)
{
	// 取较小长度进行比较
	size_t size = std::min(_size, s._size);
	int ret = memcmp(_str, s._str, size);
	
	// 由于是取较小长度进行比较, 那么会出现如下几种情况:
	// 1. str1 = hello, str2 = hello
	// 2. str1 = hello\0xxx, str2 = hello
	// 3. str1 = hello, str2 = hello\00xxx
	// 这几种情况都是根据较小长度比较的结果都是 相等
	if (ret == 0)
	{
		if (_size > s._size)
			return true;
		else
			return false;
	}

	return ret > 0;

}

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

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

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

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

流操作

流操作要写在全局位置 ⇐ cout/cin 要抢占第一个参数. 若要是在类中, 第一个参数就默认是this

流插入 <<

ostream& operator<<(ostream& out, const string& s)
{
	for (auto ch : s)
	{
		out << ch;
	}

	return out;
}

流提取 >>

istream& operator>>(istream& in, string& s)
{
	// 每一次新的读取要进行清理一下
	// 要不然就会接着读取, 而不是覆盖
	s.clear();

	// get()函数可以读到每一个字符, 包括空格 和 换行
	char ch = in.get();
	// 处理前缓冲区前面的空格或者换行
	while (ch == ' ' || ch == '\n')
	{
		ch = in.get();
	}

	// in >> ch;
	char buff[128]; // buff数组的作用是: 减少开空间的次数
	int i = 0;

	while (ch != ' ' && ch != '\n')
	{
		buff[i++] = ch;
		if (i == 127)
		{
			buff[i] = '\0';
			s += buff;
			i = 0;
		}

		//in >> ch;
		ch = in.get();
	}
	
	// 如果最后 buff数组还有数据, 那么就加到s中
	if (i != 0)
	{
		buff[i] = '\0';
		s += buff;
	}

	return in;
}

C接口

c_str()函数

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

substr()函数

string substr(size_t pos = 0, size_t n = _nops)
{
	assert(pos >= 0);
	
	// 是否需要扩容
	int len = n; // 默认是n
	if (n == _nops || pos + n >= _size)
	{
		len = _size - pos;
	}

	string tem;
	tem.reverse(len);
	//for (size_t i = pos; i < len; i++)
	//{
	//	tem[i] = _str[i + pos];
	//}

	//tem._size = len;
	//tem[_size] = '\0';

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

	return tem;
}

源码

#pragma once

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

namespace muyu
{
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;

		// friend ostream& operator<<(ostream& out, const string& s);


		iterator begin()
		{
			return _str;
		}

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

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

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

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

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

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

		void reverse(size_t n)
		{
			if (n > _capacity)
			{
				char* tem = new char[n + 1];
				memcpy(tem, _str, _capacity + 1);
				_capacity = n;
				delete[] _str;
				_str = tem;
			}
		}

		void resize(size_t n, char ch = '\0')
		{
			if (_size > n)
			{
				_str[n] = '\0';
				_size = n;
			}
			else
			{
				reverse(n); // 不管需不需要扩容,都丢给reverse. reverse内部有判断是否需要扩容
				for (size_t i = _size; i < n; i++)
				{
					_str[i] = ch;
				}
				_str[n] = '\0';
			}
		}

		void push_back(const char ch)
		{
			// 是否扩容
			if (_size == _capacity)
			{
				size_t newcapacity = (_capacity == 0 ? 4 : _capacity * 2);
				reverse(newcapacity);
			}

			_str[_size] = ch;
			_size++;
			_str[_size] = '\0';

		}

		void append(const char* ch)
		{
			size_t len = strlen(ch);

			// 是否扩容
			if (len + _size > _capacity)
			{
				reverse(len + _size);
			}

			for (size_t i = 0; i < len; i++)
			{
				_str[_size + i] = ch[i];
			}
			_size += len;
			_str[_size] = '\0';

		}

		void operator+=(const char ch)
		{
			push_back(ch);

		}

		void operator+=(const char* ch)
		{
			append(ch);

		}

		void insert(size_t pos, const char* ch)
		{
			assert(pos >= 0);

			// 是否需要扩容
			size_t len = strlen(ch);
			if (_size + len > _capacity)
			{
				reverse(_size + pos);

			}

			// 挪动数据
			size_t end = _size;
			// 挪动数据时, 下标不能小于0(即不能等于 -1)
			while (end >= pos && end != _nops)
			{
				_str[end + len] = _str[end];
				end--;
			}

			// 插入数据
			for (size_t i = 0; i < len; i++)
			{
				_str[pos + i] = ch[i];
			}

			_size = _size + len;
		}

		void erase(size_t pos, size_t n = _nops)
		{
			assert(pos >= 0);

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

		size_t size()const
		{
			return _size;
		}

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

		bool empty()const
		{
			return _size > 0;
		}

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

		 //不是&, 那就返回的是常量临时拷贝
		char& operator[](size_t n)
		{
			assert(n <= _size);

			return _str[n];
		}

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

			return _str[n];
		}

		string substr(size_t pos = 0, size_t n = _nops)
		{
			assert(pos >= 0);

			int len = n; // 默认是n
			if (n == _nops || pos + n >= _size)
			{
				len = _size - pos;
			}

			string tem;
			tem.reverse(len);
			//for (size_t i = pos; i < len; i++)
			//{
			//	tem[i] = _str[i + pos];
			//}

			//tem._size = len;
			//tem[_size] = '\0';

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

			return tem;
		}

		bool operator==(const string& s)
		{
			return _size == s._size &&
				memcmp(_str, s._str, _size) == 0;
		}

		bool operator>(const string& s)
		{
			// 取较小长度进行比较
			size_t size = std::min(_size, s._size);
			int ret = memcmp(_str, s._str, size);

			if (ret == 0)
			{
				if (_size > s._size)
					return true;
				else
					return false;
			}

			return ret > 0;

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

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

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

		size_t find(const char ch, size_t pos = 0)
		{
			assert(pos < _size);

			for (int i = pos; i < _size; i++)
			{
				if (_str[i] == ch)
				{
					return i;
				}
			}

			return _nops;
		}

		size_t find(const char* ch, size_t pos = 0)
		{
			assert(pos < _size);

			const char* res = strstr(_str, ch);

			if (res)
			{
				return res - _str;
			}
			else
			{
				return _nops;
			}

		}

		//string& operator=(const string& s)
		//{
		//	// 传统写法 -- 找一块空间, 把s的内容搞过去, 然后和*this交换
		//	// 1. 找空间, 移内容;  2. 释放this的空间

		//	//string tem;
		//	//tem.reverse(s._capacity + 1);
		//	//memcpy(tem._str, s._str, s._capacity + 1);
		//	//tem._size = s._size;

		//	//swap(tem);

		//	//return *this;

		//}

		string& operator=(string s)
		{
			swap(s);

			return *this;
		}


	private:
			size_t _size;
			size_t _capacity;
			char* _str;

	public:
			static size_t _nops;
	};


	size_t string::_nops = -1;

	ostream& operator<<(ostream& out, const string& s)
	{
		for (auto ch : s)
		{
			out << ch;
		}

		return out;
	}

	istream& operator>>(istream& in, string& s)
	{
		// 每一次新的读取要进行清理一下
		// 要不然就会接着读取, 而不是覆盖
		s.clear();

		// get()函数可以读到每一个字符, 包括空格 和 换行
		char ch = in.get();
		// 处理前缓冲区前面的空格或者换行
		while (ch == ' ' || ch == '\n')
		{
			ch = in.get();
		}

		// in >> ch;
		char buff[128]; // buff数组的作用是: 减少开空间的次数
		int i = 0;

		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == 127)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}

			//in >> ch;
			ch = in.get();
		}

		// 如果最后 buff数组还有数据, 那么就加到s中
		if (i != 0)
		{
			buff[i] = '\0';
			s += buff;
		}

		return in;
	}

}


持志如心痛. — — 王阳明
译:心在痛上,岂有工夫说闲话、管闲事.

  • 37
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 35
    评论
评论 35
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

雨 子

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

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

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

打赏作者

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

抵扣说明:

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

余额充值