一、与C语言的联系
C++的string类其实是从C语言的字符串演变过来的,是STL中用于处理字符串的一个容器。
在C语言中,字符串是以 '\0' 为结尾的一串字符,为了便于操作,C语言标准库中也提供了不少str系列的库函数供人使用。但是这些库函数是与字符串相分离的,并不符合面向对象编程的思想,而且C语言字符串的底层空间还需要人自行管理,繁琐且存在越界等风险,所以C++中规定了string类,来对C语言的字符串进行"升级"。
二、string的成员函数
1、构造函数
string类中提供了7种构造函数,
第一种是无参的默认构造函数,这时字符串末尾会自动加一个'\0',
第二种是拷贝构造函数,
第三种是把str中的,pos位置之后的npos个字符拷贝到对象中,
第四种和第五种是兼容C语言的表现,前者是将C字符串初始化给对象,后者是用C字符串中的前n个字符来初始化对象,
第六种是用n个字符c来为对象初始化,
第七种是关于迭代器的使用,目前先不做介绍。
cpp
#include <iostream>
using namespace std;
int main()
{
string a0("hello world!");
const char* pb = "hello world!";
string a1;
string a2(a0);
string a3(a0, 2, 3);
string a4("hello world!");
string a5(pb, 3);
string a6(8, 'c');
cout << a1 << endl;
cout << a2 << endl;
cout << a3 << endl;
cout << a4 << endl;
cout << a5 << endl;
cout << a6 << endl;
return 0;
}
第三个构造函数的最后一个参数是缺省参数,可以在传参的时候省略,缺省值为-1。而size_t是无符号整数,所以-1也就是整型最大值。相当于如果不传这个参数,这个函数会从pos之后开始拷贝后,一直拷贝到str结尾为止。
2、析构函数
string的析构函数是比较简单的,我们平时也基本用不到,往往是由编译器自动调用的。
3、赋值操作符
string中提供了赋值操作符的三种重载,分别支持了用另一个对象,用C字符串,以及用一个单个的字符,来为对象进行赋值。
cpp
#include <iostream>
using namespace std;
int main()
{
string a("hello world!");
string b, c, d;
b = a;
c = "hello world!"; //注意和c("hello world!");的区别,一个是构造,一个是赋值
d = 'c';
cout << b << endl;
cout << c << endl;
cout << d << endl;
return 0;
}
4、大小、容量相关函数
(1)size和length
这两个函数都是返回对象中存储的字符串的大小,都不计算字符串末尾的'\0',单位都是字节,区别只是函数名不一样罢了,他们的功能完全相同。
这俩函数返回的是对象中实际存储的字符串的大小,而不是对象存储空间的大小。因为string类底层是使用动态内存管理来创建空间,存储字符串的,所以对象实际占用的存储空间往往大于对象所存储的字符串的大小。
(2)max_size与capacity
这个函数的作用是返回string类的最大容量,然而这只是一个理论最大值,实际上受限于各种客观因素,往往达不到。
capacity函数的作用是返回当前这个对象中为字符串开辟的空间大小。
(3)resize与reserve
resize函数能重新设置字符串的大小。
如果设定的n值小于当前字符串的大小,那么字符串中前n个字符会保留,第n个之后的数据会被丢弃。
如果设定的n值大于当前字符串的大小,那么多出来的空间会用指定的字符c来填充,如果没有指定字符c,则会填充 '\0' ,但这时如果调用size函数会发现,字符串的大小已经变成了设定值。
cpp
#include <iostream>
#include <string>
using namespace std;
int main()
{
string a("abcd");
a.resize(7);
cout << a << endl;
cout << a.size() << endl;
return 0;
}
reserve函数可以改变字符串的容量到 n ,n需要大于字符串当前的容量。
如果 n 大于字符串当前的容量,那么字符串的容量就会扩大,扩大到 n 或者比 n 更大一些,也就是说,扩大后的字符串容量并不一定就是 n 。字符串的大小不会改变。
如果 n 小于字符串当前的容量,那么这个函数可能不会有作用,这时函数的行为是没有被C++明确规定的,容量可能会缩小到 n ,也可能不变,具体看编译器。
(4)clear与empty
这个函数的作用就是清空字符串中的数据,把这个字符串变成空字符串,大小也变成 0 。
它的作用是判断这个字符串的大小是否为0,如果是返回true,如果不是返回false。
cpp
#include <iostream>
#include <string>
using namespace std;
int main()
{
string a;
cout << a.empty() << endl;
a.append(2, '\0');
cout << a.empty() << endl;
a.resize(5);
cout << a.empty() << endl;
a.resize(0);
cout << a.empty() << endl;
return 0;
}
5、元素访问相关函数
(1)下标访问操作符
下标访问操作符也提供了重载,第一个是对于一般的string类创建的对象使用的,第二个是对于const修饰的string类的对象使用的。
区别在于前者可用于修改值的语句,而后者不可以,符合了其常属性。
(2)at
at 函数用于获取pos位置的下一个元素,返回值是该元素别名,因此可以对该字符直接进行修改。
cpp
#include <iostream>
#include <string>
using namespace std;
int main()
{
string a("1234567");
cout << a.at(5) << endl;
a.at(5) = 'x';
cout << a << endl;
return 0;
}
6、迭代器相关函数
迭代器是设计用于我们遍历访问修改字符串中的元素的,它的行为和指针很相似,但底层不一定就是指针,具体是什么要看编译器是怎么实现的。
(1)begin和end
begin函数返回一个迭代器,这个迭代器指向的是对象存储的字符串的起始位置,也就是字符串的第一个字符的位置。
end函数返回一个迭代器,指向字符串的最后一个字符的下一个位置,也就是 '\0' ,注意这个迭代器是不可被解引用的,会报越界错误。
如果对象是空字符串,那么begin函数和end函数返回的迭代器指向的位置是相同的。
(2)rbegin和rend
rbegin函数返回一个迭代器,指向字符串的最后一个字符。
rend函数返回一个迭代器,指向字符串的第一个字符的前一个位置。
这俩函数和begin与end相似,他们函数名前 'r' 的意思是reverse逆序,也就是说begin与end是正序的,从前往后的遍历,而rbegin与rend则是逆序的从后往前的遍历。
当使用这两个函数时,接收其返回值时也要使用相应类型的迭代器。
cpp
string a("hello");
string::reverse_iterator it = a.rbegin();
(3)cbegin、cend、crbegin、crend
这四个函数和上面四个函数的作用是基本相同的,唯一区别在于返回值一定是被const修饰的迭代器,所以调用这四个函数得到的迭代器,只能对字符串进行遍历操作,而不能修改字符串的值。
7、修改字符串的相关函数
对于字符串的增加减少,插入删去,使用这些函数:
(1)+= 操作符
+= 操作符有三种重载,分别支持字符串 += string字符串、C字符串、一个字符。
cpp
#include <iostream>
using namespace std;
int main()
{
string a("ab");
string b("cd");
cout << a << endl;
a += b;
cout << a << endl;
a += "efg";
cout << a << endl;
a += 'h';
cout << a << endl;
return 0;
}
(2)append与insert
这个函数是在字符串末尾增加上指定的字符串。
第一种重载是将字符串str拷贝到原字符串的末尾,
第二种重载是将字符串str的第subpos之后的sublen个字符拷贝到原字符串的末尾,
第三种重载是将C字符串拷贝到原字符串的末尾,
第四种重载是将C字符串的前n个字符拷贝到原字符串的末尾,
第五种重载是将n个字符c拷贝到原字符串的末尾。
cpp
#include <iostream>
using namespace std;
int main()
{
string a("abcdef");
string b1, b2, b3, b4, b5;
b1.append(a);
b2.append(a, 0, 3);
b3.append("abcd");
b4.append("abcdef", 4);
b5.append(4, 'x');
cout << b1 << endl;
cout << b2 << endl;
cout << b3 << endl;
cout << b4 << endl;
cout << b5 << endl;
return 0;
}
insert函数的作用是在原字符串第pos个字符之后,插入一个字符串,原来字符串的内容依次后移。
cpp
#include <iostream>
using namespace std;
int main()
{
string a("123456789");
a.insert(3, "abc");
cout << a << endl;
return 0;
}
(3)push_back与pop_back
这两个函数用于在字符串末尾添加或删除一个字符,并把字符串大小增大或减小 1 。
注意他们不可以增删一个字符串,只能增删一个字符,所以并不常用。
(4)assign
assign的作用是用新字符串覆盖原字符串。
cpp
#include <iostream>
using namespace std;
int main()
{
string a("abcdefg");
a.assign("hello");
cout << a << endl;
return 0;
}
(5)erase
erase函数会删除字符串中的字符。
第一种重载是一个全缺省参数的函数,它可以删除原字符串中从第pos个字符开始的,长为len个字符的数据,当len不传参时,会一直删到字符串结尾,当pos、len都不传参时,会把字符串中的字符全部删掉。
cpp
#include <iostream>
using namespace std;
int main()
{
string a("123456789");
cout << a.erase(3, 4) << endl;
return 0;
}
(6)replace
这个函数将原字符串中,第pos个字符之后的len个字符,替换为指定的字符串,原字符串中再之后的字符不动或依次前移后移。
cpp
#include <iostream>
#include <string>
using namespace std;
int main()
{
string a("123456789");
a.replace(1, 3, "abc");
cout << a << endl;
a.replace(1, 3, "x");
cout << a << endl;
a.replace(1, 3, "hello");
cout << a << endl;
return 0;
}
(7)swap
swap函数用于交换原字符串和指定字符串中的数据,所以只支持和string字符串进行调用。
8、字符串操作的相关函数
(1)c_str和data
c_str 函数返回一个指向C字符串的指针,C字符串的内容就是string字符串中的有效内容(用size函数看到的大小,这些string字符串的数据,不包括底层包含的 '\0' ),再在结尾额外增加一个 '\0' 。
data 函数返回一个指向C字符串的指针,C字符串的内容就是string字符串中的有效内容,结尾没有 '\0' 。
这两个函数返回的都是被const修饰的指针,不允许通过解引用修改值。
(2)copy
copy 函数向C字符串 s 中拷贝一个字符串,这个字符串是从原字符串的第pos个字符的下一个开始,长为len的子串。
cpp
#include <iostream>
#include <string>
using namespace std;
int main()
{
char a[] = "xxxxxx";
char* pa = a;
string b("12345");
b.copy(pa, 3, 0);
cout << pa << endl;
return 0;
}
(3)find与rfind
这两个函数都是从原字符串中查找 string字符串str 或 C字符串s 或 字符c 的,前者是查找最先出现的,后者是查找最后出现的。
这两个函数需要符合查找的字符串中的所有字符。
(4)find_first_of与find_last_of
这两个函数都是从原字符串中查找 string字符串str 或 C字符串s 或 字符c 的,前者是查找最先出现的,后者是查找最后出现的。
这两个函数不需要符合查找的字符串中的所有字符,只查找到其中任何一个字符就会停止并返回位置。
(5)substr
substr 函数用来获得一个子串,从原字符串的第pos个字符的下一个开始,长为len的子串。
如果len未被指定,则子串一直延续到原字符串结束。
cpp
#include <iostream>
#include <string>
using namespace std;
int main()
{
string a("123456");
string b;
b = a.substr(1, 2);
cout << b << endl;
return 0;
}
(6)compare
compare 函数用于比较两个字符串的大小,按照字典序进行比较,如果相等返回0,原字符串大返回一个大于 0 的值,原字符串小返回一个小于 0 的值。
三、string的遍历与修改
1、下标 [ ]
用下标方式来遍历和修改字符串中的元素:
cpp
#include <iostream>
using namespace std;
int main()
{
string a("hello world!");
for (size_t i = 0; i < a.size(); i++) {
cout << ++a[i] << ' ';
}
cout << endl;
return 0;
}
2、迭代器 iterator
用迭代器方式来遍历和修改字符串中的元素:
cpp
#include <iostream>
using namespace std;
int main()
{
string a("hello world!");
string::iterator it = a.begin();
while (it != a.end()) {
cout << ++(*it) << ' ';
++it;
}
cout << endl;
return 0;
}
迭代器 iterator 是定义在类域中的,所以在使用迭代器之前一定要加上所属类名以及域作用限定符(也可以展开类域,但我们通常不这么用)。
3、范围for
cpp
#include <iostream>
using namespace std;
int main()
{
string a("hello world!");
for (auto& e : a) {
cout << ++e << ' ';
}
cout << endl;
return 0;
}
SQL容器的范围for,底层其实就是迭代器,我们自己自定义一个类时,如果按格式定义了一个迭代器,那么我们自定义的类也可以使用范围for。
所以从语法上讲,有三种遍历方式,但从底层上讲,只有两种遍历方式,一是下标,二是迭代器。