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

一、string类的由来

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

面向过程编程OPP:Procedure Oriented Programming,是一种以事物为中心的编程思想。主要关注“怎么做”,即完成任务的具体细节。

面向对象编程OOP:Object Oriented Programming,是一种以对象为基础的编程思想。主要关注“谁来做”,即完成任务的对象。

于是在C++设计过程中,就添加了string类,用来专门管理字符串。

 string这个类是被typedef出来的,basic_string是一个类模板,string内部原理可以理解为是一个动态开辟的顺序表,每个元素是一个char类型的字符。我们知道一说到字符串一定牵扯到编码问题,比如我们汉字的编码(unicode)和英文字母(ascii)的编码方式可能就会不同,常见的编码方式有ASCII,UTF-8,UTF-16,UTF-32等等,string是用模板实例化出来的一个类,它的编码方式就是常见的UTF-8。UTF-8编码的一个主要优点是它向后兼容ASCII,即任何ASCII字符在UTF-8中都使用相同的单字节表示。所以,它的成员变量都是char类型。有人会问为什么不直接搞个ASCII编码的字符串类出来,那就忽略了一个问题,汉字字符串怎么办?

u16string也是通过basic_string模板实例化出来的一个类,它的编码方式是UTF-16。

u32string同样是通过basic_string模板实例化出来的一个类,它的编码方式是UTF-32。

这里我们主要讲string这个类。

 二、string类的基本使用

在使用string类时,必须包含#include<string>这个头文件以及using namespace std;如果不加using namespace std;创建对象时必须指明命名空间(std::string)。本篇主要讲如何使用,深层的东西不过多涉及。

1、构造函数

C++98版本下有7个构造函数,我们这里只说C++98,不谈论C++11。

接下来我们来看看它们是怎么使用的:

int main()
{
	string s1;  //(1)默认构造函数,空串
	string s2("hello world"); //(4)传参构造
	string s3(s2);  //(2)拷贝构造

	cout << s1 << endl;  //重载了流插入,能够打印输出string类型的对象
	cout << s2 << endl;
	cout << s3 << endl;

	//cin >> s1;  //也重载了流提取,能够向string类型的对象中输入值
	//cout << s1 << endl;  

	string s4(s2, 6, 5);//(3)s2中,下标为6的字符向后拷贝5个字符初始化给s2
	//假设s2下标为6的字符后的字符不够5个,则拷贝到结尾即可
	 cout << s4 << endl;

	 string s5(s2, 6);//(3)
	//我们可以看到库中第三个参数有个缺省值npos(size_t类型)
	//它是类中静态成员变量,值为-1,-1在内存中存储就是32个1,
	//因为npos是size_t类型也就是无符号整形,即npos就是整数的最大值
	//它表示的意思就是从某个下标位置开始拷贝到结尾
	 cout << s5 << endl;

	string s6("hello world",5);//(5)拷贝第一个参数字符串的前5个字符初始化给s6
	cout << s6 << endl;

	string s7(3,'x'); //(6)用3个'x'字符初始化给s7
	cout << s7 << endl;

	return 0;
}

运行结果:

2、析构函数

析构函数我们不需要使用,因为编译器会自动帮我们调用来释放空间。构造函数需要我们写是因为初始化的形式是多样的。

3、赋值重载

int main()
{
	string s1("hello world");
	string s2("xxx");
	cout << s1 << endl;
	cout << s2 << endl;

	s1 = s2; //(1)对象参数类型重载
	cout << s1 << endl;
	cout << s2 << endl;


	s1 = "hah"; //(2)字符串参数类型重载
	cout << s1 << endl;
	cout << s2 << endl;

	s2 = 'q'; //(3)字符参数类型重载
	cout << s1 << endl;
	cout << s2 << endl;

	return 0;
}

4、重载[]

string可以像其他内置类型一样直接用下标引用操作符[]来访问内部元素。像下面这样:

int main()
{
	string s1("hello world");

	cout << s1[0] << endl; //h
	cout << s1[1] << endl; //e

	return 0;
}

我们知道自定义类型不能直接通过下标引用操作符来访问内部元素,而string的底层其实是在类内对下标引用操作符[]进行了重载。我们可以简单想象一下它的实现:

class string
{
public:
	char& operator[](int i)
	{
		assert(i <= _size);
		return _str[i];
	}
private:
	char* _str; //指向空间的起始位置
	int _size; //记录元素个数
	int _capacity; //由于要扩容,所以这里需要记录容量大小
};

 能用引用返回吗?为什么要引用返回呢?

