首先,我们对术语“string”的定义是:C++标准程序库中某个字符串类型(string或wstring)的对象。至于一般字符串,也就是char* 或const char*,我们用术语“C-string”来表示。
1.引言
C++标准程序库中的string class使你可以将string当做一个一般类型而不会令用户感觉有任何问题。你可以像对待基本类型那样地复制、赋值和比较string,再也不必担心内存是否足够、占用的内存实际长度等问题,只需运用操作符操作函数即可,例如以=进行赋值动作,以==进行比较动作,以+进行串联动作。
简而言之,C++标准程序库对于string的设计思维就是,让它的行为尽可能像基本类型,不会再操作上引起什么麻烦。
注意,字符串字面常数(例如,”hello”)会被转为const char*。为了向下兼容,它们可以被隐式转换为char*,不过这种转换并不值得赞赏。
2.string class描述
(1)string各种相关类型
- 头文件
使用string需要包含头文件<string>。一如既往,所有标识符都定义于命名空间std之中。 - 模板类别basic_string<>
在<string>之中,basic_string<>被定义为所有字符串类型的基本模板类别:
12345namespace std {template<class charT, class traits = char_traits<charT>,class Allocator = allocate<charT> >class basic_string;}
此一类别将字符类型、字符类型特性(traits)、内存模型(memory model)加以参数化:
第一个参数是单个字符所属类型;
第二个参数是个特性类别(traits class),提供字符串类别中所有的字符核心操作。此种特性类别规定了“赋值字符”或“比较字符”的做法,如果没有指定它,就会根据现有的字符类型采用缺省的特性类别。
第三个参数定义了字符串类型所采用的内存模式,通常设定为“缺省的内存模型allocator”。 - string类型和wstring类型
C++标准程序库提供了两个basic_string<>特化版本:- string是针对char而预先定义的特化版本:
namespace std {
typedef basic_string<char> string;
} - wstring是针对wchar_t而预先定义的特化版本:
namespace std {
typedef basic_string<wchar_t> wstring;
}
- string是针对char而预先定义的特化版本:
(2)操作函数
1)字符串的各种操作函数:
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 |
构造函数(constructors) 产生或复制字符串 析构函数(destructors) 销毁字符串 =, assign() 附以新值 swap() 交换两个字符串的内容 +=,append(),push_back() 添加字符 insert() 插入字符 erase() 删除字符 clear() 移除全部字符 resize() 改变字符数量(在尾端删除或添加字符) replace() 替换字符 + 串联字符串 ==,!=,<,<=,>,>=,compare() 比较字符串内容 size(),length() 返回字符数量 max_size() 返回字符的最大可能个数 empty() 判断字符串是否为空 capacity() 返回重新分配之前的字符串容量 reserve() 保留一定量内存以容纳一定数量字符 [ ], at() 存取单一字符 >>, getline() 从stream中读取某值 << 将某值写入string copy() 将内容复制为一个C-string c_str() 将内容以C-string形式返回 data() 将内容以字符数组形式返回 substr() 返回某个子字符串 搜寻函数(find function) 搜寻某个子字符串或字符 begin(), end() 提供正常的(正向)迭代器支持 rbegin(), rend() 提供逆向迭代器支持 get_allocator() 返回配置器(allocator) |
2)字符串操作函数的参数:STL提供了很多字符串操作函数。其中许多往往具有数个重载版本,分别以一个、两个或三个参数来指定新值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
const string& str 整个str字符串 const string& str, size_type idx, size_type num 大部分情况下是指字符串str中以idx开始的num个字符 const char* cstr 整个C-string cstr const char* chars,size_type len 字符数组chars中的len个字符 char c 字符c size_type num, char c num个字符c iterator beg, iterator end 区间[beg, end)内所有字符 |
3)注意,只有在单参数版本中,才将char*字符’\0’当做字符串结尾特殊符号来处理,其他所有情况下’\0’都不被视为特殊字符:
1 2 3 4 5 6 |
string s1("nico"); //initializes s1 with:'n' 'i' 'c' 'o' string s2("nico",5);//initializes s2 with:'n' 'i' 'c' 'o' '\0' string s3(5,'\0'); //initializes s3 with:'\0' '\0' '\0' '\0' '\0' s1.length() //yields 4 s2.length() //yields 5 s3.length() //yields 5 |
因此,一般而言一个字符串内可以包含任何字符,甚至可以包含二进制文件的内容。
(3)string构造函数和析构函数
1 2 3 4 5 6 7 8 9 |
string s 生成一个空字符串s string s(str) copy构造函数,生成字符串str的一个复制品 string s(str,idx) 将字符串str内“始于位置idx”的部分,当做s的初值 string s(str,idx,strlen) 将str内“始于idx且长度strlen”的部分,当做s的初值 string s(cstr) 以C-string cstr作为字符串s的初值 string s(cstr, strlen) 以C-string cstr的前strlen个字符作为s的初值 string s(num,c) 生成一个字符串,包含num个c字符 string s(beg,end) 以区间[beg,end)内的字符作为字符串s的初值 s.~string() 销毁所有字符,释放内存 |
注意,不能以单一字符来初始化字符串:
1 2 |
string s('x'); //error string s(1,'x'); //OK |
这表示编译器提供了一个从const char*到string的自动类型转换功能,但不存在一个从char到string的自动类型转换功能。
(4)string和C-string
1)C++ Standard将字符串字面常数的类型由char*改为const char*。为了提供向下兼容性,C++ Standard规定了一个颇有争议的隐式转换,可从const char*隐式转换为char*。
2)由于字符串字面常数的类型并非string,因此在新的string object和传统的C-string之间必须存在一种强烈关系:在“string和string-like object共通的操作场合”(例如比较、追加、插入等动作)都应该使用C-string。或者具体地说,存在一个从const char*到string的隐式类型转换。然后却不存在一个从string object到C-string的自动类型转换。(c_str()可以得到“string对应的C-string”,所得结果和“以’\0’为结尾的字符数组一样)
运用copy(),可以将字符串内容复制或写入既有的C-string或字符数组内。
3)注意,’\0’在string之中并不具有特殊意义,但在一般C-string中却用来标识字符串结束。在string中,字符’\0’和其它字符的地位完全相同。
4)有三个函数可以将字符串内容转换为字符数组或C-string:
- data():以字符数组的形式返回字符串内容。由于并未追加’\0’字符,所以返回类型并非有效的C-string。
- c_str():以C-string形式返回字符串内容,也就是在尾端添加’\0’字符。
- copy():将字符串内容复制到“调用者提供的字符数组”中。不添加’\0’字符。
(5)大小size和容量capacity
- 一个string存在三种“大小”
1)size()和length()
返回string中出现的字符个数。上述两个函数等效。
2)max_size()
此函数返回一个string最多能够包含的字符数。一个string通常包含一块单独内存区块内的所有字符,所有可能跟PC机器本身的限制有关系。
3)capacity()
重新分配内存之前,string所能包含的最大字符数。
- 让string拥有足够的容量是很重要的:其一,重新分配会造成所有指向string的reference、pointers、iterators失效;其二,重新分配(reallocation)很耗时。
1)reserve()使你得以预留一定容量:
string s;
s.reserve(80);
2)容量概念应用于string和应用于vector是相同的,但有一个显著差异:对string来说可以调用reserve()来缩减实际容量;而对vector来说却不可以。
(6)元素存取
两种方法可以访问单一字符:subscript下标和成员函数at()。
- operator[]并不检查索引是否有效,at()则会检查。如果调用at()时指定的索引无效,系统会抛出out_of_range异常。
- 对于operator[]的const版本,最后一个字符的后面位置也是有效的。此时的实际字符数是有效索引。在此情况下operator[]返回值是“由char类型之default构造函数所产生”的字符。因此,对于类型string的对象,返回值为’\0’。举例说明:
12345678910111213const string cs("nico");string s("abcde");s[2] //yiedls 'c's.at(2) //yiedls 'c's[100] //ERROR:undefined behaviors.at[100] //throws out_of_ranges.[s.length()] //ERROR:undefined behaviorcs.[cs.length()] //yields '\0's.at(s.length()) //throws out_of_rangecs.at(cs.length()) //throws out_of_range
(7)比较
string支持常见的比较操作符,操作数可以是string或C-string。
1)可以使用<, <=, >, >=来比较string,得到的结果是根据“当前字符特性”将字符依字典顺序逐一比较。
2)也可以使用成员函数compare()来比较子字符串,此函数针对一个string可使用多个参数进行处理。compare()返回的是整数值而非布尔值。返回值意义如下:0表示相等,小于0表示小于,大于0表示大于:
1 2 3 4 5 6 7 8 9 |
std::string s("abcd") ; s.compare("abcd"); //return 0 s.compare("dcba"); //return a value < 0(s is less) s.compare("ab"); //return a value > 0(s is greater) s.compare(s); //return 0 s.compare(0,2,s,2,2); //return a value < 0("ab" is less than "cd") s.compare(1,2,"bcx",2); //return 0("bc" is equal to "bc") |
(8)更改内容
可以使用不同的成员函数或操作符来更改字符串内容。
1)赋值
可使用operator=赋新值,如果需要多个参数来描述新值,可采用成员函数assign():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
const string aString("othello"); string s; s = aString; //assign "othello" s = "two\nlines"; //assign a C-string s = ' '; //assign a single character s.assign(aString); //assign "othello" s.assign(aString,1,3); //assign "the" s.assign(aString,2,string::npos);//assign "hello" s.assign("two\nlines"); //assign a C-string s.assign("nico",5); //assign the character array:'n' 'i' 'c' 'o' '\0' s.assign(5,'x'); //assign five character:'x' 'x' 'x' 'x' 'x' |
2)交换swap()
3)令string成空
s = ” “;
s.clear();
s.erase();
4)插入和移除字符
- string提供了许多成员函数用于插入、移除、替换、擦除字符。另有operator+=, append()和push_back()可添加字符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
const string aString("othello") ; string s; s += aString; //append "othello" s += "two\nlines"; //append C-string s += '\n'; //append single character s.append(aString); //append "othello" s.append(aString,1,3); //append "the" s.append(aString,2,string::npos); //append "hello" s.append("two\nlines"); //append C-string s.append("nico", 5); //append character array:'n' 'i' 'c' 'o' '\0' s.append(5,'x'); //append five character:'x' 'x' 'x' 'x' 'x' s.push_back('\n'); //append single character |
- 成员函数insert()也可以插入字符,使用insert()时需知道插入位置的索引:
12345const string aString("age") ;string s("p");s.insert(1,aString); //s:pages.insert(1,"ersifl"); //s:persifl
注意,insert()不接受“索引+单独字符”的参数组合,必须传入一个string或一个额外数字:
12s.insert(0,' '); //errors.insert(0," "); //OK
由于insert()具有以下重载形式,容易导致模棱两可的现象:
12insert (size_type idx, size_type num, charT c); //position is indexinsert (iterator pos, size_type num, charT c); //position is iterator
string的size_type通常被定义为unsigned, string的iterator通常被定义为char*。所以,如下调用是错误的:
s.insert(0, 1, ‘ ‘); //error:ambiguous
这种情况下,第一个参数0有两种转换可能,为了获得正确操作,你必须如此:
s.insert( (string::size_type)0, 1, ‘ ‘); //OK - 成员函数erase()用来移除字符,成员函数replace()用来替换字符:
1 2 3 4 5 |
string s = "il8n" ; s.replace(1,2,"nternationalizatio"); //s:internationalization s.erase(13); //s:international s.erase(7,5); //s:internal s.replace(0,2,"ex"); //s:external |
(9)子串和字符串接合(concatenation)
成员函数substr()从string身上提取子字符串:
1 2 3 4 5 6 |
string s("interchangeability"); s.substr(); //return a copy of s1 s.substr(11); //return string("ability") s.substr(5,6); //return string("change") s.substr(s.find('c')); //return string("changeability") |
operator+把两个string(或C-string,但+号两边至少有一个是string)接合起来:
1 2 3 4 5 |
string s1("enter"); string s2("nation"); string i18n; i18n = 'i' + s1.substr(1) + s2 + "aliz" + s2.substr(1); cout << "i18n means: " + i18n << endl; |
输出如下:
i18n means: internationalization
(10)I/O操作符
string定义了常用的I/O操作符:
- operator >> 从input stream读取一个string。
- operator << 把一个string写到output stream中。
这些操作符的使用方法和面对一般C-string时相同。更明确地说,operator>>的执行方式如下:
1)如果设置了skipws标志,则跳过开头空格。
2)持续读取所有字符,直到发生以下情形之一:
下一个字符为空格符
stream不再处于good状态(例如遇到end-of-file)
stream的width()结果大于0,而目前已读出width()个字符
以读取max_size()个字符
3)stream width()被设为0
string class在命名空间std内还提供了一种用于逐行读取的特殊函数——std::getline()。该函数读取所有字符,包括开头的空格符,知道遇到分行符号或end-of-file。
1 2 3 4 5 6 7 |
string s; while (getline(cin, s)) { //for each line read from cin ... } while (getline(cin, s, ':')) { //for each token separated by ':' ... } |
(11)搜索和查找
string提供了许多用于搜索和查找字符及字符串的函数。另外,如果配上迭代器,STL的所有搜寻算法都可派上用场。
string搜寻函数:
1 2 3 4 5 6 |
find() 搜寻第一个与value相等的字符 rfind() 搜寻最后一个与value相等的字符(逆向搜寻) find_first_of() 搜寻第一个“与value中的某个值相等”的字符 find_last_of() 搜寻最后一个“与value中某值相等”的字符 find_first_not_of() 搜寻第一个“与value中任何值都不相等”的字符 find_last_not_of() 搜寻最后一个“与value中任何值都不相等”的字符 |
所有搜寻函数都返回符合搜寻条件之字符区间内的第一个字符的索引。如果搜寻不成功,则返回npos。这些受损函数都采用下面的参数方案:
- 第一个参数总是被搜寻的对象。
- 第二个参数(可有可无)指出string内的搜寻起点
- 第三个参数(可有可无)指出搜寻的字符个数
不幸的是上面这个参数方案与其他string相关函数不同。其他string函数的第一个参数是起点索引,随后是数值和长度。特别要指出的是,每个搜寻函数都以下面的参数进行重载:
- const string& value
搜寻对象为value(一个string) - const string& value, size_type idx
从*this的idx索引位置开始,搜寻value(一个string) - const char* value
搜寻value(一个C-string) - const char* value, size_type idx
从*this的idx索引位置开始,搜寻value(一个C-string) - const char* value, size_type idx, size_type value_len
从*this的idx索引位置开始,搜索value(一个C-string)内的前value_len个字符组成的字符区间。value内的null字符(‘\0’)将不复特殊意义。 - const char value
搜寻value(一个字符) - const char value, size_type idx
从*this的idx索引位置开始,搜寻value(一个字符)
1 2 3 4 5 6 7 8 9 10 |
string s("Hi Bill, I'm ill, so please pay the bill"); s.find("il"); //return 4 (first substring "il") s.find("il", 10); //return 13 (first substring "il" starting from s[10]) s.rfind("il"); //return 37 (last substring "il") s.find_first_of("il"); //return 1 (first char 'i' or 'l') s.find_last_of("il"); //return 39 (last char 'i' or 'l') s.find_first_not_of("il"); //return 0 (first char neither 'i' nor 'l') s.find_last_not_of("il"); //return 36 (last char neither 'i' nor 'l') s.find("hi"); //return npos |
(12)数值npos的意义
如果搜寻函数失败,就会返回string::npos。考虑如下示例:
1 2 3 4 5 6 7 |
string s; string::size_type idx; ... idx = s.find("substring") ; if (idx == string::npos) { ... } |
1)只有当“substring”不是s的子字符串时,if语句才会得到true。使用string的npos值及其类型要格外小心:若要检查返回值,一定要使用类型string::size_type,不能以int或unsigned作为返回值类型;否则返回值与string::npos之间的比较可能无法正确执行。
2)这是因为npos被设计为-1:
1 2 3 4 5 6 7 8 9 10 11 |
namespace std { template<class charT, class traits = char_traits<charT>, class Allocator = allocate<charT> class basic_string { public: typedef typename Allocator::size_type size_type; ... static const size_type npos = -1; ... }; } |
3)而size_type需为无正负号整数类型。于是-1被转换为无正负号整数类型,npos也就成了该类型的最大无符号值。不过实际数值还是取决于类型size_type的实际定义。不幸的是,这些最大值都不相同,比如(unsigned long)-1和(unsigned short)-1不同。
4)因此,比较式idx == string::npos,如果idx值为-1,由于idx和字符串string::npos类型不同,比较结果可能得到false。
(13)string对迭代器的支持
1)string是字符的有序群集。所以C++标准程序库为string提供了相应的接口,以便字符串当做STL容器使用。
2)string迭代器是random access迭代器,也就是说它支持随机存取,所以任何一个STL算法都可与它搭配。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
s.begin() 返回一个随机存取迭代器,指向第一个字符 s.end() 返回一个随机存取迭代器,指向最后一个字符的下一个位置 s.rbegin() 返回一个逆向迭代器,指向倒数第一个字符 s.rend() 返回一个逆向迭代器,指向倒数最后一各字符的下一个位置 string s(beg,end) 以区间[beg,end)内的所有字符作为s的初值 s.append(beg,end) 将区间[beg,end)内的所有字符添加于s尾部 s.assign(beg,end) 将区间[beg,end)内的所有字符赋值给s s.insert(pos,c) 在迭代器pos所指位置插入字符c,并返回新字符的迭代器位置 s.insert(pos,num,c) 在迭代器pos所指位置插入num个字符c,并返回第一个新字符的迭代器位置 s.insert(pos,beg,end)在迭代器pos所指位置插入区间[beg,end)内的所有字符 s.erase(pos) 删除迭代器pos所指字符,并返回下一个字符位置 s.erase(beg,end) 删除区间[beg,end)内的所有字符,并返回下一个字符位置 s.replace(beg,end,str) 以str内的字符替代[beg,end)区间内的所有字符 s.replace(beg,end,cstr) 以C-string内的字符替代[beg,end)区间内的所有字符 s.replace(beg,end,cstr,len) 以字符数组cstr的前len个字符替代[beg,end)区间内所有字符 s.replace(beg,end,num,c) 以num个字符c替代[beg,end)区间内的所有字符 s.replace(beg,end,newBeg,newEnd) 以[newBeg,newEnd)区间内的所有字符替代[beg,end)内的字符 |
3)string_iter.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#include <iostream> #include <string> #include <cctype> #include <algorithm> using namespace std; int main() { string s("The zipe code of Hondelage in Germany is 38108"); cout << "original: " << s << endl; //lowercase all characters transform (s.begin(), s.end(), //source s.begin(), //destination (int (*)(int)) tolower); //operation cout << "lowered: " << s << endl; //uppercase all character transform (s.begin(), s.end(), s.begin(), (int(*)(int))toupper); cout << "uppered: " << s << endl; } |
输出:
1 2 3 4 |
[root@VM_198_209_centos cppstl]# ./string_iter original: The zipe code of Hondelage in Germany is 38108 lowered: the zipe code of hondelage in germany is 38108 uppered: THE ZIPE CODE OF HONDELAGE IN GERMANY IS 38108 |