【C++】:string的模拟实现

简介: 【C++】:string的模拟实现

1. 基本构造

为了区别库中本来的string,我们可以将模拟实现的string封装在我们自己的命名空间中,string的基本构造构造函数、拷贝构造、析构函数。

头文件:string.h

#pragma once
#include <iostream>
using namespace std;
//
//模拟实现string,将实现的string封装在自己的命名空间中
namespace ywh
{
  class string
  {
  public:
    //构造函数
    string(const char* str = "")
      :_size(strlen(str))
      ,_capacity(_size)
    {
      //根据空间大小为_str开辟空间
      _str = new char[_capacity + 1];  //多开辟一个存放'\0'
      //将str拷贝至_str
      strcpy(_str, str);
    }
    //析构
    ~string()
    {
      //匹配使用
      delete[] _str;
      _str = nullptr;
      _size = _capacity = 0;
    }
  private:
    char* _str;
    size_t _size;      //有效元素个数
    size_t _capacity;  //空间大小
  };
}

2. 深拷贝

在之前的文章中提到过关于浅拷贝的问题,我们不写编译器默认生成的拷贝构造就是浅拷贝,但是string存在资源的创建的,所以我们得自己实现一个深拷贝的拷贝构造。

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

2.1 传统写法

#pragma once
#include <iostream>
using namespace std;
//
//模拟实现string,将实现的string封装在自己的命名空间中
namespace ywh
{
  class string
  {
  public:
    //构造函数
    string(const char* str = "")
      :_size(strlen(str))
      ,_capacity(_size)
    {
      //根据空间大小为_str开辟空间
      _str = new char[_capacity + 1];  //多开辟一个存放'\0'
      //将str拷贝至_str
      strcpy(_str, str);
    }
    //析构
    ~string()
    {
      //匹配使用
      delete[] _str;
      _str = nullptr;
      _size = _capacity = 0;
    }
    //传统写法
    //拷贝构造
    string(const string& s)
    {
      _str = new char[s._capacity + 1];
      _size = s._size;
      _capacity = s._capacity;
      strcpy(_str, s._str);
    }
    //赋值运算符重载
    string& operator=(const string& s)
    {
      if (this != &s)
      {
        //使用一块新的空间来保存s的数据
        char* tmp = new char[s._capacity + 1];
        strcpy(tmp, s._str);
        delete[] _str;
        _str = tmp;  //直接将_str重新指向tmp
        _size = s._size;
        _capacity = s._capacity;
      }
      return *this;
    }
  private:
    char* _str;
    size_t _size;      //有效元素个数
    size_t _capacity;  //空间大小
  };
}

传统写法的代码不容易看懂而且还比较麻烦,我们可以尝试新的方法。

2.2 现代写法

#pragma once
#include <iostream>
using namespace std;
//
//模拟实现string,将实现的string封装在自己的命名空间中
namespace ywh
{
  class string
  {
  public:
    //构造函数
    string(const char* str = "")
      :_size(strlen(str))
      ,_capacity(_size)
    {
      //根据空间大小为_str开辟空间
      _str = new char[_capacity + 1];  //多开辟一个存放'\0'
      //将str拷贝至_str
      strcpy(_str, str);
    }
    //析构
    ~string()
    {
      //匹配使用
      delete[] _str;
      _str = nullptr;
      _size = _capacity = 0;
    }
    /*
    //传统写法
    //拷贝构造
    string(const string& s)
    {
      _str = new char[s._capacity + 1];
      _size = s._size;
      _capacity = s._capacity;
      strcpy(_str, s._str);
    }
    //赋值运算符重载
    string& operator=(const string& s)
    {
      if (this != &s)
      {
        //使用一块新的空间来保存s的数据
        char* tmp = new char[s._capacity + 1];
        strcpy(tmp, s._str);
        delete[] _str;
        _str = tmp;  //直接将_str重新指向tmp
        _size = s._size;
        _capacity = s._capacity;
      }
      return *this;
    }
    */
    //现代写法
    void swap(string& s)
    {
      std::swap(_str, s._str);
      std::swap(_size, s._size);
      std::swap(_capacity, s._capacity);
    }
    //拷贝构造
    string(const string& s)
      :_str(nullptr)
      ,_size(0)
      ,_capacity(0)
    {
      //复用构造函数
      string tmp(s._str);
      swap(tmp);
    }
    //赋值运算符重载
    string& operator=(string tmp)
    {
      //直接交换
      swap(tmp);
      return *this;
    }
  private:
    char* _str;
    size_t _size;      //有效元素个数
    size_t _capacity;  //空间大小
  };
}

