【CPP】string 类的模拟实现(下)

简介: 【CPP】string 类的模拟实现(下)

👉find👈


size_t find(char ch, size_t pos) const
{
  assert(pos < _size);
  while (pos < _size)
  {
    if (_str[pos] == ch)
    {
      return pos;
    }
    ++pos;
  }
  return npos;
}
size_t find(const char* str, size_t pos) const
{
  assert(pos < _size);
  const char* ptr = strstr(_str + pos, str);
  if (ptr == nullptr)
  {
    return npos;
  }
  else
  {
    return ptr - _str;
  }
}


注:strstr函数是暴力匹配的查找算法,除了这种算法外,还有KMP算法。如果想要了解KMP算法的话,可以看一下这篇文章。需要注意的是,strstr函数的返回值是指针,而find函数的返回值为下标,所以我们要将指针进行相减转换成下标。


b78ac18dbe834524830453196c815f10.png


fe78eb3883224c579d6050e1475e30ea.png


👉流插入和流提取重载👈


<< 运算符重载


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


<< 运算符是将类对象里的字符一个个打印出来的,所以它跟 c.str() 有一点区别。


c0d5241310f14a46aef945dde3da6337.png


bdf54f633245402aacc2a84ea3a01caa.png


clear 函数清空类对象的数据


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


>> 运算符重载


istream& operator>>(istream& in, string& s)
{
  s.clear();
  // 第一种方式
  /*char ch = in.get();
  while (ch != ' ' && ch != '\n')
  {
    s += ch;
    //in >> ch;
    ch = in.get();
  }
  return in;*/
  // 第二种方式
  char buff[128] = { '\0' };
  size_t i = 0;
  char ch = in.get();
  while (ch != ' ' && ch != '\n')
  {
    if (i == 127)
    {
      s += buff;
      i = 0;
    }
    buff[i++] = ch;
    ch = in.get();
  }
  if (i > 0)
  {
    buff[i] = '\0';
    s += buff;
  }
  return in;
}


以上两种方式,都能实现流插入。但是第二种方式相较于第一种方式不需要频繁地扩容。注:流提取是以空格或者换行结束的。


3c3a6a735542442190c853474cf11be0.png


注:以上的流插入和流提取重载可以用友元实现,友元的话就可以直接访问类对象的数据了,而我实现的方式是通过函数接口来访问类对象的数据。


👉拷贝构造和赋值运算符重载的传统写法👈


拷贝构造


//传统拷贝构造写法 s2(s1)
string(const string& s)
{
  _str = new char[s._capacity + 1];
  _capacity = s._capacity;
  _size = s._size;
  strcpy(_str, s._str);
}


赋值运算符重载


// 传统赋值运算符重载 s2 = s1
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;
}


0b3b641d27ef4b5bb8c5648075bced67.png


注:拷贝构造和赋值运算符重载都是先申请或释放空间,然后再把数据拷贝到对象中去。


👉拷贝构造和赋值运算符重载的现代写法👈


拷贝构造


8610f157b2864cddb9f3b05546fadb0a.png


交换类对象的数据


void swap(string& s)
{
  std::swap(_str, s._str);
  std::swap(_size, s._size);
  std::swap(_capacity, s._capacity);
}


库里面也有交换的函数模板,但是这个函数接口会涉及三次深拷贝,效率不高。所以我们就自己实现一个交换类对象数据的函数接口,string 类中的交换函数也是这样实现的,效率较高。

c217415d65a84885a9e8941c1aa42ddb.png


// 现代拷贝构造写法 s2(s1)
string(const string& s)
  : _str(nullptr)
  , _size(0)
  , _capacity(0)
{
  string tmp(s._str); // 构造函数
  //this->swap(tmp);
  swap(tmp);
}


拷贝构造现代写法说明


先初始化列表,初始化对象 s2

然后调用构造函数string tmp(s._str),构造一个对象出来

最后将 tmp 的数据和 s2 的数据进行交换

注:s2 一定要走初始化列表,如果不走初始化列表,s2 的数据将会是随机值,随机指向一块空间。将 tmp 和 s2 的数据交换后,tmp 会被销毁,那么随机指向的空间将会被delete掉,程序会崩溃。


未写初始化列表


906ad23afbaf487d98d4a1f504b7c6d6.png


赋值运算符重载


交换类对象的数据


void swap(string& s)
{
  std::swap(_str, s._str);
  std::swap(_size, s._size);
  std::swap(_capacity, s._capacity);
}


// 现代赋值运算符重载 s2 = s1
/*string& operator=(const string& s)
{
  if (this != &s)
  {
    //string tmp(s._str);
    string tmp(s);
    swap(tmp);
  }
  return *this;
}*/
// s2 = s1
string& operator=(string s)
{
  swap(s);
  return *this;
}


赋值运算符重载现代写法有两种:第一种现代赋值运算符重载的参数是类对象的引用,可以减少拷贝构造。当 s2 != s1 时,才进行调用拷贝构造函数构造对象tmp,再将tmp的数据和s2的数据进行交换。第二种现代赋值运算符重载的参数类对象,传参需要调用拷贝构造函数构造s,然后将s的数据和s2的数据进行交换。


6400eb56ea164437bc70c26c3eff098f.png


注:拷贝构造和赋值运算符重载的现代写法并不是追求效率,而是追求简洁。深拷贝不要求_capacity相同。


有了模板之后,自定义类型也需要有构造函数和析构函数。见下图代码:

7f83523b73004557a9c135c38ce9c24e.png


为什么要支持内置类型的构造函数和析构函数呢?因为有了模板,要支持泛型编程。比如下图:如果 T1 和 T2 为内置类型,就要去调用构造函数和析构函数了。


2cea2505461945a8a209c0cb95b36fda.png



👉浅拷贝和深拷贝👈


浅拷贝


浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共

享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为

还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。

就像一个家庭中有两个孩子,但父母只买了一份玩具,两个孩子愿意一块玩,则万事大吉,万一不想分享就 你争我夺,玩具损坏。

0899c6b268ad433d90c055ab1839d1cf.png

e75c98cfe5b0416aa744cbe3dbccad3a.png

可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享。父母给每个孩

子都买一份玩具,各自玩各自的就不会有问题了。


9a277440e73749b79670343edb7bb4eb.png



深拷贝


如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情

况都是按照深拷贝方式提供。


66492d2f5d054077b1503379d543707c.png


👉总结👈


其实 string 类的函数接口远不止这些,还有很多。而我们模拟实现的只是一些常用的、重点的,那些比较字符串大小的函数接口不经常使用,我们就不实现了。那么,以上就是本篇博客的全部内容,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家!💖💝❣️









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