【C++】_string类字符串详细解析(1)

news2024/11/27 2:36:02

假如没有给你生命,你连失败的机会都没有。你已经得到了最珍贵的,还需要抱怨什么!💓💓💓

目录

  ✨说在前面

🍋知识点一:什么是string?

•🌰1.string类的概念

•🌰2.string类的主要特点

•🌰3.常用接口说明

🍋知识点二:string类常用接口

•🌰1.默认成员函数

🔥构造函数(⭐)

🔥析构函数

•🌰2.string类对象的访问和遍历操作

🔥operator[ ](⭐)

 🔥迭代器(⭐)

•🌰3.string类对象的容量操作

🔥size、length、capacity

🔥reserve(⭐)

🔥clear

🔥empty

🔥resize

•🌰4.string类对象的修改操作

 🔥push_back、append

🔥operator+=

🔥assgin

🔥insert(⭐)

🔥erase

🔥replace

🔥swap

🔥pop_back

•🌰5.string类对象的其他操作

🔥c_str

🔥find

🔥rfind

🔥substr(⭐)

🔥find_first_of

🔥find_last_of

🔥find_first_not_of

🔥find_last_not_of

•🌰6.string类的非成员函数

🔥operator+

🔥relational operators

🔥getline

•🌰7.例题训练

🔥题目一:仅仅反转字母

🔥题目二:第一个唯一字符

🔥题目三:字符串相加

 • ✨SumUp结语


  ✨说在前面

亲爱的读者们大家好!💖💖💖,我们又见面了,上一篇文章我给大家介绍了一下STL是什么,以及相关的一些历史和容器介绍。如果大家没有掌握好相关的知识,上一篇篇文章讲解地很详细,可以再回去看看,复习一下,再进入今天的内容。

我们今天简单给大家讲解一下C++标准库中一个非常重要的组成部分——string字符串。如果大家准备好了,那就接着往下看吧~

  👇👇👇
💘💘💘知识连线时刻(直接点击即可)

【C++】STL简介

  🎉🎉🎉复习回顾🎉🎉🎉

         

 博主主页传送门:愿天垂怜的博客

 ​​​

 ​​​​

🍋知识点一:什么是string?

•🌰1.string类的概念

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

于是 C++ 中就引入了string类,它可以看做是一个管理字符串的数据结构

 C++中的string类是一个非常强大的类,它提供了对字符串的丰富操作。string类是C++标准模板库(STL)的一部分,定义在头文件<string>中。这个类封装了字符数组的功能,并且添加了许多便利的成员函数来操作字符串,如查找、替换、插入、删除等。

 ​​​

•🌰2.string类的主要特点

🔥动态内存管理

string类自动管理内存,当你修改字符串的大小时,它会自动调整内存分配。

🔥丰富的成员函数

提供了大量的成员函数来执行各种字符串操作,如长度获取(length()或size())、子串提取(substr())、字符串比较(compare())、查找(find())、替换(replace())、插入(insert())、删除(erase())等。

🔥安全

使用string类比直接使用字符数组更安全,因为它避免了缓冲区溢出的风险。

🔥标准库支持

作为STL的一部分,string类得到了广泛的支持

 ​​​

•🌰3.常用接口说明

🔥string类对象的常见构造:

(constructor) 函数名称
功能说明
string() (重点)
构造空的string类对象,即空字符串
string(const char* s) (重点)
用C-string来构造string类对象
string(size_t n, char c)
string类对象中包含n个字符c
string(const string&s) (重点)
拷贝构造函数

🔥string类对象的容量操作:

函数名称
功能说明

size(重点)

返回字符串有效字符长度
length返回字符串有效字符长度
capacity返回空间总大小
empty (重点)检测字符串释放为空串,是返回true,否则返回false
clear (重点)
清空有效字符
reserve (重点)
为字符串预留空间 * *
resize (重点)将有效字符的个数该成n个,多出的空间用字符c填充

🔥string类对象的访问及遍历操作:

函数名称功能说明
operator[] (重
点)
返回pos位置的字符,const string类对象调用
begin+ end
begin 获取一个字符的迭代器 + end 获取最后一个字符下一个位 置的迭代器
rbegin + rend
begin 获取一个字符的迭代器 + end 获取最后一个字符下一个位 置的迭代器
范围forC++11支持更简洁的范围for的新遍历方式

 🔥string类对象的修改操作:

函数名称功能说明
push_back在字符串后尾插字符c
append在字符串后追加一个字符串
operator+= (
)
在字符串后追加字符串str
c_str(重点)返回C格式字符串
find + npos (
)
从字符串pos 位置开始往后找字符 c,返回该字符在字符串中的位置
rfind
从字符串pos 位置开始往前找字符 c,返回该字符在字符串中的位置
substr在str中从pos位置开始,截取n个字符,然后将其返回

 🔥string类非成员函数:

函数功能说明
operator+尽量少用,因为传值返回,导致深拷贝效率低
operator>> (重点)输入运算符重载
operator<< (重点)输出运算符重载
getline (重点)获取一行字符串
relational operators (重点)大小比较

 ​​​​

🍋知识点二:string类常用接口

•🌰1.默认成员函数

🔥构造函数(⭐)

接口如下,前面也有,大家再仔细看看:

在文档中我们可以查看它们的具体用法:

