【C++】string类的基本使用

news2024/11/30 0:36:11

层楼终究误少年,自由早晚乱余生。你我山前没相见,山后别相逢…

在这里插入图片描述

文章目录

  • 一、编码(ascll、unicode字符集、常用的utf-8编码规则、GBK)
    • 1.详谈各种编码规则
    • 2.汉字在不同的编码规则中所占字节数
  • 二、string类的基本使用
    • 1.string类的本质(管理字符的动态顺序表)
    • 2.string类对象的常见构造
    • 3.三种遍历string类对象的操作
      • 3.1 [ ] + 下标(operator[ ]的重载函数)
      • 3.2 基于范围的for循环(C++11新增特性:auto自动类型推导)
      • 3.3 迭代器(正向、反向、const正向、const反向)
      • 3.4 类中哪些函数需要实现两个版本?哪些不需要呢?
    • 4.string类对象的容量操作(reserve( ):对比vs和g++对于扩容采取策略的差异)
    • 5.string类对象的元素访问
    • 6.string类对象的修改操作(operator+= 才是yyds)
    • 7.string类对象的字符串操作(String operations)
    • 8.string类的非成员函数重载



一、编码(ascll、unicode字符集、常用的utf-8编码规则、GBK)

1.详谈各种编码规则

ASCII码表里的字符总共有多少个?(转载自百度知道博主教育达人小李的文章)

在这里插入图片描述

在这里插入图片描述

百度百科:统一码Unicode

百度百科:UTF-8编码

UTF-8兼容ascll编码,linux下默认使用的就是UTF-8的编码方式。

百度百科:GBK字库
vs编译器默认使用的编码方式是GB2312编码。

在这里插入图片描述

在这里插入图片描述
下面这篇文章写的非常不错,十分推荐大家看看。(我的建议是搞懂UTF-8的编码规则即可,UTF-16和32不常用,所以掌握UTF-8的编码原理就足够了,因为这些知识只要了解即可,知道有这么回事就行,不必过于细究。)

Unicode、UTF-8、UTF-16 终于懂了(转载自知乎博主程序员十三的文章)

2.汉字在不同的编码规则中所占字节数

utf-8中的汉字占用多少字节(转载自博客园博主醉人的文章)

在这里插入图片描述
我的vs编译器默认的编码规则就是GB2312编码,字母和数字都是一个字节,汉字占用2个字节。

在这里插入图片描述
各种编码的中文占用几个字节?Unicode、ISO 10646、UTF-8、GB-2312、GBK的区别是什么?(转载自csdn博主天上的云川的文章)

在这里插入图片描述
字符编码ASCII,GB2312,GBK,Unicode,UTF-8(转载自知乎博主sunny的文章)

二、string类的基本使用

1.string类的本质(管理字符的动态顺序表)

1.
最常用的类就是string,这些类都是基于类模板basic_string,由于类模板参char16_t、char32_t、wchar_t、的不同,实例化出对应不同需求的类,例如u16string,u32string,wstring。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

我应该用std::string、std::u16string还是std::u32string?(转载自知乎博主王万霖的文章)

2.
最常用的类就是string,实际上string就是我们数据结构初阶学习的动态顺序表,在理解上,只需要将其看成一个动态开辟的柔性数组即可

 动态增长的字符数组,string可以看成是一个管理字符的顺序表
template<class T>
class my_basic_string
{

private:
	T* _str;
	size_t _size;
	size_t _capacity;
};
typedef my_basic_string<char> string;//存UTF-8的,所以模板参数传char。如果是UTF-16,那就传char16_t。

2.string类对象的常见构造

在这里插入图片描述

1.
string的constructor构造函数里面重载了许多的string类的构造函数,其中包括多个构造函数和拷贝构造函数,由于第一次学STL,所以我们将string这个类讲的细致全面一些,以便于了解STL是怎么学习的,后面的其他容器包括string实际上只需要掌握常见的重要接口即可,遇到其他不常见的接口,只需要查询C++文献即可。

2.
我们所使用的string类被包含在< string >头文件里面,而string头文件的内容又被封装在std命名空间里面,大型项目里面建议使用域作用限定符不展开命名空间std,但在我们自己平常的学习过程中,无需太过繁琐,展开std命名空间即可。

在这里插入图片描述

3.
最常用的构造函数就是string类默认的无参构造函数和以常量字符串为参数的构造函数。
s2和s3对象的构造结果是一样的,只是书写的形式不同,s3形式看起来更像赋值重载,但他其实是进行了隐式类型转换,编译器做了优化,本质实际上是构造+拷贝构造,先根据常量字符串构造出一个string类对象,然后再将这个对象拷贝构造给对象s3。
从拷贝构造和赋值重载的定义也可以知道,s3是拷贝构造,因为赋值重载是已经存在的两个对象之间的赋值,而拷贝构造是已经存在的对象去初始化创建另外一个对象,对象s3明显不是已经存在的,所以s3是拷贝构造,编译器在这里会优化为直接构造。

在这里插入图片描述

编译器对于拷贝构造的优化

