【string类的简单模拟实现】

简介: 【string类的简单模拟实现】

1 类中成员变量的声明

通过上一篇文章对string类的简单使用相信大家对于string类中成员变量已经很熟悉了,这里就不再多说了:

class string
  {
  private:
    char* _str;
    int _size;//当前有效数据个数
    int _capacity;//存储有效数据的最大容量,不包括'\0'
    static const size_t npos;
    typedef char* iterator;
    typedef const char* const_iterator;
    };

但是为了测试时避免与库里面的冲突我们就把该类放在一个独立的命名空间中:

namespace grm
{
  class string
  {
  private:
    char* _str;
    int _size;//当前有效数据个数
    int _capacity;//存储有效数据的最大容量,不包括'\0'
    static const size_t npos;
    typedef char* iterator;
    typedef const char* const_iterator;
    };
}

2 迭代器

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

3 一些常用接口

        size_t size()const     //无论是不是const对象都能够调用
    {
      return _size;
    }
    char* c_str()const
    {
      return _str;
    }
        char& operator[](size_t pos)
    {
      assert(pos < _size);
      return _str[pos];
    }
    const char& operator[](size_t pos)const
    {
      assert(pos < _size);
      return _str[pos];
    }
        void clear()const
    {
      _str[0] = '\0';
      _size = 0;
    }
    size_t capacity()const
    {
      return _capacity;
    }
        bool empty()const
    {
      return _size == 0;
    }

4 六大默认函数

4.1 构造

       string(const char* s)
      :_size(strlen(s))
      ,_capacity(_size)
    {
      _str = new char[_capacity + 1];//多开出一个空间用来存储'\0'
      strcpy(_str, s);
    }

但是我们发现如果是无参又该怎么办?

有人会说像这样特殊处理一下就好了:

        string()
      :_str(new char[1])
      ,_size(0)
      ,_capacity(0)
    {
      _str[0] = '\0';
    }

这样处理是没有问题的,但是我们学过缺省参数,在这里能不能够给一个缺省值呢?缺省值又该给那个呢?

C++中是这样处理的:

        string(const char* s="")//给一个空字符串
      :_size(strlen(s))
      ,_capacity(_size)
    {
      _str = new char[_capacity + 1];//多开出一个空间用来存储'\0'
      strcpy(_str, s);
    }

缺省值给的是一个空字符串。

4.2 拷贝构造

传统写法:

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

这种写法就是自己手动开空间拷贝。

现代写法:我们发现上面我们已经实现好了默认的构造函数,我们可以直接用构造函数来解决

        void swap(string& s)
    {
      std::swap(s._str, _str);
      std::swap(s._capacity, _capacity);
      std::swap(s._size, _size);
    }
    //现代写法
    string(const string& s)
      :_str(nullptr)//必须要给初始值,否则交换后调用析构函数就会析构随机值会崩溃
      ,_size(0)
      ,_capacity(0)
    {
      string tmp(s._str);
      swap(tmp);
    }

其中需要注意的是使用这种方式用初始化列表将_str初始化成nullptr,不然交换后调用tmp的析构函数就会释放随机地址而出错。

4.3 赋值运算符重载

与拷贝构造一样,赋值运算符重载也分为传统版本和现代版本。

传统版本:

        //传统写法
    string& operator=(const string& s)
    {
      if (this != &s)
      {
        //这种方式如果new失败了抛异常就会有问题,我们并不想_str维护的空间被释放
        delete[] _str;
        _str = new char[s._capacity+1];
        strcpy(_str, s._str);
        _size = s._size;
        _capacity = s._capacity;
      }
      return *this;
    }

大家可以从注释中看见传统写法都是要自己手动开空间拷贝,而且大家也发现了代码中还多了一个判断this是否与&s相等,否则当直接delete_str后由于s与_str指向的是同一块空间,再对s解引用就是典型的野指针问题了。而且还有一个问题就是当我们new空间失败时我们并不想_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;
    }

而且大家发现没有这样写即使不用写上面的if判断程序依旧可以正常运行,但是加上判断当自己给自己赋值时可以减少拷贝。

现代写法 方法1:

        //现代写法 方法1:
    string& operator=(const string& s)
    {
      if (this != &s) //这里加上判断条件在自己给自己赋值能够减少拷贝,不加也是没有问题的
      {
        string tmp(s);
        swap(tmp);
      }
      return *this;
    }

现代写法 方法2:

        //现代写法 方法2:
    string& operator=(string s)
    {
      swap(s);
      return *this;
    }

这种方式就更加巧妙了,直接用了传值传参,而传值传参就是一个拷贝构造。

4.4 析构

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