开辟空间是在堆上开辟的,调用[]结束后,空间还在,所以能用引用返回。至于为什么要用引用返回,第一,减少一次拷贝构造;第二,也是最重要的一点,可以修改变量的值。这也和下标引用操作符的功能进行了重合。

int main()
{
	string s1("hello world");

	cout << s1[0] << endl; //h
	cout << s1[1] << endl; //e

    s1[0] = 'x'; //支持修改,也印证了我们的想象
    s1[1] = 'x';
    
    cout << s1[0] << endl; //x
	cout << s1[1] << endl; //x

	return 0;
}

内置类型越界访问数组程序不会崩溃,也不会报错。

int main()
{
	int a[3] = { 1,2,3 };
	cout << a[5] << endl; //越界访问
	return 0;
}

 运行结果:

说明了越界访问,编译器也不管,但我们在类中重载下标引用操作符时,如果越界访问,直接报错,这样就会更好。所以我们加了一句"assert(i < _size);",可以避免发生越界情况。

我们可以来验证一下我们的猜想是否正确:

由此可见,string底层就是有这一机制的。 

在此,介绍几种遍历成员的方式:

int main()
{
	string s("hello world");

	//方式1
	for (size_t i = 0;i < s.size();i++)
	{
		cout << s[i] << " ";
	}
	cout << endl;

	//方式2
	//iterator是STL六大组件之一的迭代器
	//使用迭代器必须指明在哪个容器的类域,每个容器都有自己的迭代器,它们的名字相同,用法相同但内部结构可能"天差地别",用法相同说明了在其他容器中也可以用这种方式来遍历成员
	//用迭代器定义出来的对象,功能上像指针,可能是指针也可能不是指针,这里暂且理解为指针,也可以理解为像指针的东西
	//begin()是返回这段空间开始位置的迭代器,end()是返回最后一个有效元素的下一个位置的迭代器
	string::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << " "; //这里可能就有人说了,不是指针怎么解引用,不是指针可能会对*进行重载,这里的*it如果改变就会修改s中字符的值
		it++; //这里同样也是,若不是指针,就会对++进行重载
	}
	cout << endl;


	//方式3(C++11)
	//范围for:从s这个容器中自动取值给e,直到取完为止
	//auto自动推导类型,这里的auto也可以写成char,但一般都写auto
	//自动赋值,自动迭代,自动判断结束
	//它的底层其实是迭代器,*it的值赋给e,支持迭代器就支持范围for
	for (auto e : s) //写起来更简单
	{
		cout << e << " "; //这里的e只是s中每个字符的拷贝,修改e的值不影响s中字符的值,若想修改在auto后面加上引用&
	}
	cout << endl;

	return 0;
}

这3种方式在性能上没有区别,都是遍历,只是写法上不同。

(1)、auto(C++11语法)

在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,会自动释放,后来局部变量都能自动释放,所以auto在这里就不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得

如果一个对象的类型太长,我们就可以用auto来简化代码。比如:

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

虽然auto可以简化代码,但在一定程度上"牺牲了"可读性。

int main()
{
	int a = 10;
	auto b = a;
	cout << typeid(b).name() << endl; //打印int,typeid可以查看对象类型
	
	return 0;
}

 auto不能去定义数组。auto不能做参数但可以做返回值,auto做返回值建议谨慎使用。auto可以同时定义两个对象,但对象的类型必须一致,否则会报错。

auto后跟*,代表是指针,必须给地址,否则会报错。

(2)、范围for(C++11语法)

范围for主要用于容器。

for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。

范围for可以作用到数组和容器对象上进行遍历,范围for的底层很简单,容器遍历实际就是替换为迭代器。

范围for用来遍历数组也是很方便的:

int main()
{
	int arr[] = { 1,2,3,4,5 };

	for (auto e : arr)
	{
		cout << e << " ";
	}
	cout << endl;
	
	return 0;
}
 (3)、迭代器
