C++:String的模拟实现

小明 2025-04-28 22:52:28 3

��     

     模拟实现的节奏比较快,大家可以先去看看博主的关于string的使用,然后再来看这里的模拟实现过程

C++:String类的使用-CSDN博客

      String模拟实现大致框架迭代器以及迭代器的获取(public定义,要有可读可写的也要有可读不可写的)/成员变量(private定义)  并且为了不和库的string冲突,我们需要自己搞一个命名空间

namespace cyx
{
	class string
	{
	public:
		//迭代器的实现(可读可写)
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		//迭代器的实现(可读不可写)
		typedef const char* const_iterator;
		const_iterator begin() const
		{
			return _str;
		}
		const_iterator end() const
		{
			return _str + _size;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
		static const size_t npos;
		//static const size_t npos=-1  vs下int类型支持给static和const同时修饰的变量用缺省值
	};
	 const size_t string::npos = -1;//静态成员遍历类外初始化
}

      nops是一个静态变量,要类内定义类外初始化,由于nops是size_t类型,赋值-1会被强转成最大的无符号整数

一、构造+析构+赋值重载(Member functions)

1.1 全缺省构造函数

//构造函数
string(const char* str = "")
	:_size(strlen(str))
	{
		_capacity = _size == 0 ? 3 : _size;
		_str = new char[_capacity + 1];//   多开一块

1.2 拷贝构造函数的传统写法

的空间 strcpy(_str, str); }

1、 “ ”空字符串其实里面默认就有\0,所以缺省值直接给空字符串就行

2、_capacity 一定要给初始空间,不然后面如果涉及到2倍扩容,为0的话就扩不了了

3、要多开一块空间,连这个\0 

4、可以复用strcpy函数

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

     传统的思路就是拷贝,也就是我们先根据被拷贝的对象的_capacity开空间,然后再进行拷贝

1.3 拷贝构造函数的现代写法和swap函数

//交换字符串
void swap(string& s)
{
	std::swap(_str, s._str);//浅拷贝,没有开空间,只是改变指针指向
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
}

       现代的思路就是,尝试去复用,比如说我们可不可以直接去利用前面的构造函数去构造一个新对象,然后再窃取新对象的成果(利用swap)

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

1. 4 迭代器区间构造

 传统写法和现代写法参数一样,不能重载,只能保留一个 

		//迭代器区间构造
		template 
		string(InputIterator first, InputIterator last)
			:_str(new char[last-first+1])
			,_size(0)
			,_capacity(last-first)
		{
			while (first != last)
			{
				push_back (*first);
				++first;
			}
		}

1.5 赋值重载的传统写法

       这里定义的模版InputIterator的意思其实是这边我们可以传不同类型对象的迭代器,我们并不知道这个迭代器里面有多少元素,所以得用指针-指针,即last-first来确定我们的容量,然后再开空间,一个个进行尾插。

		//赋值重载(传统写法)
		string& operator=(const string& s)
		{
			if (this != &s)//避免自赋值
			{
				//先开新空间再毁旧空间,避免新空间开失败导致数据丢失
				char* temp = new char[s._capacity + 1];
				strcpy(temp, s._str);
				delete[]_str;
				_str = temp;
				_size = s._size;
				_capacity = s._capacity;
			}
			return *this;
		}

       传统的思路就是,先开一块新空间拷贝旧数据,然后再释放掉原空间,这里尽量是先开空间再释放,避免我们开空间失败导致原始数据的丢失。   

1.6 赋值重载的现代写法

注意:要注意自赋值情况!!否则刚拷贝完自己就被释放了

//赋值重载(现代写法)
string& operator=(string s)//必须用值传递,否则会导致原数据的丢失
{
	swap(s);
	return *this;
}

      现代的思路就是,既然被赋值这个空间不想要,那就和形参直接交换吧!!但是要注意的是,这里就不能像传统的一样用const引用了,否则不想要的空间就给到我们的赋值对象了,这边就得用传值传参,这样被交换的就只是一个临时拷贝,不想要的空间随着栈帧的结束被销毁。

1.7 析构函数

  传统写法和现代写法参数不一样,一个是const引用,一个传值传参,所以可以同时存在。

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

