【C++】—— string 类的了解与使用

【CPP】—— string类的了解与使用

  • 1、 为什么学习string 类
    • 1.1、 C语言中的字符串
    • 1.2、 面试题中更多以 string 类出现
  • 2、 标准库中的 string 类
  • 3、 string 的默认成员函数
    • 3.1、 string 的构造与拷贝构造
    • 3.2、 string 的赋值重载
    • 3.3、 string 的析构函数
  • 4、 operator[ ]
    • 4.1、 访问
    • 4.2、 修改
    • 4.3、 检查越界
  • 5、 string类的三种遍历方式
    • 5.1、 下标访问遍历
      • 5.1.1、 size和length函数
      • 5.1.2、 下标访问
    • 5.2、 迭代器访问遍历
      • 5.2.1、 初识迭代器访问
      • 5.2.2、 反向迭代器
    • 5.3、 范围 for 访问遍历
      • 5.3.1、 初识范围for访问
      • 5.3.2、 范围for访问的注意事项
      • 5.3.3、 auto关键字
        • 5.3.3.1、 auto关键字的简单认识
        • 5.3.3.1、 auto关键字的注意事项
  • 6、 string类的容量操作
    • 6.1、 reserve
    • 6.1、 resize
    • 6.2、 注意事项
  • 7、 string类对象的修改操作
    • 7.1、 operator+=
    • 7.2、 c_str
    • 7.3、 swap
  • 8、 string类非成员函数
    • 8.1、 getline
    • 8.2、 swap
  • 9、 不同平台下string类的结构
    • 9.1、 VS 下string类的结构
    • 9.2、 g++ 下string类的结构

1、 为什么学习string 类

1.1、 C语言中的字符串

  C语言中,字符串是以 '\0' 结尾的一些字符的集合,为了操作方便,C标准库 提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合 OP 的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
  C语言中的字符串有诸多弊端。
  

1.2、 面试题中更多以 string 类出现

  在 OJ 中,有关字符串的题目基本以 string类 的形式出现,而且在常规工作中,为了简单、方便快捷,基本都使用 string类,很少有人去使用 C语言 库中的字符串操作函数。
  

下面是两个面试题(暂不做讲解)

  • 把字符串转换成整数
  • 字符串相加

  

2、 标准库中的 string 类

学习 string类,是离不开查阅文档

在使用 string类 时,必须包含 #include<string> 头文件。

   s t r i n g string string 底层类似一个顺序表:是由一个指向一个开辟出的空间的指针,和两个记录字符串长度和空间大小的变量 _size_capacity 组成的
  
简单结构如下:

class string
{
private:
	char* str;//指向存储字符串的空间
	size_t _size;//记录当前字符串长度
	size_t capacity;//记录当前空间大小
};

  
  

3、 string 的默认成员函数

3.1、 string 的构造与拷贝构造

在这里插入图片描述
  :因 s t r i n g string string 产生的比较早,有些部分考虑的并没有那么成熟,所以 s t r i n g string string 设计的有些冗余。只需重点学习标记出来的 3 个构造函数即可

  

  • e m p t y empty empty s t r i n g string string c o n s t r u c t o r ( d e f a u l t c o n s t r u c t o r ) constructor (default constructor) constructor(defaultconstructor)
    • 构造一个空字符串,长度为零个字符
  • c o p y copy copy c o n s t r u c t o r constructor constructor
    • 构造 s t r str str 的副本。
  • s u b s t r i n g substring substring c o n s t r u c t o r constructor constructor
  • f r o m from from c c c- s t r i n g string string
    • 复制 s s s 指向的以 n u l l null null 结尾的字符序列(C 字符串)。
  • f r o m from from b u f f e r buffer buffer
    • s s s 指向的字符数组中复制前 n n n 个字符。
  • f i l l fill fill c o n s t r u c t o r constructor constructor
    • 用字符 c c c n n n 个连续副本填充字符串。
  • r a n g e range range c o n s t r u c t o r constructor constructor
    • 以相同的顺序复制 [ f i r s t first first l a s t last last] 范围内的字符序列。

  
  第三个默认构造函数的缺省值 n o p s nops nops 是什么呢?
   n o p s nops nops s t r i n g string string 类中的一个静态成员变量 n o p s nops nops的值是 -1,但其为 s i z e size size_ t t t 类型,所以实际值为整型的最大值
  编译器认为你的字符串不可能有这么长 (42亿9千万字节),所以 n o p s nops nops 的意思是有多长取多长

在这里插入图片描述

  
我们来一起来实践一下

void Test1()
{
	//使用默认构造函数,不需要传参。
	string s1;
	//带参构造,使用指定字符数组初始化。
	string s2("hello world");
	//使用拷贝构造初始化
	string s3 = s2;
	//使用拷贝构造初始化
	//字符串会隐式类型转换。生成一个临时对象,再用临时对象进行拷贝(实际执行编译器会进行优化)
	string s4 = "你好";

	//string库中重载了流插入与流提取,我们可以直接使用
	cout << s1 << endl;
	cout << s2 << endl;
	cout << s3 << endl;
	cout << s4 << endl;

}

