• 库中是如何实现string类的?


    在这里插入图片描述

    🎈个人主页:🎈 :✨✨✨初阶牛✨✨✨
    🐻推荐专栏1: 🍔🍟🌯C语言初阶
    🐻推荐专栏2: 🍔🍟🌯C语言进阶
    🔑个人信条: 🌵知行合一
    🍉本篇简介:>:讲解如何模拟实现C++中的string类.
    金句分享:
    ✨你要做多大的事情,就要承受多大的压力!✨

    前言

    我们先认识一下string类的框架.

    class string
       {
       	public:
       		//成员函数
        private:
            char* _str;                 //字符串指针 
            size_t _capacity;           //容量
            size_t _size;               //当前字符有效个数
       }:
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述

    框架图:
    在这里插入图片描述

    一、构造函数与析构函数

    在这里插入图片描述

    (1) 无参构造:

    我们可以试着看一下库里面是如何赋值的?

    	std::string s1;
    	cout << "s1= " << s1 << endl;
    
    • 1
    • 2

    在这里插入图片描述
    所以,对于无参构造,我们只需要将*str赋值为空串就行了.

    注意:
    ""(中间没有空格)

    (2) 使用常量字符串构造

    1. 先计算字符串的长度.
    2. 将长度值赋值给_size_capacity .
    3. 申请一块为_capacity+1大小的空间.(+1是为了存储'\0')
    4. 将字符串中的值按字节拷贝至string类中的_str.

    代码实现:

    //全缺省构造函数,,默认初始化为空字符
    string(const char* str = "")//无参也是调用这个 
      {
          _size = strlen(str);
          _capacity = _size;
          _str = new char[_capacity + 1];//里面其实有一个字符'\0'
         // strcpy(_str, str);              //遇到'\0'结束拷贝,也会吧'\0'拷贝过去
          memcpy(_str, str, _size + 1);
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    (3) 拷贝构造

    这里注意实现深拷贝即可.

    string(const string& s)       //注意这里+const  普通对象可以调用,const对象也可以调用
    {
        _size = s._size;
        _capacity = s._capacity;
        _str = new char[_capacity + 1];//需要多一个字节存放,字符'\0'
        memcpy(_str,s._str,s._size+1);      
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    (4) 析构函数

    析构函数注意释放动态申请的空间即可.

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

    二、capacity相关操作

    (1) reserve函数

    reserve():请求改变容量的大小,类似于扩容从操作.

    1. 向堆区申请一块n+1大小的新空间.
    2. 将旧空间的数据拷贝到新空间,
    3. 释放旧空间
    4. _str指向新空间
    5. 更新容量capacity
    void reserve(size_t n)
    {
        if (n > _capacity)
        {
        //申请一块新空间
            char* tmp = new char[n + 1];
            memcpy(tmp, _str, _size + 1);//不建议使用strcpy,可能存在中间有'\0'的强开
    
            delete[] _str;//释放旧空间
            _str = tmp;
            _capacity = n;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    (2) resize函数

    resize():用于改变字符串的有效字符长度.不够的地方用第二个参数填充.

    在这里插入图片描述

    步骤:

    1. 如果n(即n<当前长度):
      ①直接在n位置处赋值为’\0’.
      _size更新为n

    2. 如果n>=size:
      ①调用扩容函数,扩容至n大小.
      ②超出部分用字符'c'填充
      ③更新_size,并在最后一个位置设置为’\0’

    代码实现:

      void resize(size_t n, char c = '\0')
      {
          if (n < _size)
          {
              _str[n] = '\0';
              _size = n;
          }
          else
          {
              reserve(n);//无论需不要扩容,不需要扩容时reserve内部会什么也不做,需要扩容时,reserve会扩容.
              memset(_str + _size, c, n - _size);
              _size = n;
              _str[_size] = '\0';
    
          }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    (3) empty函数

    如果_size值为0,则为空,返回true.
    否则返回false;

      bool empty()const
       {
           //size=0则为空,返回true
           return _size == 0 ? true : false;
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (4) size和capacity函数

    这两个函数,直接返回值即可.

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

    三、访问与遍历

    (1) 迭代器

    迭代器的介绍

    C++迭代器是一个用于遍历容器(如vector、list、set等)中的元素的对象。迭代器的作用类似于指针,可以通过解引用操作符(*)获取容器中的元素值,也可以通过自增操作符(++)移动迭代器指向下一个元素。迭代器可以访问容器中的元素,也可以修改容器中的元素值。

    在这里插入图片描述

    注意迭代器的定义,迭代器是左闭右开的区间.

    public:
        typedef char* iterator;
        typedef const char* const_iterator;
        
        //普通迭代器
        iterator begin()
        {
            return _str;             //返回第一个字符位置
        }
    
        iterator end()
        {
            return _str + _size;     //返回最后一个有效字符的下一个位置
        }
        
    	//常类迭代器
        const const_iterator begin()const
        {
            return _str;             //返回第一个字符位置
        }
    
        const const_iterator end()const
        {
            return _str + _size;     //返回最后一个有效字符的下一个位置
        }
    
    • 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

    (2)下标访问符 方括号[ ]重载

    返回_str中第index的位置

     char& operator[](size_t index)
     {
     	//判断位置是否合法
         assert(index >= 0 && index < _size);
         return *(_str + index);
     }
    
     const char& operator[](size_t index)const
     {
         assert(index >= 0 && index < _size);
         return *(_str + index);
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    四、修改与查找

    (1) push_back函数

    push_back尾部插入一个字符

    在进行插入操作之前,要先考虑扩容的情况.

    需要注意的是,如果采用无参构造,刚开始容量是0.
    这就导致是初次扩容,容量开始是0,所以这里要判断扩容前,容量是否是0,再考虑1.5倍或者二倍扩容.

    void push_back(char c)
    {
        if (_size + 1 > _capacity)
        {
        	//如果capacity是0,则无法进行1.5倍扩容
            //reserve(_capacity * 1.5);
            reserve(_capacity == 0 ? 4 : _capacity * 1.5);//扩容多少没有标准,2倍或者1.5倍扩容都可,
        }
        _str[_size] = c;
        _size++;        //有效数据+1
        _str[_size] = '\0';
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    (2) append函数

    append尾部追加字符串

     void append(const char* str)
     {
         int sz=strlen(str);
         //如果容量不够,就申请新空间
         if (_size + sz > _capacity)
         {
             reserve(_capacity +sz);
         }
         //追加新的字符串
         memcpy(_str + _size, str, sz);
         _size += sz;
         _str[_size] = '\0';
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    (3) find函数

    string中查找目标字符,通过遍历比较.
    第二个参数表示从pos位置开始查找.

    顺序查找即可

    size_t find(char c, size_t pos = 0) const
    {
        assert(pos < _size);
       
        for (int i = pos; i < _size; i++)
        {
            if (_str[i] == c)
            {
                return i;
            }
        }
        return npos;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    字符串匹配:查找string类的中的目标字串

    字符串匹配算法,这里简化,直接调用库函数strstr,就不手撕算法了.

     // 返回子串s在string中第一次出现的位置
     size_t find(const char* s, size_t pos = 0) const
     {
         assert(pos < _size);
    
         const char* ptr = strstr(_str + pos, s);//通过调用库函数strstr找到字符串出现的位置的指针
         if (ptr)
         {
             return ptr - _str;
         }
         else
         {
             return npos;
         }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    (4) insert函数

    pos位置插入一个字符:
    老规矩,先扩容,学过数据结构的小伙伴应该知道,需要先移动数据再进行插入数据操作.

    顺序表任意位置插入

    // 在pos位置上插入字符c/字符串str,并返回该字符的位置
    
    string& insert(size_t pos, char c)
    {
        assert(pos >= 0 && pos<= _size);
        if (_size + 1 > _capacity)
        {
            reserve(_capacity * 1.5);
        }
        int i = 0;
        //移动数据
        for (i = _size; i > pos - 1; i--)
        {
            _str[i] = _str[i - 1];
        }
        _str[pos - 1] = c;
        _size++;
        return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    pos位置插入一段字符串:

    这里在移动数据时,注意0号位置插入时移动.
    如果我们只是将前面的数据直接往后移动字符串长度大小的位置,则到插入0号位置时,前面的数据是非法的,此处设计时,需要注意.

    string& insert(size_t pos, const char* str)
    {
        assert(pos >= 0 && pos <= _size);
        int sz = strlen(str);
        //如果容量不够,就申请新空间
        if (_size + sz > _capacity)
        {
            reserve(_capacity + sz);
        }
    
        int i = 0;
        //移动数据,这里需要注意0号位置插入时是否移动数据非法.
        size_t end = _size;
        while (end >= pos && end != npos)
        {
            _str[end + sz] = _str[end];
            --end;
        }
        //插入字符串
        for (i = pos; i <pos+sz; i++)
        {
            _str[i] = str[i-pos];
        }
        _size+=sz;
        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

    (5) erase函数

    erase:删除从pos位置开始往后len长度的元素,并返回删除后的string.

    在这里插入图片描述

    在这里插入图片描述

     // 删除pos位置上的元素,并返回该元素的下一个位置
     string& erase(size_t pos, size_t len=npos)
     {
         assert(pos <= _size);
    
         if (len == npos || pos + len >= _size)//如果要求删除的长度+pos超过了string中有效字符的长度
         {
             _size = pos;
             _str[_size] = '\0';
         }
         else
         {
             size_t end = pos + len;
             while (end <= _size)
             {
                 _str[pos++] = _str[end++];
             }
             _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

    五、运算符重载

    (1) 流运算符重载

    采用友元函数的方式,实现流提取与流插入运算符重载.

    流插入运算符

     ostream& operator<<(ostream& _cout, const cjn::string& s)//记得包在cjn命名空间里面
     {
         //在实现了迭代器的情况下,可以使用范围for
         for (auto& in : s)      //依次取出string类中的全部字符,插入进流
         {
             _cout << in;
         }
         return _cout;   //返回输出流
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    流提取运算符

     istream& operator>>(istream& in, string& s)
     {
         s.clear();//如果s中本身有数据,先将有效数据清除
         
         char ch = in.get();
         //处理缓冲区的空格和换行,因为可能有人先输入了空格或者换行导致读取数据失败
         while (ch == ' ' || ch == '\n')
         {
             ch = in.get();
         }
         
         //有效数据插入进s
         char buff[128];//为了避免从小的容量一次次扩容
         int i = 0;
         while (ch != ' ' && ch != '\n')
         {
             buff[i++] = ch;//将读取到的数据先存放进临时数组
             if (i == 127)//只有等数据满127时,才将数据插入进s
             {
                 buff[i] = '\0';
                 s += buff;
                 i = 0;
             }
             ch = in.get();//继续获取有效数据
         }
         if (i != 0)//最后,如果buff数组中还有数据,则将这些剩余数据插入
         {
             buff[i] = '\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
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    (2) 比较运算符重载

    两个字符串比较,我们利用memcmp函数比较两字符串中较短字符串的长度位数.
    然后根据memcmp返回值进行进一步判断.

     bool operator<(const string& s)
     {
         int length = s._size;
         //谁短,length就等于谁
         if (_size < length)   length = _size;
         int ret=memcmp(_str, s._str, length);
         
         if (ret == 0)//如果短的 与长的前半部分相等
         {
             if (_size< s._size)//比比较的字符串短,则<成立,true
             {
                 return true;
             }
             return false;
         }
         
         if(ret==-1)//表示右操作数大,<满足
             return true;
             
         if (ret == 1)//表示左操作数大,<不满足
             return false;
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    三目运算符写起来可能不大好理解,但是代码看起来很简洁

     bool operator<(const string& s) const
     {
         int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);//按短的进行比较
         //如果ret==0,则比较长度,s长则返回真,否则返回假
         //ret!=0则-1表示真,1表示假
         return ret == 0 ? _size < s._size : ret < 0;                      
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    其他运算符直接复用或者比较简单,这里不做解释.

    bool operator<=(const string& s)
     {
         return *this < s || *this == s;
     }
    
     bool operator>(const string& s)
     {
         return !(*this <= s);
     }
    
     bool operator>=(const string& s)
     {
         return !(*this < s);
     }
    
     bool operator==(const string& s)
     {
         if (memcmp(_str, s._str, _size) == 0)
         {
             if (_size == s._size)
             {
                 return true;
             }
             return false;
         }
     }
    
     bool operator!=(const string& s)
     {
         return !(*this == s);
     }
    
    • 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
    • 30
    • 31

    博主能力有限,无法严格按照库中的方法实现,比如采用内存池等技术,还有部分函数并未实现,模拟实现string的目的只是为了我们更好的理解string类,而不是真正让我们去写一个库函数.
    拜拜了.
    在这里插入图片描述

  • 相关阅读:
    python如何调用另一个.py文件中的类和函数
    GPU Microarch 学习笔记【3】Tensor Core
    C++笔记:从零开始一步步手撕高阶数据结构AVL树
    Java 得到当前时间距离第二天凌晨还剩多少秒
    MYSQL 根据唯一索引键更新死锁问题
    选举
    【Linux】/proc/stat解析
    docker系列7:docker安装ES
    【AI】《动手学-深度学习-PyTorch版》笔记(二十二):单发多框检测(SSD)
    Nginx 如何做流量拷贝
  • 原文地址:https://blog.csdn.net/qq_67276605/article/details/132706721