int main()
{
	string s1("hello world");

	//1、正向迭代器
	string::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " "; 
		it++; 
	}
	cout << endl;

	//2、反向迭代器
	string::reverse_iterator rit = s1.rbegin();
	while (rit != s1.rend())
	{
		cout << *rit << " ";
		rit++; //逆向走,++被重载了
	}
	cout << endl;


	const string s2("day day up");
	//3、const迭代器,只能读,不能写(修改指针指向的内容)
	//cbegin和cend专门用于const迭代器,它们的功能和begin/end一样
	//这里用begin/end来替代cbegin/cend也可以
	string::const_iterator cit = s2.cbegin();
	while (cit != s2.cend())
	{
		cout << *cit << " ";
		cit++; 
	}
	cout << endl;

	//4、const反向迭代器,只能读,不能写(修改指针指向的内容)
	//crbegin和crend专门用于const反向迭代器,它们的功能和rbegin/rend一样
	//这里用rbegin/rend来替代crbegin/crend也可以
	 
	//string::const_reverse_iterator crit = s2.crbegin();
	auto crit = s2.crbegin();

	while (crit != s2.crend())
	{
		cout << *crit << " ";
		crit++;
	}
	cout << endl;

	return 0;
}

 begin指向第一个有效元素,end指向最后一个有效元素的后一个位置。

 rbegin指向最后一个有效元素,rend指向第一个有效元素的前一个位置。

5、成员函数

string类中成员函数有许多,在这里只写一部分常用到的,对于所写的每个成员函数我会写一些用法代码帮助大家理解,但有些成员函数有多个重载,我不会把每个重载的用法都写一遍,只挑选一些来写,希望大家理解。

(1)、size() / length()

这两个成员函数的功能都是返回字符串的长度,但不包括'\0';

(2)、max_size()

它的功能是返回最大字符串的长度(这里是整形的最大值)。

(3)、capacity()

它的功能是返回申请容量大小。这个大小不包括'\0',假设capacity的初始值是15字节,字符串中有15个字符,它是不会扩容的,当字符串中有16个字符它才扩容。也就是说实际的空间是比容量多一个字节的,这一个字节用来存放'\0'。

int main()
{
	string s1("a");
	cout << s1.capacity() << endl; //容量为15字节

	s1 = "aaaaaaaaaaaaaaa"; //15个字符
	cout << s1.capacity() << endl; //容量为15字节

	s1 = "aaaaaaaaaaaaaaaa"; //16个字符
	cout << s1.capacity() << endl; //容量为31字节
}

运行结果: 

我们可以用一个例子来测试容量的变化:

void TestPushBack()
{
	string s;
	size_t sz = s.capacity();
	cout << "capacity of start:" << sz << endl;//扩容前容量

	cout << "making s grow:" << endl;
	for (int i = 0;i < 100;++i) //循环控制插入100个字符
	{
		s.push_back('c'); //尾插一个字符'c'
		if (sz != s.capacity())
		{
			sz = s.capacity(); //扩容后新的容量给sz
			cout << "capacity changed:" << sz << endl;
		}
	}
}
int main()
{
	TestPushBack();
	return 0;
}

运行结果:

 我们知道容量是只包含有效数据不包含'\0',但实际空间要比capacity的值多1用来存放'\0',我们在运行结果的基础上每个值都加1才是实际的空间。

加1后我们发现第一次空间是扩二倍,接下来差不多都是1.5倍左右扩容。

这里的原因是什么呢?

VS2019在这里自己做了单独的处理,当size小于16时,它将元素存放在一个buff数组中而不是直接存放在堆上。

//VS下多了一个类似_buff的一个数组
class string
{
private:
	char _buff[16];
	char* _str;


	int _size;
	int _capacity;
};

如果_size小于16,就会存放在_buff中,在没有数据时先给你开16字节的空间,capacity大小是15(加上'\0'就是16);大于16就全部存放在_str指向的堆中,同时清空_buff,但_buff这个空间还在,_size大于16首次扩容就会扩到32,这是单独处理的。后续扩容就是1.5倍左右。

我们可以看一下一个string类对象的大小是多少:

int main()
{
	string s;
	cout << s.capacity() << endl;
	cout << sizeof(s) << endl;    
	return 0;
}

 运行结果:

这里的15就是没有数据时,capacity的大小,验证了我们上面说的在没有数据时先给你开16字节的空间,capacity大小是15(加上'\0'就是16)

而28就是_buff占16字节,char*占4字节,_size占4字节,_capacity占4字节,一共占28字节。

同样一段代码在Linux环境下的结果是不同的:

我们可以看到,在Linux下它是严格的二倍扩容,它没有_buff这一说,因为开始时capacity的值为0。 

为什么在两种不同的环境下会有差异呢?

C++标准规定,string类必须实现什么功能,但怎么实现的是靠编译器来决定的。比如扩容,C++标准规定string类要实现自动扩容,但怎么扩容,扩多大是每个编译器自己实现的,这里VS和Linux下的扩容机制就不大相同。

