C++:String的模拟实现

简介: C++:String的模拟实现

     

    模拟实现的节奏比较快,大家可以先去看看博主的关于string的使用,然后再来看这里的模拟实现过程

C++:String类的使用-CSDN博客

     String模拟实现大致框架迭代器以及迭代器的获取(public定义,要有可读可写的也要有可读不可写的)/成员变量(private定义)  并且为了不和库的string冲突,我们需要自己搞一个命名空间

namespace cyx
{
  class string
  {
  public:
    //迭代器的实现(可读可写)
    typedef char* iterator;
    iterator begin()
    {
      return _str;
    }
    iterator end()
    {
      return _str + _size;
    }
    //迭代器的实现(可读不可写)
    typedef const char* const_iterator;
    const_iterator begin() const
    {
      return _str;
    }
    const_iterator end() const
    {
      return _str + _size;
    }
  private:
    char* _str;
    size_t _size;
    size_t _capacity;
    static const size_t npos;
    //static const size_t npos=-1  vs下int类型支持给static和const同时修饰的变量用缺省值
  };
   const size_t string::npos = -1;//静态成员遍历类外初始化
}

     nops是一个静态变量,要类内定义类外初始化,由于nops是size_t类型,赋值-1会被强转成最大的无符号整数

一、构造+析构+赋值重载(Member functions)

1.1 全缺省构造函数

//构造函数
string(const char* str = "")
  :_size(strlen(str))
  {
    _capacity = _size == 0 ? 3 : _size;
    _str = new char[_capacity + 1];//   多开一块\0的空间
    strcpy(_str, str);
  }

1、 “ ”空字符串其实里面默认就有\0,所以缺省值直接给空字符串就行

2、_capacity 一定要给初始空间,不然后面如果涉及到2倍扩容,为0的话就扩不了了

3、要多开一块空间,连这个\0

4、可以复用strcpy函数

1.2 拷贝构造函数的传统写法

    传统的思路就是拷贝,也就是我们先根据被拷贝的对象的_capacity开空间,然后再进行拷贝

string(const string& s)
    :_size(s._size)
    , _capacity(s._capacity)
  {
    _str = new char[_capacity + 1];
    strcpy(_str, s._str);
  }

1.3 拷贝构造函数的现代写法和swap函数

      现代的思路就是,尝试去复用,比如说我们可不可以直接去利用前面的构造函数去构造一个新对象,然后再窃取新对象的成果(利用swap)

//交换字符串
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)
    {
      string temp(s._str);
      swap(temp);
    }

传统写法和现代写法参数一样,不能重载,只能保留一个

1. 4 迭代器区间构造

//迭代器区间构造
    template <class InputIterator>
    string(InputIterator first, InputIterator last)
      :_str(new char[last-first+1])
      ,_size(0)
      ,_capacity(last-first)
    {
      while (first != last)
      {
        push_back (*first);
        ++first;
      }
    }

      这里定义的模版InputIterator的意思其实是这边我们可以传不同类型对象的迭代器,我们并不知道这个迭代器里面有多少元素,所以得用指针-指针,即last-first来确定我们的容量,然后再开空间,一个个进行尾插。

1.5 赋值重载的传统写法

      传统的思路就是,先开一块新空间拷贝旧数据,然后再释放掉原空间,这里尽量是先开空间再释放,避免我们开空间失败导致原始数据的丢失。  

//赋值重载(传统写法)
    string& operator=(const string& s)
    {
      if (this != &s)//避免自赋值
      {
        //先开新空间再毁旧空间,避免新空间开失败导致数据丢失
        char* temp = new char[s._capacity + 1];
        strcpy(temp, s._str);
        delete[]_str;
        _str = temp;
        _size = s._size;
        _capacity = s._capacity;
      }
      return *this;
    }

注意:要注意自赋值情况!!否则刚拷贝完自己就被释放了

