目录
一.为什么要学习string类
1.1 C语言中的字符串
二. string类的常用操作
2.1 string类的初始化
2.2 string类常用的容量操作
2.2.1 size()
2.2.2 length() 函数
2.2.3 capacity() 函数
2.2.4 empty()函数
2.2.5 resize()函数和reserve()函数
2.2.5 clear() 函数
2.3 string类对象的访问及遍历操作函数
2.3.1 [] (operator[])
2.3.2 begin() 和 end()
2.3.3 rbegin() 和 rend()
2.3.4 范围for
2.4. string类对象的修改操作
2.4.1 push_back() 函数
2.4.2 append() 函数
2.4.3 +=(operator+=)
2.4.4 c_str
2.4.5 find() + npos
2.4.6 rfind()
2.4.7 substr()
2.5 常用string类非成员函数
2.5.1 operator+
2.5.2 >>和<<
2.5.3 getline
2.5.4 relational operators()
在C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
为了弥补这些C语言中的部分设计缺陷,C++研发出了string类,更好的服务于程序员。
重点:
在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。为了找到好Offer,熟练掌握string类是我们C艹程序员的必要工作。
string类的文档链接:https://cplusplus.com/reference/string/string/
同时,使用string类大多数情况还需要头文件#include
string对象的初始化和普通类型变量的初始化基本相同,只是string作为类,还有类的一些特性:使用构造函数初始化。
String类的常用初始化
函数名称(红色为重点) | 功能说明 |
string s() | 构造空的string类对象,即空字符串 |
string s(const char* s) | 用C-string来构造string类对象 |
string s(size_t n, char c) | string类对象中包含n个字符c |
string s(const string&s) | 拷贝构造函数 |
如:
//常用构造函数
#include
//用string类的话需要包含头文件#include
using namespace std;
int main()
{
string s1;
string s2("abcdefg");
string s3(99, 'a');
string s4(s2);
cout << s2 << endl;
cout << s3 << endl;
cout << s4 << endl;
return 0;
}
代码结果为:
函数名称(红色为重点) | 功能说明 |
size() | 返回字符串有效字符长度,(有效空间) |
length() | 返回字符串有效字符长度,(有效空间) |
capacity() | 返回空间总大小 |
empty() | 检测字符串释放为空串,是返回true,否则返回false |
clear() | 清空有效字符 |
reserve(size_t n=0) | 为字符串预留空间 |
resize(size_t n,char c) | 将有效字符的个数该成n个,多出的空间用字符c填充 |
如:
//容量操作
#include
using namespace std;
// size()函数
int main()
{
string s1(99, 'a');
cout << s1.size() << endl;
return 0;
}
代码结果为:
//容量操作
#include
using namespace std;
//length()函数
int main()
{
string s1(99, 'a');
cout << s1.length() << endl;
return 0;
}
代码结果为:
注意:
size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。(这个属于C++的早期设计缺陷)
//容量操作
#include
using namespace std;
//capacity()函数
int main()
{
string s1(10,'a');
cout << s1.capacity() << endl;
return 0;
}
代码结果为:
根据vs 开辟空间的规则,创建string类时初始分配十五个字节的总空间,但实际上这里的有效空间为10。
我们可以用size求出s1的有效空间 如:
#include
using namespace std;
//capacity()函数
int main()
{
string s1(10,'a');
cout << s1.capacity() << endl;
cout << s1.size() << endl;`
return 0;
}
//容量操作
#include
using namespace std;
// empty()函数
int main()
{
string s1;
string s2("12345");
cout << s1.empty() << endl;
cout << s2.empty() << endl;
return 0;
}
代码结果为:
一.resize() 重新规划string的大小,但是注意,这里实际上规划的是有效空间的大小。
通过前边的知识,我们可以用size()求出有效空间的大小,用resize进行重新规划。
如:
//容量操作
#include
using namespace std;
//resize()函数和reserve()函数
int main()
{
string s1("Hello World");
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s1.resize(5);//通过resize把有效空间改为5
cout << s1.size() << endl;
cout << s1.capacity() << endl;
cout << s1 << endl;
return 0;
}
输出结果为:
这时候就分两种情况
1.缩小有效空间:
这种情况下,容器中长度在n之外的部分会被截取掉,只保留n长度内的元素,但是容器的容量却没有改变,更不会出现扩容的状况,上段代码体现了这一点。
2.扩大有效空间:
这种情况下,容器为了能够放的下更多的元素,会发生扩容,扩容之后,其容量会比原来大,这时候就会出现没有用过的有效空间,此时我们通过函数的第二个元素 c 进行剩余空间的初始化。
也就是说,当扩容时,扩容的空间内存放的元素全都是第二个元素 C。如果没有指定第二个元素c,那就用string的默认初始化了,这种情况下,容器一定是分配了内存并全部发生了初始化的。
如:
//容量操作
#include
using namespace std;
int main()
{
string s1("Hello World");
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s1.resize(20, 'x');
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
return 0;
}
代码结果为:
注意:有效空间的变化可能会影响最大空间的变化,最大空间的变化也可能影响有效空间的变化。
二.reserve() 函数改变最大空间。
容量操作
#include
using namespace std;
int main()
{
string s1("Hello World");
cout << s1.capacity() << endl;
s1.reserve(20);
cout << s1.capacity() << endl;
return 0;
}
代码结果为:
注意:这里代码结果不同预想也是因为VS 的容量规则
容量操作
#include
using namespace std;
int main()
{
string s1("Hello World");
cout << s1.size() << endl;
s1.clear();
cout << s1.size() << endl;
cout << s1.capacity() << endl;
return 0;
}
代码结果为:
注意:clear()只是将string中有效字符清空,不改变底层空间大小。
函数名称 (重点为红色) | 功能说明 |
[] (operator[]) | 返回pos位置的字符,const string类对象调用 |
begin() | 获取第一个字符位置的迭代器 |
end() | 获取最后一个字符下一个位置的迭代器 |
rbegin() | 获取最后一个元素的反向迭代器 |
rend() | 获取第一个位置之前一个位置的反向迭代器 |
范围for | C++11支持更简洁的范围for的新遍历方式 |
实际上就是数组的[],可以随机访问元素,底层是操作符重载(不讲)。
//string类对象的访问及遍历操作函数
#include
using namespace std;
// []
int main()
{
string s1("abcde");
cout << s1[0] << endl;
cout << s1[s1.size()-1] << endl;
cout << s1[2] << endl;
return 0;
}
代码结果为:
//string类对象的访问及遍历操作函数
#include
using namespace std;
//begin()和end()
int main()
{
string s1("12345");
string::iterator it = s1.begin();
while (it != s1.end())
{
cout << *it << endl;
it++;
}
return 0;
}
代码结果为:
两图解迭代器原理(狗头保命):
C++中,迭代器就是一个类似于指针的对象,它能够用来遍历C++标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。
但在string中,我们最常用的还是数组的下标访问,故不在多描述。
//string类对象的访问及遍历操作函数
#include
using namespace std;
//rbegin()和rend()
int main()
{
string s1("12345");
string::reverse_iterator it = s1.rbegin();
while (it != s1.rend())
{
cout << *it << endl;
it++;
}
return 0;
}
结果为:
//string类对象的访问及遍历操作函数
#include
using namespace std;
//for(auto )
int main()
{
string s1("12345");
for (auto it : s1)
{
cout << it << endl;
}
return 0;
}
代码结果为:
是不是看起来很高端:
函数名称 | 功能说明 |
push_back(c) | 在字符串后尾插字符c |
append(s) | 在字符串后追加一个字符串 |
+=(operator+= ) | 在字符串后追加字符串str |
c_str | 返回C格式字符串 |
find + npos | 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置 |
rfind | 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置 |
substr | 在str中从pos位置开始,截取n个字符,然后将其返回 |
类似于链表的尾插
//string类对象的访问及遍历操作函数
#include
using namespace std;
int main()
{
string s1("Hello ");
cout << s1 << endl;
s1.push_back('W');
s1.push_back('o');
s1.push_back('r');
s1.push_back('l');
s1.push_back('d');
cout << s1 << endl;
return 0;
}
结果为:
也类似于链表的尾插,但是插入的是一个字符串。
//string类对象的访问及遍历操作函数
#include
using namespace std;
int main()
{
string s1("Hello ");
cout << s1 << endl;
s1.append("World");
cout << s1 << endl;
return 0;
}
结果为:
本质上是一个符号重载,依旧类似于链表的尾插。
//string类对象的访问及遍历操作函数
#include
using namespace std;
int main()
{
string s1("Hello ");
cout << s1 << endl;
s1 += "World";
cout << s1 << endl;
return 0;
}
结果为:
注意:
在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
在C语言中,字符串是以'\0'结尾的一些字符的集合。但在C++中,string类不以'\0'结尾,而是根据有效空间的大小结束。
这个函数的本质作用是:将 const string* 类型 转化为 const char* 类型
find有很多函数重载,我们只学一种就好了。
size_t find (const string &s,int pos=0);
s是需要查找的字符串,pos是从哪个位置查找 , 如果未查找到则返回npos(size_t 的最大值)。
如:
//string类对象的访问及遍历操作函数
#include
using namespace std;
int main()
{
string s1("fghasdabc");
cout << s1.find('a') << endl;
string s2("abc");
cout << s1.find(s2) << endl;
cout << s1.find('e') << endl;
return 0;
}
代码结果为:
同样,rfind()和find()只有一个往前找,一个像后找的区别:
size_t find (const string &s,int pos=npos);
//string类对象的访问及遍历操作函数
#include
using namespace std;
int main()
{
string s1("fghasdabc");
cout << s1.rfind('a') << endl;
string s2("abc");
cout << s1.rfind(s2) << endl;
cout << s1.rfind('e') << endl;
return 0;
}
此时 第一个打印结果就会发生变化:
函数定义为: string substr(size_t pos=0,size_t len=npos) const;
作用为:在string的pos位置截取n个字符,然后返回
//string类对象的访问及遍历操作函数
#include
using namespace std;
int main()
{
string s1("Hello World");
cout << s1 << endl;
string s2(s1.substr(0, 5));
cout << s1 << endl;
cout << s2 << endl;
return 0;
}
结果为:
函数名字 (红色为重点) | 作用说明 |
operator+ | 尽量少用,因为传值返回,导致深拷贝效率低 |
operator>> | 输入运算符重载 |
operator<< | 输出运算符重载 |
getline | 获取一行字符串 |
relational operators | 字符串大小比较 |
这个大家根据 + 和 +=的特性 ,再结合类的知识点,自己推一下为什么效率低(绝对不是因为我懒!!!)。
重载输入输出。
函数定义为:
istream& getline (istream & is,string& str);
注意:
cin输入会自动吃点前置换行和空格,即cin输入得到的不可能是空串。而getline不会吃掉换行符号和空格,可能会使得输入出现意料之外的错误。
比如说:
#include
#include//需要头文件
using namespace std;
int main()
{
string s1;//输入个Hello World 看看
cin >> s1;
cout <
代码结果为:
可见,cin遇见空格就结束了,可能会使得输入出现意料之外的错误。
正确的做法是使用getline();
//string类对象的访问及遍历操作函数
#include
#include//需要头文件
using namespace std;
int main()
{
//string s1;//输入个Hello World 看看
//cin >> s1;
//cout <
就是符号重载了关系运算符;
使得>=,<=等运算符可以用于字符串比较