【从零开始学c++】———模拟实现string类(常用接口的实现)

在这里插入图片描述

1.前言

之前学到了string类常用接口的后,我很好奇string类在底层是怎样实现的,在由之前学习到c++有关的之后,所以我打算模拟实现一下string类常用的接口,以便加深我对之前的知识的理解,和更好的去使用string类,比如在哪些场景使用哪些接口较为合适。接下来我会用c++来模拟实现各个接口。

2.string类常用接口实现

string类就是存储字符的顺序表,它的实现与与顺序表的实现是非常相似,主要接口为增删查改。实现如下

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
#include<assert.h>
using namespace std;

//定义一个属于自己的域
namespace sjp
{

	class string
	{
	private:
		char* _str;
		size_t _size;//字符串中的有效的字符个数
		size_t _capacity;//字符串中的最大容量
		static const size_t npos;//npos是string类对象中的一个静态成员变量,为整数的最大值
	public:
	    //构造函数
		string(const char* str ="")
		{
			_size = strlen(str);//计算出初始化字符串的大小
			_capacity = _size;
			_str = new char[_capacity+1];//开辟空间多开辟一块空间,这块空间留给\0
			strcpy(_str, str);
		}
		
		//交换函数,可以与另一个sting对象交换数据
		void swap(string& s)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}

       //析构函数,清理资源
		~string()
		{
			delete _str;
			_str = nullptr;
			_size = 0;
			_capacity = 0;
		}

       //拷贝构造,对象创建时,可以将另一个string对象的数据拷贝给要创建的对象
		string(const string& s)
		{
			sjp::string tmp(s._str);
			swap(tmp);//函数调用结束时tmp会被析构函数给清理掉
		}

         //迭代器,string的迭代器的底层就是指针
		typedef char* iterator;

		iterator begin()
		{
			return this->_str;//返回字符串首元素的地址
		}

		iterator end()
		{
			return this->_str + _size;//返回字符串最后一个地址
		}

          // 返回字符的个数
		size_t size()
		{
			return _size;
		}

         //预留字符串的空间,只改变_capacity,不改变初始化空间
		void reserve(size_t capacity)
		{
		//如果预留的空间大于原本的空间,直接开辟一块capacity的空间,并把数据拷贝给这块空间
			if (capacity > _capacity)
			{
				char* str= new char[capacity];
				strcpy(str, _str);
				_str = str;
				_capacity = capacity;
			}
			//如果capacity小于_size,则_capacity不做改变,_size改变即可
			else if(capacity<_size)
			{
				_size = capacity;
				_str[_size] = '\0';
			}
		}


        //预留空间,并且初始化这块空间,既改变capacity和size
		void resize(size_t capacity,char ch='\0')
		{
			if (capacity < _size)
			{
				_size = capacity;
				_str[_size] = '\0';
			}
			else
			{
				if (capacity > _capacity)
				{
					reserve(capacity);
				}
				memset(_str + _size, ch, (capacity-_size));//memset是创建一块空间,并初始化这块空间
				_str[_size] = '\0';
				_size = capacity;
			}
		}
 