1.6 赋值重载的现代写法

     现代的思路就是,既然被赋值这个空间不想要,那就和形参直接交换吧!!但是要注意的是,这里就不能像传统的一样用const引用了,否则不想要的空间就给到我们的赋值对象了,这边就得用传值传参,这样被交换的就只是一个临时拷贝,不想要的空间随着栈帧的结束被销毁。

//赋值重载(现代写法)
string& operator=(string s)//必须用值传递,否则会导致原数据的丢失
{
  swap(s);
  return *this;
}

 传统写法和现代写法参数不一样,一个是const引用,一个传值传参,所以可以同时存在。

1.7 析构函数

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

二、容量相关的接口(Capacity)

2.1 size、capacity

//获取当前size
  size_t size() const
  {
    return _size;
  }
  //获取当前capacity
  size_t capacity() const
  {
    return _capacity;
  }

2.2 reserve

如果n比_capacity大,思路就是先开新空间进行拷贝,然后再释放旧空间

//改变capacity
  void reserve(size_t n)
  {
    if (n > _capacity)
    {
      char* tmp = new char[n + 1];
      strcpy(tmp, _str);
      delete[] _str;
      _str = tmp;
      _capacity = n;
    }
  }

2.3 resize

如果n比_size大,我们根据n去扩容,然后因为string的底层是字符数组,所以memset就很适合,他就是可以去一个字节一个字节设置成我们想要的。缺省值给‘\0’

//改变size
void resize(size_t n, char ch = '\0')
{
  if (n > _size)
  {
    reserve(n);
    memset(_str + _size, ch, n - _size);
  }
  _size = n;
  _str[_size] = '\0';
}

2.4 clear和empty

//清理字符串
void clear()
{
  _str[0] = '\0';
  _size = 0;
}
//判断字符串是否为空
bool empty()
{
  return _capacity == 0;
}

2.5 shrink_to_fit(用得少)

缩容到size位置,平时用的很少,我们要尽量减少扩容,思路也是一样的,开辟新空间去拷贝,再释放旧空间

void shrink_to_fit()
  {
    char* temp = new char[_size + 1];
    strcpy(temp, _str);
    delete[] _str;
    _str = temp;
    _capacity = _size;
  }

三、[ ]和比较运算符重载

//[]重载(可读可写)
char& operator[](size_t pos)
{
  assert(pos < _size);//确保地址有效
  return _str[pos];
}
//[]重载(可读不可写)
const char& operator[](size_t pos) const
{
  assert(pos < _size);//确保地址有效
  return _str[pos];
}
//比较类型重载
//>
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);
}

有了[ ]、迭代器,我们可以展示3种遍历方法:下标访问、迭代器区间访问、范围for访问

void Print(const string& s)
  {
    //下标遍历
    for (size_t i = 0; i < s.size(); ++i)
      cout << s[i] << " ";
    cout << endl;
    //迭代器遍历访问
    string::const_iterator it = s.begin();
    while (it != s.end())
    {
      cout << *it << " ";
      ++it;
    }
    cout << endl;
    //范围for
    for (auto &e : s)
      cout << e << " ";
    cout << endl;
  }

四、增删接口(Modifiers)

      字符串的增删接口一般要设置两个版本,一个是操作字符,一个是操作字符串,我们先把最难的insert和erase搞了,其他的就可以复用了

4.1 insert

//指定位置插入一个字符
  string& insert(size_t pos, char ch)
  {
    assert(pos <= _size);
    //判断是否需要扩容
    if (_size + 1 > _capacity)
      reserve(2 * _capacity);
    //pos后的数据要往后挪,所以要从后往前移
    size_t end = _size + 1;
    while (end > pos)
    {
      _str[end] = _str[end - 1];
      --end;
    }
    _str[pos] = ch;
    ++_size;
    return *this;
  }
  //指定位置插入一个字符串
  string& 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 + len;
    while (end > pos + len - 1)
    {
      _str[end] = _str[end - len];
      --end;
    }
  //拷贝插入
    strncpy(_str + pos, str, len);
    _size += len;
    return *this;
  }

