目录
![请添加图片描述](https://img-blog.csdnimg.cn/direct/34f4dfc0b4f04fda8fe4058e02c598fa.gif#pic_center)
前言
上一节我们简单介绍了STL是什么以及对string字符串类做了个开头,那这节我们就来继续了解string类的使用。大家也可以对照着这个链接来查看string类的各种接口和使用说明。
string-C++Reference
本文只介绍string中的比较常见重要的接口,不会每一个都细说。
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 s1; //无参构造
string s2("hello world");//用c格式字符串构造string类对象s2
string s3(s2);//拷贝构造
string s4(10, 'x');//用10个字符c类型构造s4对象
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
cout << s4 << endl;
结果如下:
当调用无参构造函数时,系统默认将对象初始化为空串,即只包含’\0’的字符串。所以会发现第一行是空出来的。
string类对象的容量操作
函数名称 | 功能说明 |
---|---|
size(重点) | 返回字符串的有效字符长度 |
length | 返回字符串的有效字符长度 |
capacity | 返回空间总大小 |
empty(重点) | 检查字符串释放为空串,是返回true,否则返回false |
clear(重点) | 清空有效字符 |
reserve(重点) | 为字符串预留空间 |
resize(重点) | 将有效字符的个数改为n个,多出的空间用字符c填充 |
1.size、length和capacity
我们用size
和capacity
来获取字符串的有效长度
和对象的容量大小
。
注意:字符串的有效长度是不包括’\0’的。
string s("hello world!!");
cout << "The string is " << s << endl;
cout << "The string size is " << s.size() << endl;
cout << "The string lengrh is " << s.length() << endl;
cout << "The string capacity is " << s.capacity() << endl;
结果如下:
我们发现size和length的结果是一样的,那他们究竟有什么区别呢:
string诞生与STL之前
,最开始设计长度就是length()。- 当string并入到STL之后,
STL中其他容器获取大小都使用size()
,为了统一,就也给string类加上了size()。 为了统一,推荐大家使用size()。
2.empty和clear
empty
是判断字符串是否为空串
,clear
是清空有效字符
。
注意:清空有效字符只是将size清0,不改变底层的capacity的大小。
//接上段代码
cout << s.empty() << endl;
s.clear();//清空字符串的有效长度
cout << "The string size is " << s.size() << endl;
cout << "The string capacity is " << s.capacity() << endl;
结果如下:
第一行0则表示字符串不为空。
使用了clear后发现size已经变成了0,但是capacity不变。
3.reserve
reserve
是为字符串预留空间,说明白话就是扩容
,知道我们需要多少空间可以提前开好。
注意:它只影响capacity,不会影响size。
如下代码:
string s;
s.reserve(100);
s[100] = 'x';
结果就会崩溃,因为size还是0:
那reserve可以扩容,那它是否可以缩容呢,答案是可以,但不建议。
看下面代码:
string s;
s.reserve(100);
cout << "The string size is " << s.size() << endl;
cout << "The string capacity is " << s.capacity() << endl;
//想缩容,把capacity缩至50.
s.reserve(50);
cout << "The string size is " << s.size() << endl;
cout << "The string capacity is " << s.capacity() << endl;
//想继续缩容,把capacity缩至5.
s.reserve(5);
cout << "The string size is " << s.size() << endl;
cout << "The string capacity is " << s.capacity() << endl;
结果如下:
我们发现缩容至50时,它的capacity并没有发生改变,但是缩容至5时,它的capacity又发生了改变。
先说结论:在 VS 下,若reserve给的值小于当前 capacity ,同时大于16,默认不缩容。
具体原因,大家可以参考这篇博客,有详细的解答:
【C++ 第五章】string 类(补充):string 扩展空间规律 + reserve 的缩容 + resize 的使用
但是在Linux下,编译器则会遵循reserve,
根据reserve改变实时改变capacity。
所以总结来说:虽然reserve有缩容功能,但是一般不使用缩容,因为不同的平台reserve缩容规则不同,会导致代码的可迁移性降低。
4.resize
resize将有效字符的个数改为n个,多出的空间用字符c填充。
它也可以算是扩容,但是它与reserve的区别是reserve只影响capacity,而resize既影响size又影响capacity。
这两种都是将字符串的有效个数变成n个,不同的是当字符个数增多时,resize(size_t n)是用’\0’来填充多出的元素空间,resize(size_t n, char c)是用字符c来填充多出的元素空间。
若原空间中有字符串时,resize时不会改变原来的字符串,只会影响剩余未初始化的空间。
注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变capacity的大小,但是如果是将元素个数减少,则底层空间总大小即capacity不变。
string类对象的访问及遍历操作
函数名称 | 功能说明 |
---|---|
operator[](重点) | 返回pos位置的字符 |
begin + end | begin获取一个字符的迭代器+end获取最后一个字符下一个位置的迭代器 |
rbegin + rend | rbegin获取最后一个字符+rend获取第一个字符的前一个字符 |
1.operator[]
我们想要遍历
这个字符串
时,可以使用下标+[]
的形式,像数组一样去遍历
。
operator[]在cplusplus中定义了两种形式:
注:两种形式都采取了
传引用返回
,它可以减少拷贝,且可以修改返回对象。
string s("hello!");
size_t pos = 0;//下标
for (pos = 0; pos < s.size(); pos++)
{
//可以像数组一样直接用下标访问每个字符
cout << "第" << pos + 1 << "个字符是" << s[pos] << endl;
}
可以修改返回对象:
string s("hello!");
size_t pos = 0;//下标
for (pos = 0; pos < s.size(); pos++)
{
s[pos]++;
}
for (pos = 0; pos < s.size(); pos++)
{
cout << s[pos] << " ";
}
cout << endl;
结果则是:i f m m p "
在使用const形式时,要注意它的字符不能被修改的。
const string s1("haha");
s1[1] = 'x';
会报错:error C3892: “s1”: 不能给常量赋值
补充:
operator[]
在库里面重载定义时已经包含了检查
,可以防止越界。
2.迭代器
在访问和遍历string类对象时,可以使用下标+[]访问,但是这种形式不适合所有的底层结构,如链表,树等就不适合,此时就有一种新的形式出现:迭代器。
迭代器
:我们暂时理解为这是一个像指针一样的东西
(类似指针),不能说它就是指针。
对于迭代器的使用如下:
string s2("haha");
//创建迭代器,要将迭代器定义在类域里面
string::iterator it1 = s2.begin();
//begin获取一个字符的迭代器,相当于it1是指向第一个字符的指针,但迭代器并不是指针。
while (it1 != s2.end())//end获取最后一个字符下一个位置的迭代器
{
cout << *it1 << " ";
it1++;
}
同样它还存在反向迭代器(倒过来遍历)
string s2("haha");
//rbegin获取最后一个字符的迭代器,相当于rit1是指向最后一个字符的指针,但迭代器并不是指针。
string::reverse_iterator rit1 = s2.rbegin();
while (rit1 != s2.rend())
{
cout << *rit1 << " ";
rit1++;
}
注:迭代器的遍历区间都是左闭右开。
当然还存在只读的迭代器,用来遍历只读字符串。
const_iterator
正向遍历常量字符串const_reverse_iterator
反向遍历常量字符串
string s2("haha");
//创建迭代器,要将迭代器定义在类域里面
string::const_iterator it1 = s2.begin();
//等价于auto it1 = s2.begin();
while (it1 != s2.end())//end获取最后一个字符下一个位置的迭代器
{
//*it1++; 错误,不能修改
cout << *it1 << " ";
it1++;
}
注:
const_iterator 是迭代器指向的数据不能修改
const iterator是迭代器本身不能修改
我们前面说到范围for
,其实它的底层结构就是迭代器
:
for(auto& e : s2)
{
cout<<e<<" ";
}
范围for的特点就是自动取值,自动加加,自动结束。
string类对象的修改操作
函数名称 | 功能说明 |
---|---|
push_back | 在字符串后尾插字符c |
append | 在字符串后追加一个字符串 |
operator+=(重点) | 在字符串后追加字符串str |
c_str(重点) | 返回c格式字符串 |
insert | 在pos位置插入字符/字符串 |
erase | 在pos位置删除字符/字符串 |
find + npos(重点) | 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置 |
rfind | 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置 |
substr | 对str中从pos位置开始,截取n个字符,然后将其返回 |
1.push_back、append和operator+=
push_back:
尾插字符。
append:
尾插字符/字符串
operator+=:
尾插字符/字符串
string s1("hello");
s1.push_back('!');
cout << "s1:" << s1 << endl;
string s2("hello ");
s2.append("world ");
cout << "s2:" << s2 << endl;
string s3("hello ");
s3 += "world";
cout << "s3:" << s3 << endl;
我们发现append和operator+=的结果是一样的,但是operator+=使用起来方便很多,所以我们推荐使用operator+=。
2.c_str
有时候需要获取使用到指向字符串的指针,而这又是c语言中的内容,C++兼容C,所以string中就提供了c_str这个接口来帮助实现。
string s4("hello");
cout << s4.c_str() << endl;
在c语言中,当指针指向常量字符串时,编译器会直接打印所有内容。
3.insert和erase
insert
是在任意位置插入
字符/字符串
string s5("0123456");
s5.insert(2, "abc");//在第2个位置插入字符串"abc"
s5.insert(4, 1, 'd');//在第4个位置插入一个字符d
cout << s5 << endl;
要注意的是:当我们想要头插一个字符时,
必须要写成:s.insert(0,1,'y');
而不能直接写成s.insert(0,‘y’),把1省略掉了这是不行的。
当然也可以使用迭代器来实现头插一个字符:s.insert(s.begin(),'y');
erase
是在任意位置删除
字符/字符串
string s6("123456");
s6.erase(3, 2);//从第3个位置开始删除两个字符
cout << "s6.erase(3, 2):" << s6 << endl;
s6.erase();//无参则代表全删
cout << "s6.erase():" << s6 << endl;
无参则代表全删是因为erase()是一个全缺省参数,pos的缺省值为0,
代表从0位置开始删,长度len的缺省值为npos,
这代表无符号整型中的-1,则为整数最大值
,肯定大于字符长度,就等于全部删完。
注意:insert和erase的时间复杂度都是
O(n)
,效率不高,都要慎用。
4. find+npos和substr
find
是找到
字符在字符串中的位置
substr
是从pos位置开始
,截取n个字符并返回。
例:我们想要对一个网址分成三段,一段是协议,一段是域名,最后一段是路径。
// https://legacy.cplusplus.com/reference/string/string/substr/
//想将这个链接分成三段
string s("https://legacy.cplusplus.com/reference/string/string/substr/");
cout << "链接为:" << s << endl;
size_t pos1 = s.find(':');//找到冒号,并返回它所在的位置
string s1 = s.substr(0, pos1 - 0);//从下标0开始,想要截取pos1-0个长度的字符串
//左闭右开区间下标相减就是长度
cout << "协议为:" << s1 << endl;
size_t pos2 = s.find('/', pos1 + 3);//从pos1+3的位置即字符c这个位置开始往后找'/'
string s2 = s.substr(pos1 + 3, pos2 - (pos1 + 3));
cout << "域名为:" << s2 << endl;
string s3 = s.substr(pos2 + 1);//从pos2+1开始直到最后
//只有一个参数,截取长度为缺省值npos,为最大整数,因此直接取到最后
cout << "路径为:" << s3 << endl;
string类非成员函数
函数 | 功能说明 |
---|---|
operator+ | 尽量少用,因为是传值返回,导致深拷贝效率低 |
operator>>(重点) | 输入运算符重载 |
operator<<(重点) | 输出运算符重载 |
getline(重点) | 获取一行字符串 |
relational operators | 大小比较 |
1. operator+
operator+和operator+=一样,同样可以连接字符/字符串,但是operator+是传值返回,深拷贝效率低下。
string s1 = "hello ";
string s2 = "world";
string ret1 = s1 + s2;
string ret2 = "hehe " + ret1;
cout << "ret1:" << ret1 << endl;
cout << "ret2:" << ret2 << endl;
由于编译器想直接像ret2一样直接字符串+string
,这样就必须重载成全局函数
,所以这是string类的非成员函数。
2. operator>>和operator<<
我们可以像内置类型一样对string类直接使用流提取和流插入是因为对这两个函数进行了重载
。
string s;
cin >> s;
cout << s << endl;
3.getline
由于cin
在输入时遇到空格或换行会结束
,因此我们想输入获取一行包含空格的字符串时,就可以用到getline。
getline的使用:
string s1;
getline(cin, s1);
cout << s1 << endl;
注:
getline
默认换行表示结束。
在使用时需要加上#include< string>头文件
4.relational operators
relational operators也就是按字符的ASCLL码(字典序)进行比较。
string s2("haha");
string s3("hehe");
cout << (s2 < s3) << endl;
感谢大家观看,如果大家喜欢,希望大家一键三连支持一下,如有表述不正确,也欢迎大家批评指正。