【C++学习手札】模拟实现string

简介: 【C++学习手札】模拟实现string

一、string实际的底层原理

       string类型底层实现通常使用动态数组,因为它允许在运行时动态分配内存空间。这使得字符串的长度可以根据需要进行调整,而不需要提前分配固定大小的空间,并且可以避免缓冲区溢出和内存泄漏等问题。 string类型还提供了各种字符串操作函数,例如拼接、查找、替换、大小写转换等。


  底层实际的储存结构:

class string
{
private:
    char* _str;
    size_t _capacity;
    size_t _size;
};

二、string的模拟实现

       基本成员函数

       构造函数

       通过设置缺省参数以及初始化列表来默认构造string对象

string(const char* str = "")
            :_size(strlen(str))
            ,_capacity(_size)
        {
            _str = new char[_capacity + 1];
            strcpy(_str, str);
        }

  在模拟实现string的构造函数时,很多人包括作者我一开始都会想着将他分为有参以及无参两部分,但是这种做法是会产生一定的问题的,如下为作者写出的错误的代码:

class string
    {
    public:
        string()//无参构造函数
            //初始化列表
            :_str(nullptr)
            , _size(0)
            , _capacity(0)
        {
        }
        string(char* str)//带参构造函数->char a[] = "abc";string kk(a)
            :_str(str),
            _size(strlen(str)),
            _capacity(strlen(str))
        {
        }
        string(const char* str)//带参构造函数->string kk("abc")
            :_str(str),
            _size(strlen(str)),
            _capacity(strlen(str))
        {
        }
    private:
        char* _str;
        size_t _capacity;
        size_t _size;
    };

对于以上代码,若调用如上的带参构造函数中string(const char* str)就会报错,因为str传给_str属于权限放大,现在有两种解决方法,一种是将它改为 string(char* str),但是这样就不能直接传递字符串,需要先生成字符串,还有一种方法是将_str改为const char*类型,但是此时就不能修改_str指向的内容,后续的一些操作会出现一些错误。

        拷贝构造函数

       由于我们使用了开辟了空间来用于储存数据,因此我们需要进行深拷贝,防止对于另外一个对象的影响。


      传统写法:

       开辟对应的空间,然后再给相应的值。

        // 传统写法
        // s2(s1)
        //string(const string& s)
        //{
        //  _str = new char[s._capacity+1];
        //  strcpy(_str, s._str);
        //  _size = s._size;
        //  _capacity = s._capacity;
        //}

较为推荐的写法:

       通过让构造函数帮我们创建一个构造相应的字符的对象,然后我们再交换两个对象的数据即可得。

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);
        }

  析构函数

       避免内存泄漏需要自己写一个析构函数用以销毁new在堆区所创建的空间

        ~string()
        {
            delete[]_str;
            _str = nullptr;
            _size = _capacity = 0;
        }

重载赋值运算符

       同上面的拷贝构造函数一样,我们对此也是需要深拷贝的

       传统写法:

       开辟对应的空间,然后再给相应的值。

        //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;
        //}

较为推荐的写法:

       通过让构造函数帮我们创建一个构造相应的字符的对象,然后我们再交换两个对象的数据即可得。

        string& operator=(const string& s)
        {
          if (this != &s)
          {
            string tmp(s);
            //this->swap(tmp);
            swap(tmp);
          }
          return *this;
        }

 再简化一点:通过在传值时进行拷贝构造,然后交换数据。

string& operator=(string tmp)
        {
            swap(tmp);
            return *this;
        }

  迭代器

       迭代器的概念

