【字符串探秘:手工雕刻的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类怎么了?

相关文章
|
12天前
|
索引 Python
String(字符串)
String(字符串)。
17 3
|
1月前
|
Java
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性
本文深入探讨了Java中方法参数的传递机制,包括值传递和引用传递的区别,以及String类对象的不可变性。通过详细讲解和示例代码,帮助读者理解参数传递的内部原理,并掌握在实际编程中正确处理参数传递的方法。关键词:Java, 方法参数传递, 值传递, 引用传递, String不可变性。
57 1
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性
|
1月前
|
NoSQL Redis
Redis 字符串(String)
10月更文挑战第16天
39 4
|
1月前
|
安全 Java 测试技术
Java零基础-StringBuffer 类详解
【10月更文挑战第9天】Java零基础教学篇,手把手实践教学!
28 2
|
1月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
22 1
|
1月前
|
数据可视化 Java
让星星月亮告诉你,通过反射创建类的实例对象,并通过Unsafe theUnsafe来修改实例对象的私有的String类型的成员属性的值
本文介绍了如何使用 Unsafe 类通过反射机制修改对象的私有属性值。主要包括: 1. 获取 Unsafe 的 theUnsafe 属性:通过反射获取 Unsafe类的私有静态属性theUnsafe,并放开其访问权限,以便后续操作 2. 利用反射创建 User 类的实例对象:通过反射创建User类的实例对象,并定义预期值 3. 利用反射获取实例对象的name属性并修改:通过反射获取 User类实例对象的私有属性name,使用 Unsafe`的compareAndSwapObject方法直接在内存地址上修改属性值 核心代码展示了详细的步骤和逻辑,确保了对私有属性的修改不受 JVM 访问权限的限制
54 4
|
1月前
|
canal 安全 索引
(StringBuffer和StringBuilder)以及回文串,字符串经典习题
(StringBuffer和StringBuilder)以及回文串,字符串经典习题
37 5
|
1月前
|
存储 安全 Java
【一步一步了解Java系列】:认识String类
【一步一步了解Java系列】:认识String类
25 2
|
2月前
|
Java 索引
java基础(13)String类
本文介绍了Java中String类的多种操作方法,包括字符串拼接、获取长度、去除空格、替换、截取、分割、比较和查找字符等。
39 0
java基础(13)String类
下一篇
无影云桌面