我们大家也需要学会查看英文文档,有不懂的就去查,锻炼我们查看英文文章的能力。 

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

int main()
{
	string s1;
	cout << s1 << endl;

	string s2("hello world");
	cout << s2 << endl;

	string s3(s2);
	cout << s3 << endl;

	string s4(s2, 6, 15);
	cout << s4 << endl;

	string s5(s2, 6);
	cout << s5 << endl;

	string s6("hello wrold", 5);
	cout << s6 << endl;

	string s7(10, 'x');
	cout << s7 << endl;

	return 0;
}

 ​​​​

🔥析构函数

析构函数直接用编译器默认生成、调用的就可以了,非常简单~

 ​​​​

•🌰2.string类对象的访问和遍历操作

🔥operator[ ](⭐)

operator[ ]允许你像访问数组元素一样访问字符串中的字符,operator[ ]返回的是对字符串中字符的引用(char*),你可以读取也可以修改该位置的字符。

void test_string1()
{
	string s1;
	string s2("hello world");

	cout << s1 << s2 <<endl;

	s2[0] = 'x';
	cout << s1 << s2 << endl;
}

 如上面的代码,我们可以直接像访问数组一样将s2的第一个元素修改为'x',本质是通过operator[]函数。

我们后面会学习【size】这个接口,可以获取字符串的有效长度,那么我们要遍历这个字符串就变得很简单方便:

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

我们还可以用迭代器的方式遍历这个字符串:

string::iterator it = s2.begin();
while (it != s2.end())
{
	cout << *it << " ";
	++it;
}
cout << endl;

那这是什么意思呢?我们可以将it想象成一个指针(但不一定是指针),begin()会返回第一个位置的迭代器,而end()是\0位置的迭代器,这样就很容易理解了。

所有的容器都可以用这种类似的方式进行遍历,例如链表:

list<int> lt = { 1,2,3,4,5,6,7 };
list<int>::iterator lit = lt.begin();
while (lit != lt.end())
{
	cout << *lit << " ";
	lit++;
}
cout << endl;

 这是第二种遍历的方式,那么我们还有第三种,这是C++11新增的:

for (auto ch : s2)
{
	cout << ch << " ";
}
cout << endl;

auto自动推导类型,范围for可以自动赋值、自动迭代、自动判断结束,非常nb,但其实也正常,因为和之前的第一种方法在底层是一样的。也就是说:

范围for的底层就是迭代器。

同时我们要注意,如果我们再第一种方法中对*it进行修改,那么会直接影响到字符串,而我们再范围for中修改,是修改不到字符串的:

void test_string1()
{
	string s2("hello world");

	string::iterator it = s2.begin();
	while (it != s2.end())
	{
		*it += 2;
		cout << *it << " ";
		it++;
	}
	cout << endl;
    //s2被修改

	for (auto ch : s2)
	{
		ch -= 2;
		cout << ch << " ";
	}
	cout << endl;
    //s2没有被修改
}

那这是为什么呢?其实是因为我们将*it一个个赋值给ch,而我们是对ch进行操作。ch只是一个局部临时变量,对其的修改并不会影响到原来的字符串。

那我们可以这么处理:

for (auto& ch : s2)
{
	ch -= 2;
	cout << ch << " ";
}
cout << endl;

我们只要修改为引用,自然就不会有临时变量,也就可以直接对原字符串进行操作了。

总结:上面三种遍历方式(下标+[ ]、迭代器、范围for)在性能上并没有什么区别,第一种和第三种用得可能相对多。

补充:auto和范围for

auto关键字

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

2. 用auto声明指针类型时,用autoauto*没有任何区别,但用auto声明引用类型时则必须加&

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

4. auto不能作为函数的参数,可以做返回值,但是建议谨慎使用

5. auto不能直接用来声明数组

void test_string2()
{
	map<string, string> dict;
	//map<string, string>::iterator mid = dict.begin();
	auto mid = dict.begin();
}

看上面的代码,由于auto可以自动推导类型,我们可以减少一些代码量。

#include <iostream>
using namespace std;

int func1()
{
	return 10;
}

void func()
{
	int a = 10;
	auto b = a;
	auto d = func1();
	auto c = 'a';
	auto e;
	//编译报错:rror C3531: “e”: 类型包含“auto”的符号必须具有初始值设定项
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;
}

int main()
{
	func();

	return 0;
}

我们可以用【typeid(variate).name()】来打印变量的类型。 

#include <iostream>
using namespace std;

int func1()
{
	return 10;
}

auto func2()
{
	return func1();
}

auto func3()
{
	return func2();
}

int main()
{
	auto ret = func3();

	return 0;
}

auto可以做返回值,但是建议谨慎使用,如上面代码,就有种递归的感觉,func3要去查看func2的返回值,func2又要去查看func1的返回值。

范围for

1. 对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。

2. 范围for可以作用到数组和容器对象上进行遍历

3. 范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。

范围for适用于容器和数组:

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

int main()
{
	int array[] = { 1,2,3,4,5 };
	//C++98的遍历
	for (int i = 0; i < sizeof(array) / sizeof(array[0]); i++)
	{
		array[i] *= 2;
	}
	for (int i = 0; i < sizeof(array) / sizeof(array[0]); i++)
	{
		cout << array[i] << endl;
	}
	//C++11的遍历
	for (auto& e : array)
	{
		e *= 2;
	}
	for (auto e : array)
	{
		cout << e << " " << endl;
	}

	return 0;
}