C++迭代器是一种用于遍历容器中元素的对象,它提供了一种统一的接口,使得不同类型的容器都可以使用相同的方式进行遍历。迭代器可以指向容器中的某个元素,并用于访问该元素的值或者进行修改。迭代器通常是一个类,它重载了指针操作符(*和->),以及自增(++)和自减(--)运算符,使得迭代器可以像指针一样进行移动。

      string的迭代器实际实现原理

      string的迭代器实际上是一个指向string底层存储空间中char类型数据的指针,不同的迭代器类型标志着不同的访问权限。例如,一个string::iterator对象可以用来读写字符,而一个const string::iterator对象只能用来读取字符

 于是我们定义出以下的迭代器:

        typedef char* iterator;
        typedef const char* const_iterator;

 begin()

       用于返回第一个字符的地址。

        iterator begin()
        {
            return _str; 
        }
        const_iterator begin()const
        {
            return _str; 
        }

 end()

       用于返回后一个字符的后一个字符的地址的地址。其实就是‘\0’的地址

        iterator begin()
        {
            return _str; 
        }
        const_iterator begin()const
        {
            return _str; 
        }

空间管理

        size_t size()const//大小
        {
            return _size;
        }
        size_t capacity()const//空间
        {
            return _capacity;
        }
        bool empty()const//空?
        {
            return _size == 0;
        }
        void reserve(size_t 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 c = '\0')//重设大小
        {
            if (n <= _size)
            {
                _str[n] = '\0';
                _size = n;
            }
            else 
            {
                reserve(n);
                while (_size < n)
                { 
                    _str[_size] = c ;
                    ++_size;
                }
                _str[_size] = '\0';
            }
        }

修改相关

       push_back()

        根据原型可知此函数是在当前字符串尾插一个字符,其中需要额外注意容量。

        void push_back(char c)
        {
            if (_size == _capacity)
            {
                reserve(_capacity == 0 ? 4 : _capacity * 2);
            }
            _str[_size] = c;
            ++_size;
            _str[_size] = '\0';
        }

         append ()

        根据原型可知此函数有很多的函数重载,但是我们最常用的就是尾插一段字符串,对此就不过多进行阐述。也需要额外注意容量。

        void append(const char* str)
        {
            size_t len = strlen(str);
            if (_size + len > _capacity)
            {
                reserve(_size + len);
            }
            strcpy(_str + _size, str);//拷贝到原字符串后
            _size += len;
        }

 重载+=运算符

        此处复用了以上的push_back和append函数,这也是根据C++中函数管用的复用习惯来做出的选择。

 string& operator+=(char c)
        {
            push_back(c);
        }
        string& operator+=(const char* str)
        {
            append(str);
            return *this;
        }

  c_str ()

       返回指向数组的指针,该数组包含以 null 结尾的字符序列(即 C 字符串),该字符表示字符串对象的当前值。简而言之就是将string对象转换为C语言风格类型的字符串。

        const char* c_str()const
        {
            return _str;
        }

clear()

        void clear()
        {
            _str[0] = '\0';
            _size = 0;
        }

swap()

        void swap(string& s)
        {
            std::swap(_str, s._str);
            std::swap(_size, s._size);
            std::swap(_capacity, s._capacity);
        }

  重载[ ](最爱的运算符!!!)

       这里其实就是按照数组的相关操作即可,但是要注意越界的判断!

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

关系运算符的重载

       关系运算符的重载的关键就是根据所需进行相应的数据变化以及做出值的返回。

        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);
        }

基本操作

       查找find()

由原型知,分为按照字符查找以及字符串查找还有常量字符串查找,对此,此次实现字符以及字符串查找。

对于字符查找:

1.        size_t find(char c, size_t pos = 0) const
        {
            for (size_t i = pos; i < _size; i++)
            {
                if (_str[i] == c)
                {
                    return i;
                }
            }
            return npos;
        }

   对于字符串查找:

       前置知识substr()

        他的主要作用就是在str中从pos位置开始,截取n个字符,然后将其返回。其中的npos实际上是一个全局的静态变量,实际的赋值为-1,都是由于类型为size_t,因此实际上为最大值!

        string substr(size_t pos, size_t len = npos)
        {
            string s;
            size_t end = pos + len;
            if (len == npos || pos + len >= _size) // 有多少取多少
            {
                len = _size - pos;
                end = _size;
            }
            s.reserve(len);
            for (size_t i = pos; i < end; i++)
            {
                s += _str[i];
            }
            return s;
        }
        // 返回子串s在string中第一次出现的位置
        size_t find(const char* s, size_t pos = 0) const
        {
            const char* p = strstr(_str + pos, s);
            if (p)
            {
                return p - _str;
            }
            else
            {
                return npos;
            }
        }