      //尾插一个字符
		void push_back(char ch)
		{
			if (_size == _capacity)//判断字符串的空间是否满了
			{
				size_t newcapacity = _capacity == 0 ? 5 : _capacity * 2;
				reserve(newcapacity);
				_capacity = newcapacity;
			}
			_str[_size] = ch;
			_size += 1;
			_str[_size] = '\0';//最后记得加上\0
		}
		//尾插一个字符串
		void append(const char* str)
		{
			size_t len = strlen(str);//尾插的字符串的个数
			if (len + _size > _capacity)//原来的字符串的个数和要插入的字符的个数大于字符串的空间
			{
				reserve(len + _size);//增容
			}
			strcpy(_str + _size, str);
			     _size += len;
		}
		  //操作符+=重载,尾插一个字符串
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}
      //操作符+=重载,尾插一个string类对象
		string& operator+=(const string& s)
		{
			append(s._str);
			return *this;
		}
       //操作符+=重载,尾插一个字符
		string& operator+=(const char ch)
		{
			push_back(ch);
			return *this;
		}
      //操作符[]重载,访问字符串中的某个字符,既可读也可以写,const的string不能调用它
		char& operator[](size_t i)
		{
			return _str[i];
		}
     //操作符[]重载,访问字符串中的某个字符,只能读不能写,const调用它
		const char& operator[](size_t i)const
		{
			return _str[i];
		}
		//在某个位置插入一个字符
		string& insert(size_t pos,char ch)
		{
			assert(pos < _size);
			if (_size == _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 5 : _capacity * 2;
				reserve(newcapacity);
				_capacity = newcapacity;
			}
			
			

			//不能这样实现,pos为0时,end减到-1时,由于end和pos是无符号,所以end会变成最大整数,一直无限循环
			/*size_t end = _size - 1;
			while (end >= pos)
			{
				_str[end - 1] = _str[end];
				end--;
			}
		*/
          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* s)
		{
			assert(pos < _size);
			size_t len = strlen(s);
			if (len + _size > _capacity)
			{
				reserve(len + _size);
			}
			char*  end = _str+_size;
			while (end >=_str+pos)
			{
				*(end + len) = *end;
				end--;
			}
			size_t cur = pos;
			strncpy(_str + pos, s, len);
			_size += len;
			return *this;
		}

               //在某个位置去掉多少个字符
		string& erase(size_t pos,size_t n=npos)
		{
			assert(pos < _size);
			size_t leftlen = _size - pos;//leftlen是尾上
			if (leftlen <= n)
			{
				_size = pos;
				_str[_size] = '\0';//注意
			}
			else
			{
				strcpy(_str + pos, _str + pos + n);
				_size -= n;
			}
			return *this;
		}
		//从pos位置开始查找查找一个字母
		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;
				}

			}
			//找不到返回npos
			return npos;
		}
    
       //从某个位置开始,查找一个字符串,如果找到,返回该字符串的位置,找不到返回npos
		size_t find(const char* ch, size_t pos = 0)
		{
			assert(pos < _size);
			//strstr(s1,s2)的功能是判断s2是否为s1的字串,并且返回s2在s1中的地址,找不到返回nullptr
			const char* ret = strstr(_str+pos, ch);

			/*char* cur = _str;
			while (cur < _str+_size)
			{
				if (cur == tmp)
				{
					return cur - _str;
				}
				cur++;
			}*/

			if (ret)
			{
				return ret - _str;
			}
			else
			{
				return npos;
			}
			return npos;
		}
		
     //访问对象中的字符串
		char* s_str()const
		{
			return _str;
		}
     //判断字符串是否为空
		bool empty()const
		{
			return _size == 0;
		}
     //清空字符串
		void clear()
		{
			_size = 0;
			_str[_size] = '\0';
		}

	
	};
	const size_t string::npos = -1;
  
    //下面这几个为比较两个string对象的大小的操作符重载
	bool operator==(const sjp::string& s1, const sjp::string& s2)
	{
		return strcmp(s1.s_str(), s2.s_str()) == 0;
	}
	bool operator>(const sjp::string& s1, const sjp::string& s2)
	{
		return strcmp(s1.s_str(), s2.s_str()) > 0;
	}


	bool operator<(const sjp::string& s1, const sjp::string& s2)
	{
		return !(s1 == s2) || !(s1 > s2);
	}

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


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

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

    //操作符<<重载,使cout<<能够输出string对象
	ostream& operator<<(ostream& out, string& s)
	{
		for (auto ch: s)//范围for的使用
		{
			out << ch;
		}
		return out;
	}
  //操作符>>重载使cin>>能够输入string对象
	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char ch = in.get();//in.get()能够获取空格和回车字符
		while (ch != ' ' && ch != '\n')//如果ch为空格或回车,将跳出输入
		{
			s += ch;
			ch = in.get();
		}

		return in;
	}
}
  

3.总结

1.strstr(char* s1,char* s2)判断s2是否为s1的字串,如果是,则返回该字串在s1中的地址位置,如果不是返回空。
strcpy(char* s1,char* s2)将s2中的字符拷贝给s1,包括\0;
memset(void* str, ch, size) 从str的位置开始,开辟size个字节的大小,并将开辟的空间初始为ch
2.写istream和iostream的重载时,记得返回的值是istream&或iostream&(注意是引用),参数也是istream&或iostream&
3.写构造函数需要多开辟一个空间给\0,
4.如果没使用strcpy,删除或则增加需要记得在结尾_str[_size]=‘\0’.
5.operator[]需要实现两个,一个为const类型的,一个非const类型的,在操作函数后面加const是为了修饰this指针指向的成员变量,若为const类型的对象,它只能读,不能写,那么它在调用时会自动调用const类型的重载函数,若不是从const类型的对象,那么调用的时候会调用不是const类型的函数。
6.对于string的实现,c++标准只规定string要实现的接口功能,但各个编译器具体怎样实现,都是不一样的,例如,在vs编译器中,string的对象的大小是28个字节,在gcc中string对象的大小为12个字节。

模拟实现string常用接口后,我们可以知道string的迭代器的底层是指针,修改数据尽量少用insert,erase,因为它们的使用需要挪动数据,时间复杂度为O(n^2),效率太低了,reserve与resize的区别。

点个赞呗~!!!!

  • 25
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 17
    评论
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值