在这里插入图片描述
4.
其他可以了解一下的接口还有字符和个数作为参数的构造函数,s4即为被10个字符*初始化构造的对象。
string类中重载了+=函数,+=函数也有三种重载函数形式,分别为以常量字符串、string类对象的引用、普通字符为参数的三个运算符重载函数,这就可以方便我们将自定义类型当作内置类型使用,极大的提高了代码可读性。
string类还重载了流提取和流插入运算符,这也可以帮助我们快速的看到string类对象的内容,也提升了代码的可读性。
由此可见,重载函数和运算符重载,真是C++的伟大之处。

在这里插入图片描述
5.
除构造函数外,string类肯定还有拷贝构造默认成员函数,常见用法就是用一个已经存在的string类对象拷贝构造出一个新对象例如s5和s6。拷贝构造的另一种写法就是看起来比较像赋值重载,但本质和拷贝构造无差别,仅仅是代码形式不同而已。
实际上这里还涉及到深拷贝的问题,底层实现中,在内存里一定是存在两份动态字符数组的,如果仅仅是浅拷贝,那么在析构的时候,会出现同一块内存释放两次的情况,这就会导致越界访问已经还给操作系统的内存,势必会导致程序崩溃。当然现在我们不需要关心这些问题,因为string类已经有人帮我们写好了,但如果我们自己实现时,就需要考虑这样的问题了。后面我们会自己模拟实现string类。
还有一种适当了解的构造函数形式就是常量字符串和字符串的前n个字符作为参数的构造的重载函数,例如s7,我用计算机网络的前6个字符来构造对象s7,因为vs默认的编码规则是GB2312,所以s7的内容就是计算机。

在这里插入图片描述
6.
还有一种拷贝构造的重载形式,就是从pos位置开始,横跨我们指定的字符长度,用str串的子串来进行构造新的string类对象,并且这个重载函数的参数是半缺省参数,按照从右向左连续缺省的原则,这个参数就是npos,默认值是-1,但是类型是size_t无符号整型,也就是unsigned int,所以这个值实际上就是四十二亿九千万多,那就意味着,如果我们不指定len的大小,则构造对象时,默认使用的是从pos位置开始直到str的结尾的字串。
如果len的指定大小过大,超出str的字符长度,则以str的末尾作为结束标志,如果指定过小,则舍去相应的字符即可。

在这里插入图片描述

7.上面说了这么多重载函数的用法,但只要重点掌握三个函数即可,即为无参,常量字符串等参数的构造函数和类对象引用作为参数的拷贝构造。
注意:拷贝构造的参数必须是类对象的引用,否则将引发无穷递归调用,疯狂建立拷贝构造的函数栈帧。

#include <iostream>
#include <string>
#include <vector>
#include <list>
using namespace std;//库文件string的内容也是被封装在std命名空间里面的。
void test_string1()
{
	string s1;
	string s2("今天是个");//传参构造
	string s3 = "操作系统";//这里支持隐式类型转换,因为string类的拷贝构造函数没有explicit修饰,构造 + 拷贝构造 = 直接优化为构造。
	//上面代码看起来像是赋值重载,但实际上是构造+拷贝构造,编译器在这个地方会做优化。(详情见文章类和对象核心总结,编译器的优化)

	string s4(10, '*');

	s2 += "好日子";//运算符重载,+=的本质实际上是在堆上有一个数组,这个数组是动态开辟的。

	cout << s1 << endl;//string类当中,支持了流插入运算符重载。所以我们可以将自定义类型当作内置类型使用。
	cout << s2 << endl;
	cout << s3 << endl;
	cout << s4 << endl;

	string s5(s3);//拷贝构造
	string s6 = s3;//这个也是拷贝构造,只不过写法不一样。
	//这里的拷贝构造涉及深拷贝的问题,因为两个指针指向同一数组,在释放时会出现越界访问已经还给操作系统的内存。

	cout << s5 << s6 << endl;

	string s7("计算机网络", 6);//GBK编码中汉字占2字节,utf8汉字占用3字节或4字节。
	cout << s7 << endl;

	string s8(s7, 0);//参数案例:4、3、30、最后一个参数不给,函数也有缺省值npos,这个npos是类里面声明的静态成员变量,定义在类外面。
	//static const size_t npos = -1;size_t修饰的无符号整型的-1是四十二亿九千万大小,所以可以直接看成取到字符串的末尾。
	//从第0个位置开始走4个字节的长度,那么s8就是"计算"。如果走3字节长度,则s8就是"计"。如果长度过大,到达末尾即结束。
	cout << s8 << endl;
	
	//****只需重点了解无参,常量字符串等参数的构造函数和类对象引用作为参数的拷贝构造,稍微知道字符和字符个数做参的构造函数即可****
}

3.三种遍历string类对象的操作

3.1 [ ] + 下标(operator[ ]的重载函数)

1.
如果我们现在遇到一个需求,让对象s1里面的每个字符的ascll码值都+1,那我们势必要对对象s1进行遍历操作,这个时候就可以用到operator[ ],string类中给我们实现了两个版本,一个用于普通对象,表示可以修改和访问string类对象的内容。另一个用于const修饰的对象,表示只能访问string类对象内容,不可以修改。
这两个版本的返回值是相同的,都是返回string类对象的字符的引用。利用普通版本,便可以遍历操作对象s1中的每一个字符并进行修改。