插入insert()

 根据所插入的空间大小挪字符

        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++;
        }
        void insert(size_t pos, const char* str)
        {
            assert(pos <= _size);
            size_t len = strlen(str);
            if (_size + len > _capacity)
            {
                reserve(_size + len);
            }
            // 挪动数据
            int end = _size;
            while (end >= (int)pos)
            {
                _str[end + len] = _str[end];
                --end;
            }
            strncpy(_str + pos, str, len);
            _size += len;
        }

   删除 erase()

 删除指定位置的字符,可根据调节删除的数量,默认是指定位置以后全删。

        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;
            }
        }

  流插入流提取

        重载<<和>>是为了能够像内置一样调用,输入打印。

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 buff[129];
    size_t i = 0;
    char ch;
    ch = in.get();
    while (ch != ' ' && ch != '\n')
    {
        buff[i++] = ch;
        if (i == 128)
        {
            buff[i] = '\0';
            s += buff;
            i = 0;
        }
        //s += ch;
        ch = in.get();
    }
    if (i != 0)
    {
        buff[i] = '\0';
        s += buff;
    }
    return in;
}

总体代码

#pragma once
#define _CRT_SECURE_NO_WARNINGS 01
#include<iostream>
#include<assert.h>
using namespace std;
namespace lt
{
  class string
  {
  public:
    typedef char* iterator;
    typedef const char* const_iterator;
    friend ostream& operator<<(ostream& out, const string& s);
    friend istream& operator>>(istream& in, string& s);
    iterator begin()
    {
      return _str;
    }
    iterator end()
    {
      return _str + _size;
    }
    const_iterator begin() const
    {
      return _str;
    }
    const_iterator end() const
    {
      return _str + _size;
    }
    /*string()
      :_str(new char[1]{'\0'})
      ,_size(0)
      ,_capacity(0)
    {}*/
    string(const char* str = "")
      :_size(strlen(str))
      , _capacity(_size)
    {
      _str = new char[_capacity + 1];
      strcpy(_str, str);
    }
    // 传统写法
    // s2(s1)
    //string(const string& s)
    //{
    //  _str = new char[s._capacity+1];
    //  strcpy(_str, s._str);
    //  _size = s._size;
    //  _capacity = s._capacity;
    //}
     s2 = s3
    //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;
    //}
    void swap(string& s)
    {
      std::swap(_str, s._str);
      std::swap(_size, s._size);
      std::swap(_capacity, s._capacity);
    }
    // s2(s1)
    string(const string& s)
      :_str(nullptr)
      , _size(0)
      , _capacity(0)
    {
      string tmp(s._str);
      swap(tmp);
    }
    // s2 = s3
    //string& operator=(const string& s)
    //{
    //  if (this != &s)
    //  {
    //    string tmp(s);
    //    //this->swap(tmp);
    //    swap(tmp);
    //  }
    //  return *this;
    //}
    // s2 = s3
    string& operator=(string tmp)
    {
      swap(tmp);
      return *this;
    }
    ~string()
    {
      delete[] _str;
      _str = nullptr;
      _size = _capacity = 0;
    }
    char& operator[](size_t pos)
    {
      assert(pos < _size);
      return _str[pos];
    }
    const char& operator[](size_t pos) const
    {
      assert(pos < _size);
      return _str[pos];
    }
    size_t capacity() const
    {
      return _capacity;
    }
    size_t size() const
    {
      return _size;
    }
    const char* c_str() const
    {
      return _str;
    }
    void reserve(size_t 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')
    {
      if (n <= _size)
      {
        _str[n] = '\0';
        _size = n;
      }
      else
      {
        reserve(n);
        while (_size < n)
        {
          _str[_size] = ch;
          ++_size;
        }
        _str[_size] = '\0';
      }
    }
    size_t find(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* sub, size_t pos = 0)
    {
      const char* p = strstr(_str + pos, sub);
      if (p)
      {
        return p - _str;
      }
      else
      {
        return npos;
      }
    }
    string substr(size_t pos, size_t len = npos)
    {
      string s;
      size_t end = pos + len;
      if (len == npos || pos + len >= _size) // 有多少取多少
      {
        len = _size - pos;
        end = _size;
      }
      s.reserve(len);
      for (size_t i = pos; i < end; i++)
      {
        s += _str[i];
      }
      return s;
    }
    void push_back(char ch)
    {
      if (_size == _capacity)
      {
        reserve(_capacity == 0 ? 4 : _capacity * 2);
      }
      _str[_size] = ch;
      ++_size;
      _str[_size] = '\0';
    }
    void append(const char* str)
    {
      size_t len = strlen(str);
      if (_size + len > _capacity)
      {
        reserve(_size + len);
      }
      strcpy(_str + _size, str);
      _size += len;
    }
    string& operator+=(char ch)
    {
      push_back(ch);
      return *this;
    }
    string& operator+=(const char* str)
    {
      append(str);
      return *this;
    }
    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++;
    }
    void insert(size_t pos, const char* str)
    {
      assert(pos <= _size);
      size_t len = strlen(str);
      if (_size + len > _capacity)
      {
        reserve(_size + len);
      }
      // 挪动数据
      int end = _size;
      while (end >= (int)pos)
      {
        _str[end + len] = _str[end];
        --end;
      }
      strncpy(_str + pos, str, len);
      _size += len;
    }
    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);
    }
    void clear()
    {
      _str[0] = '\0';
      _size = 0;
    }
  private:
    char* _str;
    size_t _size;
    size_t _capacity;
    //const static size_t npos = -1;  // 特例
    //const static double npos = 1.1;  // 不支持
  public:
    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();
    //s.reserve(128);
    char buff[129];
    size_t i = 0;
    char ch;
    ch = in.get();
    while (ch != ' ' && ch != '\n')
    {
      buff[i++] = ch;
      if (i == 128)
      {
        buff[i] = '\0';
        s += buff;
        i = 0;
      }
      //s += ch;
      ch = in.get();
    }
    if (i != 0)
    {
      buff[i] = '\0';
      s += buff;
    }
    return in;
  }
}

