【字符串探秘:手工雕刻的String类模拟实现大揭秘】(下)

简介: 【字符串探秘:手工雕刻的String类模拟实现大揭秘】

【字符串探秘:手工雕刻的String类模拟实现大揭秘】(中):https://developer.aliyun.com/article/1425677


我们这里使用我们的C++istream提供的get函数,它通常不会跳过分隔符或空白字符,而是将其留在输入流中,因此我们就可以修改我们的流提取重载了。

// 这里会修改s,所以不用带上const
istream& operator>>(iostream& in, string& s)
{
  char ch = in.get();
  //in >> ch;//拿不到空格或者换行
  while (ch != ' ' && ch != '\n')
  {
    s += ch;
    ch = in.get();
  }
  return in;
}


然后我们再来测试一下,输入"12345 678"。


结果确实被显示出来了,并且" 678"被输入到我们的缓冲区从而没有被显示在显示器上,但是我们发现我们的流提取重载并没有清空之前存在的字符串,因为我们实现的时候是使用了+=重载,如果要输入的字符串之前没有清空,那么后续输入的字符串就会在之前的字符串上追加。但是我们的库函数就是直接输入什么字符串就会显示什么字符串。


所以在输入字符串之前,如果之前的字符串还有内容,我们就要清空,所以我们要实现一下我们的clear函数。

void clear()
{
  //删除数据,但是不会释放空间
  _size = 0;
  _str[_size] = '\0';
}


注意:这里我们一定要将0位置处设置为'\0',否则就会出现错误。


然后再来改造一下我们的流提取重载。

// 这里会修改s,所以不用带上const
istream& operator>>(iostream& in, string& s)
{
  //清空历史数据
  s.clear();
  char ch = in.get();
  //in >> ch;//拿不到空格或者换行
  while (ch != ' ' && ch != '\n')
  {
    s += ch;
    ch = in.get();
  }
  return in;
}


流提取重载这样就写好了,但是还是有一个小问题,如果我们要输入的字符非常长,那我们就要经过多次开辟空间,这样消耗很大,那我们可以用reserve提前开辟空间吗?不行,因为我们不确定用户要输入的字符到底有多长,同时我们也不能获取缓冲区输入字符的长度,这里就有人设计出了一个字符数组来解决这个问题,我们来看看是怎么设计的。

istream& operator>>(iostream& in, string& s)
{
  //清空历史数据
  s.clear();
  char buff[128];
  char ch = in.get();
  int i = 0;
  //in >> ch;//拿不到空格或者换行
  while (ch != ' ' && ch != '\n')
  {
    buff[i++] = ch;//该数组出了作用域就被销毁
    if (i == 127)
    {
      //下标为127,此时数组就有128个元素
      buff[i] = '\0';
      s += ch;
      i = 0;
    }
    ch = in.get();
  }
  //i没有走到127的情况
  if (i > 0)
  {
    buff[i] = '\0';
    s += buff;
  }
  return in;
}


1.2.浅拷贝问题


浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共 享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为 还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。


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


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


1.3 深拷贝问题


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


1.4.传统版写法的String类的拷贝构造和赋值拷贝


string(const string& s)//拷贝构造 - 深拷贝
{
  _str = new char[s._capacity + 1];
  strcpy(_str, s._str);
  _size = s._size;
  _capacity = s._capacity;
}
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;
  }
}


1.5.现代版写法的String类的拷贝构造和赋值拷贝


string(const string& s)//拷贝构造 - 深拷贝
{
  string tmp(s.c_str());
  swap(tmp);
}


但是这里有些编译器可能会出现一个问题,s2和tmp交换,s2确实获得了tmp的数据,但是tmp交换之后指向谁呢?此时我们需要让他指向nullptr。


再来看一下赋值拷贝的现代写法。

// s1 = s3
string& operator=(string s)//赋值 - 深拷贝
{
  swap(s);
    return *this;
}


这里不能加引用,因为加上引用s就是s3的别名,此时就是交换是s1和s3。此时不带上引用,s对象是通过s3对象拷贝构造的,然后s再和s1交换,s交换后指向s1,当s出了作用域,就会调用析构函数释放空间。