注意:用范围for遍历数组yyds。 

 🔥迭代器(⭐)

C++中的迭代器(Iterator)是一种允许你访问容器中元素的对象,而无需暴露容器的内部结构。迭代器提供了一种统一的方法来遍历容器中的所有元素,无论容器的具体类型如何(如数组、向量vector列表list等)。通过使用迭代器,你可以读取、写入或删除容器中的元素,而无需关心容器的具体实现细节。

 我们先简单了解前四个迭代器:

接口名称使用说明
begin()返回指向第一个元素的迭代器
end()返回指向最后一个元素的下一个位置的迭代器
rbegin()返回指向最后一个元素的反向迭代器
rend()返回指向第一个元素的前一个位置的反向迭代器

对于前两个,我们之前就介绍过:

而后面两个,就是与前两个相反。rbegin指向最后一个元素,rbegin++那么这个迭代器往回走;rend指向的是第一个元素,rend++那么这个迭代器往后走。:

但是我们需要注意,上面的四个迭代器都是可读可写的,如果这个字符串是const的,C++11还可以用下面的四个。const迭代器是只读不可写的,也就是*cit不能被修改。

//const正向迭代器
void test_string5()
{
	const string s3("hello world");
	for (auto cit = s3.begin(); cit < s3.end(); ++cit)
	{
		cout << *cit << " ";
	}
	cout << endl;
}

//const反向迭代器
void test_string6()
{
	const string s4("hello world");
	for (auto cit = s4.crbegin(); cit < s4.crend(); ++cit)
	{
		cout << *cit << " ";
	}
	cout << endl;
}

所以说,一般情况下,有四种迭代器:iterator、const_iterator、reverse_iterator、const_reverse_iterator。

 ​​​​

•🌰3.string类对象的容量操作

在C++中,string类是用来处理字符串的一个非常强大的类。关于string类对象的容量操作,主要包括以下几个方面:

或如下: 

函数名称
功能说明

size(重点)

返回字符串有效字符长度
length返回字符串有效字符长度
capacity返回空间总大小
empty (重点)检测字符串释放为空串,是返回true,否则返回false
clear (重点)
清空有效字符
reserve (重点)
为字符串预留空间 * *
resize (重点)将有效字符的个数该成n个,多出的空间用字符c填充

🔥size、length、capacity

我们首先来看看【size】和【length】,他们都可以计算字符串的长度,但是【size】会比【length】更好些,因为如果数据结构是一棵二叉树呢?所以说后者没有前者通用。

 而【capacity】计算的是字符串的空间总大小

void test_string7()
{
	string s5("hello world");
	cout << s5.length() << endl;//11
	cout << s5.size() << endl;//11
	cout << s5.capacity() << endl;//15
	cout << s5.max_size() << endl;//2147483647
}

这里我们也可以简单了解一下【max_size】,它返回的是字符串可以达到的最大长度。通过代码我们可以发现,它直接返回了int的最大值INT_MAX。

【capacity】并不一定等于字符串的长度,它的容量变化我们可以通过下面的代码进行观察:

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

运行后我们可以发现大致情况下,第一次是二倍扩容,后面是1.5倍扩。当然,在不同的环境下是不一样的。

​​​​

🔥reserve(⭐)

我们现在知道了字符串的扩容,那有没有什么办法能减少扩容的次数呢?答案是有的,reserve就可以解决这个问题。

reserve用于请求改变字符串的容量(即分配的内存量),确保字符串可以存储至少指定数量的字符,而不需要重新分配内存。这可以提高性能,特别是在你知道将要向字符串中添加大量字符时,因为重新分配内存(特别是当字符串变得很大时)可能会非常耗时。

void TestPushBack()
{
	string s;
    //预留100的空间
	s.reserve(100);
	size_t sz = s.capacity();
	cout << "capacity changed:" << sz << endl;
	cout << "making s grow:\n";
	for (int i = 0; i < 100; i++)
	{
		s.push_back('c');
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity changed:" << sz << endl;
		}
	}
}

但我们可以看到VS上实际是会比100多一些,它会自动去对齐。 当然,不同平台的实现会有不同。同时注意,【reserve】大多不会造成缩容,上面文档中也有说明。

void test_string8()
{
	string s("hello world and merry christmas!");
	cout << s.size() << endl;
	cout << s.capacity() << endl << endl;;

	//n < s.size() 不会缩容
	s.reserve(20);
	cout << s.size() << endl;
	cout << s.capacity() << endl << endl;

	//s.size() < n < s.capacity() 不会缩容
	s.reserve(35);
	cout << s.size() << endl;
	cout << s.capacity() << endl << endl;

	//n > s.capacity() 增容
	s.reserve(60);
	cout << s.size() << endl;
	cout << s.capacity() << endl << endl;
}

举例说明,VS上是不会造成缩容的。

 ​​​​

🔥clear

用于移除字符串中的所有字符,将其长度设置为0,但保留其当前分配的容量(capacity)。调用clear后,字符串将不再包含任何字符,但它在内存中的空间(即字符数组的大小)不会减小。

