【C++ • STL】探究string的源码

2024-02-26 1827阅读

温馨提示:这篇文章已超过444天没有更新,请注意相关的内容是否还可用!

文章目录

  • 一、深浅拷贝
  • 二、传统版写法的string类(简单)
  • 三、string类的模拟实现
  • 四、现代版写法的string类
  • 五、总结

    ヾ(๑╹◡╹)ノ" 人总要为过去的懒惰而付出代价ヾ(๑╹◡╹)ノ"

    【C++ • STL】探究string的源码


    一、深浅拷贝

    浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。

    浅拷贝:(1)析构两次,造成程序崩溃(2)一个对象修改影响另外一个

    如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。

    编译器默认生成的拷贝构造,是浅拷贝,会是两个对象指向同一块空间,当程序结束的时候,那么两个对象都会进行销毁,那么一块空间就会进行多次释放,从而引起崩溃。

    深拷贝:给每一个对象分配资源,保证多个对象之间不会因为共享资源而导致多次释放造成程序崩溃。

    二、传统版写法的string类(简单)

    #pragma once
    #include 
    using namespace std;
    #include 
    namespace yyqx//为了与库里面的string进行区分
    {
    	//仅仅实现一个简单的string,仅仅考虑资源管理深浅拷贝问题
    	class string
    	{
    	public:
    		//构造函数
    		string(const char* str)
    			:_str(new char[strlen(str) + 1])//这里的+1,是为了'\0'开辟空间
    		{
    			strcpy(_str, str);//拷贝的时候'\0'也拷贝了
    		}
    		//拷贝构造(深拷贝)
    		//s2(s1)
    		string(const string& s)
    			:_str(new char[strlen(s._str) + 1])
    		{
    			strcpy(_str, s._str);
    		}
    		//赋值,也会有深浅拷贝的问题
    		string& operator=(const string& s)
    		{
    			if (this != &s)//避免自己给自己赋值,会导致值被释放,就会变成随机值
    			{
    				//delete[] _str;//首先进行释放
    				//_str = new char[strlen(s._str) + 1];//C++的new是不需要检查是否开辟空间
    				会抛异常
    				//strcpy(_str, s._str);
    				//为了避免开辟空间失败,而本来的空间也被我们释放,可以先开启空间,
    				//进行拷贝,然后再释放
    				char* tmp = new char[strlen(s._str) + 1];
    				strcpy(tmp, s._str);
    				delete[] _str;
    				_str = tmp;
    			}
    			return *this;
    		}
    		//析构函数
    		~string()
    		{
    			if (_str)
    			{
    				delete[] _str;
    			}
    		}
    		
    		//目的为了输出字符串
    		const char* c_str() const
    		{
    			return _str;
    		}//返回c格式的字符串
    		//重载[]
    		char& operator[](size_t pos)
    		{
    			assert(pos  
    

    赋值运算符重载也会有深浅拷贝的问题。赋值,对象本身是有值的【拷贝的时候,如果空间小,就会不够,空间大,就会造成资源浪费】

    三、string类的模拟实现

    string的增删查改以及使用string【传统】

    基本框架:

    #pragma once
    #include 
    using namespace std;
    #include 
    namespace yyqx//为了与库里面的string进行区分
    {
    	class string
    	{
    	public:
    

    构造函数+析构函数:

    写法1:

    //构造函数
    		string(const char* str)
    			:_size(strlen(str))
    			,_capacity(_size)
    		{
    			_str = new char[strlen(str) + 1];//这里的+1,是为了'\0'开辟空间
    			strcpy(_str, str);//拷贝的时候'\0'也拷贝了
    		}
    		string()//注意,这里不是给的空,而是给了一个空的字符串//标准库里的就是给了一个""
    			:_size(0)
    			,_capacity(0)
    		{
    			_str = new char[1];
    			_str[0] = '\0';
    		}
    
    • 构造函数:初始化列表,初始化的顺序并不是初始化列表的顺序,而是成员变量在类中的声明次序。
    • 构造函数:注意默认的构造函数【编译器自动生成、缺省、函数重载】,默认的构造函数这里选择写一个同名函数,注意这里并不是给一个空指针,而是给了一个空字符串。

      写法2:(最优写法)

      		string(const char* str = "")//这里默认值不能给nullptr,strlen以及拷贝strcpy会崩溃
      			:_size(strlen(str))
      			,_capacity(_size)
      		{
      			_str = new char[strlen(str) + 1];//这里的+1,是为了'\0'开辟空间
      			strcpy(_str, str);//拷贝的时候'\0'也拷贝了
      		}
      		
      		//析构函数
      		~string()
      		{
      			if (_str)
      			{
      				delete[] _str;
      				_str = nullptr;//好习惯
      				_size = 0;
      				_capacity = 0;
      			}
      		}
      
      • 缺省值这不能给nullptr,strlen以及拷贝strcpy时程序会崩溃
      • 注意初始化列表
      • strcpy注意,拷贝的时候’\0’也拷贝了
      • new开空间的时候,一定要多开一个给’\0’

        拷贝构造+赋值重载函数+其他:

        		//拷贝构造(深拷贝)
        		//s2(s1)
        		string(const string& s)
        			:_size(strlen(s._str))
        			,_capacity(_size)
        		{
        			_str = new char[_capacity + 1];
        			strcpy(_str, s._str);
        		}
        		//赋值,也会有深浅拷贝的问题
        		string& operator=(const string& s)
        		{
        			if (this != &s)//避免自己给自己赋值,会导致值被释放,就会变成随机值
        			{
        				char* tmp = new char[s._capacity + 1];
        				strcpy(tmp, s._str);
        				delete[] _str;
        				_str = tmp;
        				_size = s._size;
        				_capacity = s._capacity;
        			}
        			return *this;
        		}
        		//目的为了输出字符串
        		const char* c_str() const
        		{
        			return _str;
        		}//返回c格式的字符串
        		
        		char& operator[](size_t pos)//这里仅仅可以传入对象,不能传入const对象,如果是const对象,就会报错
        		{
        			assert(pos  
        

        添加:

        		string& operator+=(char ch)
        		{
        			push_back(ch);
        			return *this;
        		}
        		string& operator+=(const char* str)
        		{
        			append(str);
        			return *this;
        		}
        		void reverse(size_t n)//一个扩容的作用
        		{
        			if (n > _capacity)
        			{
        				char* tmp = new char[n + 1];
        				strcpy(tmp, _str);
        				delete[] _str;//注意这里的释放不是free
        				_str = tmp;
        				_capacity = n;
        			}
        		}
        		void resize(size_t n, char ch = '\0')
        		{
        			if (n  _capacity)
        				{
        					reverse(n);
        				}
        				for (size_t i = _size; i  _capacity)
        			{
        				reverse(len);
        			}
        			strcpy(_str + _size, str);
        			_size = len;
        		}//但是我们一般用+=
        
        • 判断容量是否满,如果 _size= _ capacity,容量扩2倍,new一个新容量的空间,释放旧空间,最后指针指向新的空间。
        • append (append插入的字符个数是未知的,扩容二倍也不一定足够:解决办法:reverse预留空间【一个扩容的作用】)
        • reverse 为string预留空间,避免多次扩容(提高效率)
        • resize用处:扩空间+初始化;删除数据保留前n个

          插入:

          string& insert(size_t pos, char ch)
          		{
          			assert(pos 
          				reverse(_capacity == 0 ? 4 : 2 * _capacity);
          			}
          			//不可以用strcpy,这里不可以是同一块地址,对导致内容不是我们想要的
          			//最后一个未知的字符移到_size然后就是倒数第二位移动,从后向前移动
          			size_t end = _size + 1;
          			//注意这里如果end=_size,当头插的时候,进入循环end会变成-1,因为是size_t所以又会进入循环,导致错误
          			while (end  pos)
          			{
          				_str[end] = _str[end - 1];
          				--end;
          			}
          			_str[pos] = ch;
          			_size++;
          			return *this;
          		}
          		//插入\0,用c_str(遇到\0停止打印)打印显示在屏幕的字符串长度会减小或者不变,但是_size会变大
          		//用范围for或者迭代器可以打印出来
          		string& insert(size_t pos, const char* str)
          		{
          			assert(pos  _capacity)
          			{
          				reverse(_size + len);
          			}
          			size_t end = _size + len;
          			while (end > pos + len - 1)//这里注意
          			{
          				_str[end] = _str[end - len];
          				--end;
          			}
          			strncpy(_str + pos, str, len);//防止为了遇见\0就不拷贝了(strcpy遇见\0就不拷贝了)
          			_size += len;
          			return *this;
          		}
          

          插入字符:

          • 不可以用strcpy,在字符进行向后移的时候,不可以是同一块地址,对导致内容不是我们想要的,最后一个未知的字符移到_size然后就是倒数第二位移动,从后向前移动
          • end=_size,当头插的时候,进入循环end会变成-1,因为是size_t,又是大于0所以又会进入循环,导致代码错误

            插入字符串:

            • 防止为了遇见\0就不拷贝了,所以用的是strncpy(strcpy遇见\0就不拷贝了)

              删除:

              //删除
              		string& erase(size_t pos, size_t len = npos)
              		{
              			assert(pos = npos)
              			{
              				_str[pos] = '\0';
              				_size = pos;
              			}
              			else
              			{
              				size_t begin = pos + len;
              				while (begin 
              					_str[begin - len] = _str[begin];
              					++begin;
              				}
              				_size -= len;
              			}
              			return *this;
              		}
              
              			for (; pos 
VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]