注意:此时我们就不用考虑自己给自己赋值的问题,因为此时我们传参的时候已经拷贝构造了。如果要考虑的话就要这样写

string& operator=(const string& s)//赋值 - 深拷贝
{
  if (this != &s)
  {
    string tmp(s);
    swap(tmp);
  }
}


使用上面的赋值现代写法不能兼容我们的substr函数,substr函数返回的是str的临时拷贝,临时拷贝具有常属性,而赋值现代写法参数是非cosnt,这里会存在权限放大的原因,所以会出现错误。


2.C++基本类型转string函数



3.编码表 :值 --- 符号对应的表


计算机中数据都是由二进制存储的,那我们怎么通过这些01序列分辨出我们的数据是什么呢?计算机中为我们提供了ASCII表。


比如今天我们要存储"apple!",实际上计算机存储的就是97 112 112 108 101 33 0这几个序列,我们来验证一下。


此时的计算机只能存储显示英文,那怎么显示其他国家的语言呢?以我们国家为例。也要对应的值和对应的符号相对于起来,但是中国文化上下五千年文明,博大精深,如果我们国家也用8个比特位,一个字节表示一个符号,也就是256中符号肯定不能存储文明国家所有的文字,所有我们国家就用两个字节到四个字节表示一个文字,一般常见的汉字可以考虑用两个字节对应一个汉字,所以这样就有256*256个表示情况,微软平台使用的都是GBK编码。


我们来看一下内存中是怎么样的,通过两个字节去编码表找对应的汉字。


我们可以再来看一下编码表的顺序。


编码表不是乱编的,是按照一定顺序编码的,将同音字编码在一起。我们国家的编码表是兼容SASCII表的,但是当同时出现中文和英文,我们国家的编码表怎么识别呢?它是当成两个字节去国家的编码去寻找呢?还是当成一个字节去寻找呢?在双字节编码表中,英文字符会占用一个字节,而中文字符会占用两个字节。在处理文本时,系统可以通过检查字节的高位信息来确定是一个英文字符还是一个中文字符,然后再在编码表中找到对应的字符。但是其他国家语言呢?还需要兼容其他国家的编码表,太繁琐了,于是就衍生出来万国码。


    "万国码" 广泛指的是 Unicode(统一码),而不是特指某一种具体的编码。Unicode 是一种用于文本字符的国际化标准,目的是为了能够涵盖全球范围内的所有语言和符号。


编码方式:


  • UTF-8: 是一种可变长度编码方式,使用1到4个字节来表示字符。对于ASCII字符,使用一个字节表示,对于其他字符,使用更多的字节。
  • UTF-16: 使用16位(2字节)来表示一个字符。基本多文本平面(BMP)上的字符使用16位表示。
  • UTF-32: 是一种固定长度编码,每个字符使用32位(4字节)表示,不论字符在Unicode中的位置。


Linux下一般都使用UTF-8编码。


C++标准库提供了多个字符串类型(stringwstringu16stringu32string)以适应不同的字符编码需求。这些字符串类型是为了支持不同的字符集和编码方式:


  1. string:

std::string 是标准 C++ 中用于存储单字节字符的字符串类型。它使用了默认的字符集(通常是 ASCII 或 UTF-8)。


  1. wstring:

std::wstring 是宽字符字符串类型,在 Windows 平台上通常使用。它使用 wchar_t 类型存储字符,这个类型在不同的编译器和平台上可能占据不同的字节大小(例如,Windows 上通常是 2 字节,而在 Linux 上可能是 4 字节)。


  1. u16string:

std::u16string 是存储 UTF-16 编码的字符串类型,每个字符通常占用 2 个字节。


  1. u32string:

std::u32string 是存储 UTF-32 编码的字符串类型,每个字符通常占用 4 个字节。


这些字符串类型的选择取决于需要处理的文本数据的特定要求。在多语言环境中,特别是处理 Unicode 字符时,选择适当的字符串类型非常重要。例如,如果需要处理表情符号、不同语言的字符集或者需要支持各种语言的国际化应用程序,那么使用宽字符或者 UTF-16/UTF-32 编码的字符串类型可能更为适合。