void test_string9()
{
	string s("hello world");
	cout << s << endl;//hello world
	cout << s.size() << endl;//11
	cout << s.capacity() << endl << endl;//15
	s.clear();
	cout << s << endl;//空
	cout << s.size() << endl;//0
	cout << s.capacity() << endl << endl;//15
}

 ​​​​

🔥empty

用于检查字符串是否为空。如果字符串不包含任何字符(即其长度为0),则【empty】函数返回true;否则,返回false。

【empty】函数是检查字符串是否为空的一种高效且直观的方式,因为它直接查询字符串的长度,而不需要遍历整个字符串。

void test_string10()
{
	string s1("hello world");
	cout << s1.empty() << endl;//0

	string s2;
	cout << s2.empty() << endl;//1
}

​​​​

🔥resize

用于改变字符串的大小。它可以增加或减少字符串中的字符数,如果增加大小,则新添加的字符默认初始化为空字符。在实践中,当增加大小时,resize通常会用指定的字符(如果有的话)填充新添加的空间,或者如果不指定,则不进行填充。

更常见的是,resize用于减少字符串的大小,此时会移除超出新大小的尾部字符。

resize函数有两种常见的重载形式:

void resize(size_t n);

这个版本将字符串的大小更改为n。如果n小于当前大小,则移除超出新大小的尾部字符。如果n大于当前大小,则添加足够数量的字符以达到新的大小。

void reserve(size_t n, char c);

这个版本还允许你指定一个字符c,用于填充在增加大小时新添加的空间。如果n小于当前大小,则仍然移除超出新大小的尾部字符。如果n大于当前大小,则添加n-size()个字符c。

 ​​​​

•🌰4.string类对象的修改操作

string也是一种数据结构,那么它就具有增删查改的各种操作。

我们一个一个来看。

 🔥push_back、append

【push_back】的功能是在字符串末尾添加一个字符。

而【append】的功能是在字符串的末尾添加字符串,它有很多的版本,但用法上我们其实都很熟悉了。

#include <iostream>
using namespace std;

void test_string()
{
	string s("hello world");
	s.push_back('s');
	s.push_back('t');

	s.append("Merry Christmas! ");

	cout << s << endl;
}

int main()
{
	test_string();

	return 0;
}

 结果如下:

 ​​​​

🔥operator+=

【operator+=】用于将一个字符串追加到另一个string对象的末尾。

void test_string2()
{
	string s1("hello world");
	string s2("Happy New Year!");
	s1 += ' ';
	s1 += "Merry Christmas!";
	s1 += ' ';
	s1 += s2;

	cout << s1 << endl;
    //hello world Merry Christmas! Happy New Year!
}

 ​​​​

🔥assgin

 【assign】也是一种赋值,但是用得比较少。

void test_string3()
{
	string str;
	string base = "The quick brown fox jumps over a lazy dog.";

	str.assign(base);
	cout << str << '\n';

	str.assign(base, 10, 9);
	cout << str << '\n';         // "brown fox"

	str.assign("pangrams are cool", 7);
	cout << str << '\n';         // "pangram"

	str.assign("c-string");
	cout << str << '\n';         // "c-string"

	str.assign(10, '*');
	cout << str << '\n';         // "**********"

	str.assign(base.begin() + 16, base.end() - 12);
	std::cout << str << '\n';         // "fox jumps over"
}

 ​​​​

🔥insert(⭐)

【insert】函数是一个非常有用的成员函数,它允许你在字符串的指定位置插入另一个字符串或字符。这个函数有几个重载版本,以适应不同的插入需求。

void test_string4()
{
	string s1("hello world");
	s1.insert(0, "Merry Christmas! ");

	//插入一个字符的写法
	char ch = 't';
	s1.insert(0, 1, ch);
	cout << s1 << endl;
	//tMerry Christmas! hello world

	s1.insert(s1.begin(), ch);
	cout << s1 << endl;
	//ttMerry Christmas! hello world
}

 但是注意,头插在顺序表中是需要将后面的所有元素都向后移动的,复杂度为O(n)。

 ​​​​

🔥erase

【erase】用于从字符串中删除一个或多个字符这个函数有几个重载版本,允许你指定要删除的字符的起始位置和长度,或者通过迭代器来指定要删除的字符范围。

void test_string6()
{
	string s("hello world");
	s.erase(6, 1);
	cout << s << endl;//hello orld

	s.erase(0, 1);
	cout << s << endl;//ello orld

	s.erase(s.begin());//llo orld
	cout << s << endl;

	s.erase(--s.end());//llo orl
	cout << s << endl;
	
	s.erase(s.size() - 1, 1);
	cout << s << endl;//llo or

	s.erase(2);
	cout << s << endl;//ll
}

 ​​​​

🔥replace

【replace】用于替换字符串中的一部分内容。这个函数有多个重载版本,允许你指定要替换的字符范围以及替换成的新内容。

void test_string7()
{
	string s1("hello world");
	string s2(s1);
	//少替换多-向后移动
	s1.replace(5, 1, "%%");
	cout << s1 << endl;
	//hello%%world

	//多替换少-向前移动
	s2.replace(5, 4, "%%");
	cout << s2 << endl;
	//hello%%ld
}

 ​​​​

🔥swap

