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

发布于:2024-09-18 ⋅ 阅读:(147) ⋅ 点赞:(0)

一、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:

它有两个重载函数: 

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

三、类型转换

to_string()这个全局函数可以将一些内置类型转换成string类型。

这个函数用起来是很方便的。

三、结语

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


网站公告

今日签到

点亮在社区的每一天
去签到