string的模拟实现

简介: string的模拟实现



一、string类

1、 string是表示字符串的字符串类。

2、 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。

3、 string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator>string。

4、 不能操作多字节或者变长字符的序列。在使用string类时,必须包含#include<string>头文件以及using namespace std。

那么接下来我们就来模仿标准库里的string类来进行一个模拟实现,进而更深层次地理解string容器。 (为了与标准库区分,我们在自己的命名空间里来实现)

首先根据源码,我们可以知道string中的主义成员变量我们可以写成 char* _str(字符数组),size_t  _size(数据个数),size_t  _capacity(容量大小)。


二、构造函数、拷贝构造函数及析构函数

1、构造函数

string的构造函数分为无参构造和有参构造,通过无参构造的对象会被默认生成一个空字符串,因此我们可以带一个缺省值。没有参数时就直接构造一空串。

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

注:在开空间时,我们需要多开一个空间,因为strlen算出的大小不包含 ‘\0’ ,因此我们需要给 ‘\0’留一个空间。

2、拷贝构造函数

拷贝构造函数是默认成员函数,如果不写编译器会自动生成,对于内置类型完成浅拷贝,对于自定义类型调用其拷贝构造函数完成拷贝。对于string类型来说,如果不自己写拷贝构造函数会导致浅拷贝问题。

如果仅仅依靠编译器自己提供的拷贝构造函数,就会像上图一样两个对象指向同一块空间。这就是我们所说的浅拷贝,浅拷贝有两大缺点:1、两个对象在析构时都会调用自己的析构函数,这样同一块空间就会被析构两次;2、一个对象的数据改变,另一个对象的数据也改变了。

因此我们自己实现一个拷贝构造函数尤为重要。我们需要进行深拷贝。

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

上面的这种实现方法是比较常见的一种写法,但是我们还有一种更加简便的写法。我们可以通过已经实现了的构造函数传一个常量字符串,即下面的 str . _str,来创建一个l临时对象,然后将这个临时对象的成员与自己交换,这样也完成了拷贝构造。

void swap(string& s)
{
  ::swap(_str, s._str);
  ::swap(_size, s._size);
  ::swap(_capacity, s._capacity);
}
string(const string& str)
  :_str(nullptr)
  ,_size(0)
  ,_capacity(0)
{
  string tmp(str._str);
    swap(tmp);
}

但是,为了写成这个拷贝构造函数我们还写了一个swap函数,这这么就简便了呢?

那是因为通过查阅标准库我们发现swap函数也是一个string类中提供了的函数,因此我们不仅简便了拷贝构造函数的写法,还又完成了一个函数的实现。并且,因为tmp是一个局部对象,因此在出作用域后就会自动调用析构函数,所以交换后还可以清理掉原来的空间,一举两得。

注:蓝色的swap函数是我们自己写的,而黄色的,前面写明了域的swap函数是C++库中的一个现成的函数。这两个一定要区别开来。

3、析构函数

我们可以使用delete直接释放掉 _str的空间。

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

三、string类中对容量操作的成员函数

1、size

它的功能是返回字符串有效字符的长度。我们可以直接返回其成员变量中的 _size。

size_t size()const
{
  return _size;
}

2、capacity

它的功能是返回返回空间总大小 。我们可以直接返回其成员变量中的 _capacity

size_t capacity()const
{
  return _capacity;
}

3、reserve

它的功能是为字符串预留空间

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

4、resize

它的功能是将有效字符的个数改成n个,多出的空间用字符 ch 填充。

_size < n <_capacity   先预留 n 大小的空间。直接用字符 ch 填充 n - _size 位置,记住要在最后加上 ‘\0’

_size > n  直接将  n 位置的置成 ’\0‘

n > _capacity   先预留 n 大小的空间,剩下的空间用字符 ch 填充,记住要在最后加上 ‘\0’。

