string类的理解和使用
目录
一、为什么学习string类
1.1 C语言中的字符串
1.2 面试题目
二、标准库中的string类
2.1 string类
2.2 VS下string类的构成
三、string类对象的构造函数
3.1 string()
3.2 string(const string& str)
3.3 string(const string& str,size_t pos,size_t len=npos)
3.4 string(const char* s)
3.5 string(const char* s,size_t n)
3.6 string(size_t n,char c)
四 、string对象的输出
五、string类的三种遍历方式
5.1 使用[]来访问遍历
5.2 使用迭代器来访问遍历
5.2.1 begin()和end()的使用
5.2.2 rbegin()和rend()的使用
5.3 使用范围for访问遍历
5.3.1 auto
5.3.2 范围for
5.3.3 使用范围for来遍历string
六、string类的容量方法
6.1 capacity
6.2 reserve
6.3 resize
6.4 clear
6.5 empty
七、string类的修改操作
7.1 push_back
7.2 append
7.3 operator+=
7.4 assign
7.5 insert
7.6 erase
7.7 replace
7.8 swap
7.9 pop_back
八、string类的其他操作
8.1 c_str
8.2 find
8.3 rfind
8.4 find_first_of
8.5 find_last_of
8.6 find_first_not_of
8.7 find_last_not_of
8.8 substr
8.9 compare
九、string的非成员函数
9.1 operator+
9.2 各种关系运算符重载
9.3 swap
9.4 operator<<
9.5 operator>>
9.6 getline
十、题目练习
一、为什么学习string类
1.1 C语言中的字符串
C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
1.2 面试题目
在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、 快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。
二、标准库中的string类
2.1 string类
string的参考文档如下:
string - C++ Reference
在使用string类时,必须包含#include头文件以及using namespace std;
2.2 VS下string类的构成
VS 下string 总共占 28 个字节。
- 当字符串长度小于 16 时,使用内部固定的字符数组来存放
- 当字符串长度大于等于16 时,使用从堆上开辟空间
class String
{
private:
//vs下string类里面的成员变量大概是这样
char _buff[16];
char* str;
size_t _size;
size_t capacity;
};
int main()
{
cout << sizeof(String) << endl;
return 0;
}
下面我们来介绍string类常用的接口
三、string类对象的构造函数
在上述string类的文档介绍中可以看见有以下的构造函数:
其中最常见的是第一种,第二种和第四种。接下来我们来讲解上述这几个构造函数
3.1 string()
这个构造函数是默认构造函数,当我们不传入参数时,就会实例化一个空字符串,使用代码如下:
#include<iostream>
#include<string>
using namespace std;
int main() {string s1;string s2;return 0;
}
3.2 string(const string& str)
这个构造函数是拷贝构造函数,用一个已经创建的string对象来实例化一个新的字符串,我们先用第4中构造函数实例化一个string类的对象,然后将这个对象拷贝给一个新字符串。代码如下所示:
#include<iostream>
#include<string>
using namespace std;
int main() {string s1("hello world");string s2(s1);return 0;
}
3.3 string(const string& str,size_t pos,size_t len=npos)
这个构造函数也是一个拷贝构造函数,与上面不同的是,他可以再传入两个参数,pos代表从哪一个位置开始拷贝,len指的是从pos位置开始拷贝多少个字符,如果不传入要拷贝多少个字符,就会使用默认参数npos,在c++中npos代表的是size_t 类型的-1,也就是全1,如果没有这么多个字符,就拷贝到str末尾。代码如下:
#include<iostream>
#include<string>
using namespace std;
int main() {string s1("hello world");string s2(s1, 6, 5);//从第六个字符开始,拷贝5个字符string s3(s1, 6);//从第六个字符开始,拷贝到末尾return 0;
}
3.4 string(const char* s)
这个构造函数是使用一个字符串来实例化string类型的对象,如下所示:
#include<iostream>
#include<string>
using namespace std;
int main() {string s1("hello world");string s2("hello");return 0;
}
3.5 string(const char* s,size_t n)
这个构造函数同样也是使用一个字符串来实例化string类型的对象,但是只取前n个字符,这个n不1要超过这个字符串的长度,代码如下:
#include<iostream>
#include<string>
using namespace std;
int main() {string s1("hello world",5);return 0;
}
3.6 string(size_t n,char c)
这个构造函数是使用n个c来实例化string类型的对象,代码如下所示:
#include<iostream>
#include<string>
using namespace std;
int main() {string s1(10,'c');return 0;
}
注意:大家不用把这些构造函数都记住,只需要记住常见的1,2,4中就可以了,其他的忘记了,可以查看string文档:https://cplusplus.com/reference/string/string/string/
四 、string对象的输出
在string类,重载了“<<“运算符,我们可以通过cout来直接输出string类型的对象,如下所示:
#include<iostream>
#include<string>
using namespace std;
int main() {string s1;string s2("hello world");string s3(s2, 6, 5);string s4(s2, 6);string s5(10, 'x');cout << "s1:" << s1 << endl;cout << "s2:" << s2 << endl;cout << "s3:" << s3 << endl;cout << "s4:" << s4 << endl;cout << "s5:" << s5 << endl;return 0;
}
输出如下所示:
五、string类的三种遍历方式
5.1 使用[]来访问遍历
在使用[]来遍历直接,我们先来学习两个接口,分别是size和length.
他们两个作用都是一样的,都是返回字符串的长度(不包括‘\0’),size()与length()方法底层实现原理完全相同,引入 size() 的原因是为了与其他容器的接口保持一致,一般情况下基本都是用 size()。
我们能在string中使用[]来访问string类型中的元素,就是因为string中重载了[]。函数原型如下:
它会返回pos位置的引用,我们就可以使用这个引用来访问这个字符或者修改这个字符。
使用[]访问string代码如下:
#include<iostream>
#include<string>
using namespace std;
int main() {string s1("hello world");for (int i = 0; i < s1.size(); i++){cout << s1[i];}cout << endl;return 0;
}
运行结果如下:
此外,我们不仅能够使用[]访问这个字符,还可以修改这个字符,因为返回的是这个位置的引用。
5.2 使用迭代器来访问遍历
迭代器提供了一种通用的访问容器的方式,所有的容器都可以用这种方式访问,而不需要关心容器的具体实现细节。掌握了string 的迭代器访问方式,就掌握了其他所有容器的访问方式,他们都会提供统一的接口。
我们接下来来看string文档来查看,string提供了哪些迭代器,如下所示:
5.2.1 begin()和end()的使用
这两个函数的原型如下:
iterator begin();
const_iterator begin() const;iterator end();
const_iterator end() const;
这两个函数都有两种版本,如果string类型是const类型的对象或者我们不想使用这个迭代器修改string对象,就可以使用const_iterator类型的迭代器。
我们先来看一下使用迭代器遍历string类型的代码,如下所示:
#include<iostream>
#include<string>
using namespace std;
int main() {string s1("hello world");string::iterator it = s1.begin();while (it != s1.end()) {cout << *it;it++;}cout << endl;return 0;
}
运行结果如下:
现在我们来解释一下这段代码:
1.迭代器属于其对应容器的类域。比如说string,就是string::iterator;后面还会学顺序表vector;就是vector<int>::iterator,string::iterator it:我们用string 的迭代器定义了一个变量it。我们可以将it 想象成一个指针,它的用法完全是跟指针类似的,但是他不是指针。
2.s1.begin();begin()是规定返回这块空间开始位置的迭代器;s1.end()是最后一个有效字符的下一个位置(这里是 ‘\0’ 位置)。
3.上述代码逻辑是:当 it 不等于end()时,对其进行解引用。再接着 ++it,it 往后移一位。
it和begin()以及end()的指向如下所示:
5.2.2 rbegin()和rend()的使用
这两个函数的原型如下:
reverse_iterator rbegin();
const_reverse_iterator rbegin() const;reverse_iterator rend();
const_reverse_iterator rend() const;
同理这两个函数也有两种版本,如果string类型是const类型的对象或者我们不想使用这个迭代器修改string对象,就可以使用const_reverse_iterator类型的迭代器。
这两个迭代器也叫反向迭代器,rbegin()指向的是最后一个有效字符,rend()指向的是第一个字符的前一个位置,注意,这两个函数的返回值类型是reverse_iterator。我们也可以使用这两个迭代器来访问string类类型,代码如下所示:
#include<iostream>
#include<string>
using namespace std;
int main() {string s1("hello world");string::reverse_iterator rit = s1.rbegin();while (rit != s1.rend()) {cout << *rit;rit++;}cout << endl;return 0;
}
rit和rbegin()以及rend()的指向如下所示,虽然rbegin指向的是最后一个位置,但是它还是使用++来向后面移动,这是编译器的规定。
5.3 使用范围for访问遍历
在使用访问for来遍历之前,我们先来学习auto关键字和范围for。
5.3.1 auto
- 在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
- 用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
- 当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
- auto不能作为函数的参数,可以做返回值,但是建议谨慎使用
- auto不能直接用来声明数组
使用auto来自动推导变量的类型,代码如下所示:
#include<iostream> #include<string> using namespace std; int func() {return 100; } int main() {int a = 10;auto b = a;auto c = 19.3;auto d = 'c';auto e = func();cout << "a的类型:" << typeid(a).name() << endl;cout << "b的类型:" << typeid(b).name() << endl;cout << "c的类型:" << typeid(c).name() << endl; cout << "d的类型:" << typeid(d).name() << endl; cout << "e的类型:" << typeid(e).name() << endl;return 0; }
输出如下所示:
注意1:使用auto类型必须具有初始值。以下代码是错误的。
auto f;
注意2:在声明符列表中,“auto”必须始终推导为同一类型,以下代码是错误的。
auto cc = 3, dd = 4.0;
注意3:数组不能具有其中包含“auto”的元素类型,以下代码是错误的。
auto array[]={3,3,4}
注意4:函数的参数不能是auto类型,以下代码是错误的。
void func2(auto a){}
auto的真正的用武之地在类型很长的地方,如map,代码如下所示:
#include<iostream> #include <string> #include <map> using namespace std; int main() {std::map<std::string, std::string> dict = { { "apple", "苹果" },{ "orange","橙子" }, {"pear","梨"} };// auto的用武之地//std::map<std::string, std::string>::iterator it = dict.begin();auto it = dict.begin();while (it != dict.end()){cout << it->first << ":" << it->second << endl;++it;}return 0; }
5.3.2 范围for
- 对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。
- 范围for可以作用到数组和容器对象上进行遍历
- 范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。
代码如下所示:
#include<iostream>
using namespace std;
int main()
{int array[] = { 1, 2, 3, 4, 5 };for (auto e : array)cout << e << " ";cout << endl;return 0;
}
5.3.3 使用范围for来遍历string
#include<iostream>
#include<string>
using namespace std;
int main() {string s1("hello world");for (auto ch : s1) {cout << ch << " ";}cout << endl;return 0;
}
运行如下所示:
但是这样写代码时只能访问,并不能修改的,代码如下所示:
#include<iostream> #include<string> using namespace std; int main() {string s1("hello world");for (auto ch : s1) {ch += 2;cout << ch;}cout << endl;cout << s1;return 0; }
运行如下所示:
我们使用ch来遍历s1,然后将ch每个值都+2,发现原来在输出s1,发现s1的值都没有变,这是为什么呢??不是说这个底层也是迭代器吗??
这是因为,我们是将每一个字符都拷贝给ch了,类似于对形参的改变不会影响实参。对ch的改变,不会影响原本的值。那现在我们如何使用范围for来修改s1的值呢???
我们可以使用auto& ,使用了auto&之后,我们的ch就相当于每一个字符的别名,对别名修改,也会对原始的字符串进行修改,代码如下所示:
#include<iostream> #include<string> using namespace std; int main() {string s1("hello world");for (auto& ch : s1) {ch += 2;cout << ch;}cout << endl;cout << s1;return 0; }
运行结果如下:
六、string类的容量方法
string中关于容量的方法如下所示:
函数名称 | 函数名称 |
---|---|
size(重点) | 返回字符串有效字符长度 |
length | 返回字符串有效字符长度 |
max_size | 返回字符串的最大长度 |
resize(重点) | 将有效字符的个数改成 n 个, 多出的空间用字符 c 填充 |
capacity(重点) | 返回空间总大小 |
reserve(重点) | 为字符串预留空间 |
clear(重点) | 清空有效字符 |
empty(重点) | 检查字符是否为空串,是返回true,否则返回false |
6.1 capacity
capacity这个方法返回的是给字符串开的空间总大小,我们在使用一个字符串来实例化一个string类型的对象,肯定不会只开字符串大小个空间,如果就开字符串大小个空间,我们插每入一个字符,就会发生扩容,效率就会极低,所以每次开空间就会多开一点。
那我们怎样查看每一次扩多少呢?我们创建一个string类型的对象,然后往这个对象插入100次字符,然后记录capacity,如果插入一个字符之后,记录的容量和插入之后的容量不相等了,就说明发生了扩容,代码如下所示:
#include<iostream>
#include<string>
using namespace std;
int main() {string s1;int capa = s1.capacity();cout << "first capacity:" << capa << endl;for (int i = 0; i < 100; i++) {s1.push_back('x');if (capa != s1.capacity()) {cout << "capacity change:" << s1.capacity() << endl;;capa = s1.capacity();}}return 0;
}
运行结果如下:
我们插入了100个字符扩容了四次。capacity是存放有效字符的,不会存放‘\0’,但是string还是会多开一个空间来存放\0
6.2 reserve
reserve这个函数是给string类型对象预留空间的,如果我们频繁的插入数据,例如插入10万个字符,就会导致一直扩容,扩容是有消耗的,如何这个指针后面还没有足够的空间,还会迁移数据。
所以就可以使用reserve来预定空间的。但是每次还是会比预定的空间多开一点。
6.3 resize
resize 是 调整字符串的大小,即将字符串调整为n 个字符的长度。
函数原型如下:
void resize (size_t n);
void resize (size_t n, char c);
1.当重新设置的大小n 小于 原大小时,会直接截断,只存重新设置的大小n个字符,代码如下:
#include<iostream>
#include<string>
using namespace std;
int main() {string s1("hello world");s1.resize(5);cout << s1<<endl;cout << s1.size();return 0;
}
运行结果如下:
2.当重新设置的大小n 大于 原大小时,会将size改成n,然后使用你传入的字符c来进行填充,如果没有传入字符c,就会使用\0来填充。代码如下:
#include<iostream>
#include<string>
using namespace std;
int main() {string s1("I like to code in C");s1.resize(s1.size()+2, '+');cout << s1 << endl;return 0;
}
运行结果如下:
6.4 clear
clear的作用是将这个string对象的内容都删除,将size设置为0;capacity的大小不变。
6.5 empty
empty的作用判断string对象是否为空,如果为空就返回true,否则返回false。
七、string类的修改操作
函数名称 | 函数名称 |
---|---|
push_back(重点) | 在字符串后尾插字符 |
append | 在字符串后追加一个字符串 |
operator+=(重点) | 在字符串后追加字符或字符串 |
assign | 使用string对象或者字符串修改string对象的内容 |
insert | 在指定位置追加字符或者字符串 |
erase | 删除一到多个字符,或者删除迭代器区间的字符 |
replace | 将string对象中的指定位置替换成另外的字符或者字符串或字符串一部分 |
swap(重点) | 交换两个string 对象 |
pop_back | 删除string对象最后一个有效字符 |
7.1 push_back
这个函数的作用是向string对象尾部插入一个字符。
7.2 append
这个函数的作用是给一个string对象追加一个string对象,或者追加string对象的一部分,或者追加一个字符串,或者追加一个字符串的n个字符,或者追加n个字符,函数原型如下所示:
代码如下:
#include<iostream>
#include<string>
using namespace std;
int main() {string s1("hello");string s2(" world");s1.append(s2);//追加string对象s2cout << s1 << endl;s1.append(s2, 0, 3);//追加string对象s2,第0个位置开始,追加3个字符cout << s1 << endl;s1.append(" how are you");//追加一个字符串cout << s1 << endl;s1.append("welcome", 4);//追加一个字符串的4个字符cout << s1 << endl;s1.append(10, 'x');//追加10个'x'字符cout << s1 << endl;return 0;
}
运行结果如下:
7.3 operator+=
这个运算符是重载了+=符号,可以在string对象后面追加string对象,或者字符串,或者一个字符,函数原型如下:
代码如下所示:
#include<iostream>
#include<string>
using namespace std;
int main() {string s1("hello");string s2(" world");s1 += s2;//追加string对象cout << s1 << endl;s1 += " how are you";//追加字符串cout << s1 << endl;s1 += 'x';//追加一个字符cout << s1 << endl;return 0;
}
运行如下所示:
7.4 assign
这个函数的作用是修改string对象中内容,可以使用一个string对象来修改string对象,可以使用一个string对象的一部分来修改string对象,可以使用一个字符串来修改string对象,可以使用一个字符串的一部分来修改string对象,也可以使用n个字符来修改string对象,函数原型如下所示:
代码如下所示:
#include<iostream>
#include<string>
using namespace std;
int main() {string s1("hello world");string s2("how are you");s1.assign(s2);cout << s1 << endl;s1.assign(s2, 0, 3);cout << s1 << endl;s1.assign("how are you");cout << s1 << endl;s1.assign("how are you", 3);cout << s1<<endl;s1.assign(10, 'x');cout << s1 << endl;return 0;
}
运行结果如下:
7.5 insert
insert这个函数是在一个位置或者一个迭代器指向的位置插入一个字符或者一个字符串。函数原型如下:
代码如下所示:
#include<iostream>
#include<string>
using namespace std;
//insert
int main() {string s1("hello");string s2(" world");s1.insert(5, s2);//在第五个位置插入s2cout << s1 << endl;s1.insert(s1.size(), s2, 0,3);//在s1的最后一个位置插入s2的字串,这个子串是从0位置开始的3个字符cout << s1 << endl;s1.insert(0, "how ");//在s1的第一个位置插入howcout << s1 << endl;s1.insert(0, "are you", 4);//在s1的第一个位置插入”are you“的前4个字符cout << s1 << endl;s1.insert(1, 3, 'c');//在s1的第二个位置插入3个字符ccout << s1 << endl;s1.insert(s1.end(), 10, 'x');///在迭代器s1.end()所指向的位置插入10个字符xcout << s1 << endl;s1.insert(s1.begin(), 'z');//在迭代器s1.begin()所指向的位置插入一个字符zcout << s1 << endl;return 0;
}
运行如下所示:
7.6 erase
删除一到多个字符,或者删除迭代器区间的字符,函数原型如下:
代码如下所示:
#include<iostream>
#include<string>
using namespace std;
//erase
int main() {string s1("hello world");s1.erase(0, 6);//从第0个位置开始删除,删6个字符cout << s1 << endl;s1.erase(s1.begin());//删除s1.begin()这个迭代器所指向的位置cout << s1 << endl;s1.erase(s1.begin() + 1, s1.end() - 1);//删除这两个迭代器之间的字符,左闭右开cout << s1 << endl;return 0;
}
运行结果如下:
7.7 replace
将string对象中的指定位置替换成另外的字符或者字符串或字符串一部分。
代码如所示:
#include<iostream>
#include<string>
using namespace std;
int main() {string s1("how are you");s1.replace(3, 1, "%%");//将下标为3的1个字符替换成%%cout << s1 << endl;return 0;
}
运行结果如下:
7.8 swap
用来交换两个string类型的变量。函数原型如下:
C++ 库中不是有swap模板吗?为什么string 库中又要写一个swap函数呢?是因为swap模板生成的swap函数有缺点。库中自带的swap函数原型如下:
当我们使用库里面自带的swap来交换两个string对象时,这个模板会生成一个交换string对象的函数,如下所示:
void swap(string& a, string& b)
{string c(a);a = b;b = c;
}
- 如果使用库里自带的swap来进行交换,会进行3次深拷贝:首先是用 a 拷贝构造c,然后后b 拷贝给a,最后是c 拷贝给b
- 3此拷贝都是深拷贝。
并且对自定义类型来说,深拷贝的代价是很大的,每次深拷贝都要开空间拷贝数据。而现在还是深拷贝 3 次,
效率无疑是大大降低
。
string类里面的swap函数没有采用库里面的写法,而是类里面重新写了一个swap函数,函数类似以下写法:
void swap(string& s1)
{std::swap(_str, s1._str);std::swap(_size, s1._size);std::swap(_capacity, s1._capacity);
}
当我们使用string类里面的swap来交换两个string对象时,只会交换string类里面的成员变量,这样交换仅仅只是交换内置类型,交换效率大大提高。
7.9 pop_back
删除string对象的最后一个字符,代码如下所示:
#include<iostream>
#include<string>
using namespace std;
//pop_back
int main() {string s1("hello world");s1.pop_back();s1.pop_back();cout << s1 << endl;return 0;
}
运行如下所示:
八、string类的其他操作
函数名称 | 函数名称 |
---|---|
c_str | 返回string对象中指向字符串的指针 |
find(重点) | 在string对象中从前向后开始查找一个字符或字符串 |
rfind | 在string对象中从后向前开始查找一个字符或字符串 |
find_first_of | 在string对象中从前向后查找字符串中的任意一个字符 |
find_last_of | 在string对象中从后向前查找字符串中的任意一个字符 |
find_first_not_of | 在string对象中从前向后查找不是字符串中的任意一个字符 |
find_last_not_of | 在string对象中从后向前查找不是字符串中的任意一个字符 |
substr(重点) | 获取一个string对象的字串 |
compare | 比较两个字符串 |
8.1 c_str
函数的功能是获取存储字符串空间的地址,string的底 简单来看如下:
class string
{
private:char* _str;//指向存储字符串的空间size_t _size;//记录当前字符串长度size_t _capacity;//记录当前空间大小
};
返回的就是这个_str的地址
8.2 find
在string对象中从前向后查找另外一个string对象,字符串或字符,如果找到了,就返回第一个匹配的下标,如果没有找到就返回无符号整型的最大值;
函数原型如下:
pos是从哪个位置开始搜索,默认从第0个位置开始
代码如下所示:
#include<iostream>
#include<string>
using namespace std;
int main() {string s1("There are two needles in this haystack with needles");string s2("needle");size_t index1=s1.find(s2);////在s1中查找s2cout << index1 << endl;size_t index2 = s1.find("are");////在s1中查找arecout << index2<<endl;size_t index3 = s1.find('a');////在s1中查找acout << index3<<endl;return 0;
}
运行结果如下:
8.3 rfind
在string对象中从尾向前查找另外一个string对象,字符串或字符,如果找到了,就返回第一个匹配的下标,如果没有找到就返回无符号整型的最大值;
函数原型如下:
代码如下,查找一个文件的后缀名:
#include<iostream>
#include<string>
using namespace std;
int main() {string s1("F:\\code\\cplusplus\\Project9_27\\Project9_27\\test.cpp");size_t index = s1.rfind('.');string suffix = s1.substr(index);cout << suffix << endl;return 0;
}
运行如下;
8.4 find_first_of
在string对象中从前向后查找字符串中的任意一个字符。找到了就返回第一个匹配的下标,找不到就返回string::npos。代码如下所示:
#include<iostream>
#include<string>
using namespace std;
int main()
{string str("Please, replace the vowels in this sentence by asterisks.");size_t found = str.find_first_of("aeiou");while (found != string::npos){str[found] = '*';found = str.find_first_of("aeiou", found + 1);}cout << str << '\n';return 0;
}
这一段代码会在str中从前向后查找”aeiou“这五个字符,str中只要有”aeiou“中的任意一个字符,就返回这个下标。然后将它替换成’*’。
运行结果如下:
8.5 find_last_of
在string对象中从后向前查找字符串中的任意一个字符。找到了就返回第一个匹配的下标,找不到就返回string::npos。
如下代码所示:分割unix或者windows的路径名和文件名。
#include<iostream>
#include<string>
using namespace std;
void SplitFilename(const std::string& str)
{std::cout << "Splitting: " << str << '\n';std::size_t found = str.find_last_of("/\\");std::cout << " path: " << str.substr(0, found) << '\n';std::cout << " file: " << str.substr(found + 1) << '\n';
}
int main() {std::string str1("/usr/bin/man");std::string str2("c:\\windows\\winhelp.exe");SplitFilename(str1);SplitFilename(str2);return 0;
}
运行如下所示:
8.6 find_first_not_of
在string对象中从前向后查找不是字符串中的任意一个字符。找到了就返回第一个匹配的下标,找不到就返回string::npos。和上面的find_first_of类似
8.7 find_last_not_of
在string对象中从后向前查找不是字符串中的任意一个字符。找到了就返回第一个匹配的下标,找不到就返回string::npos。和上面的find_last_of类似
8.8 substr
获取一个string对象的字串,一般搭配find使用,代码如下所示:
#include<iostream>
#include<string>
using namespace std;
int main() {string s1("hello world");size_t index = s1.find(' ');string s2 = s1.substr(0, index);string s3 = s1.substr(index+1);cout << s2 << endl;cout << s3 << endl;return 0;
}
找到s1空格,取前后两个部分。
输出如下所示:
8.9 compare
比较两个字符串,这个很少用,因为重载了比较string对象的运算符。
九、string的非成员函数
9.1 operator+
operator+这个运算符为什么不重载为成员函数呢?这是因为重载为成员函数只能string+string或者string+字符串,string对象牢牢的占住了运算符的第一个位置。有时候我们需要字符串+string。所以就把他重载成非成员函数。
9.2 各种关系运算符重载
函数原型如下所示:可以比较string和string,string和字符串,string和单个字符。
9.3 swap
在上面我们已经讲解了string类里面的swap成员函数,这个函数是string对象的全局函数,函数原型和介绍如下所示:
最后一句it behaves as if x.swap(y) was called,这句话的意思的:当我们使用全局的swap函数时,编译器会默认的调用swap的成员函数。
那现在c++库里面有一个swap的模板,现在又有一个swap的全局函数,当我们使用swap的全局函数来交换两个string类型的变量时,会调用哪一个呢?
在模板的那一张讲过,当一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
对于上述的这种情况,调用函数的时候会走非模板函数。
9.4 operator<<
重载了<<运算符。可以使用cout来直接输出string。
9.5 operator>>
重载了>>运算符。可以使用cin来直接输入string。
9.6 getline
这个函数是为了弥补cin的缺点。
如果我们想要自己输入一段英语句子,然后把它存到string对象里面。使用cin是做不到的,因为cin会默认使用空格或回车来分割,也就是cin读到空格就不读了,达不到我们的要求,getline是遇到回车就不读了。如下所示:
#include<iostream>
#include<string>
using namespace std;
int main() {string sentence;cin >> sentence;cout << sentence << endl;return 0;
}
输出如下所示:只读取了how。
这种情况我们就可以使用getline来实现,函数原型如下:
代码如下所示:
#include<iostream>
#include<string>
using namespace std;
int main() {string sentence;getline(cin,sentence);cout << sentence << endl;return 0;
}
输出如下所示:
十、题目练习
- 917. 仅仅反转字母 - 力扣(LeetCode)
- 387. 字符串中的第一个唯一字符 - 力扣(LeetCode)
- 字符串最后一个单词的长度_牛客题霸_牛客网
- 125. 验证回文串 - 力扣(LeetCode)
- 415. 字符串相加 - 力扣(LeetCode)
- 541. 反转字符串 II - 力扣(LeetCode)
- 557. 反转字符串中的单词 III - 力扣(LeetCode)
- 43. 字符串相乘 - 力扣(LeetCode)
- 找出字符串中第一个只出现一次的字符_牛客题霸_牛客网