在这里插入图片描述

2.
size()函数返回有效字符的个数,不包括\0,\0是标识字符。

void test_string2()
{
	string s1("1234");
	//需求:让对象s1里面的每个字符都加1

	//如果要让字符串的每个字符都加1,肯定离不开遍历,下面学习三种遍历string的方式。
	//1.下标 + []
	for (size_t i = 0; i < s1.size(); i++)
	{
		s1[i]++;//本质上
	}
	cout << s1 << endl;//GB2312兼容ascll编码,所以++后的结果为2345.
}

3.2 基于范围的for循环(C++11新增特性:auto自动类型推导)

1.
C++11新特性,auto自动类型推导,在基于范围的for循环情况下,可以使用auto引用来操作数组s1里面的每个元素。

void test_string2()
{
	//2.范围for
	for (auto& ch : s1)//自动推导s1数组的每个元素后,用元素的引用作为迭代变量,通过引用达到修改s1数组元素的目的。
	{
		ch++;
	}
	cout << s1 << endl;
	//在上面这种需求下,范围for看起来似乎更为方便	
}

在这里插入图片描述
2.
但在将需求改为翻转字符串之后,基于范围的for循环就不适用了,而下标加方括号的方式还可以使用,并且交换函数也不用我们实现,std命名空间里面有函数模板swap,通过我们所传参数,函数模板便可以进行隐式的实例化。

函数模板的两种实例化方式(隐式和显示实例化)

3.
对于翻转,我们还可以使用reverse算法和迭代器来实现,这个放到以后的文章去讲,现在只是提一下,有个印象就好。

在这里插入图片描述

//新需求:反转一下字符串。
	size_t begin = 0, end = s1.size() - 1;//有效字符是不包含\0的,所以end下标要再往前挪动一个位置。
	//\0不是有效字符,而是标识字符。
	while (begin < end)//begin和end相等的时候,不用交换了
	{
		swap(s1[begin++], s1[end--]);//利用库函数swap
	}
	cout << s1 << endl;

	reverse(s1.begin(), s1.end());//reverse算法和迭代器,后面会讲到,这里只是提一下
	cout << s1 << endl;

3.3 迭代器(正向、反向、const正向、const反向)

1.
迭代器实际上具有普适性,对于大多数容器都可以适用,而[ ]+下标这样的使用方式只对于vector和string适用,后面学习到的list、树等就不适用了,而迭代器由于其强大的普适性依旧可以适用,这也正是迭代器学习的意义。

2.
迭代器在使用的方式和行为上比较像指针,但是它和指针还是有区别的,它既有可能是指针,又有可能不是指针。在定义时要指定类域,譬如it1的定义,就需要指定类域里面的iterator类型,begin()会返回获取第一个字符的迭代器,end()会返回最后一个字符下一个位置的迭代器,一般情况下就是标识字符\0,其实在使用上就是类似于指针,解引用迭代器就可以获得相应的字符,然后就可以对字符进行操作,从下面代码可以看出,it1不仅可以访问而且还可以修改对象s1的内容。并且除string外,vector和list也可以照常使用iterator,这也验证了iterator的普适性。

void test_string2()
{
	//3.迭代器:通用的访问形式   iterators --- 行为上像指针一样的东西,有可能是指针,也有可能不是指针
	string::iterator it1 = s1.begin();
	// string类域里面的迭代器iterator类型,定义出it1迭代器变量。
	while (it1!=s1.end())//end返回的是最后一个有效数据的下一个位置(一般情况下是\0)
	{
		*it1 += 1;
		++it1;
	}

	it1 = s1.begin();
	while (it1 != s1.end())//end返回的是最后一个数据的下一个位置的迭代器
	{
		cout << *it1 << " ";
		++it1;
	}
	cout << endl;

	vector<int> v;
	vector<int>::iterator vit = v.begin();
	while (vit != v.end())
	{
		cout << *vit << " ";
		vit++;
	}
	cout << endl;

	list<int> lt;
	list<int>::iterator ltit = lt.begin();
	while (ltit != lt.end())
	{
		cout << *ltit << " ";
		ltit++;
	}
	cout << endl;

	//****只有string和vector这两个容器能用[],后面的list、树等容器就不支持[]了。****
}

3.
除begin()和end()外,还有rbegin()和rend(),前面两个返回的是正向迭代器,后面这两个返回的是相应字符的反向迭代器,也就是从末尾开始,到开头结束,和正常的方向反过来,在使用上和正向迭代器没有任何区别。
如果嫌string::reverse_iterator 写起来比较麻烦的话,可以用auto来进行类型的自动推导,但不建议这么干,因为这会降低代码的可读性。

4.
实现Print函数时,参数采用了引用和const,因为传值拷贝需要中间变量,一旦涉及资源的传递,则代价会非常大,堆上需要重新给中间变量开辟空间,所以我们采用了传引用拷贝。但如果你用普通的迭代器类型接收begin()返回的字符迭代器,实际上会报错,仔细观察报错信息就会发现是const和非const之间无法转换,这实际上就是因为对象s被用const修饰,所以begin()返回的是const迭代器类型,而你用普通的迭代器接收,就会出现无法正常转换的问题,所以需要用string::const_iterator来接收