4.2 erase

//删除指定位置之后的字符
string& erase(const size_t pos, size_t len = npos)
{
  assert(pos <= _size);
  //有两种情况,一种是全删完,一种是删中间的一部分
  //全删完
  if (len == npos || pos + len > _size)//len == npos必须写,因为nops是无符号最大值,+的话会溢出
  {
    _str[pos] = '\0';
    _size = pos;
  }
  //删一部分
  else
  {
    strcpy(_str + pos, _str + pos + len);
    _size -= len;
  }
  return *this;
}

len == npos这个判断条件必须写,因为nops已经是无符号最大值了,再+会溢出

4.3 push_back

//尾插一个字符
  void push_back(char ch)
  {
    insert(_size, ch);
  }

4.4 append

//尾插一个字符串
string& append(const char* str)
{
  return insert(_size, str);
}

4.5 +=重载(用的多)

//+=重载 字符
  string& operator+=(char ch)
  {
    push_back(ch);
    return *this;
  }
  //+=重载 字符串
  string& operator+=(const char* str)
  {
    append(str);
    return *this;
  }

五、字符串操作(String operations

5.1 获取c类型字符串

//获取c类型字符串
const char* c_str() const
{
  return _str;
}

5.2 寻找指定字符串并返回下标

//寻找指定字符串并返回下标
  size_t find(const char* str, size_t pos = 0)const
  {
    assert(pos < _size);
    char* p = strstr(_str + pos, str);
    if (p == nullptr)
      return npos;
    return p - _str;
  }

六、重载流插入和流提取

我们不能写在类内,否则会*this会占用第一个操作数,不符合我们的使用习惯。

6.1 流提取

//重载<<
std::ostream& operator<< (std::ostream& out, const string& s)
{
  //可以用范围for,也可以用迭代器   范围for是用宏写的,本质上也是迭代器!
  for (auto ch : s)
    out << ch;
  return out;
}

6.2 流插入

首先我们要知道两点,1.>>只会读取到空格或者换行结束 2.读取前会清理掉原空间的数据

//重载>>
  std::istream& operator>> (std::istream& in, string& s)
  {
    //读取前要先清理掉原来存在的字符
    s.clear();
    //用get获取字符
    char ch = in.get();
    //先用一个数组存起来,再一起加
    char buff[128];
    size_t i = 0;
    while (ch != ' ' && ch != '\n')
    {
      //原始方法,一个字符一个字符加太麻烦,先用一个数组存起来,再一起加
      //s += ch;
      buff[i++] = ch;
      if (i == 127)
      {
        buff[127] = '\0';
        s += buff;
        i = 0;//重置i
      }
      ch = in.get();
    }
    //循环结束后可能还要一些字母没有存进去
    if (i != 0)
    {
      buff[i] = '\0';
      s += buff;
    }
    return in;
  }

我们用一个buff数组来暂时存储需要插入的字符,等存完了再+=,这样可以提高效率,以空间换时间

七、string模拟实现全部代码

namespace cyx
{
  using std::endl;
  using std::cout;
  class string
  {
  public:
    //迭代器的实现(可读可写)
    typedef char* iterator;
    iterator begin()
    {
      return _str;
    }
    iterator end()
    {
      return _str + _size;
    }
    //迭代器的实现(可读不可写)
    typedef const char* const_iterator;
    const_iterator begin() const
    {
      return _str;
    }
    const_iterator end() const
    {
      return _str + _size;
    }
    //交换字符串
    void swap(string& s)
    {
      std::swap(_str, s._str);//浅拷贝,没有开空间,只是改变指针指向
      std::swap(_size, s._size);
      std::swap(_capacity, s._capacity);
    }
    //构造函数
    string(const char* str = "")
      :_size(strlen(str))
      {
        _capacity = _size == 0 ? 3 : _size;
        _str = new char[_capacity + 1];//   多开一块\0的空间
        strcpy(_str, str);
      }
    //拷贝构造函数(传统写法)
    /*string(const string& s)
      :_size(s._size)
      , _capacity(s._capacity)
    {
      _str = new char[_capacity + 1];
      strcpy(_str, s._str);
    }*/
        //拷贝函数的现代写法
    string(const string& s)
      :_str(nullptr)
    {
      string temp(s._str);
      swap(temp);
    }
    //迭代器区间构造
    template <class InputIterator>
    string(InputIterator first, InputIterator last)
      :_str(new char[last-first+1])
      ,_size(0)
      ,_capacity(last-first)
    {
      while (first != last)
      {
        push_back (*first);
        ++first;
      }
    }
    //赋值重载(传统写法)
    string& operator=(const string& s)
    {
      if (this != &s)//避免自赋值
      {
        //先开新空间再毁旧空间,避免新空间开失败导致数据丢失
        char* temp = new char[s._capacity + 1];
        strcpy(temp, s._str);
        delete[]_str;
        _str = temp;
        _size = s._size;
        _capacity = s._capacity;
      }
      return *this;
    }
    //赋值重载(现代写法)
    string& operator=(string s)//必须用值传递,否则会导致原数据的丢失
    {
      swap(s);
      return *this;
    }
    //析构函数
    ~string()
    {
      delete[]_str;
      _str = nullptr;
      _size = _capacity = 0;
    }
    //获取c类型字符串
    const char* c_str() const
    {
      return _str;
    }
    //获取当前size
    size_t size() const
    {
      return _size;
    }
    //获取当前capacity
    size_t capacity() const
    {
      return _capacity;
    }
    //[]重载(可读可写)
    char& operator[](size_t pos)
    {
      assert(pos < _size);//确保地址有效
      return _str[pos];
    }
    //[]重载(可读不可写)
    const char& operator[](size_t pos) const
    {
      assert(pos < _size);//确保地址有效
      return _str[pos];
    }
    //比较类型重载
    //>
    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);
    }
    //改变size
    void resize(size_t n, char ch = '\0')
    {
      if (n > _size)
      {
        reserve(n);
        memset(_str + _size, ch, n - _size);
      }
      _size = n;
      _str[_size] = '\0';
    }
    //改变capacity
    void reserve(size_t n)
    {
      if (n > _capacity)
      {
        char* tmp = new char[n + 1];
        strcpy(tmp, _str);
        delete[] _str;
        _str = tmp;
        _capacity = n;
      }
    }
    //尾插一个字符
    void push_back(char ch)
    {
      insert(_size, ch);
    }
    //尾插一个字符串
    string& append(const char* str)
      {
      return insert(_size, str);
    }
    //+=重载 字符
    string& operator+=(char ch)
    {
      push_back(ch);
      return *this;
    }
    //+=重载 字符串
    string& operator+=(const char* str)
    {
      append(str);
      return *this;
    }
    //指定位置插入一个字符
    string& insert(size_t pos, char ch)
    {
      assert(pos <= _size);
      //判断是否需要扩容
      if (_size + 1 > _capacity)
        reserve(2 * _capacity);
      //pos后的数据要往后挪,所以要从后往前移
      size_t end = _size + 1;
      while (end > pos)
      {
        _str[end] = _str[end - 1];
        --end;
      }
      _str[pos] = ch;
      ++_size;
      return *this;
    }
    //指定位置插入一个字符串
    string& 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 + len;
      while (end > pos + len - 1)
      {
        _str[end] = _str[end - len];
        --end;
      }
    //拷贝插入
      strncpy(_str + pos, str, len);
      _size += len;
      return *this;
    }
    //删除指定位置之后的字符
    string& erase(const size_t pos, size_t len = npos)
    {
      assert(pos <= _size);
      //有两种情况,一种是全删完,一种是删中间的一部分
      //全删完
      if (len == npos || pos + len > _size)//len == npos必须写,因为nops是无符号最大值,+的话会溢出
      {
        _str[pos] = '\0';
        _size = pos;
      }
      //删一部分
      else
      {
        strcpy(_str + pos, _str + pos + len);
        _size -= len;
      }
      return *this;
    }
    //寻找指定字符并返回下标
    size_t find(char ch, size_t pos = 0)const
    {
      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)const
    {
      assert(pos < _size);
      char* p = strstr(_str + pos, str);
      if (p == nullptr)
        return npos;
      return p - _str;
    }
    //清理字符串
    void clear()
    {
      _str[0] = '\0';
      _size = 0;
    }
    //缩容到他的size
    void shrink_to_fit()
    {
      char* temp = new char[_size + 1];
      strcpy(temp, _str);
      delete[] _str;
      _str = temp;
      _capacity = _size;
    }
    //判断字符串是否为空
    bool empty()
    {
      return _capacity == 0;
    }
  private:
    char* _str;
    size_t _size;
    size_t _capacity;
    static const size_t npos;
    //static const size_t npos=-1       vs下int类型支持给static和const同时修饰的变量用缺省值
  };
   const size_t string::npos = -1;//静态成员遍历类外初始化
  //重载<<
  std::ostream& operator<< (std::ostream& out, const string& s)
  {
    //可以用范围for,也可以用迭代器   范围for是用宏写的,本质上也是迭代器!
    for (auto ch : s)
      out << ch;
    return out;
  }
  //重载>>
  std::istream& operator>> (std::istream& in, string& s)
  {
    //读取前要先清理掉原来存在的字符
    s.clear();
    //用get获取字符
    char ch = in.get();
    //先用一个数组存起来,再一起加
    char buff[128];
    size_t i = 0;
    while (ch != ' ' && ch != '\n')
    {
      //原始方法,一个字符一个字符加太麻烦,先用一个数组存起来,再一起加
      //s += ch;
      buff[i++] = ch;
      if (i == 127)
      {
        buff[127] = '\0';
        s += buff;
        i = 0;//重置i
      }
      ch = in.get();
    }
    //循环结束后可能还要一些字母没有存进去
    if (i != 0)
    {
      buff[i] = '\0';
      s += buff;
    }
    return in;
  }
  //遍历方法的展示
  void Print(const string& s)
  {
    //下标遍历
    for (size_t i = 0; i < s.size(); ++i)
      cout << s[i] << " ";
    cout << endl;
    //迭代器遍历访问
    string::const_iterator it = s.begin();
    while (it != s.end())
    {
      cout << *it << " ";
      ++it;
    }
    cout << endl;
    //范围for
    for (auto &e : s)
      cout << e << " ";
    cout << endl;
  }