【swap】用于交换两个string对象的内部数据。在调用【swap】后,两个字符串对象将包含对方原本持有的数据。这种操作通常比手动复制和删除数据要快得多,因为它只需要交换内部指针或引用,而不需要实际复制字符串内容。

void test_string9()
{
	string str1 = "Hello";
	string str2 = "World";

	str1.swap(str2);

	std::cout << "str1: " << str1 << std::endl; // 输出: str1: World  
	std::cout << "str2: " << str2 << std::endl; // 输出: str2: Hello  
}

 ​​​​

🔥pop_back

 【pop_back】用于移除字符串中的最后一个字符。这个函数没有返回值,并且它会修改调用它的string对象,使其长度减少一个字符。

void test_string10()
{
	string str = "Hello, World!";

	//显示原始字符串  
	cout << "Original string: " << str << std::endl;

	//移除最后一个字符  
	str.pop_back();

	//显示修改后的字符串  
	cout << "After pop_back: " << str << std::endl;
}

 

•🌰5.string类对象的其他操作

string类除对象的访问遍历操作、对象的修改操作,还有一些其他的字符串操作:

下面我就注意给大家介绍。 

 ​​​​

🔥c_str

该函数返回一个指向以NULL结尾的字符数组(C 风格字符串)的指针,该数组包含了string对象中存储的相同字符序列。这个返回的指针可以用于需要C风格字符串的C++或C的API中。

这个接口是连接C字符串与C++string类对象的桥梁,很好地起到了一个连通的效果。

void test_string11()
{
	string file;
	cin >> file;
	FILE* pf = fopen(file.c_str(), "r");
	char ch = fgetc(pf);
	while (ch != EOF)
	{
		cout << ch;
		ch = fgetc(pf);
	}
	fclose(pf);
}

 ​​​​

🔥find

【find】用于在字符串中搜索子字符串或字符的首次出现位置。如果找到了指定的子字符串或字符,【find】函数会返回子字符串或字符首次出现的索引(基于0的索引);如果没有找到,则返回npos,这是一个特殊的常量,表示未找到的位置,通常是一个很大的整数值。

例题:将字符串中的空格替换成"%%"

void test_string8()
{
	string s("hello world and Merry Christmas!");
	size_t pos = s.find(' ');
	while (pos != string::npos)
	{
		s.replace(pos, 1, "%%");
		pos = s.find(" ");
	}
	cout << s << endl;
}

但是,当空格较多的时候,频繁的扩容、插入、移动操作使得这个过程比较复杂,我们还可以用范围for这么来处理:

void test_string8()
{
	string s("hello world and Merry Christmas!");
	string temp;
	for (auto& ch : s)
	{
		if (ch == ' ')
			temp += "%%";
		else
			temp += ch;
	}
	cout << temp << endl;
}

 ​​​​

🔥rfind

 【rfind】的功能和【find】类似,只不过它是倒着找的。如果找到了指定的子串或字符,则返回它第一次出现的位置(从0开始计数);如果没有找到,则返回npos(这是一个常量,表示一个不可能的位置,通常是一个非常大的数)。

void test_string12()
{
	string str("The sixth sick sheik's sixth sheep's sick.");
	string key("sixth");

	size_t found = str.rfind(key);
	if (found != string::npos)
		str.replace(found, key.length(), "seventh");
        //The sixth sick sheik's seventh sheep's sick.

	cout << str << '\n';
}

 ​​​​

🔥substr(⭐)

【substr】是string 类中的一个成员函数,它允许你从一个字符串中提取(或称为“截取”)一个子字符串。这个函数非常有用,特别是在需要处理字符串的一部分时。

举例1:打印test.cpp的后缀.cpp

void test_string13()
{
	string s1("test.cpp");
	size_t pos = s1.find(".");
	string suffix = s1.substr(pos);

	cout << suffix << endl;
}

 举例2:打印test.cpp.zip的后缀.zip

void test_string13()
{
	string s1("test.cpp.zip");
	size_t pos = s1.rfind('.');
	string suffix = s1.substr(pos);

	cout << suffix << endl;
}

 ​​​​

🔥find_first_of

它用于在字符串中查找第一次出现与指定字符集合中任意字符相匹配的字符的位置。这个函数在你需要查找分隔符、查找属于某个字符集的第一个字符时特别有用。

举例:将str中的aeiou全部屏蔽为 * 

void test_string14()
{
	string str("Please, replace the vowels in this sentence by asterisks.");
	size_t pos = str.find_first_of("aeiou");
	while (pos != string::npos)
	{
		str[pos] = '*';
		pos = str.find_first_of("aeiou", pos + 1);
	}

	cout << str << '\n';
}

 ​​​​

🔥find_last_of

同样的道理,【find_last_of】和【find_first_of】类似,只不过是从末尾开始找的。

 举例:分割文件路径

void SplitFilename(const string& str)
{
	cout << "Splitting: " << str << '\n';
	size_t found = str.find_last_of("/\\");
	cout << " path: " << str.substr(0, found) << '\n';
	cout << " file: " << str.substr(found + 1) << '\n';
}

int main()
{
	string str1("/usr/bin/man");
	string str2("c:\\windows\\winhelp.exe");

	SplitFilename(str1);
	SplitFilename(str2);

	return 0;
}

  ​​​​

🔥find_first_not_of

 这个就和【find_first_not_of】相反了,查找不是指定字符集合中的字符并返回它的位置,是从头开始查找,如果找不到就返回string::npos。

