【C++】string类模拟实现

105 阅读7分钟

类似于C++标准库中的std::string,基于此模拟实现一个自己的MyString::string类。

1.初步实现

基于string类是主要针对字符串的操作,所以我们需要知道一个字符串的内容、长度、可分配容量,有时还需要一个无效位置来进行判断。

所以我们的类的成员变量应包括以下几个:

  • [_str]:指向动态分配的字符数组,用于存储字符串内容。

  • [_size]:当前字符串的长度(不包括终止符 \0)。

  • [_capacity]:当前分配的字符数组的容量(包括终止符 \0)。

  • [npos]:静态成员变量,表示无效位置,值为 -1。对此在官网也有记载:

    image-20250424102734800

把它们放在private中:

private:
        char *_str;
        size_t _size;
        size_t _capacity;
        static size_t npos;

同时作为一个类,应该包含构造函数和析构函数。

对于构造函数,又分为有参、无参、拷贝构造函数。

//无参构造函数
        string()
        {
            _str = new char[1]; // 分配一个字符的内存
            _str[0] = '\0'; // 初始化为空字符串
            _size = 0;
            _capacity = 1; // 容量为1
        }
//有参构造函数
        string(const char *str) // 初始化列表
            : _str(new char[_capacity + 1]) 
            , _size(strlen(str))
            , _capacity(_size)
        {
            strcpy(_str, str); // 复制字符串
        }
//拷贝构造函数
	    string(const string &other)
        {
            _size = other._size;                       // 复制大小
            _capacity = other._capacity;               // 复制容量
            _str = new char[_capacity + 1];            // 分配内存
            memcpy(_str, other._str, other._size + 1); // 复制字符串
        }

以及析构函数:

// 析构函数
        ~string()
        {
            delete[] _str; // 释放内存
            _str = nullptr;
            _size = 0;
            _capacity = 0;
        }

2. 支持的功能

(1) 迭代器支持
  • 提供 begin()end() 方法,支持范围 for 循环和迭代器操作。
  • 提供 const 版本的 begin() end(),支持常量对象的迭代。
typedef char *iterator;

        iterator begin()
        {
            return _str;
        }
        iterator end()
        {
            return _str + _size;
        }

        iterator begin() const
        {
            return _str;
        }
        iterator end() const
        {
            return _str + _size;
        }
(2) 字符串的基本操作
  • c_str()
    • 返回 C 风格字符串(以 \0 结尾)。
  • size()
    • 返回字符串的长度(不包括终止符 \0)。
  • operator[]
    • 支持通过下标访问字符串中的字符。
    • 提供 const 和非 const 版本。
	   const char *c_str()
        {
            return _str; // 返回字符串
        }

       size_t size() const
        {
            return _size;
        }

       char &operator[](size_t pos)
        {
            assert(pos < _size);
            return _str[pos];
        }
       char &operator[](size_t pos) const
        {
            assert(pos < _size);
            return _str[pos];
        }
(3) 增删改查功能

该部分的内容都基于待会会介绍的内存管理功能。同时基于该部分的其他方法,某些方法会直接进行复用,从而达到节省代码量和提高可读性的效果。

  • 增与改

    • push_back(char ch):在字符串末尾添加一个字符。

      void push_back(char ch)
              {
                  if (_size == _capacity)
                  {
                      // 2倍扩容
                      reserve(_capacity == 0 ? 4 : _capacity * 2);
                  }
                  _str[_size] = ch;
                  ++_size;
                  _str[_size] = '\0';
              }
      
    • append(const char* str):在字符串末尾追加一个 C 风格字符串。

      void append(const char *str)
              {
                  size_t len = strlen(str);
                  if (_size + len > _capacity)
                  {
                      // 至少_size + len
                      reserve(_size + len);
                  }
      
                  strcpy(_str + _size, str);
                  _size += len;
              }
      
      
    • operator+=(char ch)operator+=(const char* str):支持通过 += 运算符追加字符或字符串。

       string &operator+=(char ch)
              {
                  push_back(ch);
                  return *this;
              }
       string &operator+=(const char *str)
              {
                  append(str);
                  return *this;
              }
      
    • insert(size_t pos, size_t n, char ch):在指定位置插入 [n](vscode-file://vscode-app/d:/Microsoft VS Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html) 个字符。

    • insert(size_t pos, const char* str):在指定位置插入一个 C 风格字符串。

       void insert(size_t pos, size_t n, char ch)
              {
                  assert(pos <= _size);
                  if (_size + n > _capacity)
                  {
                      // 至少_size + n
                      reserve(_size + n);
                  }
                  // int end = _size;
                  size_t end = _size;
      
                  // if(pos = 0)
                  // {
      
                  // }
                  while (end >= pos && end != npos)
                  {
                      _str[end + n] = _str[end];
                      end--;
                  }
      
                  for (size_t i = 0; i < n; i++)
                  {
                      _str[pos + i] = ch;
                  }
                  _size += n;
              }
      
         void insert(size_t pos, const char *str)
              {
                  assert(pos <= _size);
                  size_t len = strlen(str);
                  if (_size + len >= _capacity)
                  {
                      reserve(_size + len);
                  }
                  size_t end = _size;
                  while (end >= pos && end != npos)
                  {
                      _str[end + len] = _str[end];
                      end--;
                  }
                  for (size_t i = 0; i < len; i++)
                  {
                      _str[pos + i] = str[i];
                  }
                  _size += len;
              }
      
    • resize(size_t n, char ch = '\0'):调整字符串长度,多余部分用指定字符填充,或截断字符串。

      void resize(size_t n, char ch = '\0') // 这里缺省值设为'\0'
              {
                  if (n < _size)
                  {
                      _size = n;
                      _str[_size] = '\0';
                  }
                  else
                  {
                      reserve(n);
      
                      for (size_t i = _size; i < n; i++)
                      {
                          _str[i] = ch;
                      }
                      _size = n;
                      _str[_size] = '\0';
                  }
              }
      
    • erase(size_t pos, size_t len = npos):从指定位置开始删除指定长度的字符。

      void erase(size_t pos, size_t len = npos)
              {
                  assert(pos <= _size);
                  if (len == npos || pos + len >= _size)
                  {
                      _str[pos] = '\0';
                      _size = pos;
                      _str[_size] = '\0';
                  }
                  else
                  {
                      size_t end = pos + len;
                      while (end <= _size)
                      {
                          _str[pos++] = _str[end++];
                      }
                      _size -= len;
                  }
              }
      
    • find(char ch, size_t pos = 0):从指定位置开始查找字符,返回其位置。

    • find(const char* str, size_t pos = 0):从指定位置开始查找子字符串,返回其起始位置。

      size_t find(char ch, size_t pos = 0)
              {
                  assert(pos < _size);
                  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)
              {
                  assert(pos < _size);
      
                  const char *ptr = strstr(_str + pos, str);
                  if (str)
                  {
                      return ptr - _str;
                  }
                  else
                  {
                      return npos;
                  }
              }
      
    • substr(size_t pos = 0, size_t len = npos):返回从指定位置开始的子字符串。

      string substr(size_t pos = 0, size_t len = npos)
              {
                  assert(pos < _size);
                  size_t n = len;
                  if (len == npos || pos + len > _size)
                  {
                      n = _size - pos;
                  }
                  string tmp;
                  tmp.reserve(n);
                  // for (size_t i = pos; i < n; i++) i不是从0开始,所以应该是pos+n
                  for (size_t i = pos; i < n + pos; i++)
                  {
                      tmp += _str[i];
                  }
                  return tmp;
              }
      
(4) 比较操作
  • operator<:支持字符串的字典序比较。
  • operator==:支持字符串的相等比较。
  • operator<=operator>operator>=operator!=:支持其他常见的比较操作。
bool operator<(const string &s)
        {
            int ret = memcmp(_str , s._str,_size < s._size? _size: s._size);
            return ret == 0 ? _size < s._size : ret< 0;   
        }

        bool operator==(const string &s)
        {
            return _size == s._size
                && memcmp(_str,s._str,_size)== 0;
        }

        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)
        {
            return !(*this == s);
        }