(4)、reserve()

它的功能是改变容量的,也就是改变capacity的大小,它可以避免频繁扩容。

假设参数是n(就是改变后的容量大小),分3种情况:

1、n < size

首先会不会缩容,这个问题是根据编译器的,有些编译器会缩容,有些编译器不会缩容。如果缩容,则最多缩到size,不能把我的size也给缩没了。

在VS2019下是不缩容的,即容量保持不变,当然size也不会改变,其中的元素也不会改变。

在Linux下可能缩容。

2、size < n < capacity

有些编译器会缩容,有些编译器不会缩容。

在VS2019下是不缩容的,即容量保持不变,当然size也不会改变,其中的元素也不会改变。

在Linux下可能缩容。

3、n > capacity

会扩容,至少扩到n,也可能更多,这是不确定的。在vs下通常会扩的更多一些,而在Linux下通常就扩到n。

int main()
{
	string s1("aaaaa");
	cout << s1.size() << endl; //元素个数为5个
	cout << s1.capacity() << endl; //容量为15字节

	//1、n < size 
	s1.reserve(3);
	cout << s1.size() << endl; //元素个数为5个
	cout << s1.capacity() << endl; //容量为15字节

	//2、size < n < capacity
	s1.reserve(10);
	cout << s1.size() << endl; //元素个数为5个
	cout << s1.capacity() << endl; //容量为15字节

	//3、n > capacity
	s1.reserve(50);
	cout << s1.size() << endl; //元素个数为5个
	cout << s1.capacity() << endl; //会扩容,容量为63字节

}
(5)、resize()

功能就是将元素个数设置为n。分3种情况:

int main()
{
	string s1("hah");
	cout << s1.size() << endl;  //3
	cout << s1.capacity() << endl; //15

	//1.n < size
	s1.resize(1);
	cout << s1.size() << endl;  //1
	cout << s1.capacity() << endl; //15

	//2.size < n <capacity 
	s1.resize(10);
	cout << s1.size() << endl;  //10
	cout << s1.capacity() << endl; //15

	//3. n > capacity
	s1.resize(30);
	cout << s1.size() << endl;  //30
	cout << s1.capacity() << endl; //31

	return 0;
}

在VS2019下运行的结果:

总结一句话,通过resize,设置n为多少,size就跟着变为多少,如果n > capacity,则capacity跟size一起变化,否则只有size变为n,capacity通常不变(取决于编译器)。

如果n小于size,则size个数就会变成n,size中多余的数据被删除。如果n大于size,则size扩大到n,新增加的数据初始化为'\0'。resize也可以传第二个参数,指定一个字符,新增加的数据初始化为你传过去的字符。

(6)、clear()

功能是清除数据,但通常不清除容量。

int main()
{
	string s("hah");
	cout << s.size() << endl;
	cout << s.capacity() << endl;

	s.clear();
	cout << s.size() << endl;
	cout << s.capacity() << endl;

	return 0;
}

运行结果:

(7)、empty()

功能是判断字符个数是否为空。

int main()
{
	string s1("hah");
	if (s1.empty())
		cout << "s1 -> null" << endl;
	else
		cout << "s1 -> not null" << endl;

	string s2;
	if (s2.empty())
		cout << "s2 -> null" << endl;
	else
		cout << "s2 -> not null" << endl;

	return 0;
}

运行结果:

(8)、shrink_to_fit()

功能是缩容,将capacity减小到适应它的size。这不是强制的。

int main()
{
	string s1("hello");
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;

	s1.shrink_to_fit();
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;

	return 0;
}

运行结果:

这里并没有缩容。

(9)、at()

at的功能和重载[]几乎一样,只不过重载[]如果越界会断言报错,at越界会抛出out_of_range的异常。

int main()
{
	string s("hah");
	cout << s.at(0) << endl;
	cout << s.at(1) << endl;
	cout << s.at(2) << endl;

	return 0;
}

运行结果:

(10)、front() 

功能是返回第一个字符。

int main()
{
	string s1("hello");
	cout << s1.front() << endl; //h
	return 0;
}
 (11)、back()

功能是返回最后一个有效字符。

int main()
{
	string s1("hello");
	cout << s1.back() << endl; //o
	return 0;
}
(12)、push_back()

功能是在原有字符串的基础上追加一个字符。

