【C++STL精讲】string的模拟实现

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

0000000000000000000000000000000000000000000000000000000.png

目录


定义string类

构造函数

拷贝构造函数

赋值重载

析构函数

[]操作符重载

比较运算符重载

c_str、size、capacity

resize与reserve

添加数据 - push_back、append、+=重载

insert

erase

find

swap

clean

迭代器

>> 与 << 重载

完整源码


文章导读


本章我们将参照STL源码,来模拟实现string类,但不一定非要与库中完全相同。我们将其中重要的、常用的接口进行模拟实现,旨在加深string类的学习与记忆。


正文


定义string类


为了区别于标准库中的string类,我们使用自己的命名空间,在自己命名空间中模拟实现string类。

string类包含这三个基本成员

  • char* _str 字符数组;
  • size_t _size 大小;
  • size_t _capacity 容量;

此外还需声明一个static成员nposnpos为将来实现的某些成员函数的缺省值,值位-1

namespace hxy
{
  class string
  {
  public:
    //...
  private:
    char* _str;
    size_t _size;
    size_t _capacity;
    //类中进行声明
    static const size_t npos;
  }
  //类外定义npos
  const size_t string::npos = -1;
}


构造函数


string类提供两种构造方法:

  • 直接构造
string str;
• 1
  • 传字符串构造
string str("hello world");
  • 对于传字符串构造的情况,我们可以实现用const char* str 来接收它;
  • 对于直接构造的情况,我们可以用缺省值来解决;
  //构造函数
  string(const char* str = "")  //使用缺省值
    :_size(strlen(str))
  {
    _capacity = _size == 0 ? 3 : _size;  //_capacity初始值不能为0
    _str = new char[_capacity + 1];  //为'\0'预留位置
    strcpy(_str, str);
  }

注意

  • _capacity的值不能初始化为0,因为扩容时可能出现0*n=0的情况。

拷贝构造函数


我们还会遇到用string类对象进行构造的情况:

string s1;
  string s2(s1);

拷贝构造虽然不写,编译器会自动实现,但是自动实现的拷贝构造为浅拷贝,对于string类中,成员变量会申请资源的情况,浅拷贝是行不通的,所以需要我们自己实现。

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


赋值重载


  string s1;
  string s2 = s1;
  //赋值重载
  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()
  {
    delete[] _str;
    _str = nullptr;
    _size = _capacity = 0;
  }


[]操作符重载


注意应对const对象与非const对象须实现不同的重载函数。

  //用于const对象只读
  const char& operator[](size_t pos) const
  {
    assert(pos < _size);
    return _str[pos];
  }
  //用于普通对象可读可写
  char& operator[](size_t pos)
  {
    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);
  }


c_str、size、capacity


  • c_str:返回C风格的字符串。
  • size:返回_size;
  • capacity:返回_capacity;
  const char* c_str()
  {
    return _str;
  }
  size_t size() const
  {
    return _size;
  }
  size_t capacity() const
  {
    return _capacity;
  }