运行结果:

在这里插入图片描述

  
剩下 4 个我们也来看下

void Test2()
{
	string s1("hello world");
	
	//3. 使用一个string的某段区间初始化,其中pos是字符串下标,npos是指无符号整数的最大值。
	string s3(s1, 3, 8);
	//5. 使用的是某个字符数组前n个字符来初始化
	string s5("hello world!", 5);
	//6. 使用的是n个c字符初始化。
	string s6(8, 'a');
	//7. 使用的是某段迭代器区间初始化。(现在看不懂没关系,后面会介绍)
	string s7(s1.begin() + 2, s1.end() - 3);

	cout << s3 << endl;
	cout << s5 << endl;
	cout << s6 << endl;
	cout << s7 << endl;
}

运行结果:

在这里插入图片描述

  
  

3.2、 string 的赋值重载

在这里插入图片描述

  
s t r i n g string string 重载了 3 个赋值运算符重载函数:

  1. 使用 s t r i n g string string对象进行赋值
  2. 使用字符串进行赋值
  3. 使用单个字符进行赋值
void Test3()
{
	//使用拷贝构造进行初始化
	string s1 = "hello world";

	//使用string对象进行赋值
	string s2;
	s2 = s1;

	//使用指定字符串进行赋值
	string s3;
	s3 = "你好";

	//使用指定字符进行赋值
	string s4;
	s4 = 'a';

	cout << s2 << endl;
	cout << s3 << endl;
	cout << s4 << endl;
}

运行结果:

在这里插入图片描述

  这里,其实第二个赋值重载函数可以省略,因为字符串会生成一个临时对象,再用临时对象进行赋值拷贝。

  
  

3.3、 string 的析构函数

在这里插入图片描述

  析构函数很简单,因为会自动调用,这里不做介绍。
  
  

4、 operator[ ]

   s t r i n g string string 类重载了[],使用户可以像数组一样访问 string类 中的字符

在这里插入图片描述

   s t r i n g string string 类重载了两个operator[],一个是普通版的,一个是 c o n s t const const 不可修改版
  
  

4.1、 访问

  有了operator[],我们就可以像数组一个对 s t r i n g string string 进行访问

void Test5()
{
	string s1 = "hello world";

	cout << s1[1] << endl;
	cout << s1[3] << endl;
	cout << s1[6] << endl;
}

在这里插入图片描述

  

4.2、 修改

  同时,因为 operator[] 返回的是字符的引用,这意味着普通版的operator[]不仅仅可以获取相应位置的字符,还能对其进行修改

void Test5()
{
	string s1 = "hello world";
	s1[0] = 'a';
	s1[1] = 'a';
	s1[2] = 'a';
	cout << s1 << endl;
}

在这里插入图片描述

  
  

4.3、 检查越界

  不仅如此,operator[]还能检查是否越界,一旦越界直接报错。这样就能解决我们平时不小心越界却无法检查出来的困扰啦。

void Test5()
{
	string s1 = "hello world";
	s1[20];
}

在这里插入图片描述

  
  

5、 string类的三种遍历方式

5.1、 下标访问遍历

  我们先来看两个函数接口:sizelength
  

5.1.1、 size和length函数

在这里插入图片描述

size函数接口

在这里插入图片描述

length函数接口
  • sizelength两函数的功能是一样的:返回字符串的长度
    :计算出的长度不包含字符串中 ‘\0’

  size()length()方法底层实现原理完全相同,引入 size() 的原因是为了与其他容器的接口保持一致,一般情况下基本都是用 s i z e ( ) size() size()
  
  

5.1.2、 下标访问

  下标访问遍历的方式和数组的访问类似,我们直接上代码

void Test6()
{
	string s1 = "hello world";

	int end = s1.size();
	for (int i = 0; i < end; ++i)
	{
		cout << s1[i] << " ";
	}
	cout << endl;
}

运行结果:

在这里插入图片描述

  
  

5.2、 迭代器访问遍历

5.2.1、 初识迭代器访问

  首先,我们先看一下迭代器访问的写法:

