C++初阶 String类的模拟实现(下)

简介: C++初阶 String类的模拟实现(下)

容器和大小相关函数

size

因为我们这里的Size和Capacity是私有成员


所以说我们要写出函数来返回它们的大小



int size()
  {
  return _size;
  }


实现效果如下


b7cd264a409a4b229a75241593fe1ba3.png


capacity


代码表示如下

int capacity()
  {
  return _capacity;
  }


运行结果如下 这里也没有什么好讲的


843992b3dfc544f981265625a2830ee7.png

reserve


开空间


reserve的执行规则如下


如果要开的空间大于capacity的时候 将capacity扩大

如果要开的空间小于capacity的时候 什么都不做

代码表示如下


void reserve(int n) //n 表示要开的空间大小
  {
  // 首先判断大小
  if (n > _capacity)
  {
    char* tmp = new char[n + 1]; // 这里多开一个空间是为了存放 /0
    strncpy(tmp, _str, _size + 1); // 这里之所以使用strncpy是因为防止str里面有/0存在 
    delete[] _str;
    _str = tmp;
    _capacity = n; // 容量代表的是可以存放字符的数量 不包括/0 所以说要比真实大小少1
  }
  }


resize


resize的执行规则如下


如果重置的大小大于当前的大小并且小于容量 那就增加差值个‘/0’

如果重置的大小大于容量 那么久扩容后 后面全部赋值‘/0’

如果重置的大小小于当前的大小 那么就缩小大小 并且后面的内容截断

代码表示如下


void resize(int n) // n 表示要重置的大小
  {
  // 小于的话改变下size就好
  if (n<=_size)
  {
    _size = n;
    _str[_size] = '\0';
  }
  // 大于的话 要考虑是否要扩容
  if (n > _size)
  {
    if (n>_capacity)
    {
    reserve(n); // 扩容 
    // 后面全部赋值 /0
    int i = _size;
    while (i<=_capacity)
    {
      _str[i] = '\0';
      i++;
    }
    _size = n;
    }
    else
    {
    int i = _size;
    while (i<=n)
    {
      _str[i] = '\0';
      i++;
    }
    _size = n;
    }
  }
  }


运行结果如下


f233ed1082ad415181d55b69d6858818.png


empty


判断是否为空 这个很简单


判断下字符串和空串是否相等就好了


bool empty()
  {
  return strcmp(_str, "") == 0;
  }


c14fe581dcdd4ac39da9e06f7c06e1e6.png

修改字符串相关函数


凡是修改字符串的函数 都需要考虑是否要扩容


push_back


这里往后插入一个字符 一定记得后面要加上一个‘\0’


代码表示如下


void push_back(char ch)
  {
  // 首先判断是否需要扩容
  if (_size == _capacity)
  {
    reserve(_capacity == 0 ? 4 : 2 * _capacity);
  }
  _str[_size] = ch;
  _str[_size + 1] = '\0';
  _size++;
  }


运行结果如下


a4df83ea44a94e51939aa187ac4915af.png


append


这个函数的作用就是在字符串后面尾插一个字符串 尾插前需要判断字符串的容量是否可以容纳这么多的


字符


代码表示如下


void append(const char* str)
  {
  // 判断是否需要扩容 
  if (_size+strlen(str)>_capacity)
  {
    reserve(_capacity + strlen(str));
  }
  // 开始拼接字符串
  strcpy(_str + _size, str); // 将_str尾插到字符串末尾
  // 重置字符串长度
  _size = _size + strlen(str);
  }


我们来看看最后的效果是什么样子的

8e725f6d167b4b08a4141b0005e242c1.png


这里符合预期


operator +=


这里我们直接复用上面两个实现的函数就好啦


为了符合String内置函数的写法 我们这里写成两个重载函数


代码分别如下


String& operator+=(char ch)
  {
  // 复用就可以
  push_back(ch);
  return *this;
  }


String& operator+=(const char* str)
  {
  // 复用就可以
  append(str);
  return *this;
  }



显示效果如下


775de0a5f19b4f65869a5738f54cee26.png


insert


insert函数的作用是选择一个位置插入字符或者是字符串


所以说这里有两个参数 我们将一个设置为pos 一个设置为字符/字符串