上述方法是比较简单的,但是还是一种深拷贝的情况,那么可以通过写时拷贝来进行浅拷贝。

2.3 写时拷贝(了解)

写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。

引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。

2. 容量相关接口

以下接口都是在string类中实现:

2.1 size、capacity、clear、empty

//清理
void clear()
{
  _size = 0;
  _str[_size] = '\0';
}
//有效个数
size_t size() const
{
  return _size;
}
//容量
size_t capacity() const
{
  return _capacity;
}
//判空
bool empty()const
{
  return 0 == _size;
}

2.2 reserve

当要扩容的空间比原来的空间大时才可以进行扩容,重新开辟一块新的空间,先将_str拷贝至新空间,然后再将_str指向的旧空间进行释放,再将_str重新指向新的空间,然后将新空间的_capacity置为新的容量即可:

//预留空间
void reserve(size_t n)
{
  //当传递的n大于本来的空间才可以扩容
  if (n > _capacity)
  {
    char* tmp = new char[n + 1];
    strcpy(tmp, _str);
    delete[] _str;
      _str = tmp;
      _capacity = n;
  }
}

2.3 resize

当传递的n小于原本的_size,只需要将_str中下标为n的地方置为'\0'即可,再将_size改为n,如果大于_size,那么可以复用reserve预留空间,然后将剩余的空间用指定的字符来进行填充,如果没有指定字符,默认使用'\0'来填充,然后在最后面再添加上'\0'来表示末尾。

//修改有效个数
void resize(size_t n, char ch = '\0')
{
  //小于直接添加'\0'截止
  if (n <= _size)
  {
    _str[n] = '\0';
    _size = n;
  }
  else
  {
    //大于先预留空间
    reserve(n);
    //再将指定的字符依次插入,末尾在添加'\0'
    while(_size < n)
    {
      _str[_size] = ch;
      _size++;
    }
    _str[_size] = '\0';
  }
}

3. 迭代器

3.1 operator[ ]

//非const operator[]
char& operator[](size_t pos)
{
  assert(pos < _size);
  return _str[pos];
}
//const operator[]
const char& operator[](size_t pos) const
{
  assert(pos < _size);
  return _str[pos];
}

3.2 begin、end

typedef char* iterator;
  typedef const char* const_iterator;
  //非const 
  iterator begin()
  {
    return _str;
  }
  iterator end()
  {
    return _str + _size;
  }
  //const
  const_iterator begin() const
  {
    return _str;
  }
  const_iterator end() const
  {
    return _str + _size;
  }

4. 修改相关接口

4.1 push_back、c_str、npos

#pragma once
#include <iostream>
#include <assert.h>
using namespace std;
//
//模拟实现string,将实现的string封装在自己的命名空间中
namespace ywh
{
  class string
  {
  public:
    基本构造//
        //...
    容量接口//
    //...
    迭代器
    //...
    修改接口//
    //尾插
    void push_back(char ch)
    {
      if (_size == _capacity)
      {
        reserve(_capacity == 0 ? 4 : _capacity * 2);
      }
      _str[_size] = ch;
      _size++;
      _str[_size] = '\0';
    }
    //c格式的字符串
    const char* c_str() const
    {
      return _str;
    }
  private:
    char* _str;
    size_t _size;      //有效元素个数
    size_t _capacity;  //空间大小
  public:
    const static size_t npos;  //静态对象需要在全局定义
  };
}
const size_t string::npos = -1;

4.2 operator+=、append

//追加字符串
    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 ch)
    {
      //复用尾插
      push_back(ch);
      return *this;
    }
    string& operator+=(const char* str)
    {
      //复用append
      append(str);
      return *this;
    }

4.3 insert

在pos位置插入字符有两种写法:

1. end表示size位置,end类型为int(有符号整形)

当进行头部插入(pos == 0),如果end类型为size_t(无符号整型),那么end就永远不可能小于pos,如果end类型为int,那么在进行end与pos的比较时,end会进行类型提升,从有符号整型提升为无符号整型,所以为了防止类型提升,需要将pos强制转化为int

//在pos位置插入字符
    void insert(size_t pos, char ch)
    {
      assert(pos <= _size);
      if (_size == _capacity)
      {
        reserve(_capacity == 0 ? 4 : _capacity * 2);
      }
      //挪动数据
      int end = _size;   //end类型只能为int
      while (end >= (int)pos)  //防止类型提升,所以进行强转
      {
        _str[end + 1] = _str[end];
        end--;
      }
      _str[pos] = ch;
      _size++;
    }