4.扩展阅读


面试中string的一种正确写法

STL中的string类怎么了?

相关文章
|
6天前
|
安全 Java 测试技术
低调却重要:Java 字符串拼接,选 StringBuilder 还是 StringBuffer?
低调却重要:Java 字符串拼接,选 StringBuilder 还是 StringBuffer?
161 83
|
4天前
|
自然语言处理 Java Apache
在Java中将String字符串转换为算术表达式并计算
具体的实现逻辑需要填写在 `Tokenizer`和 `ExpressionParser`类中,这里只提供了大概的框架。在实际实现时 `Tokenizer`应该提供分词逻辑,把输入的字符串转换成Token序列。而 `ExpressionParser`应当通过递归下降的方式依次解析
51 14
|
1月前
|
存储 编译器 C语言
关于string的‘\0‘与string,vector构造特点,反迭代器与迭代器类等的讨论
你真的了解string的'\0'么?你知道创建一个string a("abcddddddddddddddddddddddddd", 16);这样的string对象要创建多少个对象么?你知道string与vector进行扩容时进行了怎么的操作么?你知道怎么求Vector 最大 最小值 索引 位置么?
37 0
|
4月前
|
数据处理
鸿蒙开发:ArkTs字符串string
字符串类型是开发中非常重要的一个数据类型,除了上述的方法概述之外,还有String对象,正则等其他的用处,我们放到以后得篇章中讲述。
198 19
|
4月前
|
缓存 安全 Java
《从头开始学java,一天一个知识点》之:字符串处理:String类的核心API
🌱 **《字符串处理:String类的核心API》一分钟速通!** 本文快速介绍Java中String类的3个高频API:`substring`、`indexOf`和`split`,并通过代码示例展示其用法。重点提示:`substring`的结束索引不包含该位置,`split`支持正则表达式。进一步探讨了String不可变性的高效设计原理及企业级编码规范,如避免使用`new String()`、拼接时使用`StringBuilder`等。最后通过互动解密游戏帮助读者巩固知识。 (上一篇:《多维数组与常见操作》 | 下一篇预告:《输入与输出:Scanner与System类》)
116 11
|
4月前
|
Java
课时14:Java数据类型划分(初见String类)
课时14介绍Java数据类型,重点初见String类。通过三个范例讲解:观察String型变量、"+"操作符的使用问题及转义字符的应用。String不是基本数据类型而是引用类型,但使用方式类似基本类型。课程涵盖字符串连接、数学运算与字符串混合使用时的注意事项以及常用转义字符的用法。
111 9
|
4月前
|
存储 JavaScript Java
课时44:String类对象两种实例化方式比较
本次课程的主要讨论了两种处理模式在Java程序中的应用,直接赋值和构造方法实例化。此外,还讨论了字符串池的概念,指出在Java程序的底层,DOM提供了专门的字符串池,用于存储和查找字符串。 1.直接赋值的对象化模式 2.字符串池的概念 3.构造方法实例化
|
4月前
|
Java 程序员
课时16:String字符串
课时16介绍了Java中的String字符串。在Java中,字符串使用`String`类表示,并用双引号定义。例如:`String str = "Hello world!";`。字符串支持使用“+”进行连接操作,如`str += "world";`。需要注意的是,当“+”用于字符串与其他数据类型时,其他类型会先转换为字符串再进行连接。此外,字符串中可以使用转义字符(如`\t`、`\n`)进行特殊字符的处理。掌握这些基本概念对Java编程至关重要。
|
8月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
240 2
|
9月前
|
Java
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性
本文深入探讨了Java中方法参数的传递机制,包括值传递和引用传递的区别,以及String类对象的不可变性。通过详细讲解和示例代码,帮助读者理解参数传递的内部原理,并掌握在实际编程中正确处理参数传递的方法。关键词:Java, 方法参数传递, 值传递, 引用传递, String不可变性。
169 1
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性