int main()
{
	string s("hello");
	cout << s << endl;

	s.push_back(' ');
	s.push_back('w');
	s.push_back('o');
	cout << s << endl;

	return 0;
}

运行结果:

 

(13)、pop_back()

 功能是删除字符串的末尾的一个有效字符。

int main()
{
	string s("hello");
	cout << s << endl;

	s.pop_back();
	cout << s << endl;

	return 0;
}

运行结果:

 

(14)、append()

它的功能是在原有字符串的基础上追加字符串,但不能追加字符。

这里以第三个重载函数为例: 

int main()
{
	string s("hello");
	cout << s << endl;

	s.append(" world");
	cout << s << endl;

	return 0;
}

运行结果:

(15)、重载+=

它的功能也是在原有字符串的基础上追加字符串,也能追加字符。

这里以第二个重载函数为例:   

int main()
{
	string s("hello");
	cout << s << endl;

	s += " world";
	cout << s << endl;

	return 0;
}

运行结果: 

(16)、assign()

它的功能是给一个字符串对象赋值,若之前字符串有内容则就覆盖掉原来的内容,同时size也会改变。

这里以第三个重载函数为例:  

int main()
{
	string s1 = "hah";
	cout << s1.size() << endl; //size为3
	cout << s1.capacity() << endl; //capacity为15

	s1.assign("x");
	cout << s1.size() << endl; //这里让size小于原来的size,size=1
	cout << s1.capacity() << endl; //capacity保持不变,capacity=15

	string s2 = "xix";
	cout << s2.size() << endl; //size为3
	cout << s2.capacity() << endl; //capacity为15

	s2.assign("xxxxxxxxxxxxxxxxxxxxxxxxxx");
	cout << s2.size() << endl; //这里让size大于原来的capacity,size=26
	cout << s2.capacity() << endl;//这里的capacity就会扩容到31

	string s3 = "pip";
	cout << s3.size() << endl; //size为3
	cout << s3.capacity() << endl; //capacity为15

	s3.assign("xxxxx");
	cout << s3.size() << endl; //这里让size大于原来的size,小于原来的capacity,size=5
	cout << s3.capacity() << endl;//capacity保持不变,capacity=15

	return 0;
}

运行结果:

(17)、insert()

它的功能是在指定位置前(这个位置必须有效,否则运行时会崩溃)插入字符或字符串。

 这里以第三个重载函数为例:

int main()
{
	string s("world");
	cout << s << endl;

	s.insert(0, "hello "); //在字符串中下标为0的位置插入"hello "
	cout << s << endl;

	return 0;
}

运行结果:

(18)、erase()

它的功能是删除指定位置的字符串或字符。

 这里以第一个重载函数为例: 

int main()
{
	string s("hello world");
	cout << s << endl;

	s.erase(6, 1);  //在下标为6的位置删除1个字符,如果不写第二个参数,默认值是npos,它是int类型最大值,可以理解为从第一个参数位置开始后面全删
	cout << s << endl;

	return 0;
}

运行结果: 

(19)、replace()

它的作用是将字符串中某一段替换成另外一段。

  这里以第三个重载函数为例: 

int main()
{
	string s("hello world");
	cout << s << endl;

	s.replace(5, 1, "%%"); //从下标为5的位置开始往后1个字符替换成"%%"
	cout << s << endl;
	return 0;
}

 运行结果:

 

用2个字符替换1个字符也是可以的。多替换少也是可以的。

replace()尽量不要频繁使用,因为它底层牵扯到扩容问题,有时需要挪动大量数据。

(20)、find()

它的功能是从某个位置开始查找某个字符或字符串,若找到则返回第一个被找到的位置的起始位置下标,否则返回npos。npos是string类中的静态成员变量。

 这里以第四个重载函数为例: 

int main()
{
	string s("hello wor ld");
	size_t pos1 = s.find(' '); //从下标为0的位置开始找' ',若有多个' ',则返回第一个位置的下标
	cout << pos1 << endl;

	size_t pos2 = s.find(' ', pos1 + 1); //从下标为pos1 + 1的位置开始找' ',若有多个' ',则返回第一个位置的下标
	cout << pos2 << endl;

	return 0;
}

运行结果:

我们可以结合replace()来实现一个小功能:将一个字符串中所有空格换成'%'

int main()
{
	string s("h el lo wo r ld");
	cout << "replace before:" << s << endl;
	size_t pos = s.find(' ');
	while (pos != string::npos)
	{
		s.replace(pos, 1, "%");
		pos = s.find(' ', pos + 1);
	}

	cout << "replace after: " << s << endl;
	return 0;
}

