当前位置: 首页 > news >正文

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

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

目录

  ✨说在前面

🍋知识点一:什么是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;//11cout << s5.size() << endl;//11cout << s5.capacity() << endl;//15cout << 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 worldcout << s.size() << endl;//11cout << s.capacity() << endl << endl;//15s.clear();cout << s << endl;//空cout << s.size() << endl;//0cout << s.capacity() << endl << endl;//15
}

 ​​​​

🔥empty

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

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

void test_string10()
{string s1("hello world");cout << s1.empty() << endl;//0string 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 worlds1.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 orlds.erase(0, 1);cout << s << endl;//ello orlds.erase(s.begin());//llo orldcout << s << endl;s.erase(--s.end());//llo orlcout << s << endl;s.erase(s.size() - 1, 1);cout << s << endl;//llo ors.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 += "%%";elsetemp += 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);elsestr.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类的相关知识。这里的内容非常多,非常丰富,下一篇章还需要继续讲解。望大家能够认真学习,打好基础,迎接接下来的挑战,期待大家继续捧场~💖💖💖

相关文章:

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

假如没有给你生命&#xff0c;你连失败的机会都没有。你已经得到了最珍贵的&#xff0c;还需要抱怨什么!&#x1f493;&#x1f493;&#x1f493; 目录 ✨说在前面 &#x1f34b;知识点一&#xff1a;什么是string&#xff1f; •&#x1f330;1.string类的概念 •&#x1…...

【Linux】——进程概念(万字解读)

一 冯诺依曼体系结构 在此之前&#xff0c;我们先要理解我们计算机的冯诺依曼体系结构&#xff0c;因为是进程的基础 我们所有的操作其实都是基于这样一个模型&#xff0c;比如你在qq上&#xff0c;和别人发送消息&#xff0c;这个消息肯定是先通过输入设备进行输入&#xf…...

03 serv00搭建WordPress

第一步 下载 serv00 官方教程 按官方教程下载 WordPress 压缩包&#xff0c;解压&#xff0c;将 WordPress 项目文件夹重命名为 public_html&#xff08;先删除原来的 public_html&#xff09; ‍ 第二步 安装 完成以上步骤后访问你的网站&#xff0c;开始安装 WordPress …...

伪共享问题如何解决?

伪共享问题是多核处理器环境下常见的性能瓶颈之一&#xff0c;特别是在多线程编程中。想要解决它&#xff0c;就必须先了解缓存行的概念。 缓存行 缓存行是指在 CPU 缓存中最小的数据单位&#xff0c;通常包含一定数量的字节&#xff08;例如&#xff0c;常见的缓存行大小为 …...

基于web框架的协同过滤的美食推荐系统【数据爬虫、管理系统、数据可更新、样式可调整】

文章目录 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主项目介绍研究背景研究的目的与意义协同过滤算法基于用户的协同过滤算法定义基于物品的协同过滤算法的定义 数据库设计db_food&#xff08;美食信息表&#xff09;db_collect&#xff08;美食…...

Eureka中的多实例配置:如何处理微服务实例动态扩展与缩减

Eureka中的多实例配置&#xff1a;如何处理微服务实例动态扩展与缩减 1. 引言 在微服务架构中&#xff0c;服务的动态扩展与缩减是确保系统弹性和高可用性的关键因素。Eureka&#xff0c;作为一个服务注册和发现的组件&#xff0c;扮演着至关重要的角色。它由Netflix开源&…...

Ubuntu 22.04使用 IPTables 配置防火墙

网络安全管理是服务器安全的重要组成部分。在这将介绍在 Ubuntu 22.04 中使用名为 iptables 的软件包管理工具设置防火墙的过程。 IPTables简介 IPTables是一个功能强大的软件包管理工具&#xff0c;可用于大多数Linux发行版&#xff0c;包括Ubuntu 22.04。该工具允许管理员定…...

Java语言程序设计——篇十三(1)

&#x1f33f;&#x1f33f;&#x1f33f;跟随博主脚步&#xff0c;从这里开始→博主主页&#x1f33f;&#x1f33f;&#x1f33f; 欢迎大家&#xff1a;这里是我的学习笔记、总结知识的地方&#xff0c;喜欢的话请三连&#xff0c;有问题可以私信&#x1f333;&#x1f333;&…...

GB/T 5023.3-2008额定电压450/750V及以下聚氯乙烯绝缘电缆

聚氯乙烯绝缘电缆产品分为固定布线用无护套电缆、固定布线用护套电缆、轻型无护套软电缆、一般用途护套软电缆、安装用电线和屏蔽电线、特殊用途护套软电缆、聚氯乙烯绝缘阻燃/耐火电缆等产品。 GB/T 5023.3-2008额定电压450/750V及以下聚氯乙烯绝缘电缆 第3部分&#xff1a;固…...

深入单例模式

1. 饿汉模式 饿坏了&#xff0c;上来就先实例化一个对象&#xff0c;好处是代码简单&#xff0c;坏处是这个对象后面如果一直用不到&#xff0c;就是个浪费。 public class A{ private static A a new A(); private A(){} public static A getInstance(){ return a; } } 2. 懒…...

MongoDB 单机和集群环境部署教程

目录 一、MongoDB 单机环境部署1. 环境准备2. 安装 MongoDB2.1 在 Ubuntu 上安装 MongoDB2.2 在 CentOS 上安装 MongoDB2.3 启动 MongoDB 服务2.4 验证 MongoDB 安装2.5 MongoDB 基本安全设置 3. 单机部署注意事项 二、MongoDB 集群环境部署1. 环境准备2. MongoDB Replica Set …...

【学习笔记】Day 20

一、进度概述 1、机器学习常识12-18&#xff0c;以及相关代码复现 二、详情 12、SVM&#xff08;support vector machines&#xff0c;支持向量机&#xff09; 实际上&#xff0c;支持向量机是一种二分类模型&#xff0c;它将实例的特征向量映射为空间中的一些点&#xff0c;…...

StringBuffer与StringBuilder 2024-8-21 22-13

目录 一、StringBuffer二、StringBuilder三、总结 一、StringBuffer StringBuffer是一个可变的字符序列&#xff0c;它的存在是为了解决频繁操作字符串时产生大量临时对象的问题。 构造方法&#xff1a; StringBuffer()&#xff1a;创建一个空的字符串缓冲区&#xff0c;初始容…...

会声会影剪辑视频收费吗,会声会影最新破解版

会声会影2024&#xff1a;引领视频创作新时代的创新之旅** 在数字时代的浪潮中&#xff0c;视频创作已成为连接世界、表达创意的重要方式。随着技术的不断进步&#xff0c;一款名为“会声会影2024”的视频编辑软件横空出世&#xff0c;它不仅继承了前代产品的优秀传统&#xf…...

在Windows11强制开启copilot

在 Windows 11 上启用自带的基于 GPT-4 的 Copilot 功能。以下是具体步骤&#xff1a; 更新系统&#xff1a; 确保你的 Windows 11 系统已经更新到最新版本&#xff08;23H2 或更高版本&#xff09;。你可以在“设置” > “Windows 更新”中检查并安装最新更新。 更改区域和…...

基于Java的开源CMS有哪些推荐,各自特点是什么

最强大、最易用的CMS&#xff0c;向大家做一个简要介绍。 01 Alfresco Alfresco是一个开源的企业网站内容管理系统&#xff0c;它提供了文档管理、多人协作、记录管理、知识管理网页内容和图像管理等功能。它使用Spring、 Hibernate、 Lucene 和JSF等最新java技术构建了模…...

IPC进程通信以及网络通信

一、IPC 进程间通信方式 共享内存 //最高效的进程间通信方式 不需要把用户的东西再复制到内核 他们与内核进行绑定 共享内存&#xff1a; 1、是一块&#xff0c;内核预留的空间 2、最高效的通信方式 //避免了用户空间到内核空间的数据拷贝 IPC通信方式 ---操作流程类似的…...

【脏数据 bug 解决】ValueError: mean must have 1 elements if it is an iterable, got 3

问题描述&#xff1a; 在训练模型的过程中&#xff0c;出现 clip_image_processor 无法处理数据的问题&#xff0c;说明数据集中很可能出现了脏数据。本文使用的数据为 LAION-Aesthetics-V2-6.5plus&#xff0c;从 https://dagshub.com/DagsHub-Datasets/LAION-Aesthetics-V2-…...

【Vue3】集成 Ant Design Vue

【Vue3】集成 Ant Design Vue 背景简介开发环境开发步骤及源码总结 背景 随着年龄的增长&#xff0c;很多曾经烂熟于心的技术原理已被岁月摩擦得愈发模糊起来&#xff0c;技术出身的人总是很难放下一些执念&#xff0c;遂将这些知识整理成文&#xff0c;以纪念曾经努力学习奋斗…...

如何处理前端项目中的SEO优化:从SPA到SSR与SSG

如何处理前端项目中的SEO优化&#xff1a;从SPA到SSR与SSG 在前端开发的日常工作中&#xff0c;你可能经常会遇到这样的情况&#xff1a;辛辛苦苦写了一个功能丰富、界面炫酷的单页应用&#xff08;SPA&#xff09;&#xff0c;但上线后发现搜索引擎的表现却不尽如人意。页面内…...

【UE5】Groom毛发系统的基本使用——给小白人添加头发

目录 效果 步骤 一、准备 二、使用3DsMax制作毛发 三、在UE中给小白人安装毛发 四、修改毛发材质 效果 步骤 一、准备 1. 新建一个第三人称模板工程 2. 在项目设置中&#xff0c;勾选“支持计算蒙皮缓存” 3. 在插件面板中&#xff0c;启用“Groom”和“Alembic Gro…...

DataWorks函数

文章目录 0、MaxCompute预置的函数分类1、日期函数2、数学函数3、算术运算符4、窗口函数5、聚合函数6、字符串函数7、复杂类型函数8、加密函数9、其他函数 0、MaxCompute预置的函数分类 函数类型说明日期函数支持处理DATE、DATETIME、TIMESTAMP等日期类型数据&#xff0c;实现…...

设计模式学习优质网站分享:refactoring.guru

地址 英文版地址&#xff1a;https://refactoring.guru/design-patterns 中文版地址&#xff1a;https://refactoringguru.cn/design-patterns 介绍 这个网站是专门学习 设计模式 和 软件重构 的网站 整体来说并不花哨&#xff0c;但我觉得他最大的优点就是&#xff1a; 概…...

JVM-Java的四种引用

引用分析 无论是通过引用计数算法判断对象的引用数量&#xff0c;还是通过可达性分析算法判断对象是否可达&#xff0c;判定对象是否可被回收都与引用有关&#xff0c;Java 提供了四种强度不同的引用类型 强引用&#xff1a; 被强引用关联的对象不会被回收&#xff0c;只有所…...

探索《黑神话:悟空》品质保障的背后:ISO体系认证

《黑神话&#xff1a;悟空》横空出世 8月20日上午10点&#xff0c;国产首款大型3A游戏《黑神话&#xff1a;悟空》正式上线。游戏一经上线便吸引了无数国内外用户的关注&#xff0c;不仅仅是因为其高超的游戏制作技术&#xff0c;极高的画面精度&#xff0c;精良的的视觉和战斗…...

ArcGIS Pro 实现人口分布栅格TIFF数据的网格提取与可视化

这里在分享一个人口1km精度栅格数据&#xff0c;LandScan是由美国能源部橡树岭国家实验室&#xff08;ORNL&#xff09;提供的全球人口分布数据集&#xff0c;具有最高分辨率的全球人口分布数据&#xff0c;是全球人口数据发布的社会标准&#xff0c;是全球最为准确、可靠&…...

select的缺点;poll ;poll的缺点;epoll

1.select的缺点&#xff1a; 1.select监听的文件描述符集合是一个数组&#xff0c;有上限&#xff08;1024个&#xff09; 2.select监听的文件描述符集合在应用层&#xff0c;内核层监听事件后需要传递给用户层带来资源开销 3.select需要用户手动查找产生事件的文件…...

keli5_报错 Cannot Load Device Description问题

1原因 之前创建的keli5的项目软件版本与当前的软件版本不同 使其算法要重新选择 2解决方法 2-1 点击图中的魔术棒 2-2 在这个界面中进入 Settings选项&#xff08;ST-Link Debugger 旁边&#xff09; 2-3 点击Flash Download 选项进入图中界面 在点击天加 2-4选择fla…...

算法的学习笔记—把二叉树打印成多行(牛客JZ78)

&#x1f600;前言 在算法面试中&#xff0c;二叉树的层序遍历是一个经典的题目。而这道题的要求是进一步将二叉树的每一层结点值打印成多行&#xff0c;即同一层结点从左至右输出&#xff0c;最终结果存放到一个二维数组中返回。接下来&#xff0c;我们将通过代码实例详细解析…...

FreeRTOS 时间管理

延时函数介绍 函数 描述 vTaskDelay() 相对延时 xTaskDelayUntil() 绝对延时 相对延时&#xff1a;指每次延时都是从执行函数vTaskDelay()开始&#xff0c;直到延时指定的时间结束 绝对延时&#xff1a;指将整个任务的运行周期看成一个整体&#xff0c;适用于需要按…...