【c++】string模拟实现(1)

简介: 【c++】string模拟实现(1)

string类的接口

namespace zjw
{
  class string
  {public:
    typedef char* iterator;
    typedef   const char* const_iterator;
  private:
    char* _str;
    int _size;
    int _capacity;
  };

这里的迭代器直接使用原生指针来封装。

_str为指向string数组的首地址的指针。

_size为string数组的大小。

_capacity为string数组的容量

基本函数

实现 函数返回string的首地址;

实现 函数返回string的尾地址;

实现 函数返回string的大小;

实现 函数返回string的容量;

iterator begin()
    {
      return _str;
    
    
    
    }
  iterator end()
    {
      return _str+_size;
    }
  size_t size()
    {
      return _size;
    
    
    
    
    }
  size_t capacity()
    {
      return _capacity;
    
    }

如果一个string类不能被修改的话,就无法调用非const的成员函数

const_iterator begin() const
    {
      return _str;
    }
    const_iterator end() const
    {
      return _str + _size;
    }

默认构造以及析构函数

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

这里有一个问题,就是默认构造传的缺省参数可以是nullptr 吗??

答案是不行的,因为strlen要将str解引用找’\0’,如果传nullptr的话,对空指针解引用就会出错。

这里也不可以传单字符‘\0’,因为这里的str是const char* 类型,而’\0’是char 类型


运算符重载下标访问函数

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的地方存的是’\0’,所以不用访问,pos<_size,为了让const迭代器不修改对于下标的值,修改上面得到下面,对上面进行函数重载

reserve函数

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

注意:这里多开一个是为了存’\0’;

push_back()函数

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

原来下标是_size的地方是‘\0’,然后之间在_size的地方放入ch,_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;
      //_str[_size] = '\0';
    }

尾插一个字符串,如果string的大小+要插入的字符串大小>_capacity,就扩容到_size + len,刚好,strcpy会从str开始直到遇到‘\0’一个字节一个字节拷贝到_str + _size(原字符串’\0’的地方),这里会把str字符串后面的‘\0’也拷贝过来。

运算符重载+=字符以及+=字符串

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

在这里传引用返回是因为_str指向的空间是在堆上开的,出函数不会被销毁,所以可以传引用返回

insert函数(插入字符)

void insert(size_t pos, char ch)
    {
      assert(pos<=_size);
      if (_capacity == _size)
      {
        reserve(_capacity == 0 ? 4 : _capacity * 2);
      }
      size_t end = _size;
      while (end >=pos)
      {
        _str[end+1] = _str[end];
        end--;
      }
      _str[pos] = ch;
      _size++;
    
    
    
    
    
    }

上面这个写法可以吗??

是不可以的

怎么修改呢??

void insert(size_t pos, char ch)
    {
      assert(pos<=_size);
      if (_capacity == _size)
      {
        reserve(_capacity == 0 ? 4 : _capacity * 2);
      }
      size_t end = _size+1;
      while (end >pos)
      {
        _str[end] = _str[end-1];
        end--;
      }
      _str[pos] = ch;
      _size++;
    
    
    
    
    
    }

这样就可以了,我们从’\0’的下一个位置开始end,此时end结束的条件是=0,所以不会陷入循环,这里和顺序表的insert很像,可以参考顺序表那里。


earse函数

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

删除从pos位置开始的len个字符长度的字符,如果len大于剩下的字符,就全删了,这里给的缺省值为npos=-1,可以认为是很大的数,绝对超过了pos后面的字符数,我们要在string里面声明一个npos,在类外面定义初始化。

如果len==npos或者len+pos>_size,就始要将pos开始后面的字符串全删除,我们直接在pos位置放’\0’即可,

_size=pos;

如果没有超过字符串


insert(插入字符串)

void insert(size_t pos, const char* str)
    {
      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;
    }

void test6()
  {
    string str;
    str.push_back('a');
    str.push_back('a');
    str.push_back('b');
    str.insert(2, "hello");
    cout << str.c_str();
  }

swap

void swap(string& str)
    {
      std::swap(_str, str._str);
      std::swap(_size, str._size);
      std::swap(_capacity, str._capacity);
}
void test12()
  {   string str1;
    string str2;
    str1 += "hello";
    str2+="nihao";
    cout << "str1:"<<str1 << endl;
    cout << "str2:"<<str2 << endl;
    str1.swap(str2);
    cout << "str1:" << str1 << endl;
    cout << "str2:" << str2 << endl;
  }


这里我们自己实现的swap是类成员函数,但是#include< algorithm >这个头文件库里面也有一个swap,swap全局也有一个非成员函数swap

我们可以发现算法库中的swap需要完成三次的拷贝,以及一次析构(临时变量c的析构),而string里面定义一个全局的swap,就是为了防止调用算法库里面的swap,因为会先在全局找,然后会在展开的库里找,而全局的swap实现只需要调用类里面的swap即可

void swap(string& x, string& y)
  {
    x.swap(y);
    cout << "没使用库里的" << endl;
  
  }
void test13()
  {
    string str1;
    string str2;
    str1 += "hello";
    str2 += "nihao";
    cout << "str1:" << str1 << endl;
    cout << "str2:" << str2 << endl;
    swap(str1, str2);//检测是调用库里的,还是全局的
    cout << "str1:" << str1 << endl;
    cout << "str2:" << str2 << endl;
  
  
  }

赋值以及拷贝构造

string(const string& s)
    {
      string tmp(s._str);
      swap(tmp);
    
    
    
    
    }
    string& operator=(string tmp)
    {
      swap(tmp);
      return *this;
    }

根据调试发现

老版本赋值

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

find函数(查找字符)

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

从pos位置开始查找,如果pos使用缺省,则从第一个位置开始查找,从pos位置开始遍历,如果找到返回下标,如果找不到返回npos

void test7()
  {
    string str;
    str.push_back('a');
    str.push_back('a');
    str.push_back('b');
    str.insert(2, "hello");
    cout << endl;
    int ret = str.find('b', 0);
    cout << ret;
  }


find函数(查找子串)

size_t find(const char* sub, size_t pos = 0) const
    {
      assert(pos < _size);//下标为_size的是‘\0’
      const char* ptr = strstr(_str + pos, sub);
      if (ptr != nullptr)//找到了,但是strstr返回的是找到子串的起始地址-字符串起始地址就是子串相对起始位置的长度
      {
        return ptr - _str;
      }
      else
      {
        return npos;
      }
    }
void test8()
  {
    string str;
    str += "beijing huanyingni zhangjiawang";
    int ret = str.find("zhangjiawang");
    cout << ret;
  }


substr函数

string substr(size_t pos = 0, size_t len = npos) const
    {
      string substr;
      if (pos > _size - len)
      {
        for (int i = pos; i <= _size; i++)
        {
          substr += _str[i];
        }
      }
      else
      {
        for (int i = pos; i < pos + len; i++)
        {
          substr += _str[i];
        }
        substr += '\0';
      }
      return substr;
    }

从pos位置开始取,取len个长度,分两种情况,如果从pos开始还没取到len长,就结束,就取到结尾,遍历pos到_size,string substr 保存遍历的值.

第二种,遍历pos到pos+len,string substr 保存遍历的值.最后记得加‘\0’;


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