 二、容量相关的接口(Capacity)

2.1 size、capacity

	//获取当前size
	size_t size() const
	{
		return _size;
	}
	//获取当前capacity
	size_t capacity() const
	{
		return _capacity;
	}

2.2 reserve

	//改变capacity
	void reserve(size_t n)
	{
		if (n > _capacity)
		{
			char* tmp = new char[n + 1];
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = n;
		}
	}

如果n比_capacity大,思路就是先开新空间进行拷贝,然后再释放旧空间

2.3 resize

//改变size
void resize(size_t n, char ch = '

2.4 clear和empty

') { if (n > _size) { reserve(n); memset(_str + _size, ch, n - _size); } _size = n; _str[_size] = '
//清理字符串
void clear()
{
	_str[0] = '

2.5 shrink_to_fit(用得少)

'; _size = 0; } //判断字符串是否为空 bool empty() { return _capacity == 0; }
'; }

如果n比_size大,我们根据n去扩容,然后因为string的底层是字符数组,所以memset就很适合,他就是可以去一个字节一个字节设置成我们想要的。缺省值给‘\0’

	void shrink_to_fit()
	{
		char* temp = new char[_size + 1];
		strcpy(temp, _str);
		delete[] _str;
		_str = temp;
		_capacity = _size;
	}

三、[ ]和比较运算符重载

//[]重载(可读可写)
char& operator[](size_t pos)
{
	assert(pos 
bool operator>(const string& s) const
{
	return strcmp(_str, s._str) > 0;
}
//==
bool operator==(const string& s) const
{
	return strcmp(_str, s._str) == 0;
}
//>=
bool operator>=(const string& s) const
{
	return *this > s || *this == s;
}
// s || *this == s;
		}
		// _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				_capacity = n;
			}
		}
		//尾插一个字符
		void push_back(char ch)
		{
			insert(_size, ch);
		}
		//尾插一个字符串
		string& append(const char* str)
	    {
			return insert(_size, str);
		}
		//+=重载 字符
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
		//+=重载 字符串
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}
		//指定位置插入一个字符
		string& insert(size_t pos, char ch)
		{
			assert(pos  _capacity)
				reserve(2 * _capacity);
			//pos后的数据要往后挪,所以要从后往前移
			size_t end = _size + 1;
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				--end;
			}
			_str[pos] = ch;
			++_size;
			return *this;
		}
		//指定位置插入一个字符串
		string& insert(size_t pos, const char* str)
		{
			assert(pos  _capacity)
				reserve(_size + len);
			size_t end = _size + len;
			while (end > pos + len - 1)
			{
				_str[end] = _str[end - len];
				--end;
			}
		//拷贝插入
			strncpy(_str + pos, str, len);
			_size += len;
			return *this;
		}
		//删除指定位置之后的字符
		string& erase(const size_t pos, size_t len = npos)
		{
			assert(pos  _size)//len == npos必须写,因为nops是无符号最大值,+的话会溢出
			{
				_str[pos] = '';
				_size = pos;
			}
			//删一部分
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
			return *this;
		}
		//寻找指定字符并返回下标
		size_t find(char ch, size_t pos = 0)const
		{
			assert(pos  (std::istream& in, string& s)
	{
		//读取前要先清理掉原来存在的字符
		s.clear();
		//用get获取字符
		char ch = in.get();
		//先用一个数组存起来,再一起加
		char buff[128];
		size_t i = 0;
		while (ch != ' ' && ch != '\n')
		{
			//原始方法,一个字符一个字符加太麻烦,先用一个数组存起来,再一起加
			//s += ch;
			buff[i++] = ch;
			if (i == 127)
			{
				buff[127] = '';
				s += buff;
				i = 0;//重置i
			}
			ch = in.get();
		}
		//循环结束后可能还要一些字母没有存进去
		if (i != 0)
		{
			buff[i] = '';
			s += buff;
		}
		return in;
	}
	//遍历方法的展示
	void Print(const string& s)
	{
	  //下标遍历
		for (size_t i = 0; i 

缩容到size位置,平时用的很少,我们要尽量减少扩容,思路也是一样的,开辟新空间去拷贝,再释放旧空间

The End
微信