运行结果:

 还用另外一种实现方法:

int main()
{
	string s("h el lo wo r ld");
	cout <<  s << endl;

	string tmp;
	for (auto e : s)
	{
		if (e == ' ')
			tmp += '%';
		else
			tmp += e;
	}
    
	cout << tmp << endl;
	return 0;
}

运行结果: 

 

(21)、swap()

它的功能是交换两个string类型对象的成员变量的值。

int main()
{
	string s1("hah");
	string s2("pip");
	cout << "s1 = " << s1 << endl;
	cout << "s2 = " << s2 << endl;

	s1.swap(s2);
	cout << "s1 = " << s1 << endl;
	cout << "s2 = " << s2 << endl;

	return 0;
}

运行结果: 

 

通过调试时的监视窗口,也可以看出它们的交换情况:

交换前:

交换后: 

不难看出只有buff没变,其余都交换了。

(22)、c_str()

它的功能是返回底层字符串的指针。它的出现就是兼容C语言的,比如一些C语言的函数参数是char*类型的,不能直接传string类型,必须传char*,我们就可以调用c_str()。转换为char*后末尾会放一个‘\0’。

int main()
{
	string file;
	cin >> file;

	FILE* fout = fopen(file.c_str(), "r"); //fopen第一个参数是const char*,所以这里必须要转换一下
	char ch = fgetc(fout);
	while (ch != EOF)
	{
		cout << ch;
		ch = fgetc(fout);
	}
	fclose(fout);

	return 0;
}
(23)、substr()

它的功能是从某个位置开始,取长度为n的字串,构造一个string类型的对象进行返回。

int main()
{
	string s1("hello world");
	string s2 = s1.substr(0, 5); //拷贝构造,取下标为0的位置开始向后5个字符给s2

	cout << s2 << endl;
	return 0;
}

运行结果: 

(24)、rfind()

 它的功能是从某个位置开始从后往前找。找到第一个符合条件的就返回对应的下标。

  这里以第四个重载函数为例: 

int main()
{
	//获取文件名的后缀
	string s("Test.txt");
	size_t pos = s.rfind('.');//从后向前找
	string tmp = s.substr(pos);

	cout << tmp << endl;
	return 0;
}

运行结果:

 这时候就有人说了,将rfind换成find也行啊,rfind感觉没什么用,那么请看下面一种情况:

int main()
{
	//目的:打印.zip
	string s("Test.txt.zip");
	size_t pos = s.rfind('.');//从后向前找,如果这里是find,就不行了
	string tmp = s.substr(pos);

	cout << tmp << endl;
	return 0;
}

运行结果: 

(25)、 find_first_of()

它的功能是从指定位置开始查找任意个字符(查找的范围是参数中的所有字符),找到返回对应下标。

int main()
{
    string str("Please, replace the vowels in this sentence by asterisks.");
    size_t found = str.find_first_of("aeiou"); //找出串中的'a','e','i','o','u'任意首个出现的字符的下标
    while (found != string::npos)
    {
        str[found] = '*';
        found = str.find_first_of("aeiou", found + 1);
    }

    std::cout << str << '\n';

    return 0;
}

运行结果: 

这段代码的功能是将串中的所有'a','e','i','o','u'替换成'*'。

(26)、find_last_of()

它的功能与find_first_of()一样,只不过是从后往前找。这里就不赘述了。

(27)、find_first_not_of()

它的功能是从指定位置开始查找任意个字符(查找的范围是除了参数之外的所有字符),找到返回对应下标。

int main()
{
    string str("Please, replace the vowels in this sentence by asterisks.");
    size_t found = str.find_first_not_of("aeiou"); //找出串中不是'a','e','i','o','u'任意首个出现的字符的下标
    while (found != string::npos)
    {
        str[found] = '*';
        found = str.find_first_not_of("aeiou", found + 1);
    }

    std::cout << str << '\n';

    return 0;
}

 运行结果:

 这段代码的功能是将串中的所有不是'a','e','i','o','u'的字符替换成'*'。

(28)、find_last_not_of()

它的功能与find_first_not_of()一样,只不过是从后往前找。这里就不赘述了。

6、非成员函数

(1)重载+

 这里以第二个重载函数为例:  

int main()
{
	string s1("hello");

	string s2 = s1 + " world"; //(2)
	string s3 = "world " + s1;  //(2)

	cout << s2 << endl;
	cout << s3 << endl;

	return 0;
}