void resize(size_t n, char ch = '\0')
{
  if (n > _size)
  { 
    reserve(n);
    for (size_t i = _size; i < n; i++)
    {
      _str[i] = ch;
    }
    _size = n;
    _str[_size] = '\0';
  }
  else
  {
      //删除数据
    _str[n] = '\0';
    _size = n;
  }
}

5、clear

它的作用是清空有效字符

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

6、empty

它的作用是检测字符串释放为空串,是返回true,否则返回false

bool empty()const
{
    return _size == 0;
}

四、string类中对象的增删查改操作

1、push_back

在字符串后尾插字符c

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

2、append

在字符串后追加一个字符串

void append(const char* str)
{
  size_t len = strlen(str);
  if (_capacity < len + _size)
  {
    reserve(len + _size);
  }
  strcpy(_str + _size, str);
  _size += len;
}

3、c_str

返回C格式字符串

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

补充:C语言字符串和 string类字符串的区别(下面我们来通过一段代码来看看他们的区别)

void test_string8()
{
  string s1("hello");
  s1 += '\0';
  s1 += "De Bruyne";
  cout << s1 << endl;
  cout << s1.c_str() << endl;
}

代码运行结果如下:

通过上面的运行结果我们可以看出C语言的字符串一定是通过 ’\0‘ 结束的;而string 的字符串中 ’\0‘ 不一定就是字符串结束的标志。

4、find

npos是string类的静态成员变量,静态成员变量要在类外定义的。我们一般将它设为公共权限,并赋值为 -1。

在字符串中寻找一个字符

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

在字符串中寻找字符串

size_t find(const char* sub, size_t pos = 0)const
{
  assert(pos < _size);
  const char* ptr = strstr(_str + pos, sub);
  if (ptr == nullptr)
  {
    return npos;
  }
  else
  {
    return ptr - _str;
  }
}

5、substr

它的作用是在str中从pos位置开始,截取n个字符,然后将其返回

string substr(size_t pos, size_t len = npos)const//取子串
{
  assert(pos < _size);
  size_t reallen = len;
  if (reallen == npos || reallen + pos > _size)
  {
    reallen = _size - pos;
  }
  string sub;
  for (size_t i = pos; i < reallen + pos; i++)
  {
    sub += _str[i];
  }
  return sub;
}

6、insert

插入一个字符

string& 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++;
  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 (pos + len <= end)
  {
    _str[end] = _str[end - len];
    end--;
  }
  strncpy(_str + pos, str, len);
  _size += len;
  return *this;
}

7、erase

void erase(size_t pos, size_t len = npos)
{
  assert(pos < _size);
  if (len == npos || len >= _size)
  {
    _str[pos] = '\0';
    _size = pos;
  }
  else
  {
    strcpy(_str + pos, _str + pos + len);
    _size -= len;
  }
}

五、string对于重要的运算符重载

1、赋值运算符的重载

编译器默认生成的赋值重载也会导致浅拷贝,所以我们需要实现深拷贝。

string& operator=(const string& str)
{
  if (this != &str)
  {
    char* tmp = new char[str._capacity + 1];
    strcpy(tmp, str._str);
    delete[] _str;
    _str = tmp;
    _size = str._size;
    _capacity = str._capacity;
  }
  return *this;
}

和拷贝构造函数一样,我们也可以用简便写法来实现赋值运算符的重载。(注意不要自己给自己赋值)。

string& operator=(const string& str)
{
  if (this != &str)
  {
      string tmp(str);
    swap(tmp);
  }
  return *this;
}

2、流插入 <<

类外定义

ostream& operator<<(ostream& out, const string& s)
{
  for (size_t i = 0; i < s.size(); i++)
  {
    out << s[i];
  }
  return out;
}

3、流提取 >>

