手搓string类(下)

简介: 手搓string类

5.erase

从pos位置开始删除len个长度的字符

//实现一下erase
string& erase(size_t pos = 0, size_t len = npos)
{
  assert(pos < _size);
  //删除要判断是否会越界
  if (len == npos || pos + len >= _size)
  {
    //说明要删除的长度超过了pos后面有的字符串,只要直接在pos位置插入'\0'就行
    _str[_size] = '\0';
    _size = pos;
  }
  else
  {
    //如果不越界,就要覆盖删除,可以复用strcpy
    strcpy(_str + pos, _str + pos + len);
    _size -= len;
  }
  return *this;
}

说到erase就不得不说一下npos,npos是一个无符号整形默认是-1也就是四十二亿九千万,如果长度大于npos就是越界了。

五.查找

find

从pos位置开始查找一个字符或者一段字符串

size_t find(char c, size_t pos)
{
  assert(pos < _size);//断言检查
  for (int i = pos; i < _size; i++)
  {
    if (_str[i] == c)
    {
      return i;
    }
  }
  return npos;
}
size_t find(const char* s, size_t pos)
{
  //这里可以考虑复用C的库函数strstr
  assert(pos < _size);
  char *ret = strstr(_str + pos, s);//这个C语言库函数返回的是一个指针
  //检查合法性
  if (ret == NULL)
  {
    return npos;
  }
  else
  {
    return ret - _str;
  }
}

其实在一般的情况下使用strstr查找子串已经够了,kmp算法其实是一个外强中干的家伙。

六.流插入<<和流提取>>的重载

这个我们在日期类中就已经接触过了,不能写在类中否则会被this指针抢第一个参数位置,还是使用友元然后定义在类外。

1.流插入<<重载

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

2.流提取>>重载

istream& operator>>(istream& in, string& s)
{
  s.clear();
  char ch = in.get();
  while (ch != ' ' && ch != '\n')
  {
    s += ch;//我就说+=好用吧
    ch = in.get();
  }
  return in;
}

但这个代码有一个不好的地方在于插入长字符串时可能会频繁的扩容。

为了减少扩容次数,我可以建立一个数组,这个数组满了就往s中插入,数组满一次才扩容一次,有效减少扩容次数

stream& operator>>(istream& in,  string& s)//要对s插入数据,s不能为const类型
{
  s.clear();//清空s
  //定义一个数组,往这个数组中放数据,当这个数组满了以后+=给s,这样可以避免频繁的扩容
  char buff[128] = { '\0' };
  char ch = in.get();//从缓冲区拿字符
  size_t i = 0;
  while (ch != ' ' && ch != '\n')
  {
    if (i == 127)
    {
      s += buff;
      i = 0;//满了以后+=geis再将i重置为0,开始下一轮
    }
    buff[i++] = ch;
    ch = in.get();
  }
  //如果输入的数据不能让数组满呢?
  if (i > 0)
  {
    //只要有数据就应该导进来,字符数组是以'\0'作为结束标志的,对于只插入几个字符的情况,要手动补'\0'
    buff[i] = '\0';//后置++,是有效字符的下一个位置
    s += buff;
  }
  return in;
}

getline就是在判断的时候把空格去掉就行,只以换行作为结束标志。但是cout,cin以及scanf和printf都是以空格和换行为结束标志的。

