博文内容是学习很多博客所总结的,类似学习笔记,如有错误欢迎指出!
什么是string
类似Java中的String类,使用容器实现了字符串的操作,但是本质仍然是数组,所以也能使用数组的操作方式。我们在C语言中使用char类型来模拟字符串。而STL已经创建好了string对象,我们可以像Java使用String类一样方便地使用string对象。首先,我们要先引入头文件:
#include<string>
引入之后我们就可以方便地使用string了。具体的使用方法请见下文。
初识string-定义、初始化与输入
string的声明方法有非常多种,用哪一种都非常方便,下面几种方法都是定义了一个字符串"hello string!"
string s1 = "hello string!";
string s2("hello string!");
string s3;
s3 = "hello string!";
当然,习惯用哪一种就可以用哪一种。最常用的还是从键盘读入,下面也有几种常用的读取方法:
string s1;
cin >> s1;
getline(cin, s1);
getline(cin, s1, 'a');
getline()这个函数用处是比较大了。如果使用cin的时候,碰见空格、回车符,就算是字符串的结束,例如:
string s;
cin >> s;
cout << s << endl;
//input:aaa bbb
//output:aaa
空格后面就不再读取了(如果是cin >> s1 >> s2;那么s2将获得值"bbb"),如果我们想把空格也读取,就是说直接读取一行,就要使用getline()函数。即:
string s;
getline(cin,s);
cout << s << endl;
//input:aaa bbb
//output:aaa bbb
这个函数有第三个参数,可以一直从标准输入设备中读取字符,直到碰见该字符再停止,回车符也能够读取。例:
string s;
getline(cin,s,'s');
cout << s << endl;
//input:aaa bbb ccs
//output:aaa bbb cc
不过既然是整行读取,使用getline()就能碰到C/C++经典问题——吃回车。这个问题大一的时候还是挺困扰的,据说谭X也经常拿这个出来考试。不过大家都是学计算机的,这个问题可以一定要好好解决的,博主就不再赘述了,如果没碰见这个问题的同学,连续写2个getline()就能发现问题了(笑),解决方法敬请百度。
当然,C语言之前怎么用字符串,C++还是能使用的,但是gets函数这个还是推荐不使用吧。另外string如果用scanf的话有这样的操作:
string s1;
s1.resize(10);
scanf("%s", &s1[0]);
等于说我们在声明一个string后,直接对其分配了空间,然后用scanf就能读取数据了。但是注意,我们开辟的空间(就是使用resize函数)有多大,读取的时候最多就是多大,比如我们定的是10,那么就最多读取10个字符。这里注意:这里的10就是有效数据10个,不是跟C语言一样,考虑最后预留一位给 '\0' 。
但是,加入我们没有读取够10个字符,后面的就会被空格补充。见下例:
string s1;
s1.resize(10);
scanf("%s", &s1[0]);
cout << s1;
input: 0123
output: 0123 请按任意键继续. . .
常用方法
首先列举一下常用的string方法,这些并不是全部方法,博主只写了一些经常使用的:
begin() //得到指向字符串开头的Iterator
end() //得到指向字符串结尾的Iterator
rbegin() //得到指向反向字符串开头的Iterator
rend() //得到指向反向字符串结尾的Iterator
size() //得到字符串的大小
length() //和size函数功能相同
empty() //判断是否为空
push_back() //将一个字符压栈
erase() //删除字符串
clear() //清空字符容器中所有内容
这里单独说一下compare()函数
string s = "str";
cout << s.compare("strr") << endl;
output:-1
这个compare表示字符串与另一个字符串进行比较,例如代码中的"str"与"strr",根据ASCII码,如果原字符串相比比较小,那么返回值是“-1”,如果一样返回值是“0”,如果比较大返回值是“1”。
我们可以利用这个函数进行字符串之间的比较。
string基本操作-遍历与读取
我们可以用cout来输出整个字符串,有的时候也需要对字符串中的字符进行操作。读取与遍历是最常见的操作了,下面有一些例子,请看:
#include<iostream>
#include<string>
using namespace std;
int main() {
string s = "0123456789";
for (int i = 0; i < s.size(); i++) {
cout << s[i] << " ";
}
cout << endl;
cout << "char 2 is " << s[1] << endl;
string::iterator it;
for (it = s.begin(); it != s.end(); it++) {
cout << *it << " ";
}
cout << endl;
string::reverse_iterator r_it;
for (r_it = s.rbegin(); r_it != s.rend(); r_it++) {
cout << *r_it << " ";
}
cout << endl;
system("pause");
return 0;
}
上面的代码是不是看到string::iterator瞬间懵逼了?不要担心,由于这里跟迭代器有关,博主分开来写了,如果看不懂可以百度一下C++STL的迭代器用法以及逆向迭代用法,或者看博主的另一篇博文:
留坑- -
这里只用了解使用for循环的遍历方法就行了。
我们可以看出来,string也可以像数组一样直接得到某一个位置的数据。但是注意,此时获取的数据是char类型的,所以要:
char c = s[1];
0 1 2 3 4 5 6 7 8 9
char 2 is 1
0 1 2 3 4 5 6 7 8 9
9 8 7 6 5 4 3 2 1 0
另外有种C++的foreach遍历方法,感兴趣的可以百度,博主不再赘述了,挺好用的。
string基本操作-删除与清空
一般来说不太常需要删除与清空这些操作,至少我是用的比较少,数组的话在竞赛中经常使用memset来初始化(要了解memset的初始化原理,一般来说初始化0或者无限大0x3f3f3f3f,如果是其他数还是不能用memset的),string就用的很少了,这里也会介绍一下顺便用一下上面没有提到的方法:
#include<iostream>
#include<string>
using namespace std;
int main() {
string s = "rua!";
if (!s.empty()) {
cout << "string is " << s << endl;
s.clear();
}
if (s.empty()) {
cout << "clear!" << endl;
}
system("pause");
return 0;
}
bool empty(),如果字符串为空返回true,否则为false,博主在自己写一些判定为空的函数一般是定义为:
bool isEmpty()
clear(),顾名思义就是清空string,变成空字符串。
下面的删除就是稍微多一点了,使用的函数是erase()。针对string的区间进行删除操作,有多个重载函数方便完成不同的操作。简单说,总共有3种erase()的用法,下面的代码会解释一下这3种用法:
#include<iostream>
#include<string>
using namespace std;
int main(){
string s1 = "hello world!";
s1.erase(2, 3); // 删除从下标2开始的3个字符
cout << s1 << endl;
string s2 = "hello world!";
string::iterator it = s2.begin() + 2;
s2.erase(it); // 删除当前迭代器指向位置的字符
cout << s2 << endl;
string s3 = "hello world!";
s3.erase(s3.begin()+1, s3.end());
// 删除2个指针之间的字符(不包括第二个参数)cout << s3 << endl;system("pause");return 0;}
输出如下:
he world!
helo world!
hlo world!
请按任意键继续. . .
每个用法已经在注释中标记。可以比较一下实际输出。第二种用法要用到迭代器,实际上删除的是迭代器指向的字符。第三种是直接删除2个指针之间的数据,这里注意,第二个参数是不被包括的。例如上面代码,删除的是"hello"中的"el",即从begin()+1开始删除,到begin()+3的前一位为止。
关于第三种用法为什么要这样写,这里给出博主的个人见解,首先看一下这句代码:
s3.erase(s3.begin()+1, s3.end());
我们知道,end()函数指向的是最后一位的下一个内存地址,如果erase()函数包括第二个参数的话,等于说要删除一个不存在的内存空间。为了保护,不包括第二个参数是很稳妥的,这样最多只能删除到最后一个字符。
string基本操作-字符串操作
这里的一些函数就与C语言中string.h库中的函数有关了,至少名字是一样的,但是用法换成了面向对象的思想,就是封装进了string中。首先我们谈一下字符串的拼接,例:
string s1 = "aa";
string s2 = "bb";
string s3 = s1 + s2;
cout << s3 <<endl;
output:aabb
这个就非常简单了,使用+号就能直接将2个字符串进行拼接,但是注意没有“减号”这个说法的。
string s = "0123456789";
string ans = s.substr(0, 5);
cout << ans << endl;
output:01234
可以看到,substr的2个参数中,第一个参数表示截取的起点下标,第二个参数表示截取的长度(包括第一个下标)。代码就是从s[0]开始,截取5位,那么就是01234。
string操作-插入
string属于顺序存储结构,底层使用了数组来实现,学过数据结构就能知道,数组的插入操作是非常浪费时间的。算法复杂度上是O(n),而链表的插入操作仅仅为O(1)。所以string虽然也有插入操作,但是效率非常低。不过string的插入可以插入另一个字符串,不追求速度的情况下还是比较方便的。
string的插入也是有insert函数,其他的容器类很多也有,这里提一下一些用法即可:
string s = "str";
string ans = "233";
s.insert(1, ans);
cout << s << endl;
output:s233tr
最基本的用法,insert的2个参数中,第一个参数表示从下标1开始插入,第二个参数表示插入字符串。可以看到,是从下标1开始插入的,所以s[1] = ans[0],并不是下标1的元素不变,这里需要注意一下。
string s = "str";
char ans = '6';
string::iterator it = s.begin();
for (; *it != 't' && it != s.end(); it++);
if (it != s.end()) {
s.insert(it, ans);
}
output:s6tr
这里又牵扯到迭代器了,不懂迭代器的可以放一放。这里是先用迭代器迭代到适应位置。博主利用了for循环,但是没有循环体,而是满足条件迭代到字符串s的‘t’字符或者迭代完毕退出循环。下面的if先判断一下迭代器的位置,是不是迭代了整个字符串发现并没有字符't'。然后,insert的第一个参数是iterator类型,第二个参数必须是char类型,不能为string。表示在当前iterator所在位置插入字符。
上面这个操作也可以不使用迭代器来实现:
string s = "str";
string ans = "233";
for (int i = 0; i < s.size(); i++) {
if (s[i] == 't') {
s.insert(i, ans);
break;
}
}
cout << s << endl;
可见,这样还能插入字符串。只是注意,不要忘记break,否则就会陷入死循环了。
string操作-查找
跟其他容器一样,利用了find函数,例:
string s = "str233";
string f = "r2";
cout << s.find(f) << endl
output:2
就是查找第一个出现匹配目标的下标。这里注意,这个find函数并不是algorithm库中的find函数,不要搞混了。这个是string类所实现的一个方法。