void Test7()
{
	string s1 = "hello world";
	string::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

运行结果:

在这里插入图片描述

  

看不懂没关系,我们现在来讲解。

  • 迭代器属于其对应容器的类域。比如说 s t r i n g string string,就是 s t r i n g : : i t e r a t o r string::iterator string::iterator;后面还会学顺序表 v e c t o r vector vector;就是 v e c t o r < i n t > : : i t e r a t o r vector <int>::iterator vector<int>::iterator
  • string::iterator it:我们用 s t r i n g string string 的迭代器定义了一个对象 i t it it。我们可以将 i t it it 想象成一个指针(但它底层不一定是指针),它的用法完全是跟指针类似的
  • s1.begin();begin()是规定返回这块空间开始位置的迭代器;s1.end()是最后一个有效字符的下一个位置(这里是 ‘\0’ 位置)
  • 上述代码逻辑是:当 it 不等于end()时,对其进行解引用。 i t it it 不是指针怎么进行解引用呢?可以进行运算符重载 operator*。再接着 ++it i t it it 往后移一位,不是原生指针同样进行运算符重载。

  

在这里插入图片描述

  

在这里插入图片描述

begin函数接口

在这里插入图片描述

end函数接口

  

  迭代器提供了一种 通用的 访问容器的方式所有的容器都可以用这种方式访问,而不需要关心容器的具体实现细节。掌握了 s t r i n g string string 的迭代器访问方式,就掌握了其他所有容器的访问方式,他们都会提供统一的接口

  我们可以通过迭代器进行修改

void Test7()
{
	string s1 = "hello world";
	string::iterator it = s1.begin();
	while (it != s1.end())
	{
		*it += 1;
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

运行结果:

在这里插入图片描述

  

  当然,如果对象本身是被 c o n s t const const 修饰,那就不能用普通迭代器了,因为普通迭代器可读可写,这时就要用 const迭代器 了。

void Test12()
{
	const string s1 = "hello world";
	string::const_iterator it = s1.begin();  //const迭代器
	while (it != s1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

  const迭代器的特点是只读,其不能修改指向的内容,但其自身是可以修改的(它自己还要++呢)
  
  

5.2.2、 反向迭代器

  上述使用迭代器string::iterator来遍历,我们将其称为正向迭代器。此外,还有反向迭代器string::reverse_iterator
  反向迭代器是用来倒着遍历的,获取反向迭代器的起始位置用 r b e g i n ( ) rbegin() rbegin() 函数,获取结束位置用 r e n d ( ) rend() rend() 函数

void Test11()
{
	string s1 = "hello world";

	string::reverse_iterator rit = s1.rbegin();
	while (rit != s1.rend())
	{
		cout << *rit;
		
		//这里是++而不是--,它的++是倒着走的,因为是反向迭代器
		++rit;
	}
	cout << endl;
}

运行结果:

在这里插入图片描述

  
  我们可以这样来理解:rbegin指向最后一个有效位置rend指向第一个字符的前一个位置。当然,其实际底层并不一定是这样,只是为了方便我们理解
  

在这里插入图片描述

  

在这里插入图片描述

rbegin函数接口

  

在这里插入图片描述

rend函数接口

  而同样,反向迭代器也是有 c o n s t const const版本 的:const_reverse_iterator。这里就不再演示了

  
  

5.3、 范围 for 访问遍历

5.3.1、 初识范围for访问

  • 对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此 C++11 中引入了基于范围的 f o r for for循环 f o r for for 循环后的括号由冒号 “:” 分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围
  • 范围 f o r for for 可以作用到数组容器对象上进行遍历
  • 范围 f o r for for 的底层很简单,其实就是迭代器,这个从汇编层也可以看到

我们先来看 范围 f o r for for 是怎么写的

void Test8()
{
	string s1 = "hello world";
	for (auto ch : s1)
	{
		cout << ch << " ";
	}
	cout << endl;
}

运行结果:

在这里插入图片描述

  
  范围for是一种自动赋值、自动迭代、自动判断结束的访问方式。

  • for (auto ch : s1):自动从容器 ( s 1 s1 s1) 中取其每一个值(字符),给 c h ch ch 变量。该变量的类型是 a u t o auto auto(自动推导),这里不写 a u t o auto auto 也可以写 c h a r char char,但一般都写 auto
  • {}中的内容,就是用户需要对容器中每个值的具体操作

  范围 f o r for for 看起来非常厉害,不用自动来迭代和判断,但其本质上这段代码编译以后,会替换成迭代器其底层就是迭代器,就像引用底层时指针一样。所以所有的容器,只要支持迭代器,就支持 范围 f o r for for
  
  当然,范围 f o r for for 也可以用来遍历数组:

void Test10()
{
	int array[] = { 1,2,3,4,5,6,7,8,9,10 };

	for (auto e : array)
	{
		e *= 2;
		cout << e << " ";
	}
	cout << endl;
}

运行结果:

在这里插入图片描述

  
  

5.3.2、 范围for访问的注意事项

  范围 f o r for for 访问也是可以进行修改

void Test8()
{
	string s1 = "hello world";
	for (auto ch : s1)
	{
		ch += 1;
		cout << ch << " ";
	}
	cout << endl;
}

运行结果:

在这里插入图片描述

  可是我们直接打印 s 1 s1 s1 会发现 s 1 s1 s1 的值并没有改变

void Test8()
{
	string s1 = "hello world";
	for (auto ch : s1)
	{
		ch += 1;
		cout << ch << " ";
	}
	cout << endl;

	cout << s1 << endl;
}

运行结果:

在这里插入图片描述

  这是为什么呢?

  for (auto ch : s1):可以简单理解在底层转换成迭代器后 *it 取出 s 1 s1 s1 中的值,拷贝 c h ch ch。既然是拷贝,对 c h ch ch 的修改自然无法改变 s 1 s1 s1 中的值啦。迭代器能够修改,是因为 it 相当于是指针一样,通过指针来修改当然可以修改啦

  范围 f o r for for 想修改应用 引用

void Test8()
{
	string s1 = "hello world";
	for (auto& ch : s1)
	{
		ch += 1;
		cout << ch << " ";
	}
	cout << endl;

	cout << s1 << endl;
}

运行结果:

在这里插入图片描述

  这样, c h ch ch 相当于 s 1 s1 s1 中每个值的别名,就可以对 s 1 s1 s1 的值进行修改啦

  
  

5.3.3、 auto关键字

5.3.3.1、 auto关键字的简单认识

在这里补充 2 个 C++11 的小语法

  • 在早期 C/C++ 中 a u t o auto auto 的含义是:使用 a u t o auto auto 修饰的变量,是具有自动存储器的局部变量,后来这个功能没什么用,没废除了
  • C++11 中,标准委员会变废为宝赋予了 a u t o auto auto 全新的含义,即: a u t o auto auto 不再是存储类型指示符,而是作为一个新的类型指示符来指示编译器 a u t o auto auto 声明的变量必须由编译器在编译时期推导而得
int func1()
{
	return 10;
}
void Test9()
{
	int a = 10;
	auto b = a;
	auto c = 'a';
	auto d = func1();
	
	//typeid().name()可以帮助我们看变量的类型
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;
	
}

typeid().name()可以帮助我们看变量的类型。
  
运行结果:

在这里插入图片描述

  
但是 a u t o auto auto 不能直接这样定义:

auto e;

   a u t o auto auto 的具体类型是 根据右边的表达式或者返回值来推导 的,上述定义方式无法推导出 e e e 的类型,这样就不知道给 e e e开多大的空间
  
  auto的价值主要是简化代码,如果类型太长,我们可以使用 a u t o auto auto 让编译器自己来推导。

string::iterator it = s1.begin();
auto it = s1.begin();

map<string, string>::iterator mit = dict.brgin();
auto mit = dict.begin();

  但是 a u t o auto auto 也有一些缺陷,某种程度上 a u t o auto auto 减小了代码的可读性。比如上述代码我们不能一眼看出 i t it it 的类型就是string::iterator
  
  

5.3.3.1、 auto关键字的注意事项
  • auto 声明指针类型时,用 a u t o auto auto a u t o auto auto* 没有任何区别,只是 a u t o auto auto* 必须是指针。但是用 a u t o auto auto 声明引用类型时,必须加 &
void Test9()
{
	int x = 10;
	auto y = &x;
	auto* z = &x;
	auto& m = x;

	cout << typeid(x).name() << endl;
	cout << typeid(y).name() << endl;
	cout << typeid(z).name() << endl;
	cout << typeid(m).name() << endl;
}

在这里插入图片描述

  

  • 当在同一行声明多个变量时,这些变量必须是相同类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出的类型定义其他变量
auto aa = 1, bb = 2;

// 编译报错:error C3538: 在声明符列表中,“auto”必须始终推导为同一类型
auto cc = 3, dd = 4.0;

  

  • a u t o auto auto 不能直接用来声明数组
// 编译报错:error C3318: “auto []”: 数组不能具有其中包含“auto”的元素类型
auto array[] = { 4, 5, 6 };

  

  • a u t o auto auto 不能做参数,但是 a u t o auto auto 可以做返回值,但一定要谨慎使用
auto func1()
{
	auto a = 1;
	return a;
}
auto func2()
{
	return func1();
}
auto func3()
{
	return func2;
}
int main()
{
	auto ret = func3();
	return 0;
}

  像这样, r e t ret ret 的类型是什么?看 f u n c 3 func3 func3 的返回值, f u n c 3 func3 func3 去看 f u n c 2 func2 func2 f u n c 2 func2 func2 去看 f u n c 1 func1 func1。如果每个函数又有一大堆逻辑,那么代码效率将会很低,可读性也很低

  
  

6、 string类的容量操作

函数名称功能说明
s i z e size size (重点)返回字符串有效字符长度
l e n g t h length length返回字符串有效字符长度
m a x max max_ s i z e size size返回字符串的最大长度
c a p a c i t y capacity capacity返回空间总大小
e m p t y empty empty (重点)检查字符是否为空串,是返回 t r u e true true,否则返回 f a l s e false false
c l e a r clear clear (重点)清空有效字符
r e s e r v e reserve reserve (重点)为字符串预留空间
r e s i z e resize resize (重点)将有效字符的个数改成 n n n, 多出的空间用字符 c c c 填充

  
  

6.1、 reserve

   r e s e r v e reserve reserve 函数的作用是为字符串预留空间

在这里插入图片描述

void test10()
{
	string s1;
	s1.reserve(50);
	cout << s1.capacity() << endl;
}

在这里插入图片描述

  程序实际上开辟的空间往往是大于等于程序员所要求的空间的,这是为了遵循对齐原则。上述就是对齐到 64,因为 ‘\0’ 是不计入空间的,所以为 63

  这样,我们就能通过 r e s e r v e reserve reserve 函数提前在 s t r i n g string string 中开辟空间,以减少扩容的次数了。
  

但是,当预留空间小于原空间,甚至小于字符个数呢?

  • s i z e size size < n n n < c a p a c i t y capacity capacity :C++ 并没有做出明确规定,编译器可自行选择缩容还是不做处理
  • n n n < s i z e size size:C++ 也没有做出明确规定,编译器也是自行选择是否缩容,但有一点:即使缩容也不改变字符的长度

  在VS编译器中,两种情况都是不缩容

void test11()
{
	string s1 = "hello world hello world hello world";
	cout << "字符个数:" << s1.size() << endl;
	cout << "当前空间" << s1.capacity() << endl;

	s1.reserve(40);
	cout << "当前空间" << s1.capacity() << endl;

	s1.reserve(20);
	cout << "当前空间" << s1.capacity() << endl;
}

在这里插入图片描述

  

在这里插入图片描述

  
  

6.1、 resize

在这里插入图片描述

   r e s i z e resize resize调整字符串的大小,即将字符串调整为 n n n 个字符的长度

  • n 小于当前字符串长度将当前值将缩短为其前 n n n 个字符,并 删除 超出第 n n n 个字符的字符。

  • n 大于当前字符串长度:则通过在末尾插入所需数量的字符来扩展当前内容,以达到 n n n 的大小 (若空间不够,则进行扩容)。如果指定了 c c c,则新元素将初始化为 c c c 的副本,否则,它们是值初始化字符(空字符)。

  

  • n n n < s i z e size size
void test12()
{
	string s1 = "hello world";
	cout << " 字符个数:" << s1.size() << endl;
	cout << " 空间大小:" << s1.capacity() << endl << endl;

	s1.resize(5);
	cout << " 修改后字符个数:" << s1.size() << endl;
	cout << " 修改后空间大小:" << s1.capacity() << endl;
}

在这里插入图片描述

  

  • s i z e size size < n n n < c a p a c i t y capacity capacity
void test12()
{
	string s1 = "hello world";
	cout << " 字符个数:" << s1.size() << endl;
	cout << " 空间大小:" << s1.capacity() << endl << endl;

	s1.resize(13);
	cout << " 修改后字符个数:" << s1.size() << endl;
	cout << " 修改后空间大小:" << s1.capacity() << endl;
}

在这里插入图片描述

  

  • n n n > c a p a c i t y capacity capacity
void test12()
{
	string s1 = "hello world";
	cout << " 字符个数:" << s1.size() << endl;
	cout << " 空间大小:" << s1.capacity() << endl << endl;

	s1.resize(20);
	cout << " 修改后字符个数:" << s1.size() << endl;
	cout << " 修改后空间大小:" << s1.capacity() << endl;
}

在这里插入图片描述

  

在这里插入图片描述

  
  

6.2、 注意事项

  1. size()length() 方法底层实现原理完全相同,引入size() 的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()
  2. clear() 只是将 s t r i n g string string有效字符清空不改变底层空间的大小
  3. resize(size_t n)resize(size_t n, char c)都是将字符串中有效字符个数改变到 n n n。不同的是当字符个数增多时:resize(n)0 来填充多出的元素空间,resize(size_t n,charc)用字符 c 来填充多出的元素空间。注意: r e s i z e resize resize 在改变元素个数时,如果是将元素个数增多可能改变底层容量的大小,如果将元素个数减少底层空间总大小不变
  4. reserve(size_t res_arg=0):为 s t r i n g string string 预留空间,不改变有效元素个数,当 r e s e r v e reserve reserve 的参数小于 s t r i n g string string 的底层空间总大小时, r e s e r v e reserve reserve 的行为是不确定的 。

  
  

7、 string类对象的修改操作

函数名称功能说明
p u s h push push_ b a c k back back在字符串后尾插字符 c c c
a p p e n d append append在字符串后追加一个字符串
o p e r a t o r operator operator+= (重点)在字符串后追加字符或字符串
c c c_ s t r str str (重点)返回 C 格式字符串
f i n d find find从字符串 p o s pos pos 位置开始往后找字符 c c c,返回该字符在字符串中的位置
r f i n d rfind rfind从字符串 p o s pos pos 位置开始往前找字符 c c c,返回该字符在字符串中的位置
s u b s t r substr substr s t r str str 中从 p o s pos pos 位置开始,截取 n n n 个字符,然后将其返回
i n s e r t insert insert在指定位置追加字符或者字符串
e r a s e erase erase删除字符串指定部分
s w a p swap swap (重点)交换两个 s t r i n g string string 对象
f i n d find find _ f i r s t first first _ o f of of在字符串中搜索与其参数中指定的任何字符匹配的第一个字符

  
  

7.1、 operator+=

在这里插入图片描述

  

  • o p e r a t o r operator operator+= 是在后面追加字符或字符串,甚至是 s t r i n g string string 对象
void test14()
{
	string s1 = "hello";
	cout << s1 << endl;

	s1 += 'a';
	cout << s1 << endl;

	s1 += "你好";
	cout << s1 << endl;

	string s2 = "haha";
	s1 += s2;
	cout << s1 << endl;
}

在这里插入图片描述

  通常,我们对 string 对象进行尾插都是用 o p e r a t o r operator operator+=函数,因为 ‘+=’ 生动形象。当然尾插还有 push_backappend 等函数
  
  

7.2、 c_str

在这里插入图片描述

   c c c_ s t r str str 函数的功能是获取存储字符串空间的地址

   s t r i n g string string底层 简单来看如下:

class string
{
private:
	char* _str;//指向存储字符串的空间
	size_t _size;//记录当前字符串长度
	size_t _capacity;//记录当前空间大小
};

  
  而 c c c_ s t r str str 就是获取 _ s t r str str

void test16()
{
	string s1 = "hello world";
	printf("%p\n", s1.c_str());
	printf("%s\n", s1.c_str());
}

在这里插入图片描述

  
  

7.3、 swap

在这里插入图片描述

  swap函数交换两个 s t r i n g string string 对象。

  可能有小伙伴很疑惑,C++ 库中不是有 s w a p swap swap模板 吗?为什么 s t r i n g string string 库中又要写一个 s w a p swap swap函数 呢?
  这肯定是因为 s w a p swap swap模板 生成的 s w a p swap swap函数 有缺点
  
  我们一起来看下 s w a p swap swap模板 的缺点。

  • 下面是 s w a p swap swap模板 的内部实现
      在这里插入图片描述

  
  当交换两个 s t r i n g string string 对象时,生成的函数是这样的:

void swap(string& a, string& b)
{
	string c(a);
	a = b;
	b = c;
}

  

  • 如果是传统的交换,会进行 3次 深拷贝
    • 首先是用 a a a 拷贝构造 c c c
    • b b b 拷贝给 a a a
    • 最后是 c c c 拷贝给 b b b
  • 3次 拷贝都是深拷贝
      

  对自定义类型来说,深拷贝的代价是很大的,每次深拷贝都要开空间拷贝数据。而你现在还是深拷贝 3 次,效率无疑是大大降低

  
  那有没有办法提升效率呢?
  首先,我们知道, s t r i n g string string 底层类似一个顺序表:

class string
{
private:
	char* _str;//指向存储字符串的空间
	size_t _size;//记录当前字符串长度
	size_t _capacity;//记录当前空间大小
};

  

  那么我们可不可以就两个对象指针所指向的空间进行交换。这样,仅仅是内置类型进行交换效率会大大提高
  在这里插入图片描述
  
  再把两个对象中的 _size_capacity 各自交换一下,就完成啦

  
  实际上, s t r i n g string string 库中就是这么实现的

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

  而我们知道,当模板和函数命名重合时,若函数更适合,则优先调用函数。因此是不会调用库中的函数模板的。
  
  

8、 string类非成员函数

函数名称功能说明
o p e r a t o r operator operator+尽量少用,因为传值返回,导致深拷贝效率低
o p e r a t o r operator operator>> (重点)输入运算符重载
o p e r a t o r operator operator<< (重点)输出运算符重载
g e t l i n e getline getline (重点)获取一行字符串
r e l a t i o n a l relational relational o p e r a t o r s operators operators(重点)大小比较
s w a p swap swap (重点)交换两个对象

  
  

8.1、 getline

在这里插入图片描述

  getline函数是从输入流中获取一串字符串到 s t r i n g string string 对象中,以 d e l i m delim delim 为结束标志,默认是 ‘\n’

  那 g e t l i n e getline getline 有什么用呢?库中不是重载了 o p e r a t o r operator operator>> 吗?
  我们知道,用 c i n cin cin 输入字符串默认是以 “ ”“\n” 为分隔符的,因此即使读到了 “ ”“\n” c i n cin cin 也会忽略他们,将他们跳过。
  当我们想输入的字符串中有 “ ”“\n” 时,就可以用 g e t l i n e getline getline 函数
  

void test15()
{
	string s1;
	cout << "请输入字符串:";
	getline(cin, s1);
	cout << "s1内容:" << s1 << endl;
}

在这里插入图片描述

  

void test15()
{
	string s1;
	cout << "请输入字符串:";
	getline(cin, s1, '#');
	cout << endl;
	cout << "s1内容:" << endl << s1 << endl;
}

在这里插入图片描述

  
  

8.2、 swap

  非成员的 swap函数 的实际行为与成员函数中 swap 的行为是一样的,这里就不再过多介绍
  
  

9、 不同平台下string类的结构

  注:下述结构是在 32 位平台下进行验证,32位平台下指针占 4 字节

9.1、 VS 下string类的结构

  VS 下 s t r i n g string string 总共占 28 个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义 s t r i n g string string 中字符串的存储空间

  • 当字符串长度小于 16 时,使用内部固定的字符数组来存放
  • 当字符串长度大于等于16 时,使用从堆上开辟空间
union _Bxty
{ // storage for small buffer or pointer to larger one
	value_type _Buf[_BUF_SIZE];
	pointer _Ptr;
	char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;
class String
{
private:
	//vs下string类里面的成员变量大概是这样
	char _buff[16];
	char* str;
	size_t _size;
	size_t capacity;
};
int main()
{
	cout << sizeof(String) << endl;
	return 0;
}

在这里插入图片描述

  这种设计也是有一定道理的,大多数情况下字符串的长度都小于 16,那 s t r i n g string string 对象创建好之后,内部已经有了 16 个字符数组的固定空间,不需要通过堆创建,效率高。

  其次:还有一个 s i z e size size _ t t t 字段保存字符串长度,一个 s i z e size size_ t t t 字段保存从堆开辟空间的容量
  最后:还有一个指针做一下其他事情

  故总共占 16 + 4 + 4 = 28 个字节

在这里插入图片描述

  
VS 下 s t r i n g string string 的扩容

void test17()
{
	string s;
	size_t sz = s.capacity();
	cout << "原始大小:" << sz << endl;
	cout << "making s grow:" << endl;
	for (int i = 0; i < 1000; i++)
	{
		s.push_back('c');
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity change:" << sz << "\n";
		}
	}
}

在这里插入图片描述

  capacity函数所获得的空间是容纳有效字符的最大空间,是不包括 ‘\0’ 的,实际空间还要再 +1

  我们可以看到,在 VS 下,从数组转到堆开辟的空间时,是 2 倍扩容,即16 -> 32;之后在堆上的扩容都是 1.5倍 扩容

  
  

9.2、 g++ 下string类的结构

  g++ 下, s t r i n g string string 是通过写实拷贝实现的, s t r i n g string string 对象总共占4个字节,内部只含有一个指针,该指针将来指向一块堆空间,内部包含了如下字段:

  • 空间总大小
  • 字符串有效长度
  • 引用计数1
  • 指向堆空间的指针,用来存储字符串

  

在这里插入图片描述

struct _Rep_base
{
	size_type _M_length;
	size_type _M_capacity;
	_Atomic_word _M_refcount;
};

  
  g++ 下, s t r i n g string string 的扩容

在这里插入图片描述

  
运行结果:

在这里插入图片描述

  可以看到,g++ 下是标准的 2 倍扩容


  1. 引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成 1,每增加一个对象使用该资源,就给计数增加 1,当某个对象被销毁时,先给该计数减 1,然后再检查是否需要释放资源, 如果计数为 1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。想进一步了解可浏览下面两篇文章:写实拷贝、写实拷贝在读取时是缺陷的 ↩︎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/872369.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Golang | Leetcode Golang题解之第388题文件的最长绝对路径

题目&#xff1a; 题解&#xff1a; func lengthLongestPath(input string) (ans int) {n : len(input)level : make([]int, n1)for i : 0; i < n; {// 检测当前文件的深度depth : 1for ; i < n && input[i] \t; i {depth}// 统计当前文件名的长度length, isFi…

图文解析保姆级教程:Postman专业接口测试工具的安装和基本使用

文章目录 1. 引入2. 介绍3. 安装4. 使用 此教程摘选自我的笔记&#xff1a;黑马JavaWeb开发笔记16——请求&#xff08;postman、简单参数、实体参数、数组集合参数、日期参数、Json参数、路径参数&#xff09;想要详细了解更多有关请求各种参数介绍的知识可以移步此篇笔记。 1…

KNN算法

1 KNN算法简介 KNN算法思想&#xff1a;如果一个样本在特征空间中的K个最相似的样本中的大多数属于某一个类别&#xff0c;则该样本也属于这个类别。 K值过小&#xff1a;用较小领域中的训练实例进行预测。 容易受到异常点的影响K值的减小意味着整体模型变得复杂&#xff0c;容…

深度学习——强化学习算法介绍

强化学习算法介绍 强化学习讨论的问题是一个智能体(agent) 怎么在一个复杂不确定的环境(environment)里面去极大化它能获得的奖励。 强化学习和监督学习 强化学习有这个试错探索(trial-and-error exploration)&#xff0c;它需要通过探索环境来获取对环境的理解。强化学习 ag…

仕考网:公务员笔试和面试哪个难?

公务员笔试和面试哪个难?二者之间考察的方向不同&#xff0c;难度也是不同的。 笔试部分因其广泛的知识点和有限的考试时间显得难度更高一些&#xff0c;在笔试环节中&#xff0c;考生需在有限的时间内应对各种问题&#xff0c;而且同时还要面对激烈的竞争&#xff0c;在众多…

最基本的SELECT...FROM结构

第0种&#xff1a;最基本的查询语句 SELECT 字段名&#xff0c;字段名 FROM 表名 SELECT 1&#xff1b; SELECT 11,3*2&#xff1b; FROM SELECT 11,3*2 FROM DUAL&#xff1b;#dual&#xff1a;伪表 我们可以用它来保持一个平衡 这里我们的值不需要在任何一个表里&#xf…

【Java毕业设计】基于SpringBoot+Vue+uniapp的农产品商城系统

文章目录 一、系统架构1、后端&#xff1a;SpringBoot、Mybatis2、前端&#xff1a;Vue、ElementUI4、小程序&#xff1a;uniapp3、数据库&#xff1a;MySQL 二、系统功能三、系统展示1、小程序2、后台管理系统 一、系统架构 1、后端&#xff1a;SpringBoot、Mybatis 2、前端…

idea中git提交或push到远程后回退到之前的某次提交简单有效的解决方案

场景&#xff1a; 1、先模拟出团队多人开发多分支&#xff0c;在各个分支分别提交代码&#xff0c;然后都合并到master分支 2、模拟如下两个分支dev、dev-0902合并到master&#xff0c;并且合并master后push到远程了 3、假如更新版本计划有变&#xff0c;只上dev-0902分支&…

华为达芬奇人像引擎2.0,人像体验有哪些升级

对于年轻人而言&#xff0c;拍照已成为生活中不可或缺的一部分&#xff0c;不仅是为了记录世界、更重要的是成为生活的主角&#xff0c;大胆表达自己。然而很多喜欢使用手机记录生活的人&#xff0c;既希望能够实现媲美单反的影像实力&#xff0c;同时还想呈现出真实、更具自然…

PDB插件式数据库

如果 项查看根容器&#xff1a; alter session set containercdb$root; 创建插件数据库&#xff0c;一定是在root容器下进行&#xff0c;否则在其他插件数据库容器下创建插件数据库会报如下错误&#xff1a; 创建插件数据库命令&#xff1a; create pluggable database Z1 ad…

Leetcode3256. 放三个车的价值之和最大 I

Every day a Leetcode 题目来源&#xff1a;3256. 放三个车的价值之和最大 I 解法1&#xff1a;贪心 从大到下排序矩阵所有值, 记为数组v。 转化此题&#xff1a;从r*c个数中选取3个数分别给到车1&#xff0c;车2&#xff0c;和车3&#xff0c;使得符合条件的三数之和最大。…

Guitar Pro 8.2.1 Build 32+Soundbanks Win/Mac音色库 开心激活版 音乐软件Guitar Pro 8中文破解版

音乐软件Guitar Pro 8中文破解版是一个受吉他手喜爱的吉他和弦、六线谱、BASS 四线谱绘制、打印、查看、试听软件&#xff0c;它也是一款优秀的 MIDI 音序器&#xff0c;MIDI 制作辅助工具&#xff0c;可以输出标准格式的 MIDI。GP 的过人之处就在于它可以直接用鼠标和键盘按标…

毒枸杞事件启示录:EasyCVR视频AI智能监管方案如何重塑食品卫生安全防线

一、方案背景 近年来&#xff0c;食品安全问题频发&#xff0c;引发了社会各界的广泛关注。其中&#xff0c;毒枸杞事件尤为引人关注。新闻报道&#xff0c;在青海格尔木、甘肃靖远等地&#xff0c;部分商户为了提升枸杞的品相&#xff0c;违规使用焦亚硫酸钠和工业硫磺进行“…

Android之LiveTemplate注释模板

目录 效果图步骤 效果图 步骤 1.首先通过File->Setting->Editor->LiveTemplate 我是放在Android下的&#xff0c;然后点击右侧&#xff08;新版本的话不在右侧&#xff09;加号&#xff0c; 点击&#xff08;加号&#xff09;之后&#xff0c;如图 /*** author:T…

【go-zero】win启动rpc服务报错 panic: context deadline exceeded

win启动rpc服务报错 panic: context deadline exceeded 问题来源 在使用go-zero生成的rpc项目后 启动不起来 原因 这个问题原因是wndows没有启动etcd 官方文档是删除了etcd配置 而我自己的测试yaml配置有etcd&#xff0c;所以需要启动etcd 下载安装好etcd后&#xff0…

第R2周:LSTM-火灾温度预测

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 一、什么是LSTM 1.LSTM的本质 长短时记忆网络&#xff08;Long Short-Term Memory, LSTM&#xff09;的本质是一种特殊的循环神经网络&#xff08;Recurrent…

大模型构建合作性的Agent,多代理框架MetaGpt

大模型构建合作性的Agent,多代理框架MetaGpt 前言 MetaGPT 框架将标准的操作程序(SOP)与基于大模型的多智能体相结合,使用标准操作程序来编码提示,确保协调结构化和模块化输出。 MetaGPT 允许 Agent 在类似流水线的范式中扮演多中角色,通过结构化的 Agent 协作和强化领…

香橙派列出附近所有的WiFi

使用 nmcli nmcli 是 NetworkManager 的命令行工具&#xff0c;它可以用来检索和管理网络连接。 nmcli device wifi list这个命令会列出所有周围的WiFi网络。

Vitis AI 进阶认知(pybind11)

目录 1. 简介 2. 代码分析 2.1 pybind11 介绍 2.2 writefile 魔法命令 2.3 快速编译和加载 2.3.1 语法主体 2.3.2 查看模块位置 2.3.3 中间文件 2.4 编译器链接器标志 3. pybind11 语法 3.1 基础示例 3.1.1 example.cpp 3.1.2 编译 3.1.3 关键字参数 3.1.4 帮助…

java 设计模式-代理模式

目录 概述 一. 什么是代理模式 1. 举例说明 二. 代理模式作用 1. 保护代理 2. 增强功能 3. 代理交互 4. 远程代理&#xff1a; 三. 代理模式3个角色 四. 静态代理 1. 代码示例&#xff1a; 五. JDK动态代理 1. 代码示例&#xff1a; 六. CGLIB 动态代理 1.代码示…