5 开空间&&增删查改

    void reserve(size_t n) //这里要求开n个有效空间,不包括'\0'
    {
      if (_capacity < n)
      {
        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)
      {
        if (n > _capacity)
        {
          reserve(n);
        }
        memset(_str + _size, ch, n - _size);
      }
      _size = n;
      _str[_size] = '\0';
    }
        void push_back(char ch)
    {
      if (_capacity == _size)
        reserve(_capacity == 0 ? 4 : _capacity * 2);
      _str[_size] = ch;
      ++_size;
      _str[_size] = '\0';
    }
    void append(const char* s)
    {
      int len = strlen(s);
      if (_size + len > _capacity)
        reserve(_size + len);
      strcpy(_str + _size, s);
      _size += len;
    }
    string& operator+=(char ch)
    {
      push_back(ch);
      return *this;
    }
    string& operator+= (const char* s)
    {
      append(s);
      return *this;
    }
    size_t find(char ch)
    {
      for (size_t i = 0; i < _size; i++)
        if (_str[i] == ch)
          return i;
      return -1;
    }
    size_t find(const char* s, size_t pos=0)
    {
      int i = pos, j = 0;//i是主串遍历,j是子串遍历
      while (i < _size && j < strlen(s))
      {
        if (s[j] == _str[i])
        {
          i++;
          j++;
        }
        else
        {
          i = i - j + 1;
          j = 0;
        }
      }
      if (j == strlen(s))
        return i - j;
      else
        return -1;
    }
        string& insert(size_t pos, const char* s)//在pos位置插入,字符串从pos位置开始插
    {
      assert(pos <= _size);
      int len = strlen(s);
      if (_size + len > _capacity)
        reserve(_size + len);
      int end = _size + len - 1;
      int gap = _size - pos ;
      while (gap--)
      {
        _str[end] = _str[end - len];
        end--;
      }
      strncpy(_str + pos, s, len);//不要拷贝'\0'
      _str[_size + len] = '\0';
            _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;
                return *this;
      }
      strcpy(_str + pos, _str + len + pos);
      _size -= len;
      return *this;
    }

这些代码我们之前上顺序表都已经比较详细的介绍了,大家可以参考参考。

我们可以自己测试一下来看看:

b86763940de043b1aad75618ef733f5e.png

8e8e58305ddb4abf8af998ad62129610.png

可以发现是没有多大问题的。

6 其他运算符重载

        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 strcmp(_str, s._str) <= 0;
    }
    bool operator>=(const string& s)const
    {
      return strcmp(_str, s._str) >= 0;
    }bool operator!=(const string& s)const
    {
      return strcmp(_str, s._str) != 0;
    }
  ostream& operator<<(ostream& out, const string& s)
  {
    //out << s.c_str();//能这么写吗? 不能,也许我们会在字符中间插入一个'\0'
    for (auto& e : s)
    {
      out << e;
    }
    /*for (int i = 0; i < s.size(); i++)
    {
      out << s[i];
    }*/
    return out;
  }
  istream& operator>>(istream& in, string& s)
  {
    s.clear();//要加上这个,否则可能出错
    char ch = in.get();
    while (ch != '\0' && ch != '\n')
    {
      s += ch;
      ch = in.get();
    }
    return in;
  }

上面这几个比较可以自己重载成成员方法,当然也可以重载成全局函数。下面流提取运算符和流插入运算符我们在之前已经讲过了,里面需要注意的细节代码中都有注释。


目录
相关文章
|
19天前
|
Java 编译器 ice
【Java开发指南 | 第十五篇】Java Character 类、String 类
【Java开发指南 | 第十五篇】Java Character 类、String 类
31 1
|
19天前
|
C语言 C++
【C++】string类(常用接口)
【C++】string类(常用接口)
21 1
|
1天前
|
Java 安全 索引
滚雪球学Java(48):面向对象编程中的StringBuffer类详解
【6月更文挑战第2天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
23 5
滚雪球学Java(48):面向对象编程中的StringBuffer类详解
|
2天前
|
存储 Java 测试技术
滚雪球学Java(47):String类教程:如何在Java中使用字符串操作
【6月更文挑战第1天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
14 2
滚雪球学Java(47):String类教程:如何在Java中使用字符串操作
|
6天前
|
缓存 安全 Java
初识String类
初识String类
|
12天前
|
存储 Java API
【JAVA学习之路 | 提高篇】[内部类与常见API]String类
【JAVA学习之路 | 提高篇】[内部类与常见API]String类
|
12天前
|
Java API
【JAVA学习之路 | 提高篇】包装类(包装类与基本数据类型及String类之间的转换)
【JAVA学习之路 | 提高篇】包装类(包装类与基本数据类型及String类之间的转换)
|
13天前
模拟实现string类--重载输入输出流
模拟实现string类--重载输入输出流
|
13天前
|
编译器 测试技术 C语言
从C语言到C++_11(string类的常用函数)力扣58和415(下)
从C语言到C++_11(string类的常用函数)力扣58和415
8 0
|
13天前
|
存储 编译器 C语言
从C语言到C++_11(string类的常用函数)力扣58和415(中)
从C语言到C++_11(string类的常用函数)力扣58和415
12 0