首先我们要考虑pos的合法性 它一定是大于等于0的


之后我们来看后面的字符 就要考虑扩容问题啦 移位问题啦


首先我们按照这个思路来设计插入字符的代码


代码表示如下


void append(const char* str)
  {
  // 判断是否需要扩容 
  if (_size+strlen(str)>_capacity)
  {
    reserve(_capacity + strlen(str));
  }
  // 开始拼接字符串
  strcpy(_str + _size, str); // 将_str尾插到字符串末尾
  // 重置字符串长度
  _size = _size + strlen(str);
  }
  void insert(int pos, char ch)
  {
  // 首先考虑pos是否合法
  assert(pos >= 0);
  assert(pos <= _size);
  // 其次考虑是否需要扩容
  if (_size == _capacity)
  {
    reserve(_capacity == 0 ? 4 : 2* _capacity );
  }
  // 之后开始考虑移位 为插入数据腾出位置 
  for (int i = _size; i >= pos; i--)
  {
    _str[i + 1] = _str[i];
  }
  _str[pos] = ch;
  // 调整下size的大小
  _size++;
  }



接下来我们来看看字符串应该怎么插入


代码表示如下


void insert(int pos, const char* str)
  {
  // assert
  assert(pos >= 0);
  assert(pos <= _size);
  // 判断是否需要扩容
  int len = _size + strlen(str);
  if (len > _capacity)
  {
    reserve(len + _capacity);
  }
  // 之后开始考虑位移
  for (int  i = _size; i >= pos; i--)
  {
    _str[i + strlen(str)] = _str[i];
  }
  // 开始插入 注意这个时候要用strncpy才可以
  // 如果这个时候你用了strcpy 后面会被/0覆盖 
  strncpy(_str + pos, str, strlen(str));
  }


运行结果如下


a638eadae06c4b12928f044d157d65b3.png


这里尤其要注意的一点就是最后必须要使用strncpy 很多人都是忽略这个细节导致出错


erase


这里还是我们给出两个参数


一个位置还有要删除字符的个数


void earse(int pos, size_t n = -1)
  {
  // assert
  assert(pos >= 0);
  assert(pos <= _size);
  // 判断要删除的是不是后面所有的值 
  if (n >= _size - pos)
  {
    _str[pos] = '\0';
    // 改变size的大小
    _size = pos;
  }
  // 否则就不是要删除所有的值 那么我们这个时候来看看被删除的值有多少 n
  else // n < _size - pos
  {
    strcpy(_str + pos, _str + pos + n);
    _size+=n;
  }
  }


运行结果如下

703a84006f3947c7984a752dd2d5546e.png

4a195b7856314822b0d3ca9b78cba399.png


clear


这个很简单啦 直接将字符串大小变成0 然后赋值空字符串就可以


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

很简单 这样子两行代码搞定


c_str
这里也很简单 返回一个字符串就可以
  const char* c_str() const
  {
  return _str;
  }

访问字符串相关函数


operator[]


重载这个运算符只是为了符合我们使用喜欢


想想看 当我们要使用这个运算符的时候是为了什么?


访问数组中的某个元素是吧 那么怎么写这个函数是不是很清晰了


char& operator[](size_t i)
  {
  assert(i < _size); //检测下标的合法性
  return _str[i]; //返回对应字符
  }
  const char& operator[](size_t i ) const
  {
  assert(i < _size); //检测下标的合法性
  return _str[i]; //返回对应字符
  }


使用效果如下


813cd3ddaa43484ca7cb164ceba57203.png


find


这个函数的意思很明确 找到第一个出现的字符 如果找到 返回下标 如果找不到 返回一个-1就可以


我们这里再加一个功能 就是用户可以设置从默认的第n个位置开始查找 默认为0


那么代码表示如下


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


15b4f4e68c564bd098818e7f0fd00ca6.png

这里我们可以发现结果没问题


如果我们想要找字符串呢?


很简单 我们使用strstr查找就可以


//正向查找第一个匹配的字符串
  int find(const char* str, int pos = 0)
  {
   assert(pos < _size); //检测下标的合法性
   const char* ret = strstr(_str + pos, str); //调用strstr进行查找
   if (ret) //ret不为空指针,说明找到了
   {
    return ret - _str; //返回字符串第一个字符的下标
   }
   else //没有找到
   {
    return -1; //返回-1
   }
  }