2. end表示size的后一个位置,end的类型为size_t(无符号整型)

//在pos位置插入字符
    void insert(size_t pos, char ch)
    {
      assert(pos <= _size);
      if (_size == _capacity)
      {
        reserve(_capacity == 0 ? 4 : _capacity * 2);
      }
      //挪动数据
      size_t end = _size + 1;
      while (end > pos)
      {
        _str[end] = _str[end-1];
        end--;
      }
      _str[pos] = ch;
      _size++;
    }

在pos位置插入字符串同样的也有两种写法,这里就不做演示了。

//在pos位置插入字符串
    void insert(size_t pos, const char* str)
    {
      assert(pos <= _size);
      size_t len = strlen(str);
      if (_size + len > _capacity)
      {
        reserve(len + _size);
      }
      //挪动数据
      int end = _size;  //end类型只能为int
      while (end >= (int)pos) //防止越界进行强转,防止类型提升
      {
        _str[end + len] = _str[end - 1];
        end--;
      }
      strncpy(_str + pos, str, len);
      _size += len;
    }

4.4 erase

//删除pos位置
    void erase(size_t pos, size_t len = npos)
    {
      assert(pos < _size);
      if (len == npos || pos + len >= _size)
      {
        _str[pos] = '\0';
        _size = pos;
      }
      else
      {
        size_t begin = pos + len;
        while (begin <= _size)
        {
          _str[begin - len] = _str[begin];
          begin++;
        }
        _size -= len;
      }
    }

5. 运算符重载

bool operator<(const string& s) const
    {
      return strcmp(_str, s._str) < 0;
    }
    bool operator==(const string& s) const
    {
      return strcmp(_str, s._str) == 0;
    }
    bool operator<=(const string& s) const
    {
      return *this < s || *this == s;
    }
    bool operator>(const string& s) const
    {
      return !(*this <= s);
    }
    bool operator>=(const string& s) const
    {
      return !(*this < s);
    }
    bool operator!=(const string& s) const
    {
      return !(*this == s);
    }

6. 流插入和流提取

6.1 流提取

//全局operator<<
//流插入
ostream& operator<<(ostream& out, const string& s)
{
  for (size_t i = 0; i < s.size(); i++)
  {
    out << s[i];
  }
  /*for (auto ch : s)
    out << ch;*/
  return out;
}

6.2 流插入

重载流提取时要注意:库里面的cin与scanf是不读取空格和换行的,即便是我们输入了空格和换行它也不会读取,所以在重载流插入时要使用库里面的get

进行流插入之前先要清理一下字符串,避免上一次的字符串进行混淆,这里还要注意一下,当我们输入的东西很多的时候,它就面临了要不断的进行扩容,如果我们刚开始就进行扩容一点空间,那么还可能存在空间不够用,或者是浪费空间的问题,那么该怎么办呢?

我们可以使用一个字符数组buff当作一个缓存区,将这个buff大小设为129,将输入的字符先储存在这个数组里面,当这个数组满了之后再将这个数组的内容尾插到string中,如果没有满,就将剩下的内容继续尾插,这样就避免了string自己进行多次扩容的问题:

//流提取
istream& operator>>(istream& in, string& s)
{
  s.clear();   //清理
  char ch;
  char buff[129];  //设置缓存区
  ch = in.get();   //输入
  int i = 0;
  while (ch != ' ' && ch != '\n')
  {
    buff[i++] = ch;
    if (i == 128)  //满了就往string中尾插
    {
      buff[++i] = '\0';
      s += buff;
      i = 0;
    }
    ch = in.get();
  }
  //将剩下的内容插入string
  if (i != 0)
  {
    buff[i] = '\0';
    s += buff;
  }
  return in;
}

7. 完整代码

头文件:string.h

