很多应用程序都需要处理字符串。C语言在string.h(C++中为cstring)中提供了一系列的字符串函数,很多早期的C++实现为处理字符串提供了自己的类。
string类是由头文件string支持的(注意,头文件string.h和cstring支持对C-风格字符串进行操纵的C库字符串函数,但不支持string类)。
要使用类,关键在于知道它的公有接口,而string类包含大量的方法,其中包括了若干构造函数,用于将字符串赋给变量、合并字符串、
比较字符串和访问各个元素的重载运算符以及用于在字符串中查找字符和子字符串的工具等。
简而言之,string类包含的内容很多。
=========================================
一、构造字符串
首先看string的构造函数。
毕竟对于类而言,最重要的内容之一是,有哪些方法可以用于创建其对象。
ctor用于标识构造函数。
实际上string是模板具体化basic_string<char>的一个typedef。同时省略了与内存管理相关的参数。
size_type是一个依赖于实现的整型,是在头文件string中定义的。
string类讲string::npos定义为字符串的最大长度,通常为unsigned int的最大值。
NBTS:Null-terminated string 表示以空字符结束的字符串——传统字符串;
接下来看一下一个关于string类的demo程序:
1 //str1.cpp -- introducing the string class 2 #include <iostream> 3 #include <string> 4 //using string constructors 5 6 int main() 7 { 8 using namespace std; 9 string one("Lottery Winner!"); //ctor #1 10 cout<<one<<endl; //overloaded << 11 12 string two(20, '$'); //ctor #2 13 cout<<two<<endl; 14 15 string three(one); //ctor #3 16 cout<<three<<endl; 17 18 one +="Oops!"; //overloaded += 19 cout<<one<<endl; 20 21 two = "Sorry! That was"; 22 three[0]='p'; 23 24 string four; //ctor #4 25 four = two + three; //overloaded + , = 26 cout << four << endl; 27 28 char alls[] = "All's well that ends well"; 29 string five(alls,20); //ctor #5 30 cout<<five<<"! "; 31 32 string sixe(alls+6, alls+10); //ctor #6 33 cout<<six<<", "; 34 35 string seven(&five[6], &five[10]); //ctor #6 again 36 cout<<seven<<"... "; 37 38 sting eight(four, 7, 16); //ctor #7 39 cout<<eight<<" in motion!"<<endl; 40 return 0; 41 }
程序说明:
可以将string对象初始化为常规的C-风格字符串,然后使用<<重载运算符来显示它。
string one("Lottery Winner!"); //ctor #1
cout<<one<<endl; //overloaded <<
接下来的构造函数将string对象two初始化为由20个$字符组成的字符串:
string two(20, '$'); //ctor #2
复制构造函数将string对象three初始化为string对象one:
string three(one); //ctor #3
重载+=运算符将字符串"Oops!"附加到字符串one的后面:
one += "Oops!"; //overloaded +=
这里是将一个C-风格字符串附加到一个string对象后面。但+=运算符被多次重载,以便能够附加string对象和单个字符;
同样,=运算符也被重载,以便可以将string对象、C-风格字符串或char值赋给string对象:
two = "Sorry! That was";
two = one;
two = '?';
重载[ ]运算符,使得可以用数组表示法访问string对象中的各个字符:
three[0] = 'P';
默认构造函数创建一个以后可以对其进行赋值的空字符串:
string four;
four = two + three; //该行使用了+运算符创建了一个临时string对象,然后使用重载=运算符将它赋给对象four。
第5个构造函数将一个C-风格字符串和一个整数作为参数,其中的整数参数表示要复制多少个字符:
char alls[] = "All's well that ends well";
string five(alls,20); # ctor #5
第6个构造函数有一个模板参数:
template<class Iter> string(Iter begin, Iter end);
begin和end像指针那样,指向内存中两个位置。构造函数将使用begin和end指向的位置之间的值,对string对象进行初始化。
[begin, end)来自数学中,意味着包括begin,但不包括end在内的区间。
也就是说,end指向被使用的最后一个值后面的一个位置。
string six(alls+6, alls+10); //ctor #6
数组名相当于指针,alls+6和alls+10的类型都是char* 。因此使用模板时,将用类型char *替换Iter。
第一个参数指向了数组alls中的第一个w,第二个参数指向了第一个well后面的空格。
因此,six将被初始化为字符串“well”。
第7个构造函数将一个string对象的部分内容复制到构造的对象中:
string eight(four, 7, 16); //ctor #7
上述语句从four的第8个字符开始,将16个字符复制到eight中。
接下来讨论两个C++11新增的构造函数
构造函数string(string && str)类似于复制构造函数,导致新创建的string为str的副本。但与复制构造函数不同的是,它不保证将str视为const。
这种构造函数是移动构造函数(move constructor)。在有些情况下,编译器可以使用它而不是复制构造函数,以优化性能。
构造函数string(initializer_list<char>il)让您能够将列表初始化语法用于string类。也就是说,它使得下面这样的声明是合法的:
string piano_man = {'L', 'i', 's', 'z', 't'};
string comp_land {'L', 'i', 's', 'p'};
这个构造函数确实实现了让列表初始化语法普遍实用的意图;
=========================================
二、string类输入
对于类,很有帮助的一点,知道有哪些输入方式可用。
对于C-风格字符串,有3种方式:
char info[100];
cin >> info; //读word,一段字符串
cin.getline(info, 100); //读一行,丢弃末尾换行符 ;
cin.get(info, 100); //读一行,包括末尾换行符 ;
对于string对象,有两种方式:
string stuff;
cin>>stuff; // read a word
getline(cin, stuff) //read a line, discard
两个版本的getline()都有一个可选参数,用于指定使用哪个字符来确定输入的边界:
cin.getline(info, 100, ':') //read up to : discard :
getline(stuff, ':') //read up to : discard :
在功能上一个主要的区别在于,string版本的getline()将自动调整string对象的大小,使之刚好能够存储输入的字符:
char fname[10];
string lname;
cin >> fname; //如果超过9个字符长度的词,会出现问题;
cin >> lname; //可以读取很长很长的词
cin.getline(fname, 10); //可能缩短字符串
getline(cin, fname); //没有缩短字符串
自动调整大小的功能,让string版本的getline()不需要指定读取多少个字符的数值参数。
另外我们发现对于string版本而言,cin是作为getline函数的参数。如下:
// lname作为string对象
getline(cin, lname);
这种规则也适用于>>,如下:
operator>>(cin, lname); //等效于 cin>> lname;
读取C-风格字符串的函数是istream类的方法;所以对于C-风格字符串而言,cin是调用对象;所以有:
cin.getline(fname, 10); //cin对象调用了getline方法
cin >> info; //cin对象重载了>>运算符;如果info是string对象,则可以自动调整对象大小以满足输入的实际情况;如果info是字符数组,则要注意输入的字符串长度不要超过字符数组的长度。
string类的输入函数会自动调整目标string的大小,使之与输入匹配。但也存在一些限制;
第一个限制是string对象的最大允许长度;由常量string::npos指定。这通常是最大的unsigned int值。//对于普通的交互式输入,这不会带来实际的限制;但如果试图将整个文件的内容读取到单个string对象中,这可能成为一种限制因素;
第二个限制是程序可以使用的内存量;
string版本的getline()函数从输入中读取字符,并将其存储到目标string中,直到发生下列三种情况:
1、到达文件尾;在这种情况下,输入流的eofbit将被设置,这意味着方法fail()和eof()都将返回true;
2、遇到分界符(默认为 );在这种情况下,将把分界字符从输入流中删除,但不存储它;
3、读取的字符数达到最大允许值;string::npos和可供分配的内存字节数中较小的一个,在这种情况下,将设置输入流的failbit,这意味着方法fail()将返回true。
输入流对象有一个统计系统,用于跟踪流的错误状态。在这个系统中,检测到文件尾后将设置eofbit寄存器,检测到输入错误时将设置failbit寄存器,出现无法识别的故障时将设置badbit寄存器,一切顺利时将设置goodbit寄存器。后续会深入讨论这一点。
string版本的operator>>()函数的行为:它不断读取,直到遇到空白字符,并将其留在输入队列中;
空白字符:空格、换行符、制表符;更普遍地说,任何将其作为参数调用isspace(),该函数返回true的字符。
=========================================
三、使用字符串
String类对全部6个关系运算符都进行了重载。
对于每个关系运算符都以3种方式进行重载;以便能够将string对象与另一个string对象、C-风格字符串进行比较、C-风格字符串与string对象进行比较;
string snake1("cobra");
string snake2("coral");
char snake3[20] = "anaconda";
if(snake2 <snake1) //operator < (const string &, const string &)
..
if(snake1 == snake3) // operator == (const string &, const char *)
...
if(snake3 != snake2) //operator != (const char *, const string &)
...
可以确定字符串的长度。size()和length()成员函数都返回字符串中的字符数:
if (snake1.length() == snake2.size())
cout<<"Both strings have the same length. "
为什么这两个函数完成相同的任务呢?length()成员来自较早版本的string类,而size()则是为提供STL兼容性而添加的。
find()方法:可以以多种不同的方式在字符串中搜索给定的子字符串或字符。
=========================================
四、string还提供了哪些功能
string库提供了很多其他工具,包括完成下述功能的函数:
1、删除字符串的部分或全部内容;
2、用一个字符串的部分或全部内容替换另一个字符串的部分或全部内容;
3、将数据插入到字符串中或删除字符串中的数据;
4、从字符串中提取子字符串;
5、将一个字符串中的内容复制到另一个字符串中;
6、交换两个字符串的内容;
7、将一个字符串的部分或全部内容与另一个字符串的部分或全部内容进行比较;
这些函数大多数都被重载,以便能够同时处理C-风格字符串和string对象;
==============================================
五、字符串种类
事实上string库实际上是基于一个模板类的:
template<class charT, class traits = char_traits<charT>, class Allocator = allocator<charT>>
basic_string{...};
模板basic_string有4个具体化,每个具体化都有一个typedef名称:
typedef basic_string<char> string;
typedef basic_string<wchar_t> wstring;
typedef basic_string<char16_t> u16string;
typedef basic_string<char32_t> u32string;
这让您能够使用基于类型wchar_t,char16_t,char32_t,char的字符串。