istream& operator>>(istream& in, string& s)
{
  s.clear();
  char ch;
  ch = in.get();
  const size_t N = 32;
  char buff[N];
  size_t i = 0;
  while (ch != ' ' && ch != '\n')
  {
    buff[i++] = ch;
    if (i == N - 1)
    {
      buff[i] = '\0';
      s += buff;/*先将输入的字符存在buff数组中,然后一次性+=到s中,这样就避免了输入的
              字符串过大,使s频繁扩容的现象。*/
      i = 0;
    }
    ch = in.get();
  }
  buff[i] = '\0';
  s += buff;
  return in;
}

4、下标访问 [ ]

普通对象:可读可写

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

const对象:可读不可写

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

5、加等一个字符 +=

我们可以直接复用push_back来实现一个字符的加等

string& operator+=(char ch)
{
  push_back(ch);
  return *this;
}

6、加等字符串 +=

我们可以直接复用append来实现字符串的加等

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

7、大于 >

bool operator>(const string& s)const
{
  return strcmp(_str, s._str) > 0;
}

8、等于 ==

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

9、小于 <

bool operator<(const string& s)const
{
  return !(_str > s._str) && !(_str == s._str);
}

10、大于等于 >=

bool operator>=(const string& s)const
{
  return !(_str < s._str);
}

11、小于等于 <=

bool operator<=(const string& s)const
{
  return !(_str > s._str);
}

12、不等于 !=

bool operator!=(const string& s)const
{
  return !(_str == s._str);
}

六、迭代器与范围for

1、迭代器

迭代器作为STL的六大组件之一,它的作用十分重要。而在string中迭代器的本质就是一个 char* 或 const char* 的指针。

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

2、范围for

范围for是C++11支持的更简洁的新遍历方式。但是其实它没有什么高大上的,它的本质还是去调用迭代器来实现遍历的。因此我们只要实现了迭代器后,就可以直接使用范围for,根本不用自己去实现。

我们从下面的汇编代码的红色标记处可以看出范围for和迭代器的底层实现都是去call的begin()和end(),因此范围for的底层就是迭代器。

3、结果对比

void test_string9()
{
  string s("hello world");
  string::iterator it = s.begin();
  while (it != s.end())
  {
    cout << *it << " ";
    it++;
  }
  cout << endl;
  for (auto e : s)
  {
    cout << e << " ";
  }
    cout << endl;
}


七、完整代码

string.h