#pragma once
#include <assert.h>
//
//模拟实现string,将实现的string封装在自己的命名空间中
namespace ywh
{
  class string
  {
  public:
    //构造函数
    string(const char* str = "")
      :_size(strlen(str))
      ,_capacity(_size)
    {
      //根据空间大小为_str开辟空间
      _str = new char[_capacity + 1];  //多开辟一个存放'\0'
      //将str拷贝至_str
      strcpy(_str, str);
    }
    //析构
    ~string()
    {
      //匹配使用
      delete[] _str;
      _str = nullptr;
      _size = _capacity = 0;
    }
    /*
    //传统写法
    //拷贝构造
    string(const string& s)
    {
      _str = new char[s._capacity + 1];
      _size = s._size;
      _capacity = s._capacity;
      strcpy(_str, s._str);
    }
    //赋值运算符重载
    string& operator=(const string& s)
    {
      if (this != &s)
      {
        //使用一块新的空间来保存s的数据
        char* tmp = new char[s._capacity + 1];
        strcpy(tmp, s._str);
        delete[] _str;
        _str = tmp;  //直接将_str重新指向tmp
        _size = s._size;
        _capacity = s._capacity;
      }
      return *this;
    }
    */
    //现代写法
    void swap(string& s)
    {
      std::swap(_str, s._str);
      std::swap(_size, s._size);
      std::swap(_capacity, s._capacity);
    }
    //拷贝构造
    string(const string& s)
      :_str(nullptr)
      ,_size(0)
      ,_capacity(0)
    {
      //复用构造函数
      string tmp(s._str);
      swap(tmp);
    }
    //赋值运算符重载
    string& operator=(string tmp)
    {
      //直接交换
      swap(tmp);
      return *this;
    }
    容量接口//
    //清理
    void clear()
    {
      _size = 0;
      _str[_size] = '\0';
    }
    //有效个数
    size_t size() const
    {
      return _size;
    }
    //容量
    size_t capacity() const
    {
      return _capacity;
    }
    //判空
    bool empty()const
    {
      return 0 == _size;
    }
    //预留空间
    void reserve(size_t n)
    {
      //当传递的n大于本来的空间才可以扩容
      if (n > _capacity)
      {
        char* tmp = new char[n + 1];
        strcpy(tmp, _str);
        delete[] _str;
        _str = tmp;
        _capacity = n;
      }
    }
    //修改有效个数
    void resize(size_t n, char ch = '\0')
    {
      //小于直接添加'\0'截止
      if (n <= _size)
      {
        _str[n] = '\0';
        _size = n;
      }
      else
      {
        //大于先预留空间
        reserve(n);
        //再将指定的字符依次插入,末尾在添加'\0'
        while(_size < n)
        {
          _str[_size] = ch;
          _size++;
        }
        _str[_size] = '\0';
      }
    }
    ///迭代器//
    //非const operator[]
    char& operator[](size_t pos)
    {
      assert(pos < _size);
      return _str[pos];
    }
    //const operator[]
    const char& operator[](size_t pos) const
    {
      assert(pos < _size);
      return _str[pos];
    }
  public:
    typedef char* iterator;
    typedef const char* const_iterator;
    //非const 
    iterator begin()
    {
      return _str;
    }
    iterator end()
    {
      return _str + _size;
    }
    //const
    const_iterator begin() const
    {
      return _str;
    }
    const_iterator end() const
    {
      return _str + _size;
    }
    /修改接口///
    //尾插
    void push_back(char ch)
    {
      if (_size == _capacity)
      {
        reserve(_capacity == 0 ? 4 : _capacity * 2);
      }
      _str[_size] = ch;
      _size++;
      _str[_size] = '\0';
    }
    //c格式的字符串
    const char* c_str() const
    {
      return _str;
    }
    //追加字符串
    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 ch)
    {
      //复用尾插
      push_back(ch);
      return *this;
    }
    string& operator+=(const char* str)
    {
      //复用append
      append(str);
      return *this;
    }
    //查找字符
    size_t find(const char ch, size_t pos = 0)
    {
      for (size_t i = pos; i < _size; i++)
      {
        if (_str[i] == ch)
        {
          return i;
        }
      }
      return npos;
    }
    //查找字符串
    size_t find(const char* str, size_t pos = 0)
    {
      const char* tmp = strstr(_str, str);
      if (tmp)
      {
        return tmp - _str; //返回第一次出现的下标
      }
      else
      {
        return npos;
      }
    }
    //截取字符串
    string substr(size_t pos, size_t len = npos)
    {
      string s;  //将取到的字符串存放在s中
      size_t end = pos + len;
      if (len == npos || end > _size)
      {
        end = _size;
        len = _size - pos;   //有多少取多少
      }
      s.reserve(len);
      for (size_t i = pos; i < end; i++)
      {
        s += _str[i];   //依次插入
      }
      return s;
    }
    //在pos位置插入字符
    //1. end为size位置
    void insert(size_t pos, char ch)
    {
      assert(pos <= _size);
      if (_size == _capacity)
      {
        reserve(_capacity == 0 ? 4 : _capacity * 2);
      }
      //挪动数据
      int end = _size;   //end类型只能为int
      while (end >= (int)pos)  //防止类型提升,所以进行强转
      {
        _str[end + 1] = _str[end];
        end--;
      }
      _str[pos] = ch;
      _size++;
    }
    //在pos位置插入字符
    //2. end为size后一个位置
    //void insert(size_t pos, char ch)
    //{
    //  assert(pos <= _size);
    //  if (_size == _capacity)
    //  {
    //    reserve(_capacity == 0 ? 4 : _capacity * 2);
    //  }
    //  //挪动数据
    //  size_t end = _size + 1;
    //  while (end > pos)
    //  {
    //    _str[end] = _str[end-1];
    //    end--;
    //  }
    //  _str[pos] = ch;
    //  _size++;
    //}
    //在pos位置插入字符串
    void insert(size_t pos, const char* str)
    {
      assert(pos <= _size);
      size_t len = strlen(str);
      if (_size + len > _capacity)
      {
        reserve(len + _size);
      }
      //挪动数据
      int end = _size;  
      while (end >= (int)pos) 
      {
        _str[end + len] = _str[end - 1];
        end--;
      }
      strncpy(_str + pos, str, len);
      _size += len;
    }
    //删除pos位置
    void erase(size_t pos, size_t len = npos)
    {
      assert(pos < _size);
      if (len == npos || pos + len >= _size)
      {
        _str[pos] = '\0';
        _size = pos;
      }
      else
      {
        size_t begin = pos + len;
        while (begin <= _size)
        {
          _str[begin - len] = _str[begin];
          begin++;
        }
        _size -= len;
      }
    }
    运算符重载///
    bool operator<(const string& s) const
    {
      return strcmp(_str, s._str) < 0;
    }
    bool operator==(const string& s) const
    {
      return strcmp(_str, s._str) == 0;
    }
    bool operator<=(const string& s) const
    {
      return *this < s || *this == s;
    }
    bool operator>(const string& s) const
    {
      return !(*this <= s);
    }
    bool operator>=(const string& s) const
    {
      return !(*this < s);
    }
    bool operator!=(const string& s) const
    {
      return !(*this == s);
    }
  private:
    char* _str;
    size_t _size;      //有效元素个数
    size_t _capacity;  //空间大小
    const static size_t npos; 
  };
  const size_t string::npos = -1;
  //流插入
  ostream& operator<<(ostream& out, const string& s)
  {
    for (size_t i = 0; i < s.size(); i++)
    {
      out << s[i];
    }
    /*for (auto ch : s)
      out << ch;*/
    return out;
  }
  //流提取
  istream& operator>>(istream& in, string& s)
  {
    s.clear();   //清理
    char ch;
    char buff[129];  //设置缓存区
    ch = in.get();   //输入
    int i = 0;
    while (ch != ' ' && ch != '\n')
    {
      buff[i++] = ch;
      if (i == 128)  //满了就往string中尾插
      {
        buff[++i] = '\0';
        s += buff;
        i = 0;
      }
      ch = in.get();
    }
    //将剩下的内容插入string
    if (i != 0)
    {
      buff[i] = '\0';
      s += buff;
    }
    return in;
  }
}