举例:将str中的除aeiou的字符全部屏蔽为 * 

void test_string14()
{
	string str("Please, replace the vowels in this sentence by asterisks.");
	size_t pos = str.find_first_not_of("aeiou");
	while (pos != string::npos)
	{
		str[pos] = '*';
		pos = str.find_first_not_of("aeiou", pos + 1);
	}

	cout << str << '\n';
}

  ​​​​

🔥find_last_not_of

 同理,这个和【find_last_not_of】相反,查找不是指定字符集合中的字符并返回它的位置,是从尾开始查找,如果找不到就返回string::npos。

举例:删除空白字符

void test_string15()
{
	string str("Please, erase trailing white-spaces   \n");
	string whitespaces(" \n");

	size_t found = str.find_last_not_of(whitespaces);
	if (found != string::npos)
		str.erase(found + 1);
	else
		str.clear();

	std::cout << '[' << str << "]\n";
}

 

•🌰6.string类的非成员函数

string类提供了丰富的成员函数来操作字符串,但也有一些与string相关的非成员函数,这些函数虽然不是string类的一部分,但经常与string 一起使用,以提供额外的功能或操作。

这些非成员函数通常定义在<string>或其他相关的头文件中,并且它们通常是为了支持更通用的操作或算法,这些操作可能不特定于string,但可以与string 一起很好地工作。

🔥operator+

【operator+】用于将两个string对象、或者一个string对象和一个C风格字符串(const char*)连接起来,生成一个新的string 对象,该对象包含了两个操作数连接后的结果。

void test_string16()
{
	string s1("hello");

	string s2 = s1 + " world";
	cout << s2 << endl;

	string s3 = "world " + s1;
	cout << s3 << endl;
}

  ​​​​

🔥relational operators

string类提供了关系运算符(relational operators)来比较两个字符串对象。这些关系运算符不是string类的成员函数,而是作为非成员函数重载的,以便它们能够接收两个string类型的操作数。这些运算符返回布尔值(true或false),表示字符串之间的比较结果,功能上类似于C语言的【strcmp】函数。

以下是string类支持的关系运算符:

等于(==)检查两个字符串是否包含相同的字符序列。

不等于(!=)检查两个字符串是否不同。

小于(<=)按字典顺序比较两个字符串。如果第一个字符串在字典上小于第二个字符串,则返回 true。

大于(>=)按字典顺序比较两个字符串。如果第一个字符串在字典上大于第二个字符串,则返回 true。

小于等于(<=)按字典顺序比较两个字符串。如果第一个字符串在字典上小于或等于第二个字符串,则返回 true。

大于等于(>=)按字典顺序比较两个字符串。如果第一个字符串在字典上大于或等于第二个字符串,则返回 true。

void test_string17()
{
	string foo = "alpha";
	string bar = "beta";

	if (foo == bar) std::cout << "foo and bar are equal\n";
	if (foo != bar) std::cout << "foo and bar are not equal\n";
	if (foo < bar) std::cout << "foo is less than bar\n";
	if (foo > bar) std::cout << "foo is greater than bar\n";
	if (foo <= bar) std::cout << "foo is less than or equal to bar\n";
	if (foo >= bar) std::cout << "foo is greater than or equal to bar\n";
}

结果如下:

foo and bar are not equal
foo is less than bar
foo is less than or equal to bar

  ​​​​

🔥getline

【getilne】是一个非成员函数,它可以从输入流中读取字符,直到遇到换行符\n然后丢弃换行符,并将读取的字符序列(不包括换行符)存储到提供的string对象中。

举例:字符串最后一个单词的长度

#include <iostream>
using namespace std;

int main() 
{
   string str;
   //cin和scanf拿不到空格
   //cin >> str;
   getline(cin, str);

   size_t pos = str.rfind(' ');
   cout << str.size() - pos - 1 << endl;

   return 0;
}

 

•🌰7.例题训练

那么学习到现在,我们已经具备了写一些简单题目的能力,那就来几道题目练练手吧!

🔥题目一:仅仅反转字母

题目链接:917. 仅仅反转字母 - 力扣(LeetCode)

思路:设置左右边界,向中间走,如果不是字母就继续往中间走,知道都是字母就交换,整个思路和快速排序的霍尔方法差不多。

代码如下:

class Solution {
public:
    string reverseOnlyLetters(string s) {
        int left = 0;
        int right = s.size() - 1;
        while(left < right)
        {
            while(!isalpha(s[right]) && left < right)
            {
                right--;
            }
            while(!isalpha(s[left]) && left < right)
            {
                left++;
            }
            swap(s[left++], s[right--]);
        }
        return s;
    }
};

大家不要学习了我们string类的相关操作就忘记了普通而本质的方法,如同不要像学习数学只顾秒杀而忽略了最基本的方法。

 ​​​​

🔥题目二:第一个唯一字符

题目链接:387. 字符串中的第一个唯一字符 - 力扣(LeetCode)

题目描述:

思路:直接利用计数排序的思想统计所有字母字符出现的次数,再寻找第一个为1的数组元素就可以了。 

代码如下:

