• 模拟实现string


    image-20221116120149781

    第一部分:构造,析构,拷贝构造,赋值重载,打印函数这几个大头写出来先

    string类框架

    
    namespace xxx
    {
    class string
    {
    public:
    //
    //
    
    private:
    char* _str;
    size_t _size;
    size_t _capacity;
       const static size_t npos = -1;//c++允许const static整数可以定义在类里-但只有几个特定的整数可以这样
    };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    构造函数

    	//构造函数
    		string(const char* str = "")//给缺省值
            //  :_size(strlen(str))
    		//	,_capacity(strlen(str))
    		//这里不用初始化列表,这里要一个个strlen赋值比较麻烦
            {
    			_size = strlen(str);//_size为有效字符的长度
    			_capacity = _size;//capacity初始化都为有效字符的长度
    			_str = new char[_capacity + 1];//留一个字符给'\0'
    			strcpy(_str, str);//拷贝过去
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    析构函数

    	//析构函数
    		~string()
    		{
    			delete[] _str;
    			_str = nullptr;
    			_size = _capacity = 0;
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    拷贝构造

    		//拷贝构造-深拷贝噢
    		string(const string& s)//传引用
    		{
    			_str = new char[s._capacity + 1];//留一个字符给斜杠0
    			strcpy(_str, s._str);//_str拷贝
    			_size = s._capacity;
    			_capacity = s._capacity;
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    赋值重载

    	//赋值重载
    		string& operator=(const string& s)//传引用
    		{
    			if (this != &s)
    			{
    				char* tmp = new char[s._capacity + 1];//开新空间
    				strcpy(tmp, s._str);//拷贝
    				delete[]_str;//删除_str的就空间
    				_str = tmp;//指向新空间
    				_size = s._size;
    				_capacity = s._capacity;
    			}
    			return *this;
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    对于【为什么要开一块新空间然后还要把之前的旧空间释放掉】有疑惑的朋友可以来看这篇
    https://blog.csdn.net/m0_71841506/article/details/127291467/

    通过c_str打印

    	//返回一个指向正规C字符串的指针常量, 内容与本string串相同
    		const char* c_str()const
    		{
    		return 	_str;
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5

    size和capacity

    	size_t size()const
    		{
    			return _size;
    		}
    		// capacity
    		size_t capacity()const
    		{
    			return _capacity;
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    基本上写到这就能输出hello world拉!

    image-20221114203417918

    第二部分:各类功能和升级

    迭代器iterator

    	typedef char* iterator;
    		iterator begin()
    		{
    			return _str;//返回第一个指针位置
    		}
    		iterator end()
    		{
    			return _str + _size;//返回最后一个有效字符的下一个位置
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    实现iterator之后我们可以这样玩

    image-20221114204504544

    还能用范围for

    image-20221114204913418

    但是我们把iterator实现的部分注释掉的话,范围for就用不了了;又或者说把iterator begin()改为iterator Begin()我们会发现iterator打印还能用,但范围for也用不了了;这说明范围for底层与iterator实现有关!至于为啥嘛。。。知道的可以在评论区留言~

    方括号重载

    char &operator[](int pos)const//方括号重载
    		{
    			return _str[pos];
    		}
    
    • 1
    • 2
    • 3
    • 4

    重载之后我们就能用数组【】访问

    image-20221114214055011

    push_back追加字符

    		void push_back(char ch)//尾插字符
    		{
    			if (_size == _capacity)//扩容
    			{
    				size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
    				reserve(newcapacity);
    			}
    			_str[_size] = ch;//在末尾追加字符ch
    			_size++;
    			_str[_size] = '\0';//加上\0
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    operator+=追加字符运算符重载

    	string& operator+=(char ch)//追加字符重载
    		{
    			push_back(ch);
    			return *this;
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5

    append追加字符串

    	//后面追加追加字符串
    		void append(const char* str)
    		{
    			size_t len = strlen(str);//记录str有效字符串长度
    			if (_size + len > _capacity)//直接扩容
    			{
    				reserve(_size + len);
    		}
    			strcpy(_str + _size, str);//strcpy把str连同\0一起拷贝过来了,所以不需要考虑\0
    			_size += len;	
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    operator+=追加字符串运算符重载

    	string& operator+=(const char* str)//追加字符串重载
    		{
    			append(str);
    			return *this;
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5

    clear清除字符串内有效字符

    	void clear()//清除所有有效字符
    		{
    			_str[_size] = '\0';
    			_size = 0;
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5

    流插入<<

    	ostream& operator<<(ostream& out, const string&s )//流插入
    	{
    		for (size_t i = 0; i < s.size(); ++i)
    		{
    			out << s[i];
    			
    		}
    		return out;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    流提取>>

    流提取是当遇到空格或者换行则截断提取,那么我们要按照这个底层来实现它

    
    	istream& operator>>(istream& in, string& s)//流提取
    	{
    		s.clear();//如果流提取之前s里面有字符,则要清除
    		char buff[128] = { '\0' };//开一块空间做中转站
    		size_t i = 0;
    		char ch= in.get();//用ch一个个接收s的字符
    		while (ch != ' ' && ch != '\n')//当ch接收到换行或者空格则停止
    		{
    			if (i == 127)
    			{
    				//满了-buff尾接给s
    				s += buff;
    				i = 0;
    			}
    			//没满ch继续接收
    			buff[i++] = ch;
    			ch = in.get();
    		}
    		if (i>0)//如果没满则buff没有尾插到s后面;又或者buff满过的部分尾接给了s,但剩下的部分没有尾接给s
    		{
    			buff[i] = '\0';//要携带\0 !
    			s += buff;
    }
    		return in;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    那么我们就能用流插入打印,流提取我们输入的内容拉

    image-20221114223428759

    image-20221115091827032

    insert

    size_t和int类型同时在操作符两边,会造成整形提升-小的往大的提升(int->size_t);那么int也转变为无符号数,则没有负数,比较大小会出错,会进入死循环;

    insert插入字符

    在pos位置插入字符ch

    string& insert(size_t pos, char ch)//插入字符-
    		{
    			assert(pos <=_size);
    
    			if (_size == _capacity)//扩容
    			{
    				size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
    				reserve(newcapacity);
    			}
    			//挪动数据
    			//size_t end = _size+1;//(size_t)无符号版
    			//while (end>pos)
    			//{
    			//	_str[end] = _str[end-1];
    			//	end--;
    			//}
    			//(int)有符号版
    			int end = _size-1;
    			while (end >=(int) pos)
    			{
    				_str[end + 1] = _str[end];
    				end--;
    			}
    			//插入字符
    			_str[pos] = ch;
    			_size++;
    			_str[_size] = '\0';
    			return *this;
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    image-20221115101414713

    insert插入字符串

    在pos位置插入len长的字符串

    string& insert(size_t pos, const char* str)//插入字符串
    		{
    			//扩容
    			size_t len = strlen(str);//记录要插入字符串的长度
    			if (_size + len > _capacity)
    			{
    				reserve(_size + len);
    			}
    			
    			//挪动数据
    			//(size_t)无符号版
    			//size_t end = _size+1;//+1把\0也挪到_str[_szie+len-1]的位置上
    			//while (end > pos)
    			//{
    			//	_str[end + len-1] = _str[end-1];
    			//	end--;
    			//}
    			//有(int)符号版
    			int end = _size;//把\0也挪到_str[_size+len]位置上
    			while (end >= (int)pos)
    			{
    				_str[end + len] = _str[end];
    				--end;
    			}
    			//插入字符串
    			strncpy(_str + pos, str, len);
    			_size += len;
    			return *this;
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    image-20221115104633841

    erase 删除

    从pos位置往后删除len个字符,不给len则按缺省值npos删除(全删完)

    string& erase(size_t pos, size_t len = npos)
    		{
    			assert(pos < _size);
    
    			if (len == npos || len >= _size - pos)
                    //如果len长度大于pos之后的有效字符串长度,那么也在pos位置\0
                {
    				_str[pos] = '\0';
    				_size = pos;
    			}
    			else
    			{
    				strcpy(_str + pos, _str + pos + len);
    				_size -= len;
    			}
    
    			return *this;
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    image-20221115114423146

    find

    find寻找字符

    	size_t find(char ch, size_t pos=0)//寻找字符-给缺省值从0开始找
    		{
    			assert(pos < _size);
    			while (pos<_size)
    			{
    				if (_str[pos] == ch)
    				{
    					return pos;//,返回下标
    				}
    					++pos;
    			}
    			return npos;//没找到返回npos
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    image-20221115120002408

    find寻找字符串

    	size_t find(const char* s, size_t pos = 0)//寻找字符串-给缺省值从0开始找
    		{
    			assert(pos < _size);
    			const char* ptr = strstr(_str, s);//strstr在字符串里面寻找字串-找到返回指针-没找到返回空
    			if (ptr == nullptr)
    			{
    				return npos;//没找到
    			}
    			else
    			{
    				return ptr - _str;//指针相减得指针之间字符串数量-即为pos的位置
    			}
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    image-20221115120051552

    高级版本的拷贝构造和赋值重载

    拷贝构造

    我们要用s1来拷贝构造s2,那么我们可以先用s1来构造tmp;然后在让指向tmp的指针和指向s2的指针交换指向;那么s2也完成了对s1内容的深拷贝;(这里要注意:要给原本是野指针的s2一个初始化空指针,交换后保证后续tmp析构时不越界析构其他空间【析构函数遇到空指针不析构】

    image-20221116111951347

    image-20221116112611005

    	//拷贝构造-现代写法
    		//s2(s1)
    		string(const string& s)
    			:_str(nullptr)//要初始化给个空指针,不然是野指针,tmp析构的时候会越界空间析构报错
    			,_size(0)
    			,_capacity(0)
    		{
    			string tmp(s._str);//用s构造tmp
    			swap(_str,tmp._str);
    			swap(_size, tmp._size);
    			swap(_capacity, tmp._capacity);
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    image-20221116112757177

    但是我们盯着上面的现代写法,一共是交换了_str, _szie, _capacity一共三次深拷贝。那么能不能再简化呢?

    我们通过查表格知道std库里的swap是先把对象实例化再交换;那么在这里是要进行三次深拷贝,深拷贝要开空间然后赋值。。。可见这样做代价非常大!

    image-20221116113352782

    string库里的swap是直接把两个string的指针交换这样代价相对小拉!

    image-20221116113609167

    所以我们可以写一个swap函数

    string里的swap

    void swap(string& s)
    		{
    		    std::swap(_str, s._str);
    			std::swap(_size, s._size);
    			std::swap(_capacity, s._capacity);
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    那么简化后的版本是这样的:

    //拷贝构造-现代写法-简化后
    		//s2(s1)
    		string(const string& s)
    			:_str(nullptr)//要初始化给个空指针,不然是野指针,tmp析构的时候会越界空间析构报错
    			,_size(0)
    			,_capacity(0)
    		{
    			string tmp(s._str);//用s构造tmp
    			//this->swap(tmp);this可以不写
    			swap(tmp);
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    赋值重载

    //赋值重载-现代写法
    		string& operator=( const string& s)//传引用
    		{
    			if (this != &s)
    			{
    				string tmp(s);//s构造tmp
    				swap(tmp);//交换this和tmp指针
    			}
    			return *this;
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    简化后版本

    string& operator=(string& s)//传引用
    		{
    			if (this != &s)
    			{
    				swap(s);//不需要tmp做中间人直接交换
    			
    			}
    			return *this;
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    关于string类的模拟实现的总结就到这里拉,看到这里的观众老爷们不妨点赞收藏起来看吧~~~

  • 相关阅读:
    string类型常用命令与其底层数据结构
    云计算介绍
    linux安装Samba
    Python爬虫入门
    魔域服务端数据库说明
    通用接口平台开源版正式发布2.0版本
    某微e-office协同管理系统存在任意文件读取漏洞复现 CNVD-2022-07603
    使用Shell终端访问Linux
    STM32实战总结:HAL之DAC
    exists 子查询中 having 包含外表的列计算结果出错问题现象
  • 原文地址:https://blog.csdn.net/m0_71841506/article/details/127883006