#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include<iostream>
using namespace std;
#include<cstring>
#include<cassert>
namespace zdl
{
  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;
    }
    void swap(string& s)
    {
      ::swap(_str, s._str);
      ::swap(_size, s._size);
      ::swap(_capacity, s._capacity);
    }
    //构造函数
    string(const char* str = "")
      :_str(new char[strlen(str) + 1])
      , _size(strlen(str))
      , _capacity(strlen(str))
    {
      strcpy(_str, str);
    }
    /*string()
      :_str(new char[1])
      , _size(0)
      , _capacity(0)
    {
      _str[0] = '\0';
    }*/
    //拷贝构造函数:要深拷贝
    string(const string& str)
      :_str(new char[str._capacity+1])
      ,_size(str._size)
      ,_capacity(str._capacity)
    {
      strcpy(_str, str._str);
    }
    /*简便写法 string(const string& str)
          :_str(nullptr)
          ,_size(0)
          ,_capacity(0)
        {
          string tmp(str._str);
          swap(tmp);
        }*/
    //赋值
    string& operator=(const string& str)
    {
      if (this != &str)
      {
        char* tmp = new char[str._capacity + 1];
        strcpy(tmp, str._str);
        delete[] _str;
        _str = tmp;
        _size = str._size;
        _capacity = str._capacity;
      }
      return *this;
    }
      /*简便写法  string& operator=(const string& str)
      {
        if (this != &str)
        {
          string tmp(str);
          swap(tmp);
        }
        return *this;
      }  */
    //析构函数
    ~string()
    {
      delete[] _str;
      _str = nullptr;
      _size = _capacity = 0;
    }
    const char* c_str()const
    {
      return _str;
    }
    size_t size()const
    {
      return _size;
    }
    size_t capacity()const
    {
      return _capacity;
    }
    char& operator[](size_t pos) const
    {
      assert(pos < _size);
      return _str[pos];
    }
    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 == _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 (_capacity < len + _size)
      {
        reserve(len + _size);
      }
      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 == _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++;
      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 (pos + len <= end)
      {
        _str[end] = _str[end - len];
        end--;
      }
      strncpy(_str + pos, str, len);
      _size += len;
      return *this;
    }
    void erase(size_t pos, size_t len = npos)
    {
      assert(pos < _size);
      if (len == npos || len >= _size)
      {
        _str[pos] = '\0';
        _size = pos;
      }
      else
      {
        strcpy(_str + pos, _str + pos + len);
        _size -= len;
      }
    }
    void clear()
    {
      _str[0] = '\0';
      _size = 0;
    }
    size_t find(char ch, size_t pos = 0)const
    {
      assert(pos < _size);
      for (size_t i = pos; i < _size; i++)
      {
        if (ch == _str[i])
        {
          return i;
        }
      }
      return npos;
    }
    size_t find(const char* sub, size_t pos = 0)const
    {
      assert(pos < _size);
      const char* ptr = strstr(_str + pos, sub);
      if (ptr == nullptr)
      {
        return npos;
      }
      else
      {
        return ptr - _str;
      }
    }
    string substr(size_t pos, size_t len = npos)const//取子串
    {
      assert(pos < _size);
      size_t reallen = len;
      if (reallen == npos || reallen + pos > _size)
      {
        reallen = _size - pos;
      }
      string sub;
      for (size_t i = pos; i < reallen + pos; i++)
      {
        sub += _str[i];
      }
      return sub;
    }
    void resize(size_t n, char ch = '\0')
    {
      if (n > _size)
      {
        reserve(n);
        for (size_t i = _size; i < n; i++)
        {
          _str[i] = ch;
        }
        _size = n;
        _str[_size] = '\0';
      }
      else
      {
        //删除数据
        _str[n] = '\0';
        _size = n;
      }
    }
    bool operator>(const string& s)const
    {
      return strcmp(_str, s._str) > 0;
    }
    bool operator==(const string& s)const
    {
      return strcmp(_str, s._str) == 0;
    }
  private:
    char* _str;
    size_t _size;
    size_t _capacity;
  public:
    static size_t npos;
  };
  size_t string::npos = -1;
  ostream& operator<<(ostream& out, const string& s)
  {
    for (size_t i = 0; i < s.size(); i++)
    {
      out << s[i];
    }
    return out;
  }
  istream& operator>>(istream& in, string& s)
  {
    s.clear();
    char ch;
    ch = in.get();
    const size_t N = 32;
    char buff[N];
    size_t i = 0;
    while (ch != ' ' && ch != '\n')
    {
      buff[i++] = ch;
      if (i == N - 1)
      {
        buff[i] = '\0';
        s += buff;/*先将输入的字符存在buff数组中,然后一次性+=到s中,这样就避免了输入的字符串过大,使s频繁扩容的现象。*/
        i = 0;
      }
      ch = in.get();
    }
    buff[i] = '\0';
    s += buff;
    return in;
  }
  void test_string1()
  {
    string s1("hello world");
    string s2(s1);
    string s3;
    cout << "s1: " << s1 << endl;
    cout << "s2: " << s2 << endl;
    cin >> s3;
    cout << "s3: " << s3 << endl;
    string s4("ZD");
    s4 += 'L';
    cout << "s4: " << s4 << endl;
    s4 += " ";
    s4 += "and";
    s4 += " ";
    s4 += "De Bruyne";
    cout << "s4: " << s4 << endl;
  }
  void test_string2()
  {
    string s5("hello");
    s5.append("ZDL 17");
    cout << "s5: " << s5 << endl;
  }
  void test_string3()
  {
    string s6("hello");
    s6.insert(0, '#');
    cout << "s6: " << s6 << endl;
  }
  void test_string4()
  {
    string s7("hello");
    s7.insert(2, "ZDL");
    cout << "s7: " << s7 << endl;
  }
  void test_string5()
  {
    string s8("helloeeeeeelllooo");
    s8.erase(2, 2);
    cout << "s8: " << s8 << endl;
  }
  void test_string6()
  {
    string s9("hello world ZDL waiting for love");
    string s10 = s9.substr(6, 5);
    cout << "s10: " << s10 << endl;
  }
  void test_string7()
  {
    string s11("hello world ZDL waiting for love");
    string s12("shfshf");
    cout << "比较: " << (s11 > s12) << endl;
    string s13("De Bruyne");
    string s14("De Bruyne");
    cout << "s13 与 s14: " << (s13 == s14) << endl;
  }
  void test_string8()
  {
    string s1("hello");
    s1 += '\0';
    s1 += "De Bruyne";
    cout << s1 << endl;
    cout << s1.c_str() << endl;
  }
  void test_string9()
  {
    string s("hello world");
    string::iterator it = s.begin();
    while (it != s.end())
    {
      cout << *it << " ";
      it++;
    }
    cout << endl;
    for (auto e : s)
    {
      cout << e << " ";
    }
    cout << endl;
  }
}