class Solution {
public:
    int firstUniqChar(string s) {
        int count[26] = { 0 };
        //统计次数
        for(auto& ch : s)
        {
            count[ch - 'a']++;
        }
        for(size_t i = 0; i < s.size(); i++)
        {
            if(count[s[i] - 'a'] == 1)
                return i;
        }
        return -1;
    }
};

 ​​​​

🔥题目三:字符串相加

题目链接:415. 字符串相加 - 力扣(LeetCode)

题目描述:

思路: 实现两个字符串表示的非负整数相加时,我们主要利用字符串从右到左(即最低位到最高位)的顺序,模拟手工加法的过程。

class Solution {
public:
    string addStrings(string num1, string num2) {
        string str;
        int end1 = num1.size() - 1, end2 = num2.size() - 1;
        //进位
        int next = 0;
        while(end1 >=0 || end2 >= 0)
        {
            int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;
            int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;
            int ret = val1 + val2 + next;
            next = ret / 10;
            ret %= 10;
            //头插
            str.insert(str.begin(), '0' + ret);
        }
        if(next != 0)
        {
            //头插
            str.insert(str.begin(), '1');
        }
        return str;
    }
};

关键点

  • 从低位到高位相加:模拟手工加法的过程,从字符串的末尾(即最低位)开始相加。
  • 处理不同长度的字符串:通过检查索引是否越界,将不存在的位视为0,从而统一处理不同长度的字符串。
  • 进位处理:使用变量next来记录进位值,并在每次相加时更新。
  • 结果字符串的构建:使用头插法构建结果字符串,确保最终得到的字符串顺序是正确的。

但是这个方法其实不太好,原因就是不断头插构成O(n^2)的时间复杂度,因此我们可以用尾插的方法:

class Solution {
public:
    string addStrings(string num1, string num2) {
        string str;
        int end1 = num1.size() - 1, end2 = num2.size() - 1;
        //进位
        int next = 0;
        while(end1 >=0 || end2 >= 0)
        {
            int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;
            int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;
            int ret = val1 + val2 + next;
            next = ret / 10;
            ret %= 10;
            //尾插
            str += ('0' + ret);
        }
        if(next != 0)
        {
            str += '1';
        }
        //逆置
        reverse(str.begin(), str.end());

        return str;
    }
};

此时是时间复杂度就降低至O(n)。

 • ✨SumUp结语

到这里本篇文章的内容就结束了,本节介绍了C++中string类的相关知识。这里的内容非常多,非常丰富,下一篇章还需要继续讲解。望大家能够认真学习,打好基础,迎接接下来的挑战,期待大家继续捧场~💖💖💖

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

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

相关文章

嘉立创PCB4层板

视频&#xff1a; 四层板PCB设计保姆级教程&#xff08;1&#xff09;&#xff1a;3.0HUB设计概述_哔哩哔哩_bilibili&#xff08;虽然是四层板实际这个还是两层板&#xff01;&#xff09; 不太建议看这个。 四层PCB 最简单终教学 高校培训课程 深入浅出 不会电路也能学会 设…

Nginx的核心!!! 负载均衡、反向代理

目录 负载均衡 1.轮询 2.最少连接数 3.IP哈希 4.加权轮询 5.最少时间 6.一致性哈希 反向代理 测试 之前讲过Nginx 的简介和正则表达式&#xff0c;那些都是Nginx较为基础的操作&#xff0c;Nginx 最重要的最核心的功能&#xff0c;当属反向代理和负载均衡了。 负载均…

YOLOv8实例分割+双目相机实现物体尺寸测量

1&#xff0c;YOLOv8实例分割原理介绍 YOLOv8是YOLO系列的最新版本&#xff0c;它在目标检测和实例分割方面都进行了显著的改进和创新。以下是YOLOv8实例分割原理的一些关键点&#xff1a; 先进的骨干和颈部架构&#xff1a;YOLOv8采用了先进的骨干和颈部架构来提高特征提取和物…

分治,1875C - Jellyfish and Green Apple

目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 1875C - Jellyfish and Green Apple 二、解题报告 1、思路分析 n 个苹果…

小阿轩yx-Kubernetes Pod入门

小阿轩yx-Kubernetes Pod入门 前言 Kubernetes 中 一个重要的概念就是 Pod&#xff08;豆荚&#xff09;并不直接管理容器&#xff0c;它的最小管理单元叫做 Pod。 Docker 应用中 把一个应用程序封装在一个镜像中&#xff0c;之后启动这个镜像并映射个宿主机端口号&#x…

03、Redis实战:商户查询缓存、缓存更新策略、缓存穿透、缓存雪崩、缓存击穿

2、商户查询缓存 2.1 什么是缓存? 什么是缓存? 就像自行车,越野车的避震器 举个例子:越野车,山地自行车,都拥有"避震器",防止车体加速后因惯性,在酷似"U"字母的地形上飞跃,硬着陆导致的损害,像个弹簧一样; 同样,实际开发中,系统也需要"避震器&qu…

2、Unity【基础】Mono中的重要内容

Unity基础 MonoBehavior中的重要内容 文章目录 Mono中的重要内容1、延迟函数1、延迟函数概念2、延迟函数使用3、延迟函数受对象失活销毁影响思考1 利用延时函数实现计时器思考2 延时销毁 2、协同程序1、Unity是否支持多线程2、协同程序概念3、协同程序和线程的区别4、协程的使用…