感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o! 

相关文章
|
23天前
|
编译器 C语言 C++
配置C++的学习环境
【10月更文挑战第18天】如果想要学习C++语言,那就需要配置必要的环境和相关的软件,才可以帮助自己更好的掌握语法知识。 一、本地环境设置 如果您想要设置 C++ 语言环境,您需要确保电脑上有以下两款可用的软件,文本编辑器和 C++ 编译器。 二、文本编辑器 通过编辑器创建的文件通常称为源文件,源文件包含程序源代码。 C++ 程序的源文件通常使用扩展名 .cpp、.cp 或 .c。 在开始编程之前,请确保您有一个文本编辑器,且有足够的经验来编写一个计算机程序,然后把它保存在一个文件中,编译并执行它。 Visual Studio Code:虽然它是一个通用的文本编辑器,但它有很多插
|
1月前
|
C++ 容器
|
1月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
21 1
|
1月前
|
C++ 容器
|
1月前
|
C++ 容器
|
1月前
|
存储 C++ 容器
|
1月前
|
安全 C语言 C++
【C++篇】探寻C++ STL之美:从string类的基础到高级操作的全面解析
【C++篇】探寻C++ STL之美:从string类的基础到高级操作的全面解析
34 4
|
1月前
|
Java 编译器 C++
c++学习,和友元函数
本文讨论了C++中的友元函数、继承规则、运算符重载以及内存管理的重要性,并提到了指针在C++中的强大功能和使用时需要注意的问题。
19 1
|
1月前
|
存储 编译器 程序员
【C++篇】手撕 C++ string 类:从零实现到深入剖析的模拟之路
【C++篇】手撕 C++ string 类:从零实现到深入剖析的模拟之路
63 2
|
1月前
|
编译器 C语言 C++
【C++】C++ STL 探索:String的使用与理解(三)
【C++】C++ STL 探索:String的使用与理解