有新的后面再补充哦!

相关文章
|
20天前
|
C语言 C++ 容器
【c++丨STL】string模拟实现(附源码)
本文详细介绍了如何模拟实现C++ STL中的`string`类,包括其构造函数、拷贝构造、赋值重载、析构函数等基本功能,以及字符串的插入、删除、查找、比较等操作。文章还展示了如何实现输入输出流操作符,使自定义的`string`类能够方便地与`cin`和`cout`配合使用。通过这些实现,读者不仅能加深对`string`类的理解,还能提升对C++编程技巧的掌握。
45 5
|
20天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
30 2
|
2月前
|
C++ 容器
|
2月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
25 1
|
2月前
|
C++ 容器
|
2月前
|
C++ 容器
|
2月前
|
存储 C++ 容器
|
2月前
|
安全 C语言 C++
【C++篇】探寻C++ STL之美:从string类的基础到高级操作的全面解析
【C++篇】探寻C++ STL之美:从string类的基础到高级操作的全面解析
40 4
|
2月前
|
存储 编译器 程序员
【C++篇】手撕 C++ string 类:从零实现到深入剖析的模拟之路
【C++篇】手撕 C++ string 类:从零实现到深入剖析的模拟之路
73 2
|
2月前
|
编译器 C语言 C++
【C++】C++ STL 探索:String的使用与理解(三)
【C++】C++ STL 探索:String的使用与理解