APP架构设计_1.官方应用架构指南

1.官方应用架构指南 1.1架构的原则 应用架构定义了应用的各个部分之间的界限以及每个部分应承担的职责。谷歌建议按照以下原则设计应用架构。 分离关注点通过数据模型驱动界面单一数据源单向数据流 1.2谷歌推荐的应用架构 每个应用应至少有两个层&#xff1a; 界面层 - 在屏…

近视防控明星:蔡司小乐圆中期临床数据详解

近视防控明星&#xff1a;蔡司小乐圆中期临床数据详解 小乐圆镜片作为近视防控镜片里的明星产品&#xff0c;从22年5月上市以来防控效果就一直备受大家的关注。而最近中期临床试验的结果&#xff0c;给家长孩子吃了一颗定心丸。 本次实验中&#xff0c;240位受试者被随机分成三…

基于springboot框架的电影订票系统_wqc3k

TOC springboot611基于springboot框架的电影订票系统_wqc3k--论文 绪 论 1.1研究背景和意义 随着科学技术的不断发展&#xff0c;计算机现在已经成为了社会的必需品&#xff0c;人们通过网络可以获得海量的信息&#xff0c;这些信息可以和各行各业进行关联&#xff0c;电影…

你应该停止使用的 7 个已弃用的 Python 库

欢迎来到雲闪世界。升级您的 Python 工具包&#xff1a;发现 7 个应停止使用的过时库以及替代它们的功能。最近&#xff0c;我回顾了 Python 的新特性&#xff0c;发现每个版本都引入了创新&#xff0c;使我们的日常开发工作变得更加轻松。 这让我意识到科技是一门永无止境的艺…

8.21 QT

1.思维导图 2. 服务器端 头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTcpServer>//服务器类 #include <QMessageBox> #include <QDebug> #include <QList> #include <QTcpSocket>QT_BEGIN_NAMESPACE names…

免费高画质提取PPT/Word/Excel中的图片工具

下载地址&#xff1a;https://pan.quark.cn/s/134ccc35b8a2 软件简介&#xff1a; 好不容易搞到一个几十上百MB的ppt&#xff0c;想导出里面的图片进行二次加工&#xff0c;却被ppt超低画质的图片另存为功能劝退&#xff0c;明知里面全是高清图片&#xff0c;走时却是两手空空…

【C++从练气到飞升】14---深入浅出继承

&#x1f388;个人主页&#xff1a;库库的里昂 ✨收录专栏&#xff1a;C从练气到飞升 &#x1f389;鸟欲高飞先振翅&#xff0c;人求上进先读书&#x1f389; 目录 ⛳️推荐 一、继承的概念及定义 1.1 继承的概念 1.2 继承定义 1.2.1 定义格式 1.2.2 继承方式和访问限定符…

重新认识AbstractQueuedSynchronizer

开篇之前&#xff0c;烦请诸位允许我附庸风雅一次。近期因诸事繁杂&#xff0c;心情颇低落&#xff0c;遂于喜马拉雅APP中收听《老子》一文。其中的第八十一章《结天道》一文于我感悟颇深&#xff1a;和大怨&#xff0c;必有余怨&#xff0c;报怨以德&#xff0c;焉可以为善&am…

C++,std::bind 详解

文章目录 1. 概述2. 基本用法2.1 使用占位符2.2 示例 3. 总结 1. 概述 std::bind 是 C11 引入的一个功能&#xff0c;它允许你将函数&#xff08;或成员函数、函数对象&#xff09;与其参数绑定&#xff0c;生成一个新的可调用对象。这个功能在需要将函数及其参数一起传递给其…

DNF攻略:护石符文体系辅助详解,VMOS云手机助攻核心玩法!

在DNF游戏中&#xff0c;护石符文系统是提升角色实力的重要部分。当前版本中&#xff0c;护石符文体系经过了优化&#xff0c;使得获取方式更加便捷。以下是护石符文体系的详细介绍&#xff0c;以及如何使用VMOS云手机来更高效地管理和利用这一系统。 一、护石符文体系简介 护…

HarmonyOS 地图服务:深度解析其丰富功能与精准导航实力

目录 前期准备打造个性化地图&#xff1a;聚焦创建地图功能导入Map Kit相关模块通过MapOptions初始化地图切换地图类型设置地图中心点及层级展示定位按钮展示比例尺指定地图的日间夜间模式 通过MapComponentController对象方法控制地图切换地图类型开启3D建筑图层在指定的持续时…

【安当产品应用案例100集】008-UKEY在工业自动化数据传输中应用

工业自动化中的数据传输是确保生产过程高效、稳定运行的关键环节。工业自动化系统中&#xff0c;一般会有一个远程的客户端&#xff0c;负责将各个传感器、控制器等设备产生的信息传递到服务端&#xff0c;以实现生产过程的自动化控制和监控。它对于提高生产效率、降低生产成本…

SQL Server 2017上服务端设置强制加密启用SSL

在数据库服务端设置&#xff0c;强制所有客户端使用 SSL&#xff0c;设置完后&#xff0c;后续客户端所有连接&#xff0c;都将以密文传送&#xff0c;不论客户端是否指定安全连接&#xff08;即EncryptTrue/False&#xff09; 一、服务端强制加密使用 SSL 1.在数据库服务器上…