resize与reserve


  • resize:扩容并初始化;
  • reserve:只扩容;
  void resize(size_t n, char ch = '\0')
  {
    if (n < _size)
    {
      _str[n] = '\0';
      _size = n;
    }
    else if (n > _size)
    {
      if (n > _capacity)
      {
        reserve(n);
      }
      size_t i = _size;
      while (i < n)
      {
        _str[i] = ch;
        ++i;
      }
      _size = n;
      _str[_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;
    }
  }


添加数据 - push_back、append、+=重载


  • push_back:尾插一个字符;
  • append:尾插一个字符串;
  • +=:尾插一个字符或字符串;
  void push_back(char ch)
  {
    //考虑扩容
    if (_size + 1 > _capacity)
    {
      reserve(_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;
  }


insert


  • insert:在pos位置插入一个字符或字符串;
  string& insert(size_t pos, char ch)
  {
    assert(pos <= _size);
    if (_size + 1 > _capacity)
    {
      reserve(_capacity * 2);
    }
    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;
  }


erase


  • erase:删除pos位置向后的n个字符;
  string& erase(size_t pos, size_t len = npos)
  {
    assert(pos < _size);
    if (len == npos || pos + len >= _size)
    {
      _str[pos] = '\0';
      _size = pos;
    }
    else
    {
      strcpy(_str + pos, _str + pos + len);
      _size -= len;
    }
    return *this;
  }


find


  • find:从pos位置开始向后查找指定字符或字符串,并返回起始位置的下标。
  size_t find(char ch, size_t pos = npos)
  {
    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 = npos)
  {
    assert(pos < _size);
    char* p = strstr(_str, str);
    if (p == nullptr)
    {
      return npos;
    }
    else
    {
      return p - str;
    }
  }


swap


  • swap:交换两个对象的内容;
  void swap(string& s)
  {
    std::swap(_str, s._str);
    std::swap(_size, s._size);
    std::swap(_capacity, s._capacity);
  }


clean


  • clean:清理数据;
  void clean()
  {
    _str[0] = '\0';
    _size = 0;
  }


迭代器


有了迭代器,我们就能对自己实现的类使用范围for了。

  //迭代器
  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;
  }


>> 与 << 重载


注意这两个函数须定义在类外

    ostream& operator<<(ostream& out, const string& str)
  {
    for (auto ch : str)
    {
      out << ch;
    }
    return out;
  }
  istream& operator>>(istream& in, string& str)
  {
    str.clean();
    char ch = in.get();
    char buff[128]; //避免因频繁扩容导致效率过低
    size_t i = 0;
    while (ch != ' ' && ch != '\n')
    {
      buff[i++] = ch;
      if (i == 127)
      {
        buff[127] = '\0';
        str += buff;
        i = 0;
      }
      ch = in.get();
    }
    if (i != 0)
    {
      buff[i] = '\0';
      str += buff;
    }
    return in;
  }


完整源码


#pragma once
#include<assert.h>
#include<iostream>
namespace hxy
{
  class string
  {
  public:
    //迭代器
    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;
    }
    //构造函数
    string(const char* str = "")  //使用缺省值
      :_size(strlen(str))
    {
      _capacity = _size == 0 ? 3 : _size;  //_capacity初始值不能为0
      _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& 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()
    {
      delete[] _str;
      _str = nullptr;
      _size = _capacity = 0;
    }
    const char* c_str()
    {
      return _str;
    }
    //用于const对象只读
    const char& operator[](size_t pos) const
    {
      assert(pos < _size);
      return _str[pos];
    }
    //用于普通对象可读可写
    char& operator[](size_t pos)
    {
      assert(pos < _size);
      return _str[pos];
    }
    size_t size() const
    {
      return _size;
    }
    size_t capacity() const
    {
      return _capacity;
    }
    //比较运算符重载
    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 resize(size_t n, char ch = '\0')
    {
      if (n < _size)
      {
        _str[n] = '\0';
        _size = n;
      }
      else if (n > _size)
      {
        if (n > _capacity)
        {
          reserve(n);
        }
        size_t i = _size;
        while (i < n)
        {
          _str[i] = ch;
          ++i;
        }
        _size = n;
        _str[_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 push_back(char ch)
    {
      //考虑扩容
      if (_size + 1 > _capacity)
      {
        reserve(_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;
    }
    string& insert(size_t pos, char ch)
    {
      assert(pos <= _size);
      if (_size + 1 > _capacity)
      {
        reserve(_capacity * 2);
      }
      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(size_t pos, size_t len = npos)
    {
      assert(pos < _size);
      if (len == npos || pos + len >= _size)
      {
        _str[pos] = '\0';
        _size = pos;
      }
      else
      {
        strcpy(_str + pos, _str + pos + len);
        _size -= len;
      }
      return *this;
    }
    void swap(string& s)
    {
      std::swap(_str, s._str);
      std::swap(_size, s._size);
      std::swap(_capacity, s._capacity);
    }
    size_t find(char ch, size_t pos = npos)
    {
      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 = npos)
    {
      assert(pos < _size);
      char* p = strstr(_str, str);
      if (p == nullptr)
      {
        return npos;
      }
      else
      {
        return p - str;
      }
    }
    void clean()
    {
      _str[0] = '\0';
      _size = 0;
    }
  private:
    char* _str;
    size_t _size;
    size_t _capacity;
    static const size_t npos;
  };
  //定义npos
  const size_t string::npos = -1;
  ostream& operator<<(ostream& out, const string& str)
  {
    for (auto ch : str)
    {
      out << ch;
    }
    return out;
  }
  istream& operator>>(istream& in, string& str)
  {
    str.clean();
    char ch = in.get();
    char buff[128];
    size_t i = 0;
    while (ch != ' ' && ch != '\n')
    {
      buff[i++] = ch;
      if (i == 127)
      {
        buff[127] = '\0';
        str += buff;
        i = 0;
      }
      ch = in.get();
    }
    if (i != 0)
    {
      buff[i] = '\0';
      str += buff;
    }
    return in;
  }
}
目录
相关文章
|
4天前
|
编译器 C语言 C++
【c++丨STL】list模拟实现(附源码)
本文介绍了如何模拟实现C++中的`list`容器。`list`底层采用双向带头循环链表结构,相较于`vector`和`string`更为复杂。文章首先回顾了`list`的基本结构和常用接口,然后详细讲解了节点、迭代器及容器的实现过程。 最终,通过这些步骤,我们成功模拟实现了`list`容器的功能。文章最后提供了完整的代码实现,并简要总结了实现过程中的关键点。 如果你对双向链表或`list`的底层实现感兴趣,建议先掌握相关基础知识后再阅读本文,以便更好地理解内容。
14 1
|
17天前
|
算法 C语言 C++
【c++丨STL】list的使用
本文介绍了STL容器`list`的使用方法及其主要功能。`list`是一种双向链表结构,适用于频繁的插入和删除操作。文章详细讲解了`list`的构造函数、析构函数、赋值重载、迭代器、容量接口、元素访问接口、增删查改操作以及一些特有的操作接口如`splice`、`remove_if`、`unique`、`merge`、`sort`和`reverse`。通过示例代码,读者可以更好地理解如何使用这些接口。最后,作者总结了`list`的特点和适用场景,并预告了后续关于`list`模拟实现的文章。
33 7
|
2月前
|
存储 编译器 C语言
【c++丨STL】vector的使用
本文介绍了C++ STL中的`vector`容器,包括其基本概念、主要接口及其使用方法。`vector`是一种动态数组,能够根据需要自动调整大小,提供了丰富的操作接口,如增删查改等。文章详细解释了`vector`的构造函数、赋值运算符、容量接口、迭代器接口、元素访问接口以及一些常用的增删操作函数。最后,还展示了如何使用`vector`创建字符串数组,体现了`vector`在实际编程中的灵活性和实用性。
64 4
|
2月前
|
C语言 C++ 容器
【c++丨STL】string模拟实现(附源码)
本文详细介绍了如何模拟实现C++ STL中的`string`类,包括其构造函数、拷贝构造、赋值重载、析构函数等基本功能,以及字符串的插入、删除、查找、比较等操作。文章还展示了如何实现输入输出流操作符,使自定义的`string`类能够方便地与`cin`和`cout`配合使用。通过这些实现,读者不仅能加深对`string`类的理解,还能提升对C++编程技巧的掌握。
77 5
|
2月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
60 2
|
2月前
|
存储 算法 Linux
【c++】STL简介
本文介绍了C++标准模板库(STL)的基本概念、组成部分及学习方法,强调了STL在提高编程效率和代码复用性方面的重要性。文章详细解析了STL的六大组件:容器、算法、迭代器、仿函数、配接器和空间配置器,并提出了学习STL的三个层次,旨在帮助读者深入理解和掌握STL。
55 0
|
20天前
|
存储 编译器 C语言
【c++丨STL】vector模拟实现
本文深入探讨了 `vector` 的底层实现原理,并尝试模拟实现其结构及常用接口。首先介绍了 `vector` 的底层是动态顺序表,使用三个迭代器(指针)来维护数组,分别为 `start`、`finish` 和 `end_of_storage`。接着详细讲解了如何实现 `vector` 的各种构造函数、析构函数、容量接口、迭代器接口、插入和删除操作等。最后提供了完整的模拟实现代码,帮助读者更好地理解和掌握 `vector` 的实现细节。
30 0
|
2月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
110 5
|
2月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
109 4
|
2月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
140 4