C++学习之string类

许多的程序涉及字符串处理,C语言就在头文件string.h中提供了一系列字符串函数。C++的string类包含在头文件string里。要使用类,关键就在于了解它的公有接口。string类包含大量的方法,包括若干构造函数、赋值、合并、比较、访问各个元素的运算符重载以及用在字符串中查找子串的工具等等。

构造函数

构造函数是要首先看的,因为对于类而言,我们要知道有哪些方法可以用于创建其对象。下面列出了 string 的七个构造函数。使用构造函数时都进行了简化,即隐藏了这一个事实——string实际上是模板具体化basic_string<char>的一个typedef,同时省略了与内存管理相关的参数。size_type是一个依赖于实现的整型,是在头文件string中定义的。string::npos是string类定义的字符串最大长度,通常为unsigned int 的最大值。缩写NBTS是指null-terminated string,以空字符结束的字符串,也就是传统的C字符串。

构造函数 描述
string(const char *s) 以一个C风格字符串初始化
string(size_tpye n, char c) 构造一个包含n个字符c的string对象
string(const string &s) 复制构造函数
string() 默认构造函数
string(const char *s, size_type n) 初始化为s指向的NBTS的前n个字符,即使超过了NBTS的结尾
template string(Iter begin, Iter end) 初始化为区间[begin,end)内的字符
string(const string &s, string size_type pos = 0, size_type n = npos) 初始化为对象s从位置pos开始到结尾的字符,或从pos开始的n个字符。
string(string && s)noexcept C++11,将一个string对象初始化为str,并可能修改str(移动构造函数)
string(initializer_list il) C++11,将一个string对象初始化为初始化列表il中的字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include<iostream>
#include<string>

using namespace std;

int main()
{
string one("11111111");// 构造函数1
cout << one << endl;

string two(20, '2');//构造函数2
cout << two << endl;

string three(one);//构造函数3
cout << three << endl;

one += "233";//重载运算符+=,连接
cout << one << endl;

three[0] = 'p';//重载运算符[],可以用数组访问法访问各个字符;重载运算符=,赋值
string four;//默认构造函数构造一个可以稍后进行赋值的空串
four = two + three;
cout << four << endl;

char s[] = "abcdefghijklmnopqrstuvwxyz";
string five(s, 20);//构造函数5
cout << five << endl;

string six(s + 6, s + 10);//构造函数6
cout << six << endl;

string seven(five, 7, 16);//构造函数7
cout << seven << endl;

return 0;
}

对于string类的运算符重载,往往经过了多次重载,以便第二个操作数可以是string对象、也可以是C风格字符串、char字符。例如:

1
2
3
one += two;//将stirng对象two连接到one后面
one += "123";//连接一个C风格字符串
one += '!';//连接一个char

第五个构造函数有一个模板参数:

1
template<class Iter> string(Iter begin, Iter end)

beginend像指针一样,指向内存的两个位置。begin和end可以是迭代器——一种广泛存在于STL的广义化指针。范围是[begin,end),包括begin,但不包括end在内的区间。

现在假设要使用该构造函数用一个string对象初始化另一个string对象,看看下面的语句:

1
string str(five + 6, five + 10);

该语句是不管用的。原因在于对象名,不会被看做对象的地址(不像是数组名),因此five + 6是没意义的。而数组名s可以被看做地址,因此可作为该构造函数的一个参数。

构造函数string(string &&str)类似于复制构造函数,导致将新创建的string对象初始化为str的副本。但与复制构造函数不同的是,它不保证str视为const。这种构造函数称为移动构造函数(move constructor)。有些情况下编译器使用移动构造函数而不使用复制构造函数以优化性能。

构造函数string(initializer_list<char> il) 可以让下面的声明合法:

1
string name = {'A', 'l', 'i', 'c', 'e'};

string输入

对于类,要知道有哪些输入方式可用。对于C字符串,有下面三种:

1
2
3
4
char info[100];
cin>>info; //read a word
cin.getline(info, 100); //read a line, discard '\n'
cin.get(info, 100); //read a line, leave '\n' in queue

对于string对象,有两种方式:

1
2
3
stirng str;
cin>>str; //read a word
getline(cin, str);//read a line, discard '\n'

getline()有一个可选参数,选定一个字符作为输入边界:

1
2
cin.getline(infor, 100, ':'); 
getline(str, ':'); //都到冒号或满100个字符结束,如果是遇到冒号结束输入时,丢弃冒号