朋友们、伙计们,美好的时光总是短暂的,我们本期的的分享就到此结束,欲知后事如何,请听下回分解~,最后看完别忘了留下你们弥足珍贵的三连喔,感谢大家的支持!

目录
相关文章
|
2月前
|
C++ 容器
|
2月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
22 1
|
2月前
|
C++ 容器
|
2月前
|
C++ 容器
|
2月前
|
存储 C++ 容器
|
2月前
|
安全 C语言 C++
【C++篇】探寻C++ STL之美:从string类的基础到高级操作的全面解析
【C++篇】探寻C++ STL之美:从string类的基础到高级操作的全面解析
37 4
|
2月前
|
存储 编译器 程序员
【C++篇】手撕 C++ string 类:从零实现到深入剖析的模拟之路
【C++篇】手撕 C++ string 类:从零实现到深入剖析的模拟之路
69 2
|
2月前
|
编译器 C语言 C++
【C++】C++ STL 探索:String的使用与理解(三)
【C++】C++ STL 探索:String的使用与理解
|
2月前
|
存储 编译器 C++
【C++】C++ STL 探索:String的使用与理解(二)
【C++】C++ STL 探索:String的使用与理解
|
2月前
|
编译器 C语言 C++
【C++】C++ STL 探索:String的使用与理解(一)
【C++】C++ STL 探索:String的使用与理解