在这里插入图片描述

5.
const迭代器的内容只支持读,不支持修改,非const迭代器的内容既可以读也可以修改。对于begin(),end(),rbegin(),rend(),他们每个函数都实现了两个版本,一个用于普通string对象,一个用于const修饰的对象。

在这里插入图片描述
6.
const修饰的是迭代器的内容,而不是迭代器本身。相当于const修饰的是指针指向的内容,而不是指针本身。

//迭代器分类:
//正向  反向 --- 普通对象,可以遍历读写容器数据
//const正向  const反向 --- const修饰对象,只能遍历读,不能修改容器数据。

void Print(const string& s)//涉及深拷贝时,用传值拷贝代价非常大,需要在堆上重新开一份空间,所以我们用传引用拷贝,并加const修饰。
{
	//普通迭代器的内容支持读和写,无论是正向还是反向迭代器。
	//const迭代器的内容仅仅支持遍历读,而不支持写。

	string::const_iterator cit = s.begin();//普通迭代器编译无法通过,因为const修饰的对象s里的begin返回时,需要用const迭代器来接收。
	//const修饰的是迭代器变量所指向的内容,相似于指针指向的内容,而不是修饰迭代器本身,也就是指针本身,因为迭代器需要向后遍历。
	while (cit != s.end())
	{
		//*it += 1;

		cout << *cit << " ";
		cit++;
	}
	cout << endl;

	//string::const_reverse_iterator rcit = s.rbegin();
	auto rcit = s.rbegin();//如果嫌长可以用auto推导一下。因为s是const修饰,rbegin又代表反向迭代器,auto能推导出来类型。
	while (rcit != s.rend())
	{
		cout << *rcit << " ";
		rcit++;
	}
	cout << endl;
}
void test_string3()
{
	string s1("1234");
	string::iterator it = s1.begin();//正向迭代器
	while (it != s1.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	//string::reverse_iterator rit = s1.rbegin();//反向迭代器
	auto rit = s1.rbegin();//auto用于类型的自动推导,rbegin()返回的是反向迭代器。
	while (rit != s1.rend())
	{
		cout << *rit << " ";
		rit++;
	}
	cout << endl;

	Print(s1);
}

7.
在C++11中,iterators新添了4个版本的函数,在原来的4个版本上加了前缀c,表示这四个版本的函数针对于const修饰的对象所使用,但实际上这是多此一举,没啥用,原来的四个版本完全够使用了,可能有的人类和对象,重载函数等知识没学好,搞不清什么时候调用const版本,什么时候调用非const版本。

在这里插入图片描述

3.4 类中哪些函数需要实现两个版本?哪些不需要呢?

1.
在迭代器部分可以看到C++98标准的4个函数都实现了两个版本,const和非const,所以只要修改数据的函数就应该实现两个版本,这是否正确呢?

在这里插入图片描述
2.
从push_back只实现了一个版本就可以看出,上面的推论实际是不正确的,其实是否需要实现两个版本,要看函数的具体功能是什么
push_back肯定不具有读的功能,所以只需要实现非const版本即可。而那些迭代器既有可能读又有可能写,所以那些函数具有读写功能,需要实现两个版本。

在这里插入图片描述

4.string类对象的容量操作(reserve( ):对比vs和g++对于扩容采取策略的差异)

在这里插入图片描述
1.
length()和size()返回的大小都是容器有效字符的个数,但为什么会出现两个功能相同的函数呢?这其实是因为某些历史原因,C++只能向前兼容,原本length()是比较适用于string类的,但是用在其他的类上就有些奇怪,比如树,树的长度?怪不怪,所以为了增强普适性,增加了size()来表示各个容器的大小,这样就舒服很多了

2.
clear()用于抹去容器中的数据,但内存空间是不会释放的,从clear()之后调用的capacity的结果可以看到,内存上开辟的空间是没有释放的,但size会被置为0,所以在打印s时的结果为空。

在这里插入图片描述

void test_string4()
{
	string s("hello world");
	cout << s.length() << endl;//早期就是叫做length,而不是size
	cout << s.size() << endl;//为了容器的普适性,用size更好一点,因为哈希表、树等容器叫做length不太好,叫size更好一些。
	//所以后面用size更舒服一些,容器的大小。
	
	cout << s.capacity() << endl;//空间满了就扩容,和数据结构里的动态顺序表很相似。
	
	cout << s.max_size() << endl;//max_size是写死的,具体大小看编译器的底层实现,我的编译器是四十二亿九千万的一半。
	string ss;
	cout << ss.max_size() << endl;//这是个垃圾函数,不需要我们关心。

	cout << s << endl;
	cout << s.capacity() << endl;
	s.clear();//clear是否释放s字符数组的空间是不确定的,因为不同的编译器采用的STL版本是不同的,底层实现上有区别。

	cout << s << endl;
	cout << s.size() << endl;
	cout << s.capacity();//通过capacity函数的返回值我们可以知道,vs编译器下的clear是不会释放字符串对象s的空间的。
}

3.
下面的代码可以帮助我们看到,在容器空间大小不够时,vs编译器对于扩容采取的具体策略,将这段代码放到linux的g++编译器下,我们也可以看到g++对于扩容采取的具体策略。

在这里插入图片描述
4.
从上面可以看到两个编译器进行扩容的过程,但我们知道扩容的代价实际上是非常大的,因为异地扩容还需要进行原来数据的拷贝,会降低程序的效率,所以能不能在已知空间大小的情况下,一次性提前开辟好呢?避免多次的扩容造成的效率降低
这个时候可以使用reserve()接口来提前指定好capacity的大小,一次就开辟好对应大小的空间,为字符串预留好空间

由于对齐因素,vs编译器底层实际开辟的空间要稍微大一些。
在这里插入图片描述

void TestPushBack()
{
	string s;
	s.reserve(1000);//reserve的价值就是在已知插入数据的个数前提下,提前开辟好一份空间,避免开始时多次的扩容导致的效率降低。
	//在已知容器所需最大空间的情况下,我们实际可以提前开好最大空间,这样就不需要扩容了,因为扩容的代价很大,尤其是异地扩容。
	//但是capacity()接口是只读的不可被修改,所以通过capacity接口来指定容器的最大空间是不行的,这个时候就可以使用接口reserve来指定。

	//vs由于对齐会将扩容的空间开辟的比我们显示的要大一些,这是因为内存对齐原则,后面学习到内存池的空间配置器时,就知道为什么对齐了

	//提前预留的空间不够时,push_back还是会自动扩容的,继续扩大capacity的值,我们不用担心。
	size_t sz = s.capacity();
	cout << "capacity changed: " << sz << '\n';
	

	cout << "making s grow:\n";
	for (int i = 0; i < 1000; ++i)
	{
		s.push_back('c');
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}
void test_string5()
{
	TestPushBack();
}

在这里插入图片描述

5.
reserve()改变容器的capacity,resize()改变容器的size,对于字符串hello world来说,传参n的大小对应了resize有三种情况。

在这里插入图片描述
在这里插入图片描述

void test_string6()
{
	string s1("hello world");
	s1.resize(5);//删除数据,只保留前5个字符,后面的字符是都被替换为\0还是仅仅替换第6个字符为\0是不重要的,这取决于编译器的实现者。 
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	cout << s1 << endl << endl;

	string s2("hello world");
	s2.resize(15,'x');//插入数据,如果不指定字符c,则默认用空字符\0来填充。
	cout << s2.size() << endl;
	cout << s2.capacity() << endl;
	cout << s2 << endl << endl;

	string s3("hello world");
	s3.resize(20,'x');//发生扩容,capacity会改变。
	cout << s3.size() << endl;
	cout << s3.capacity() << endl;
	cout << s3 << endl << endl;

}

6.
C++11新引进的shrink_to_fit()函数用于将capacity减少到和size相等,

在这里插入图片描述

5.string类对象的元素访问

在这里插入图片描述
1.
operator[]和at的作用一致,都是返回动态顺序表某一位置的字符。
但发生越界访问时,operator[]采用assert断言报错的方式进行解决,而at会抛异常。

在这里插入图片描述
2.
C++11引入的back和front用于返回动态数组的首尾元素,但这两个函数实际上不怎么常用,因为operator[0]和operator[s.size()-1]便可以取到顺序表的首尾元素

在这里插入图片描述

6.string类对象的修改操作(operator+= 才是yyds)

在这里插入图片描述

1.
push_back()用于单个字符的尾插,如果要向容器中增加字符串的话,利用push_back()的效率就非常低,因为我们需要一个一个字符的尾插。

在这里插入图片描述

2.
append函数重载实际和我们的构造函数非常的相似,就好比你在路上碰到一个姑娘,你觉得她非常的眼熟,你焦急的跑过去看她的脸庞,结果发现她竟和你的初恋长的一模一样,你满怀期待的细细询问,发现此女子并非当初的那个她,仅仅只是模样十分相似罢了,你心灰意冷的走开,逐渐开始回味你们美好的当初,但一切好似黄粱一梦,终散为烟……
黄昏里,你又踏上了孤独的旅途,终究还是要回到路上…

append就是那个姑娘,构造函数就是你的初恋。append最常用的接口依旧是参数为常量字符串,其他接口含义这里不做介绍,因为与前面所讲的构造函数十分相似。
在这里插入图片描述

3.
operator+=是非常好用的string类对象修改操作函数,运算符重载帮助我们使用自定义类型在形式上十分像使用内置类型,这极大的提升了代码的可读性,堪称string类对象修改函数的yyds,其重载函数有三种形式,分别是常量字符串,string类对象的引用,char型字符c。

在这里插入图片描述

void test_string7()
{
	/*string s1("hello world ");//函数重载的强大
	s1.push_back('x');
	s1.push_back('x');
	s1.append(" hello linux");
	cout << s1 << endl;

	string s2(" hello C++");
	s1.append(s2);
	cout << s1 << endl;*/

	string s1("hello world ");//运算符重载的强大
	s1 += '!';
	s1 += "hello windows";
	cout << s1 << endl;

	string s2(" hello C++");
	s1 += s2;
	cout << s1 << endl;
}

4.
insert和erase用于在数组的某个具体位置插入或删除字符,但对于string类,不建议使用insert和erase,因为我们知道对于顺序表(string类对象)来说,发生插入和删除数组中某个数据时,要进行其他数据的挪动,代价非常大,所以不到万不得已,不要轻易的使用这两个函数。
erase有自己的缺省值,如果我们用缺省值,那就是npos,-1的补码也就是全1,全1被当作无符号整型处理将会非常大,所以如果不给erase指定删除字符的个数,则会从指定位置pos处后面的所有字符进行删除。

在这里插入图片描述

void test_string8()
{
	string s("hello world");
	s.insert(0, "wyn");//头部插入,要挪动数据,效率不好,频繁头插会降低效率。
	cout << s << endl;

	s.erase(0, 3);
	cout << s << endl;
	s.erase(5, 100);//给的数字过大,则有多少删多少。
	cout << s << endl;
	s.erase(0);//npos默认值是二十一亿多,所以直接删完。-1存的是补码,也就是全1,全1看作无符号整数就会非常大。
	cout << s;
}

5.
assign用于将原有字符串用新的字符串进行替代,参数格式与构造函数也非常的相似,assign实际上对比的是赋值重载,因为赋值重载只能将一个字符串完整的赋值给另一个字符串,而assign不仅可以操作完整的字符串,字符串的substr也可以进行操作,例如代码中,我用了"hello wyn lalala"的前9个字符取代了原来的字符串s1.
replace与assign不同的是,replace进行的不是整体字符串的替代,而是字符串中部分字符的替换。例如下面代码中的s2的前5个字符被替换为"hi"

在这里插入图片描述
6.
以前在学习C语言时,比较常见的一道题是将字符串中的空格替换为类似于20%这样的字符串,在C语言阶段由于原地替换的代价较大,我们一般会采用重新开辟一块数组,然后从前向后遍历字符串,将每个字符尾插到新的数组里面,遇到空格时,就将"20%"分开尾插到新数组里面,直到字符串遍历结束。
在C++阶段中,这样的问题就显得比较简单了,因为我们有库提供的string,我们可以用find接口配合replace接口来进行字符串中空格的替换,题目解决起来就简单了许多。
除这样的方法,也是可以采用新开辟数组的方式,C++中只要新创建一个string类对象即可,我们用范围for进行遍历循环,利用尾插的思想进行空格的替换,有operator+=和范围for的帮助,解决起来同样很轻松。

void test_string9()
{
	//assign和replace的功能差别巨大
	string s1("hello linux and windows");
	string s2("hello linux and windows");
	string s3(s2);
	string s4(s2);
	//assign对比的是赋值重载,赋值重载只能将一个对象完整的赋值给另一个对象,assign可以将一个对象的某部分进行赋值。

	s1.assign("hello wyn lalala",9);
	cout << s1 << endl;

	s2.replace(0, 5, "hi");//还是不建议用replace,因为他还是要挪动数据,导致效率降低。
	cout << s2 << endl;

	//将s3中的所有空格替换为百分号
	size_t pos = s3.find(' ', 0);
	while (pos != string::npos)
	{
		s3.replace(pos, 1, "20%");//replace的效率非常低
		pos = s3.find(' ', pos);
	}
	cout << s3 << endl;

	string ret;
	ret.reserve(s4.size() + 10);//提前开好空间大小,避免扩容带来的效率降低
	for (auto ch : s4)
	{
		if (ch != ' ')
			ret += ch;
		else
			ret += "20%";
	}
	cout << ret << endl;

}

7.string类对象的字符串操作(String operations)

在这里插入图片描述

1.
find和rfind用于进行字符串中某部分字符的查找,查找的对象可以是字符,string类对象,常量字符串等等,我们可以指定开始查找的位置等等,如果不指定pos,则默认从字符串的开头进行查找,rfind与find一样,只不过rfind会将最后一个字符认定为开始,倒着去进行查找,有点像reverse_iterator反向迭代器,

在这里插入图片描述
2.
substr可以用来截取字符串中的某一部分,并将这一部分重新构造出一个string类对象然后返回,需要我们指定开始截取的位置和需要截取的长度,如果不指定截取长度,则默认从截取位置向后将所有的字符串进行截取,因为缺省值是npos.

在这里插入图片描述

3.
如果要让我们截取某一字符串的后缀名,我们就可以用find和substr配合进行使用,截取到字符串的后缀名。
在linux中的文件名后缀有很多组合在一起的,所以这时候如果要查找字符’.'的位置,我们可以从后往前查,利用rfind即可做到。

void test_string11()
{
	// "Test.cpp"
	string file;
	cin >> file;
	//要求取file的后缀
	int pos = file.find('.');
	if (pos != string::npos)
	{
		//string suffix = file.substr(pos, file.size() - pos);//计算.后面的字符有多少
		string suffix = file.substr(pos);//利用缺省值npos作为缺省参数
		cout << suffix << endl;
	}

	// "Test.cpp.zip.tar"
	//要求取file的后缀
	string file2;
	cin >> file2;
	pos = file.rfind('.');
	if (pos != string::npos)
	{
		string suffix = file2.substr(pos);
		cout << suffix << endl;
	}
	
}

4.
c_str用于返回C语言式的字符串,类型是常量字符串这个接口的设计主要是为了让C++能够和C语言的接口配合起来进行使用。
例如C语言中某些文件操作接口,参数要求传字符串,这个时候可以用c_str()来实现常量字符串的传参,让C++和C语言接口能够配合起来进行使用。
data的作用和c_str相同,他也是历史遗留下来的接口,我们只要用c_str就可以了,这个更加常用一些。

在这里插入图片描述
在这里插入图片描述

void test_string10()
{
	//c_str可以让C++更好的兼容C语言,data的功能和c_str类似,但平常都用c_str。
	string file("test.cpp");
	FILE* fp = fopen(file.c_str(), "r");//"r"代表常量字符串,'r'代表char型的字符,两者的权限是不一样的,一个可读可写,一个只读。
	assert(fp);
	char ch = fgetc(fp);
	while (ch != EOF)
	{
		cout << ch;
		ch = fgetc(fp);
	}
	fclose(fp);
}

8.string类的非成员函数重载

在这里插入图片描述

1.
getline是命名空间std封装的一个函数,它的作用有点类似于C语言的fgets,都是用于获取一行字符串,默认的scanf和cin都是以空格和换行符作为字符串的分隔,所以当出现含有空格的字符串需要输入时,cin就不起作用了,getline就派上用场了。
下面这道题就是经典的getline的使用场景题目。

在这里插入图片描述

#include <iostream>
#include <string>
using namespace std;

int main() 
{
    string str;
    getline(cin,str);
    int pos = str.rfind(' ');
    cout << str.size() - pos  - 1 << endl;
}

在这里插入图片描述
2.
下面是string类对象的关系运算符重载函数,每一个运算符都重载了三个形式,实际上是为了满足多种使用场景。
例如在比较字符串和string类对象时,运算符左右两侧的类型由于写法不同导致类型不同,则对应的运算符重载为了满足不同的写法,就必须实现多个重载函数。
而d1+10可以支持,是因为日期类有自己的类成员函数,但如果改成10+d1就无法调用对应的函数了,因为10抢了类成员函数中this指针的位置。

在这里插入图片描述

void test_string12()
{
	string s1("1111111");
	const char* p2 = "2222222";
	p2 == s1;
	s1 == p2;
}

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Hive---Hive语法(一)

Hive语法&#xff08;一&#xff09; 文章目录Hive语法&#xff08;一&#xff09;Hive数据类型基本数据类型&#xff08;与SQL类似&#xff09;集合数据类型Hive数据结构数据库操作创建库使用库删除库表操作创建表指定分隔符默认分隔符&#xff08;可省略 row format&#xff…

逆向工具之 unidbg 执行 so

1、unidbg 入门 unidbg 是一款基于 unicorn 和 dynarmic 的逆向工具&#xff0c; 可以直接调用 Android 和 IOS 的 so 文件&#xff0c;无论是黑盒调用 so 层算法&#xff0c;还是白盒 trace 输出 so 层寄存器值变化都是一把利器&#xff5e; 尤其是动态 trace 方面堪比 ida tr…

零基础机器学习做游戏辅助第十四课--原神自动钓鱼(四)yolov5目标检测

一、yolo介绍 目标检测有两种实现,一种是one-stage,另一种是two-stage,它们的区别如名称所体现的,two-stage有一个region proposal过程,可以理解为网络会先生成目标候选区域,然后把所有的区域放进分类器分类,而one-stage会先把图片分割成一个个的image patch,然后每个im…

关于SqlServer高并发死锁现象的分析排查

问题描述 通过定期对生产环境SqlServer日志的梳理&#xff0c;发现经常会出现类似事务与另一个进程被死锁在资源上&#xff0c;并且已被选作死锁牺牲品&#xff0c;请重新运行该事务的异常&#xff0c;简单分析一下原因&#xff1a;在高并发场境下&#xff0c;多个事务同时对某…

Ubuntu 使用Nohup 部署/启动/关闭程序

目录 一、什么是nohup&#xff1f; 二、nohup能做什么&#xff1f; 三、nohup如何使用&#xff1f; 四、怎么查看/关闭使用nohup运行的程序&#xff1f; 命令 实例 一、什么是nohup&#xff1f; nohup 命令运行由 Command参数和任何相关的 Arg参数指定的命令&#xff0c…

【微信小程序】--WXML WXSS JS 逻辑交互介绍(四)

&#x1f48c; 所属专栏&#xff1a;【微信小程序开发教程】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#…

Kotlin 34. recyclerView 案例:显示列表

Kotlin 案例1. recyclerView&#xff1a;显示列表 这里&#xff0c;我们将通过几个案例来介绍如何使用recyclerView。RecyclerView 是 ListView 的高级版本。 当我们有很长的项目列表需要显示的时候&#xff0c;我们就可以使用 RecyclerView。 它具有重用其视图的能力。 在 Re…

【C语言】-程序编译的环境和预处理详解-让你轻松理解程序是怎么运行的!!

作者&#xff1a;小树苗渴望变成参天大树 作者宣言&#xff1a;认真写好每一篇博客 作者gitee:gitee 如 果 你 喜 欢 作 者 的 文 章 &#xff0c;就 给 作 者 点 点 关 注 吧&#xff01; 程序的编译前言一、 程序的翻译环境和执行环境二、 详解翻译环境2.1编译环境2.1.1预编…

代码随想录算法训练营第七天 | 454.四数相加II 、 383. 赎金信、15. 三数之和、18. 四数之和 、总结

打卡第七天&#xff0c;还是哈希表。 今日任务 454.四数相加II383.赎金信15.三数之和18.四数之和总结 454.四数相加II 代码随想录 class Solution { public:int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, ve…

单元测试面试秘籍分享

1. 什么是单元测试 “在计算机编程中&#xff0c;单元测试又称为模块测试&#xff0c;是针对程序模块来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中&#xff0c;一个单元就是单个程序、函数、过程等&#xff1b;对于面向对象编程&#xff0c;最…

j-vxe-table 下拉搜索选择框数据加载过多导致前端崩溃问题

Jeeg-boot j-vxe-table 下拉搜索选择框数据加载过多导致前端崩溃问题 最近用到了Jeeg-boot j-vxe-table的组件&#xff0c;这组件时真J8难用&#xff0c;还好多BUG&#xff0c;想用个slot插槽也用不了&#xff0c;好像官方写了个基础就没怎么管了。&#x1f611; 问题&#xf…

google hacker语句

哎&#xff0c;我就是沾边&#xff0c;就是不打实战(&#xffe3;o&#xffe3;) . z Z 文章目录前言一、什么是谷歌Docker&#xff1f;二、受欢迎的谷歌docker语句谷歌docker的例子日志文件易受攻击的 Web 服务器打开 FTP 服务器SSH私钥电子邮件列表实时摄像机MP3、电影和 PDF…

php调试配置

错误信息输出 错误日志 nginx把对php的请求发给php-fpm fastcgi进程来处理&#xff0c;默认的php-fpm只会输出php-fpm的错误信息&#xff0c;在php-fpm的errors log里也看不到php的errorlog。原因是php-fpm的配置文件php-fpm.conf中默认是关闭worker进程的错误输出&#xff0…

【MySQL进阶】 锁

&#x1f60a;&#x1f60a;作者简介&#x1f60a;&#x1f60a; &#xff1a; 大家好&#xff0c;我是南瓜籽&#xff0c;一个在校大二学生&#xff0c;我将会持续分享Java相关知识。 &#x1f389;&#x1f389;个人主页&#x1f389;&#x1f389; &#xff1a; 南瓜籽的主页…

Mybatis源码学习笔记(四)之Mybatis执行增删改查方法的流程解析

1 Mybatis流程解析概述 Mybatis框架在执行增伤改的流程基本相同&#xff0c; 很简单&#xff0c;这个大家只要自己写个测试demo跟一下源码,基本就能明白是怎么回事&#xff0c;查询操作略有不同&#xff0c; 这里主要通过查询操作来解析一下整个框架的流程设计实现。 2 Mybat…

【python】argparse 模块的使用、Pycharm中使用argparse

目录1、简介2、使用步骤1&#xff09;导入argparse模块&#xff0c;并创建解释器2&#xff09;添加所需参数3&#xff09;解析参数3、使用 pycharm 传递参数给 argparse1、简介 argparse 模块是 Python 标准库中提供的一个命令行解析模块&#xff0c;它可以让使用者以类似 Uni…

给安全平台编写插件模块的思路分享

一、背景 最近在GitHub看到一个新的开源安全工具&#xff0c;可以把工具都集成到一个平台里&#xff0c;觉得挺有意思&#xff0c;但是平台现有的工具不是太全&#xff0c;我想把自己的工具也集成进去&#xff0c;所以研究了一番 蜻蜓安全工作台是一个安全工具集成平台&#x…

我的零分周赛:CSDN周赛第30期,成绩“0”分,天然气定单、小艺读书、买苹果、圆桌

CSDN周赛第30期&#xff0c;成绩“0”分&#xff0c;天然气定单、小艺读书、买苹果&#x1f34e;、圆桌。 (本文获得CSDN质量评分【91】)【学习的细节是欢悦的历程】Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经”教程《 python 完全自学教…

steam搬砖项目,小投入高回报,可放大操作,(内附教学资料)

我必须要说&#xff0c;steam搬砖项目就是全网门槛最低的副业&#xff0c;有手就行&#xff01; 本人90后底层员工一枚&#xff0c;新入csgo搬砖项目&#xff0c;轻松翻身 什么做抖音、海外问卷、直播卖货&#xff0c;电商等等对比我这个都是小钱。我这个方法是利用了大部分人…

C++线程/阻塞/同步异步----2

本章节内容为记录改写RTK代码时&#xff0c;学习的知识 同步和异步区别 1.定义不同&#xff1a;同步需要将通信双方的时钟统一到一个频率上&#xff0c;异步通信发送的字符间隔时间可以是任意的; 2.准确性不同&#xff1a;同步通信需要比较高精度的精确度&#xff0c;异步则不…