模拟实现string类

一:四个默认成员函数

1.private 成员

 private:
        char* _str;
        size_t _size;
        size_t _capacity;
        const static size_t npos;
  • _str是存储字符串的指针,_size用来存储当前字符串的结尾位置,_capacity用于存储字符串的容量大小
  • npos的值被定义为-1,又因为npos的类型为size_t,是无符号整型,所以npos实际上的值是size_t类型的最大值4294967295
  • npos做参数时,通常表示字符串的结尾,因为我们认为一个字符串的长度不会超过npos,用做返回值时通常用于查找,表示未找到要查找的值
  • npos的初始化需要在类外,因为npos是静态成员

2.构造函数

 string(const char* str = "")
        {
            //防止传nullptr
            if (str == nullptr) str = "";
            _size = strlen(str);
            _capacity = _size;
            _str = new char[_capacity + 1];
            strcpy(_str, str);
        }
  • 构造函数简单的来说就是传一个字符串用来初始化string,不过写缺省参数时不能写nullptr
  • 因为strlen计算字符串长度时不能传nullptr,所以缺省参数通常写为“”,表示空串
  • 还需要注意的是,_capacity是指存储有效字符的长度,不包括结尾的'\0',所以给_str分配空间时要比_capacity多一字节

3.析构函数

~string()
        {
            delete[] _str;
            _str = nullptr;
            _size = _capacity = 0;
        }
  • delete 针对nullptr什么也不做,所以可以不用判断_str是否为空

4.拷贝构造

<1> 传统方式

  string(const string& str)//拷贝构造,必须传引用,否则会无限递归
        :_str(new char[strlen(str._str)+1])
        {
           strcpy(_str, str._str);
        }
  • 拷贝构造和构造函数很相似,不过是用一个string对象的_str来构造一个新的string对象的_str,就是一个拷贝的过程,调用库函数即可
  • 需要注意的是:参数必须传引用
  • 如果不传引用,会造成无限递归

<2> 现代方式

void swap(string& str)
        {
            ::swap(_str, str._str);
            ::swap(_size, str._size);
            ::swap(_capacity, str._capacity);
        }
string(const string& str)
            :_str(nullptr)
            , _size(0)
            , _capacity(0)
        {
            string tmp(str._str);
            swap(tmp);
        }
  • 现在我们定义拷贝构造函数时可以不用strcpy了,我们先初始化一个空string对象,然后用构造函数构造一个和需要拷贝构造的string数值相同的string,再把它们交换,就完成了拷贝构造,并且也是深拷贝
  • 出了拷贝构造函数,我们临时构造的对象tmp会自动调用析构函数析构它
  • 这里注意我们需要对_str进行初始化,否则_str为随机值,交换后出了拷贝构造调用析构函数进行delete时会报错,因为此时_str的空间不是动态分配得来的
  • 这里的swap函数可以不自己写,调用库里的,但是库里的效率没有我们自己重新写的效率高,因为我们仅仅是交换,而库里的swap会新建变量进行三次的深拷贝

5.赋值运算符重载(operator=)

<1> 传统方式

 string& operator=(const string& str)
        {
            if (this!=&str)//防止s1=s1
            {
                delete[] _str;
                _str = new char[strlen(str._str) + 1];
                strcpy(_str, str._str);
            }
            return *this;
        }
  • 和拷贝构造不同的是,operator=需要delete释放原空间再申请和str一样大的空间再进行拷贝(因为要保证它们的空间一样大,小了能扩容,大了的话只能重新申请空间,不如直接重新申请空间)
  • 为了防止自己给自己赋值,加上if判断,当this!=&str时再进行赋值

<2> 现代方式

string& operator=(string str)//s1=s1时_str的地址会变
        {
            swap(str);
            return *this;
        }
  • 和拷贝构造一样,operator=也有现在方式,使代码更为简洁
  • 这里的参数是关键,并没有传引用,所以传参时就进行了拷贝构造,之后直接进行swap就完成了赋值
  • 缺点时是无法防止自己给自己赋值

