C语言领域揭秘第十五篇章:STL中String类的模拟实现详解

文章目录

  • 前言
  • string类的模拟实现
  • string类——迭代器的模拟
  • string类——默认成员函数
  • string类——常用函数接口
  • string类——输入输出重载
  • 总结
  • 前言

    上篇博客已经简单的介绍了string类的一些接口,并且做了一些了解
    同时也刷了一些oj题目,熟练使用一些string的函数
    今天我们来模拟实现一下string类
    fellow me

    string类的模拟实现

    首先,string类是在stl库实现之前出现的,后面的stl库的内容大部分都和string类的接口类似
    string类比起以前的一些数据结构,多了很多东西
    迭代器就是一方面,能够更好的耦合算法和类和对象

    string类——迭代器的模拟

    我们先来看迭代器以及,string类的成员变量

    class string
    {
    public:
    	typedef char* iterator;     //迭代器    begin  end
    	typedef const char* const_iterator;
    	const char* c_str() const    //   返回字符常量  
    	{
    		return _str;
    	}
    	iterator begin()    
    	{
    		return _str;
    	} 
    	iterator end()
    	{
    		return _str + _size;
    	}
    	const_iterator begin() const   //  这里是const修饰的迭代器函数
    	{							   //  在处理一些const修饰的对象时,如果直接访问上面的普通begin  
    		return _str;				//   会引发权限的  放大 导致程序不能执行  所以这里复写了const的版本
    	}
    	const_iterator end() const
    	{
    		return _str + _size;
    	}
    	size_t size() const    //  返回大小   //  这里从const是修饰大小和容量不能被改变
    	{										//    权限能缩小  不会放大
    		return _size;
    	}
    	size_t capacity() const  //  返回容量
    	{
    		return _size;
    	}
    private:
    	// 
    	char* _str = nullptr;   //   字符串
    	size_t _size = 0;		//   大小
    	size_t _capacity = 0;//     容量
    
    	const static size_t npos;   //  模拟string类里面缺省参数的   npos参数
    };
    

    string类——默认成员函数

    构造函数、析构函数、拷贝构造、运算符重载

    在string类标准库里面有好几个版本的构造函数
    这里模拟实现了其中两个
    析构函数还是比较简单的
    主要是拷贝构造函数两个版本来实现

    string::string(size_t n, char ch)  //   初始化列表
    	:_str(new char[n + 1])
    	, _size(n)
    	, _capacity(n)
    {
    	for (size_t i = 0; i < n; i++)
    	{
    		_str[i] = ch;
    	}
    
    	_str[_size] = '\0';
    }
    string::string(const char* str)   //  构造函数  
    	:_size(strlen(str))
    {
    	_capacity = _size;
    	_str = new char[_size + 1];
    	strcpy(_str, str);
    }
    string::~string()   //  析构函数
    {
    	delete[] _str;
    	_str = nullptr;
    	_size = 0;
    	_capacity = 0;
    }
    
    string::string(const string& s)   //  拷贝构造   正常我们就是这样实现拷贝构造
    {												//  防止在一些占用空间的参数  比如字符串
    	_str = new char[s._capacity + 1];			//  调用系统的拷贝构造,导致浅拷贝,多次析构同一位置
    	strcpy(_str, s._str);						//   引起程序崩溃
    	_size = s._size;
    	_capacity = s._capacity;
    }
    
    //  这里提供一种新的实现方法  同时也更简单明了
    //  而且这里的swap函数还有其他的作用  能被复用
    void string::swap(string& s)
    {
    	std::swap(_str, s._str);
    	std::swap(_size, s._size);
    	std::swap(_capacity, s._capacity);
    }
    //   现代写法  直接复用
    string::string(const string& s)  //  这里直接定义新的string  如果this和其交换
    {										//  两种方法原理是一样的
    	string tmp(s._str);
    	swap(tmp);
    }
    // 另外还有一个  销毁函数
    void clear()   //   销毁
    {
    	_str[0] = '\0';
    	_size = 0;
    }
    

    下面就是运算符重载部分了

    // s1 = s2
    // s1 = s1
    void string::swap(string& s)
    {
    	std::swap(_str, s._str);
    	std::swap(_size, s._size);
    	std::swap(_capacity, s._capacity);
    }
    string& string::operator=(const string& s)   //  赋值运算符重载
    {
    	if (this != &s)
    	{
    		string tmp(s._str);
    		swap(tmp);   //   现代写法就是直接复用swap
    
    		//delete[] _str;   //   传统的就是这样一步一步赋值给另外一个对象
    		//_str = new char[s._capacity + 1];
    		//strcpy(_str, s._str);
    		//_size = s._size;
    		//_capacity = s._capacity;
    
    	}
    	return *this;
    }
    //  处理string像访问数组一样  直接堆  [] 进行重载
    char& operator[](size_t pos)   //  []重载  
    {
    	assert(pos < _size);
    	return _str[pos];
    }
    
    const char& operator[](size_t pos) const  // const修饰  不能改变内容
    {
    	assert(pos < _size);
    	return _str[pos];
    }
    //  下面就是一些判断字符串大小的函数   比较大小的函数还是比较简单的
    bool string::operator==(const string& s) const
    {
    	return strcmp(_str, s._str) == 0;  //   这里复用了c语言里面的  strcmp  比较函数
    }
    
    bool string::operator!=(const string& s) const
    {
    	return !(*this == s);
    }
    
    bool string::operator<(const string& s) const
    {
    	return strcmp(_str, s._str) < 0;
    }
    
    bool string::operator<=(const string& s) const
    {
    	return *this < s || *this == s;
    }
    
    bool string::operator>(const string& s) const
    {
    	return !(*this <= s);
    }
    
    bool string::operator>=(const string& s) const
    {
    	return !(*this < s);
    }
    

    string类——常用函数接口

    这里模拟实现一些string类的常用接口函数
    reserve、push_back、append、insert、erase、find、substr等接口
    实现string的增删改查功能
    话不多说,上代码

    //  reserve函数  预留空间  
    void string::reserve(size_t n)
    {
    	if (n > _capacity)
    	{
    		//cout << "reserve:" << n << endl;
    		char* tmp = new char[n + 1];   //  如果原本的空间没有到达指定大小 
    		strcpy(tmp, _str);				// 扩容
    		delete[] _str;
    		_str = tmp;
    		_capacity = n;
    	}
    }
    //  尾插函数   可以做  +=单个字符的复用函数
    void string::push_back(char ch)
    {
    	if (_size + 1 > _capacity)   //  如果空间不够  扩容
    	{
    		// 扩容
    		reserve(_capacity == 0 ? 4 : _capacity * 2);
    	}
    
    	_str[_size] = ch;   //  正常尾插  
    	++_size;
    	_str[_size] = '\0';  //  注意\0  结尾
    }
    //  append函数  在字符串尾部接入字符串  可以做+=字符串函数的复用
    void string::append(const char* str)
    {
    	size_t len = strlen(str);
    	if (_size + len > _capacity)
    	{
    		// 扩容
    		size_t newCapacity = 2 * _capacity;
    		if (_size + len > 2 * _capacity)
    		{
    			newCapacity = _size + len;
    		}
    
    		reserve(newCapacity);
    	}
    
    	strcpy(_str + _size, str);
    	_size += len;
    }
    //  +=字符串 和  += 字符 函数的重载
    string& string::operator+=(char ch)
    {
    	push_back(ch);
    	return *this;
    }
    string& string::operator+=(const char* str)
    {
    	append(str);
    	return *this;
    }
    //  插入函数   在pos 位置  插入 n 个  ch 字符
    void string::insert(size_t pos, size_t n, char ch)
    {
    	assert(pos <= _size);
    	assert(n > 0);
    	if (_size + n > _capacity)   //  判断扩容
    	{
    		// 扩容
    		size_t newCapacity = 2 * _capacity;
    		if (_size + n > 2 * _capacity)
    		{
    			newCapacity = _size + n;
    		}
    		reserve(newCapacity);
    	}
    	// 挪动数据
    /*	int end = _size;   //  pos为size_t  在和int比较时 int 会转为size_t  导致程序出错
    	while (end >= (int)pos)
    	{
    		_str[end + n] = _str[end];
    		--end;
    	}*/
    	size_t end = _size + n;    //  这样写代码更健壮   而且end不会到负数部分
    	while (end > pos + n - 1)
    	{
    		_str[end] = _str[end - n];
    		--end;
    	}
    	for (size_t i = 0; i < n; i++)
    	{
    		_str[pos + i] = ch;
    	}
    	_size += n;
    	/*string tmp(n, ch);
    	insert(pos, tmp.c_str());*/   //  这里复用insert的 在pos位置插入字符串
    }
    //  insert  在pos位置插入字符串
    void string::insert(size_t pos, const char* str)
    {
    	//assert(pos <= _size);
    	//size_t n = strlen(str);
    
    	//if (_size + n > _capacity)
    	//{
    	//	// 扩容
    	//	size_t newCapacity = 2 * _capacity;
    	//	if (_size + n > 2 * _capacity)
    	//	{
    	//		newCapacity = _size + n;
    	//	}
    	//	reserve(newCapacity);
    	//}
    	//size_t end = _size + n;
    	//while (end > pos + n - 1)
    	//{
    	//	_str[end] = _str[end - n];
    	//	--end;
    	//}   //  正常写法  
    	size_t n = strlen(str); //  间接扩容
    	insert(pos, n, 'x');   //  直接用前面的insert复用  先插入 n 个字符  然后再覆盖一下
    	for (size_t i = 0; i < n; i++)
    	{
    		_str[pos + i] = str[i];//  直接覆盖   //  这样的代码就简洁很多  复用性高 好溯源
    	}
    }
    //  删除函数  erase  指定位置删除长度为 len 的字符串
    void string::erase(size_t pos, size_t len)
    {
    	if (len >= _size - pos)
    	{
    		// 删完了
    		_str[pos] = '\0';
    		_size = pos;
    	}
    	else
    	{
    		size_t end = pos + len;  //  size_t防止int强转
    		while (end <= _size)
    		{
    			_str[end - len] = _str[end];
    			++end;
    		}
    		_size -= len;
    	}
    }
    //  find查找函数  查找一个字符
    size_t string::find(char ch, size_t pos)
    {
    	for (size_t i = pos; i < _size; i++)
    	{
    		if (_str[i] == ch)
    		{
    			return i;
    		}
    	}
    	return npos;
    }
    //  查找一个字符串
    size_t string::find(const char* str, size_t pos)
    {
    	const char* p = strstr(_str + pos, str);   //  这里复用c里面的strstr  
    	if (p == nullptr)
    	{
    		return npos;
    	}
    	else
    	{
    		return p - _str;
    	}
    }
    //substr  截取字符串
    string string::substr(size_t pos, size_t len)
    {
    	size_t leftlen = _size - pos;
    	if (len > leftlen)
    		len = leftlen;
    
    	string tmp;//  构造tmp
    	tmp.reserve(len);
    	for (size_t i = 0; i < len; i++)
    	{
    		tmp += _str[pos + i];
    	}
    	return tmp;
    }
    

    string类——输入输出重载

    剩下最后一个有点麻烦但又还好的接口
    重载输入流和输出流,另外还有getline这个函数

    //   输出函数是比较简单的
    ostream& operator<<(ostream& out, const string& s)
    {
    	for (auto ch : s)
    	{
    		out << ch;
    	}
    
    	return out;
    }
    //   输入函数就有很多地方需要处理了
    istream& operator>>(istream& in, string& s)
    {
    	s.clear();    // 先清除s里面的内容  防止意外
    	// 输入短串,不会浪费空间
    	// 输入长串,避免不断扩容
    	const size_t N = 1024;   //  如果输入很长的字符串  一步一步输入的话会不断扩容
    	char buff[N];				//  这里开一个字符数组存起来 后序直接 += 就行 大大减少了扩容一步到位
    	int i = 0;
    	char ch = in.get();
    	while (ch != ' ' && ch != '\n')
    	{
    		buff[i++] = ch;
    		if (i == N - 1)
    		{
    			buff[i] = '\0';
    			s += buff;
    			i = 0;
    		}
    		ch = in.get();
    	}
    	if (i > 0)
    	{
    		buff[i] = '\0';
    		s += buff;
    	}
    	return in;
    }
    //  getline  输入字符串 直到指定字符截止
    istream& getline(istream& in, string& s, char delim)
    {
    	s.clear();
    	const size_t N = 1024;
    	char buff[N];
    	int i = 0;
    	char ch = in.get();
    	while (ch != delim)   //  如果字符不是指定截止字符  就一直输入
    	{						//  不管是空格还是\n
    		buff[i++] = ch;
    		if (i == N - 1)
    		{
    			buff[i] = '\0';
    			s += buff;
    			i = 0;
    		}
    		ch = in.get();
    	}
    	if (i > 0)
    	{
    		buff[i] = '\0';
    		s += buff;
    	}
    	return in;
    }
    

    整个string类的模拟实现就差不多到这里啦

    总结

    今天把string类模拟实现了一遍
    在模拟实现过程中,发现了很多的问题
    比如哪些重复且作用相同的代码,复用问题
    充分利用已有的一些东西,比如std::swap,这个在拷贝构造和赋值重载的时候用的很爽
    还有就是一些优化,哪些地方一直构造会费时费力,哪些地方直接复用效果会更好而且效率高
    还有就是一些简单的语法知识,比如不经意间的类型强转过程会有意想不到的误区
    总之,在模仿中一步一步优化,一步一步学习
    借前人之鉴,涨己人之学识,加油

    种一棵树最好的时间是十年前,其次是现在

    作者:daily_2333

    物联沃分享整理
    物联沃-IOTWORD物联网 » C语言领域揭秘第十五篇章:STL中String类的模拟实现详解

    发表回复