运行结果: 

(2)重载关系运算符

支持两个字符串比较大小,比较规则是根据ASCII码值的大小。这里就不举例了。

(3)重载流插入(<<)和流提取(>>)

重载后就支持输入和输出string类型的字符串了。 

(4)getline()

当cin一个字符串对象时,它是取不到空格的:

int main()
{
	string s;
	cin >> s;

	cout << s << endl;
	return 0;
}

当我们输入AAA B时,它只会取到AAA:

 因为cin默认在遇到换行或空格时停止在缓冲区中继续读数据。

getline就可以解决这个问题,cin默认在遇到换行时停止在缓冲区中继续读数据。

int main()
{
	string s;
	getline(cin,s);

	cout << s << endl;
	return 0;
}

 当我们输入AAA B时,它会取到AAA B:

它有两个重载函数: 

 它默认是以换行为终止符,我们也可以自己设置终止符,对应的是第一个构造函数。 

三、总结

本篇到这里就结束了,主要讲了string类的基本使用,希望对大家有帮助,祝大家天天开心!

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

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

相关文章

大数据之Flink(三)

9.3、转换算子 9.3.1、基本转换算子 9.3.1.1、映射map 一一映射 package transform;import bean.WaterSensor; import org.apache.flink.streaming.api.datastream.DataStreamSource; import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator; impor…

分类预测|基于改进的灰狼IGWO优化支持向量机SVM的数据分类预测matlab程序 改进策略:Cat混沌与高斯变异

分类预测|基于改进的灰狼IGWO优化支持向量机SVM的数据分类预测matlab程序 改进策略&#xff1a;Cat混沌与高斯变异 文章目录 一、基本原理原理流程1. **定义目标函数**2. **初始化GWO**3. **评估适应度**4. **更新狼的位置**5. **更新狼的等级**6. **重复迭代**7. **选择最佳解…

春招审核新策略:Spring Boot系统实现

3系统分析 3.1可行性分析 通过对本大学生入学审核系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本大学生入学审核系统采用Spring Boot框架&#xff0c;JA…

综合案例-数据可视化-柱状图

一、基础柱状图 我们绘制一个关于三种水果销售额的柱状图&#xff0c;X轴数据为三种水果的名称&#xff0c;用列表[苹果,香蕉,橘子]添加进去&#xff0c;Y轴数据为三种水果的销售额&#xff0c;用列表[50,70,60]添加进去。 步骤&#xff1a; 导包构建柱状图对象添加X轴数据生…

Android 12系统源码_窗口管理(八)WindowConfiguration的作用

前言 在Android系统中WindowConfiguration这个类用于管理与窗口相关的设置&#xff0c;该类存储了当前窗口的显示区域、屏幕的旋转方向、窗口模式等参数&#xff0c;应用程序通过该类提供的信息可以更好的适配不同的屏幕布局和窗口环境&#xff0c;以提高用户体验。 一、类定…

喜报 | 知从科技荣获 “AutoSec 安全之星 - 优秀汽车软件供应链安全方案奖”

近日&#xff0c;「AutoSec 2024第八届中国汽车网络安全周暨第五届智能汽车数据安全展」在上海盛大举行。本届大会由谈思实验室和谈思汽车主办、上海市车联网协会联合主办&#xff0c;以汽车“网络数据安全、软件安全、功能安全”为主题&#xff0c;设置了“31X”模式&#xff…

关于 PC打开“我的电脑”后有一些快捷如腾讯视频、百度网盘、夸克网盘、迅雷等各种捷方式在磁盘驱动器上面统一删除 的解决方法

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/142029325 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV…

numpy(基于Numpy外文文档的学习)

学习目标&#xff1a; Understand the difference between one-, two- and n-dimensional arrays in NumPy; Understand how to apply some linear algebra operations to n-dimensional arrays without using for-loops;&#xff08;调用一些简单的方法&#xff09; Underst…

外包干了三年,快要废了。。。

先简单说一下自己的情况&#xff0c;普通本科&#xff0c;在外包干了3年多的功能测试&#xff0c;这几年因为大环境不好&#xff0c;我整个人心惊胆战的&#xff0c;怕自己卷铺盖走人了&#xff0c;我感觉自己不能够在这样蹉跎下去了&#xff0c;长时间呆在一个舒适的环境真的会…

Docker 部署 Redis (图文并茂超详细)