6.迭代器与operator[]

  const char& operator[](int pos) const
  //专门重载用于const成员函数,防止修改_str的值
   {
       assert(pos < _size);
       return _str[pos];
   }
   char& operator[](int pos)
   {
       assert(pos < _size);
       return _str[pos];
   }
  typedef char* iterator;
  typedef const char* const_iterator;
  iterator begin()
  {
      return _str;
  }
  iterator end()
  {
      return _str + _size;
  }
  const_iterator begin() const
  {
      return _str;
  }
  const_iterator end() const
  {
      return _str + _size;
  }
  • 编写operator[]时别忘记加上assert防止越界访问
  • string类的迭代器其实就是char和const char指针的封装

二:reserve与resize

1.reserve函数

void reserve(size_t n)
{
   if (n > _capacity)
    {
       char* tmp = new char[n + 1];
       strncpy(tmp, _str, _size + 1);
       //不能用strcpy,有可能有多的\0没有被拷贝过来,拷贝_size+1个,\0也要拷贝
       delete[] _str;
       _str = tmp;
       _capacity = n;
    }
 }
  • n<_capacity时不扩容
  • 不能使用strcpy进行拷贝,因为string类中间可能含有`\0``,string类的结束标志是pos==_size
  • 所以我们使用strncpy进行拷贝,直接拷贝_size+1个字符,\0也要拷贝

2.resize函数

void resize(size_t n, char val = '\0')//别忘了缺省参数
        {
            if (n < _size)//分成n<_size和n>=size两种情况
            {
                _size = n;
                _str[_size] = '\0';
            }
            else
            {
                if (n > _capacity)
                {
                    reserve(n);
                }
                while (_size < n)
                {
                    _str[_size++] = val;
                }
                _str[_size] = '\0';
            }
        }
  • resize可以分为两种情况
  • (1) n<_size (2)n>=_size
  • 第一种情况比较简单,直接将第n个位置的字符改为\0,再修改_size的值即可
  • 第二种情况就要从_size一直尾插val字符,并在最后以\0结尾

三:插入删除和查找

1.push_back

void push_back(char c)
{
     if (_size == _capacity)
     {
         reserve(_capacity == 0 ? 4 : _capacity * 2);
     }
     _str[_size++] = c;
     _str[_size] = '\0';
 }
  • 每次新增字符前需要检查空间是否足够,不够需要扩容
  • 如果是空串,则分配4个字节的空间,否则增容2倍

2.append

 void append(const char* str)
 {
     int len = _size + strlen(str);
     if (len > _capacity)
     {
         reserve(len);
     }
     strcpy(_str + _size, str);
     _size = len;
 }
  • 和push_back一样,需要检查是否需要扩容

3.operator+=

string& operator+=(char c)
{
     push_back(c);
     return *this;
}
string& operator+=(const char* str)
 {
     append(str);
     return *this;
 }
  • 需要重载两个版本的operator+=,分别对应尾插一个字符和尾插一个字符串
  • 复用push_back和append即可

4.insert

//pos位置之前插入
 string& insert(size_t pos, char ch)
  {
      assert(pos <= _size);//只能在size及之前位置插入
      if (_size == _capacity)
      {
          reserve(_capacity == 0 ? 4 : _capacity * 2);//capacity可能为0
      }
      char* end = _str + _size;
      while (end >= _str + pos)
      {
          *(end + 1) = *end;
          end--;
      }
      _str[pos] = ch;
      _size++;
      return *this;
  }
 string& insert(size_t pos, const char* str)//不加const传常量字符串会有问题
 {
       assert(pos <= _size);
       size_t len = _size+strlen(str);
       if ( len > _capacity)
       {
           reserve(len);
       }
       char* end = _str + _size;
       while (end >= _str + pos)
       {
           *(end + len) = *end;
           end--;
       }
       _size += len;
       strncpy(_str + pos, str, len);
       return *this;
   }
  • 注意点:(1)插入的位置不能超过_size
  • (2)别忘了扩容
  • (3)如果不使用指针进行字符的移动的话,需要从_size+1进行赋值
  • 如果从_size开始,令_str[end+1]=_str[end]的话,循环条件写成while(end>=pos)
  • pos为0时,end最后会变为-1,但end的类型是size_t,会是一个很大的正数,就会导致无限循环
 string& insert(size_t pos, char ch)
        {
            assert(pos <= _size);//只能在size及之前位置插入
            if (_size == _capacity)
            {
                reserve(_capacity == 0 ? 4 : _capacity * 2);//capacity可能为0
            }
             size_t end = _size+1;//将end类型改为int也不行,会发生隐式类型转换
             while (end > pos)
             {
                 _str[end] = _str[end-1];
                 end--;
             }
            _str[pos] = ch;
            _size++;
            return *this;
        }

5.erase

 string& erase(size_t pos = 0, size_t len = npos)//不给参数默认全删
 {
      assert(pos < _size);//_size位置是\0,不能删
      //1.要删的字符数量大于剩余字符
      //2.要删的字符数量小于剩余字符
      size_t leftLen = _size - pos;
      if (len >= leftLen)//pos及以后全删
      {
          _str[pos] = '\0';
          _size = pos;
      }
      else
      {
          strcpy(_str + pos, _str + pos + len);
          _size -= len;
      }
      return *this;
  }
  • 注意点:(1)pos必须小于_size,_size位置是\0,也不能删除
  • (2)需要计算删除的长度和pos后剩余长度的大小,如果要删除的长度大于剩余长度,直接将pos后全部删除,否则只删除len长度的字符,可以通过strcpy来完成字符的移动

6.find

size_t find(char ch, size_t pos = 0)
{
     assert(pos < _size);
     for (int i = pos; i < _size; i++)
     {
         if (_str[i] == ch)
         {
             return i;
         }
     }
     return npos;
 }
        
size_t find(const char* str, size_t pos = 0)
{
    assert(pos < _size);
    const char* ret = strstr(_str + pos, str);
    if (ret)
    {
        return ret - _str;
    }
    return npos;
}
  • find函数也需重载,分为查找一个字符和查找一个字符串
  • 查找字符时遍历查找即可,查找字符串时可以通过调用库函数strstr实现
  • 需要注意的是,find返回的是下标,查找字符串时strstr返回的是指针,需要通过ret-_str计算出下标再返回

四:operator<<与operator>>

ostream& operator<<(ostream& out, const string& s)
{
     for (auto ch : s)
     {
         out << ch;
     }
     return out;
 }
 istream& operator>>(istream& in, string& s)
 {
     s.clear();
     char ch;
     ch = in.get();
     while (ch != ' ' && ch != '\n')
     {
         s += ch;
         ch = in.get();
     }
     return in;
 }
  • 这里的operator<<和operator>>不用写成友元函数,因为并没有直接修改string的内部成员
  • 而是通过string的接口间接修改,比如operator>>就是先清除原字符串,再不断进行+=操作达成输入的目的
  • 输出则是通过范围for()

五:比较大小的运算符重载

	bool operator<(string& s1, string& s2)
    {
        return strcmp(s1.c_str(), s2.c_str()) < 0;
    }
    bool operator==(string& s1, string& s2)
    {
        return strcmp(s1.c_str(), s2.c_str()) == 0;
    }
    bool operator<=(string& s1, string& s2)
    {
        return s1 < s2 || s1 == s2;
    }
    bool operator>(string& s1, string& s2)
    {
        return !(s1 <= s2);
    }
    bool operator>=(string& s1, string& s2)
    {
        return !(s1 < s2);
    }
    bool operator!=(string& s1, string& s2)
    {
        return !(s1 == s2);
    }
  • 在对string类进行排序时需要比较string类的大小,所以我们需要对>,<,==等运算符进行重载
  • 在stl库中这些重载没有写成成员函数,所以编写时也可以写在类外面
  • 可以通过调用strcmp库函数快速实现
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dhdw

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值