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

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


目录
相关文章
|
3月前
|
Java 索引
java基础(13)String类
本文介绍了Java中String类的多种操作方法,包括字符串拼接、获取长度、去除空格、替换、截取、分割、比较和查找字符等。
40 0
java基础(13)String类
|
4月前
|
API 索引
String类下常用API
String类下常用API
47 1
|
2月前
|
Java
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性
本文深入探讨了Java中方法参数的传递机制,包括值传递和引用传递的区别,以及String类对象的不可变性。通过详细讲解和示例代码,帮助读者理解参数传递的内部原理,并掌握在实际编程中正确处理参数传递的方法。关键词:Java, 方法参数传递, 值传递, 引用传递, String不可变性。
62 1
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性
|
2月前
|
安全 Java 测试技术
Java零基础-StringBuffer 类详解
【10月更文挑战第9天】Java零基础教学篇,手把手实践教学!
38 2
|
3月前
|
安全 Java
String类-知识回顾①
这篇文章回顾了Java中String类的相关知识点,包括`==`操作符和`equals()`方法的区别、String类对象的不可变性及其好处、String常量池的概念,以及String对象的加法操作。文章通过代码示例详细解释了这些概念,并探讨了使用String常量池时的一些行为。
String类-知识回顾①
|
2月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
23 1
|
2月前
|
数据可视化 Java
让星星月亮告诉你,通过反射创建类的实例对象,并通过Unsafe theUnsafe来修改实例对象的私有的String类型的成员属性的值
本文介绍了如何使用 Unsafe 类通过反射机制修改对象的私有属性值。主要包括: 1. 获取 Unsafe 的 theUnsafe 属性:通过反射获取 Unsafe类的私有静态属性theUnsafe,并放开其访问权限,以便后续操作 2. 利用反射创建 User 类的实例对象:通过反射创建User类的实例对象,并定义预期值 3. 利用反射获取实例对象的name属性并修改:通过反射获取 User类实例对象的私有属性name,使用 Unsafe`的compareAndSwapObject方法直接在内存地址上修改属性值 核心代码展示了详细的步骤和逻辑,确保了对私有属性的修改不受 JVM 访问权限的限制
57 4
|
2月前
|
存储 安全 Java
【一步一步了解Java系列】:认识String类
【一步一步了解Java系列】:认识String类
28 2
|
2月前
|
安全 C语言 C++
【C++篇】探寻C++ STL之美:从string类的基础到高级操作的全面解析
【C++篇】探寻C++ STL之美:从string类的基础到高级操作的全面解析
38 4
|
2月前
|
存储 编译器 程序员
【C++篇】手撕 C++ string 类:从零实现到深入剖析的模拟之路
【C++篇】手撕 C++ string 类:从零实现到深入剖析的模拟之路
71 2