七.整体实现代码

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<assert.h>
#include<string.h>
using namespace std;
namespace wbm
{
  class string
  {
  public:
    //重载流插入和流提取,不能写在类中,使用友元
    friend istream& operator>>(istream& in,  string& s);
    friend ostream& operator<<(ostream& out,  const string& s);
    //首先写构造函数,给一个全缺省的构造
    string(const char *str="")//注意:\0和空指针一样,'\0'是字符常量(char类型),“”和“\0"一样,
    {
      _size = strlen(str);//这是成员函数,有this指针
      _capacity = _size;
      _str = new char [_capacity + 1];//多开一个字节给'\0'
      strcpy(_str, str);//可能会传参过来构造
    }
    //析构函数
    ~string()
    {
      //释放空间
      delete[] _str;
      _str = nullptr;
      _size = _capacity= 0;
    }
    拷贝构造
    //string(const string& s)
    //{
    //  //先构造一个新空间给_str
    //  _str = new char[s._capacity + 1];
    //  //然后将s的其他值赋值给this
    //  
    //  _size = s._size;
    //  _capacity = s._capacity;
    //  strcpy(_str, s._str);
    //}
    //不当老实人,使用现代写法,主要是调用swap函数
    void swap(string&s)
    {
      std::swap(_str, s._str);//库中和string分别提供了一个swap函数,用库中的swap交换内置类型
      std::swap(_size, s._size);
      std::swap(_capacity, s._capacity);
    }
    string(const string& s)
      :_str(nullptr) //这里必须要给_str初始化,否则拿到一个非法的地址,析构会报错
      ,_size(0)
      ,_capacity(0)
    {
      string tmp(s._str);//这里复用构造函数
      swap(tmp);//有隐藏的this指针,一个参数就够了
    }
    //重载=
    //string& operator=(const string& s)
    //{
    //  if (this != &s)
    //  {
    //    //深拷贝
    //    char* tmp = new char[s._capacity + 1];
    //    strcpy(tmp, s._str);//为新空间赋值
    //    _str = tmp;
    //    _capacity = s._capacity;
    //    _size = s._size;
    //    return *this;
    //  }
    //}
    //不当老实人,现代写法主要是为了简洁,并不能提高效率
    string& operator=(string s)
    {
      swap(s);//传值传参,s是一个拷贝,直接使用这个临时变量,反正临时变量除了这个函数就被销毁了
      return *this;
    }
    //重载+=,两种形式,一种加字符,还有加字符串
    //string& operator+=(char ch)
    //{
    //  //在字符串size位置直接插入,然后补一个'\0'
    //  //但是这里要考虑一个问题,就是扩容,所以不如直接写尾插
    //  _str[_size] = ch;
    //  ++_size;
    //  _str[_size] = '\0';
    //}
    void reverse(size_t n)
    {
      //只扩容,所以先检查情况
      if (n > _capacity)
      {
        //_str中有数据,不能直接改_str的空间,要先建立临时空间
        char*tmp = new char[n + 1];
        //将_str中的数据拷贝给tmp,再将_str所指的空间释放
        strcpy(tmp, _str);
        delete[]_str;
        _str = tmp;
        _capacity = n;
      }
    }
    //reverse都有了,这不来个resize
    void resize(size_t n,char ch='\0')//改变size可能会改变capacity,默认插入补空间的字符是'\0'
    {
      if (n > _capacity)
      {
        reverse(n);
        //扩大空间以后,要用字符初始化后续空间
        for (size_t i = _size; i < n; i++)
        {
          _str[i] = ch;
        }
        //这里还要改变size
        _size = n;
        //可能使用者会传其他字符来初始化,前面的循环没有在size位置补'\0'
        _str[_size] = '\0';
      }
      else 
      {
        //如果是缩小的话,就直接在n位置补'\0'
        _str[n] = '\0';
        _size = n;
      }
    }
    void push_back(char c)
    {
      if (_size == _capacity)//容量不够,要扩容
      {
        //扩容要调用reverse,这里还要检查一下_capacity,第一次可能是0
        int newCapacity = _capacity == 0 ? 4 : 2 * _capacity;
        reverse(newCapacity);//这里面更新了_capacity,外面不用再更新
      }
      //扩容完毕以后,就可以开始插入了
      _str[_size] = c;
      _size++;
      _str[_size] = '\0';
    }
    string& operator+=(char c)
    {
      //插入一个字符直接_size位置插入,复用push_back
      push_back(c);
      return *this;
    }
    //还要重载一个字符串类型,可以复用append
    string& operator+=(const char* str)
    {
      append(str);
      return *this;
    }
    string& append(const char* s)
    {
      //这里可以使用C标准库中的strcpy,不过还是要考虑扩容的问题,要检查剩下的空间是否足够插入
      int len = strlen(s);
      if (_size + len > _capacity)
      {
        //容量不够插入就要扩容
        reverse(_capacity + len);
      }
      //复用C标准库函数
      strcpy(_str + _size, s);
      _size += len;
      return *this;
    }
    //要重载一下[],方便读写
    char& operator[](size_t pos)
    {
      //虽然string是自定义类型,但_str是内置类型
      assert(pos < _size);
      return _str[pos];
    }
    const char& operator[](size_t pos)const//const对象只读
    {
      assert(pos < _size);
      return _str[pos];
    }
    //[]都有了,这不来个迭代器(行为像指针,但不一定是指针,这里写成指针)
    typedef char* iterator;
    iterator begin()
    {
      return _str;
    }
    iterator end()
    {
      return _str + _size;
    }
    //写一个find函数,搞两个,一个是找字符,一个是找字符串
    size_t find(char c, size_t pos)
    {
      assert(pos < _size);//断言检查
      for (int i = pos; i < _size; i++)
      {
        if (_str[i] == c)
        {
          return i;
        }
      }
      return npos;
    }
    size_t find(const char* s, size_t pos)
    {
      //这里可以考虑复用C的库函数strstr
      assert(pos < _size);
      char *ret = strstr(_str + pos, s);//这个C库函数返回的是一个指针
      //检查合法性
      if (ret == NULL)
      {
        return npos;
      }
      else
      {
        return ret - _str;
      }
    }
    //还要insert和erase
    //实现insert,分为插入字符和插入字符串
    string& insert(size_t pos, char c)
    {
      assert(pos <= _size);
      //string没有单独的扩容函数,在每个插入数据的地方都要检查容量
      if (_size == _capacity)
      {
        int newCapacity = _capacity == 0 ? 4 : 2 * _capacity;
        reverse(newCapacity);
      }
      //插入字符要挪动字符,这里要小心,在pos位置插入
      size_t end = _size + 1;//指向'\0'的下一个位置
      while (end >=(int) pos)
      {
        _str[end] = _str[end - 1];
        --end;
      }
      _str[pos] = c;
      _size += 1;
      return *this;
    }
    string& insert(size_t pos, const char* str)
    {
      assert(pos <= _size);
      size_t len = strlen(str);
      if (_size +len > _capacity)
      {
        int newCapacity = _capacity == 0 ? 4 : 2 * _capacity;
        reverse(newCapacity);
      }
      //扩容完毕,开始挪动数据,这次要挪动len个位置,要考虑一下长度是否越界
      size_t end = _size + len;
      while (end > pos + len-1)
      {
        _str[end] = _str[end - len];
        end--;
      }
      //位置挪出来以后要插入字符串
      /*for (size_t i = pos; i <= pos + len; i++)
      {
        _str[i] = *str;
        str++;
      }*/
      //此外,还可以复用C库函数strncpy
      strncpy(_str + pos, str, len);
      _size += len;
      return *this;
    }
    //实现一下erase
    string& erase(size_t pos = 0, size_t len = npos)
    {
      assert(pos < _size);
      //删除要判断是否会越界
      if (len == npos || pos + len >= _size)
      {
        //说明要删除的长度超过了pos后面有的字符串,只要直接在pos位置插入'\0'就行
        _str[_size] = '\0';
        _size = pos;
      }
      else
      {
        //如果不越界,就要覆盖删除,可以复用strcpy
        strcpy(_str + pos, _str + pos + len);
        _size -= len;
      }
      return *this;
    }
    //下面写几个简单函数,比如返回_size,_capacity,返回一个C类的指针
    const char* c_str()const
    {
      return _str;
    }
    size_t size()const
    {
      return _size;
    }
    size_t capacity()const
    {
      return _capacity;
    }
    void clear()
    {
      _size = 0;
      _str[0] = '\0';
    }
  private:
    char* _str;
    size_t _size;
    size_t _capacity;
    const static size_t 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插入数据,s不能为const类型
  {
    s.clear();
    //定义一个数组,往这个数组中放数据,当这个数组满了以后+=给s,这样可以避免频繁的扩容
    char buff[128] = { '\0' };
    char ch = in.get();//从缓冲区拿字符
    size_t i = 0;
    while (ch != ' ' && ch != '\n')
    {
      if (i == 127)
      {
        s += buff;
        i = 0;//满了以后+=geis再将i重置为0,开始下一轮
      }
      buff[i++] = ch;
      ch = in.get();
    }
    //如果输入的数据不能让数组满呢?
    if (i > 0)
    {
      //只要有数据就应该导进来,字符数组是以'\0'作为结束标志的,对于只插入几个字符的情况,要手动补'\0'
      buff[i] = '\0';//后置++,是有效字符的下一个位置
      s += buff;
    }
    return in;
  }
}

文章到此就结束啦,希望对各位有所帮助。

相关文章
|
3月前
|
Java 索引
java基础(13)String类
本文介绍了Java中String类的多种操作方法,包括字符串拼接、获取长度、去除空格、替换、截取、分割、比较和查找字符等。
48 0
java基础(13)String类
|
1月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
51 2
|
2月前
|
Java
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性
本文深入探讨了Java中方法参数的传递机制,包括值传递和引用传递的区别,以及String类对象的不可变性。通过详细讲解和示例代码,帮助读者理解参数传递的内部原理,并掌握在实际编程中正确处理参数传递的方法。关键词:Java, 方法参数传递, 值传递, 引用传递, String不可变性。
70 1
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性
|
2月前
|
安全 Java 测试技术
Java零基础-StringBuffer 类详解
【10月更文挑战第9天】Java零基础教学篇,手把手实践教学!
56 2
|
3月前
|
安全 Java
String类-知识回顾①
这篇文章回顾了Java中String类的相关知识点,包括`==`操作符和`equals()`方法的区别、String类对象的不可变性及其好处、String常量池的概念,以及String对象的加法操作。文章通过代码示例详细解释了这些概念,并探讨了使用String常量池时的一些行为。
String类-知识回顾①
|
2月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
29 1
|
2月前
|
数据可视化 Java
让星星月亮告诉你,通过反射创建类的实例对象,并通过Unsafe theUnsafe来修改实例对象的私有的String类型的成员属性的值
本文介绍了如何使用 Unsafe 类通过反射机制修改对象的私有属性值。主要包括: 1. 获取 Unsafe 的 theUnsafe 属性:通过反射获取 Unsafe类的私有静态属性theUnsafe,并放开其访问权限,以便后续操作 2. 利用反射创建 User 类的实例对象:通过反射创建User类的实例对象,并定义预期值 3. 利用反射获取实例对象的name属性并修改:通过反射获取 User类实例对象的私有属性name,使用 Unsafe`的compareAndSwapObject方法直接在内存地址上修改属性值 核心代码展示了详细的步骤和逻辑,确保了对私有属性的修改不受 JVM 访问权限的限制
64 4
|
2月前
|
存储 安全 Java
【一步一步了解Java系列】:认识String类
【一步一步了解Java系列】:认识String类
32 2
|
2月前
|
安全 C语言 C++
【C++篇】探寻C++ STL之美:从string类的基础到高级操作的全面解析
【C++篇】探寻C++ STL之美:从string类的基础到高级操作的全面解析
55 4
|
3月前
|
存储 安全 Java
Java——String类详解
String 是 Java 中的一个类,用于表示字符串,属于引用数据类型。字符串可以通过多种方式定义,如直接赋值、创建对象、传入 char 或 byte 类型数组。直接赋值会将字符串存储在串池中,复用相同的字符串以节省内存。String 类提供了丰富的方法,如比较(equals() 和 compareTo())、查找(charAt() 和 indexOf())、转换(valueOf() 和 format())、拆分(split())和截取(substring())。此外,还介绍了 StringBuilder 和 StringJoiner 类,前者用于高效拼接字符串,后者用于按指定格式拼接字符串
141 1
Java——String类详解