(5) 内存管理
  • reserve(size_t n)

    • 扩展字符串的容量,避免频繁的内存分配。
    void reserve(size_t n)
            {
                if (n > _capacity)
                {
                    char *tmp = new char[n + 1]; // + "\0"
                    strcpy(tmp, _str);
                    delete[] _str;
                    _str = tmp;
                    _capacity = n;
                }
            }
    
  • clear()

    • 清空字符串内容,将其重置为空字符串。
    void clear()
            {
                _str[0] = '\0';
                _size = 0;
            }
    
  • swap(string& s)

    • 交换两个字符串对象的内容,避免不必要的拷贝。
    void swap(string &s)
            {
                std::swap(_str,s._str);
                std::swap(_size,s._size);
                std::swap(_capacity,s._capacity);
            }
    
  • operator=(string tmp)

    • 赋值操作
    string& operator=(string tmp)
            {
                swap(tmp);
                return *this;
            }
    
(6) 流操作
  • operator<<
    • 支持将字符串输出到输出流。
  • operator>>
    • 支持从输入流读取字符串,自动处理空格和换行符。
   size_t string::npos = -1;

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

    istream &operator>>(istream &in, string &s)
    {
        s.clear();
        char ch = in.get();

        //  处理缓冲区前面的空格或者换行
        while (ch != ' ' && ch != '\n')
        {
            ch = in.get();
        }

        //  建立缓冲区,使空间扩容更加节省
        char buff[128];
        int i = 0;
        // in >> ch;
        while (ch != ' ' && ch != '\n')
        {
            buff[i++] = ch;
            if (i == 127) // 要给\0留一个
            {
                buff[i] = '\0';
                s += buff;
                i = 0;
            }
            // s += ch;
            // in >> ch;
            ch = in.get();
        }

        if (i != 0)
        {
            buff[i] = '\0';
            s += buff;
        }
        return in;
    }

3. 支持的运算符重载

  • 赋值运算符
    • 支持拷贝赋值和移动赋值。
  • 比较运算符
    • <==<=>>=!=
  • 字符串拼接运算符
    • +=(支持字符和字符串)。
  • 流插入和提取运算符
    • <<>>

4. 内存管理

  • 动态分配内存存储字符串内容,支持自动扩容。
  • 在析构函数中释放内存,避免内存泄漏。
  • 使用 swap 函数优化赋值运算符的实现,避免不必要的拷贝。

5. 与 [std::string]的对比

MyString::string 类与 [std::string]类非常相似,但仍有一些功能未实现,例如:

  1. [std::string] 的 replace 方法:替换字符串中的某些部分。
  2. [std::string] 的 compare 方法:提供更灵活的字符串比较功能。
  3. [std::string] 的 find_last_offind_first_of 方法:支持更复杂的查找操作。
  4. 线程安全性:[std::string]在某些实现中是线程安全的,而你的实现没有考虑线程安全。

当然,这仅仅是string类的冰山一角。MyString仅仅是对其的一个很简单的模拟实现,为的是理解string类的用法和意义。在自己模拟实现之后,往往在字符串的操作方面能够更加得心应手。