test.cpp

#include"string.h"
int main()
{
  zdl::test_string1();
  cout << "------------------------------" << endl;
  zdl::test_string2();
  cout << "------------------------------" << endl;
  zdl::test_string3();
  cout << "------------------------------" << endl;
  zdl::test_string4();
  cout << "------------------------------" << endl;
  zdl::test_string5();
  cout << "------------------------------" << endl;
  zdl::test_string6();
  cout << "------------------------------" << endl;
  zdl::test_string7();
  cout << "------------------------------" << endl;
  zdl::test_string8();
  cout << "------------------------------" << endl;
  zdl::test_string9();
  return 0;
}

目录
相关文章
|
1月前
|
C++ 容器
|
1月前
|
C++ 容器
|
1月前
|
C++
|
4月前
|
编译器 程序员 C语言
【C++】string模拟实现
这篇博客探讨了自定义实现C++ `string` 类的关键功能,包括构造、拷贝构造、赋值运算符重载及析构函数。作者强调了理解并实现这些功能对于面试的重要性。博客介绍了`string` 类的头文件`string.h`,其中定义了迭代器、基本成员函数如`swap()`、`size()`、`c_str()`等,并提到了深拷贝概念。此外,还展示了构造函数、析构函数和赋值运算符的实现,以及迭代器的定义与使用。博客还包括对C语言字符串函数的引用,以辅助读者理解实现细节。
|
4月前
|
C语言 C++
【C++】string模拟实现(下)
本文档介绍了自定义`string`类的一些关键功能实现,包括`reserve()`用于内存管理,`push_back()`和`append()`添加字符或字符串,运算符`+=`的重载,以及`insert()`, `erase()`进行插入和删除操作。此外,还涵盖了`find()`查找函数,字符串的比较运算符重载,`substr()`获取子串,`clear()`清除内容,以及流插入和提取操作。常量`npos`用于表示未找到的标记。文档以代码示例和运行结果展示各功能的使用。
|
5月前
|
C语言 C++
【c++】string模拟实现(2)
【c++】string模拟实现(2)
24 0
|
6月前
|
存储 C++
C++:String的模拟实现
C++:String的模拟实现
|
6月前
|
存储 编译器 C++
【C++】——string的模拟实现
【C++】——string的模拟实现
|
6月前
string的模拟(下)
string的模拟(下)
51 1
string模拟实现:(三)
3.迭代器的实现 我们先来看看STL库中的string类的迭代器
36 0
string模拟实现:(三)