部署 Redis ( Docker ) [Step 1] : 拉取 Redis 镜像, 推荐使用 7 的 Redis 版本 docker pull redis:7.0.12[Step 2] : 创建 Redis 相关目录 ➡️ 启动 Redis 容器 ➡️ 拷贝文件 ➡️ 授权文件夹 ➡️ 删除容器 # 创建 Redis 相关目录 mkdir -p /data/redis/{conf,data,log…

写的一致性问题之失效模式

文章目录 1、先删除redis缓存&#xff0c;再写入mysql&#xff1a;1.1、高并发情况下分析出现的问题 2、先写入mysql&#xff0c;再删除redis缓存 失效模式存在的问题&#xff1a;在事务提交之前可能会有其他读操作重新把旧数据放入redis缓存中 1、先删除redis缓存&#xff0c;…

深入解析全连接层:PyTorch 中的 nn.Linear、nn.Parameter 及矩阵运算

文章目录 数学概念&#xff08;全连接层&#xff0c;线性层&#xff09;nn.Linear()nn.Parameter()Q1. 为什么 self.weight 的权重矩阵 shape 使用 ( out_features , in_features ) (\text{out\_features}, \text{in\_features}) (out_features,in_features)而不是 ( in_featur…

Bev pool 加速(2):自定义c++扩展

文章目录 1. c++扩展2. 案例2.1 案例12. 1.1 代码实现(1) c++ 文件(2) setup.py编写(3) python 代码编写2.2 案例22.2.1 模型搭建2.2.2 c++ 扩展实现(1)c++ 扩展代码(2)setup.py编写(3)python 调用c++扩展在bevfusion论文中,将bev_pooling定义为view transform中的效率瓶…

PROTOTYPICAL II - The Practice of FPGA Prototyping for SoC Design

The Art of the “Start” The semiconductor industry revolves around the “start.” Chip design starts lead to more EDA tool purchases, more wafer starts, and eventually to more product shipments. Product roadmaps develop to extend shipments by integrating…

FloodFill算法

文章目录 1. 图像渲染&#xff08;733&#xff09;2. 岛屿数量&#xff08;200&#xff09;3. 岛屿的最大面积&#xff08;695&#xff09;4. 被围绕的区域&#xff08;130&#xff09; 1. 图像渲染&#xff08;733&#xff09; 题目描述&#xff1a; 算法原理&#xff1a; …

DAY13信息打点-Web 应用源码泄漏开源闭源指纹识别GITSVNDS备份

#知识点 0、Web架构资产-平台指纹识别 1、开源-CMS指纹识别源码获取方式 2、闭源-习惯&配置&特性等获取方式 3、闭源-托管资产平台资源搜索监控 演示案例&#xff1a; ➢后端-开源-指纹识别-源码下载 ➢后端-闭源-配置不当-源码泄漏 ➢后端-方向-资源码云-源码泄漏 …

1、https的全过程

目录 一、概述二、SSL过程如何获取会话秘钥1、首先认识几个概念&#xff1a;2、没有CA机构的SSL过程&#xff1a;3、没有CA机构下的安全问题4、有CA机构下的SSL过程 一、概述 https是非对称加密和对称加密的过程&#xff0c;首先建立https链接需要经过两轮握手&#xff1a; T…

算法提高模板强连通分量tarjan算法

AC代码&#xff1a; #include<bits/stdc.h>using namespace std;typedef long long ll; const int MOD 998244353; const int N 2e5 10;//强联通分量模板 //tarjan算法 vector<int>e[N]; int n, m, cnt; int dfn[N], low[N], ins[N], idx; int bel[N];//记录每…

Redis高可用,Redis性能管理

文章目录 一&#xff0c;Redis高可用&#xff0c;Redis性能管理二&#xff0c;Redis持久化1.RDB持久化1.1触发条件&#xff08;1&#xff09;手动触发&#xff08;2&#xff09;自动触发 1.2 Redis 的 RDB 持久化配置1.3 RDB执行流程(1) 判断是否有其他持久化操作在执行(2) 父进…

Chainlit集成Langchain并使用通义千问实现文生图网页应用

前言 本文教程如何使用通义千问的大模型服务平台的接口&#xff0c;实现图片生成的网页应用&#xff0c;主要用到的技术服务有&#xff0c;chainlit 、 langchain、 flux。合利用了大模型的工具选择调用能力。实现聊天对话生成图片的网页应用。 阿里云 大模型服务平台百炼 API…