a560947618324d17b43db6f2ab0ce975.png


rfind


顾名思义 反向查找


那么我们这里转换下思路就好了 指针从右边开始找


代码表示如下


int rfind(const char* str, int pos = 0)
  {
   assert(pos < _size); //检测下标的合法性
   pos = _size - 1 - pos;
   // 一个个开始往后面找
   while (pos)
   {
    const char* ret = strstr(_str + pos, str);
    if (ret==nullptr)
    {
     pos--;
    }
    else
    {
     return ret - _str;
    }
   }
   return -1;
  }


代码运行结果如下


67b3a214e43c4580b725e92ffe65edc6.png


我们发现可以完美运行


关系运算符重载


关系运算符有 >、>=、<、<=、==、!= 这六个,但是对于C++中任意一个类的关系运算符重载,我们均


只需重载其中的两个,剩下的四个关系运算符可以通过复用已经重载好了的两个关系运算符来实现。


我们这里实现 == 和 >


//>运算符重载
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 (*this > s) || (*this == s);
}
//<运算符重载
bool operator<(const string& s)const
{
  return !(*this >= s);
}
//<=运算符重载
bool operator<=(const string& s)const
{
  return !(*this > s);
}
//!=运算符重载
bool operator!=(const string& s)const
{
  return !(*this == s);
}


输入输出运算符重载


重载>>运算符是为了让string对象能够像内置类型一样使用>>运算符直接输入。输入前我们需要先将对象的C字符串置空,然后从标准输入流读取字符,直到读取到’ ‘或是’\n’便停止读取


代码表示如下


//>>运算符的重载
istream& operator>>(istream& in, string& s)
{
  s.clear(); //清空字符串
  char ch = in.get(); //读取一个字符
  while (ch != ' '&&ch != '\n') //当读取到的字符不是空格或'\n'的时候继续读取
  {
  s += ch; //将读取到的字符尾插到字符串后面
  ch = in.get(); //继续读取字符
  }
  return in; //支持连续输入
}

同样的 流插入运算符表示如下


//<<运算符的重载
ostream& operator<<(ostream& out, const string& s)
{
  //使用范围for遍历字符串并输出
  for (auto e : s)
  {
  cout << e;
  }
  return out; //支持连续输出
}

还记不记得我们之前有一个getline运算符


它其实和我们的流提取差不多 唯一的区别就是它遇到换行符才会停


//读取一行含有空格的字符串
istream& getline(istream& in, String& s)
{
  s.clear(); //清空字符串
  char ch = in.get(); //读取一个字符
  while (ch != '\n') //当读取到的字符不是'\n'的时候继续读取
  {
  s += ch; //将读取到的字符尾插到字符串后面
  ch = in.get(); //继续读取字符
  }
  return in;
}

总结


本篇博客主要模拟了String类的实现


由于博主水平有限 错误在所难免 希望大佬看到能够及时指正


如果本篇博客帮助了你 别忘记一键三连啊


阿尼亚 哇酷哇酷!


相关文章
|
2月前
|
C语言 C++ 容器
【c++丨STL】string模拟实现(附源码)
本文详细介绍了如何模拟实现C++ STL中的`string`类,包括其构造函数、拷贝构造、赋值重载、析构函数等基本功能,以及字符串的插入、删除、查找、比较等操作。文章还展示了如何实现输入输出流操作符,使自定义的`string`类能够方便地与`cin`和`cout`配合使用。通过这些实现,读者不仅能加深对`string`类的理解,还能提升对C++编程技巧的掌握。
81 5
|
2月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
63 2
|
2月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
113 5
|
2月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
112 4
|
2月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
152 4
|
3月前
|
安全 Java 测试技术
Java零基础-StringBuffer 类详解
【10月更文挑战第9天】Java零基础教学篇,手把手实践教学!
64 2
|
3月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
35 4
|
3月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
33 1
|
3月前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
33 4
|
3月前
|
存储 编译器 C++
【C++类和对象(下)】——我与C++的不解之缘(五)
【C++类和对象(下)】——我与C++的不解之缘(五)