string版本的getline和C字符串的最大区别是getline可以自动调整string对象的大小,使之刚好能存储输入的字符。 自动调整大小的功能使得string版本的getline不需要一个指定读取多少字符的参数。

string输入函数都将自动调整string对象的大小,使之与输入匹配。但也存在一些限制。第一个是string对象有最大允许长度,一般是最大的unsigned int 值,对于平常的使用这不会带来麻烦。但当出现例如你要读取整个文件的内容到一个字符串中时,会产生限制。第二个限制因素是程序可用的内存量不是无限的。

string版本的getline()函数从输入中读取字符,直到发生下面三种情况之一:

  • 到达文件尾,此时输入流的eofbit将被设置,这意味着方法fail()eof()都将返回true
  • 遇到分界字符,默认为’\n’,也可以用参数指定。分界字符会从输入流中删除。
  • 读取字符数达到最大值。此时会设置输入流的failbit,这意味着方法fail()将返回true

比较、长度、子串

string类对全部的六个关系运算符< > != == >= <=进行了重载。在一种序列中,一个字符位于一个字符的前面,视为这个前者小于后者。ASCII码中是:数字小于字符,大写字符小于小写字符。对于每个运算符,和+=类似,都进行了多次重载。以便将string对象和另一个string对象、C风格字符串进行比较,也能将C风格字符串和string对象进行比较。

可以用方法 size()length()来确定字符串长度。

搜索子串

可以用多种不同的方法搜索给定的字符或子串。

函数原型 描述
size_type find(const string &str, size_type pos = 0)const 从pos开始,查找子串str,如果找到返回第一次出现位置的索引,如果找不到返回string::nops
size_type find(const char *s, size_type pos = 0)const 从pos开始,查找子串s,如果找到返回第一次出现位置的索引,如果找不到返回string::nops
size_type find(const char *s, size_type pos = 0, size_type n)const 从pos开始,查找s的前n个字符串组成的子串。如果找到返回第一次出现位置的索引,如果找不到返回string::nops
size_type find(char ch, size_type pos = 0)const 从pos开始,查找字符ch,如果找到返回第一次出现位置的索引,如果找不到返回string::nops

其他方法:rfind()find_first_of()find_last_of()find_first_not_of()find_last_not_of(),这些方法的重载函数特征标都于find()相同。rfind查找的是最后一次出现的位置。find_first_of查找参数中任何一个字符首次出现的位置。find_last_of功能和前者类似,但查找的是最后一次的出现位置。find_first_not_of查找的是第一个不包含在参数内的字符。

1
2
3
4
5
6
7
string s = "cobra";

int where;
where = s.find_first_of("hark"); // 返回的是索引3,即stirng对象中的r是hark中任何一个字符首先出现的位置
where = s.find_last_of("hark"); // 4
where = s.find_first_not_of("hark"); // 0
where = s.find_last_not_of("hark"); // 2

其他功能

还有很多其他的工具,不在此一一详细描述,可以找string的文档看一下。还包括如下功能的函数:删除字符串部分或全部内容、用一个字符串的全部或部分内容替代另一个字符串的全部或部分内容、将数据插入到字符串中、用一个字符串的全部或部分内容与另一个字符串的全部或部分内容向比较、提取子串、交换两个字符串的内容。这些方法大都被重载,以便能同时处理C字符串和stirng对象。

再来看看自动调整大小的功能。需要调整string对象的大小时会发生什么?不能仅仅将已有的内存空间加大,因为相邻的内存可能被占用。因此可能需要分配一个新的内存块,并将原来的内容复制到新块中。如果要执行大量这样的操作,效率会非常低。因此实现中需要分配空间时,都会分配一个实际较大的内存块。如果字符串不断增大,超过内存块的大小,程序将分配一个大小为原来的两倍的内存块。方法capacity()返回分配给当前字符串的内存块大小,reverse()`方法让你可以请求内存块的最小长度。

如果你有string对象,但需要C风格字符串,该怎么办呢?c_str()方法返回一个指向C风格字符串的指针。其内容与调用这个方法的string对象相同。

多种字符串种类。前面提到string是基于模板的basic_string 的,这个模板有四个具体化,每个都有一个typedef名称:

1
2
3
4
typedef basc_string<char> string;
typedef basc_string<wchar_t> wstring;
typedef basc_string<char16_t> u16string;
typedef basc_string<char32_t> u32string;

这可以让我们使用基于各种不同char类型的字符串。
对string类的介绍就到这里。