3.2.3 string对象的操作
表3-2列出了常用的string操作。
表3-2 string操作
1. string的size和empty操作
string对象的长度指的是string对象中字符的个数,可以通过size操作获取:
int main() { string st("The expense of spirit\n"); cout << "The size of " << st << "is " << st.size() << " characters, including the newline" << endl; return 0; } |
编译并运行这个程序,得到的结果为:
The size of The expense of spirit is 22 characters, including the newline |
了解string对象是否为空是有用的。一种方法是将size与0进行比较:
if (st.size() == 0) // ok: empty
|
本例中,程序员并不需要知道string对象中有多少个字符,只想知道size是否为0。用string的成员函数empty()可以更直接地回答这个问题:
if (st.empty()) // ok: empty |
empty()成员函数将返回bool值(2.1节),如果string对象为空则返回true,否则返回false。
2. string::size_type类型
从逻辑上来讲,size()成员函数似乎应该返回整型数值,或如2.2节“建议”中所述的无符号整数。但事实上,size操作返回的是string::size_type类型的值。我们需要对这种类型做一些解释。
string类类型和许多其他库类型都定义了一些伙伴类型(companion types)。这些伙伴类型使得库类型的使用是机器无关的(machine-independent)。size_type就是这些伙伴类型中的一种。它定义为与unsigned型(unsigned int或unsigned long)具有相同的含义,而且可以保证足够大可存储任意string对象的长度。为了使用由string类型定义的size_type类型,程序员必须加上作用域操作符来说明所使用的size_type类型是由string类定义的。
任何存储string的size操作结果的变量必须为string::size_type类型。特别重要的是,不要把size的返回值赋给一个int变量。
虽然我们不知道string::size_type的确切类型,但可以知道它是unsigned型(2.1.1节)。对于任意一种给定的数据类型,它的unsigned型所能表示的最大正数值比对应的signed要大一倍。这个事实表明size_type存储的string长度是int所能存储的两倍。
使用int变量的另一个问题是,有些机器上int变量的表示范围太小,甚至无法存储实际并不长的string对象。如在有16位int型的机器上,int类型变量最大只能表示32767个字符的string对象。而能容纳一个文件内容的string对象轻易就会超过这个数字。因此,为了避免溢出,保存一个string对象size的最安全的方法就是使用标准库类型string:: size_type。
3. string关系操作符
string类定义了几种关系操作符用来比较两个string值的大小。这些操作符实际上是比较每个string对象的字符。
string对象比较运算是区分大小写的,即同一个字符的大小写形式被认为是两个不同的字符。在多数计算机上,大写的字母位于小写字母之前:任何一个大写字母都小于任意的小写字母。
==操作符比较两个string对象,如果它们相等,则返回true。两个string对象相等是指它们的长度相同,且含有相同的字符。标准库还定义了!=操作符来测试两个string对象是否不等。
关系操作符<,<=,>,>=分别用于测试一个string对象是否小于、小于或等于、大于、大于或等于另一个string对象:
string big = "big", small = "small"; string s1 = big; // s1 is a copy of big if (big == small) // false // ... if (big <= s1) // true, they're equal, so big is less than or equal to s1 // ...
|
关系操作符比较两个string对象时采用了和(大小敏感的)字典排序相同的策略:
如果两个string对象长度不同,且短的string对象与长的string对象的前面部分相匹配,则短的string对象小于长的string对象。
如果两个string对象的字符不同,则比较第一个不匹配的字符。
举例来说,给定string对象:
string substr = "Hello"; string phrase = "Hello World"; string slang = "Hiya"; |
则substr小于phrase,而slang则大于substr或phrase。
4. string对象的赋值
总体上说,标准库类型尽量设计得和基本数据类型一样方便易用。因此,大多数库类型支持赋值操作。对string对象来说,可以把一个string对象赋值给另一个string对象:
// st1 is an empty string, st2 is a copy of the literal string st1, st2 = "The expense of spirit"; st1 = st2; // replace st1 by a copy of st2
|
赋值操作后,st1就包含了st2串所有字符的一个副本。
大多数string库类型的赋值等操作的实现都会遇到一些效率上的问题,但值得注意的是,从概念上讲,赋值操作确实需要的工作。它必须先把st1占用的相关内存释放掉,然后再分配给st1足够存放st2副本的内存空间,最后把st2中的所有字符复制到新分配的内存空间。
5. 两个string对象相加
string对象的加法被定义为连接(concatenation)。也就是说,两个(或多个)string对象可以通过使用加操作符+或者复合赋值操作符+=(1.4.1节)连接起来。给定两个string对象:
string s1("hello, ");string s2("world\n"); |
下面把两个string对象连接起来产生第三个string对象:
string s3 = s1 + s2; // s3 is hello, world\n |
如果要把s2直接追加到s1的末尾,可以使用+=操作符:
s1 += s2; // equivalent to s1 = s1 + s2 |
6. 和字符串字面值的连接
上面的字符串对象s1和s2直接包含了标点符号。也可以通过将string对象和字符串字面值混合连接得到同样的结果:
string s1("hello");string s2("world");string s3 = s1 + ", " + s2 + "\n"; |
当进行string对象和字符串字面值混合连接操作时,+操作符的左右操作数必须至少有一个是string类型的:
string s1 = "hello"; // no punctuation string s2 = "world"; string s3 = s1 + ", "; // ok: adding a string and a literal string s4 = "hello" + ", "; // error: no string operand string s5 = s1 + ", " + "world"; // ok: each + has string operand string s6 = "hello" + ", " + s2; // error: can't add string literals |
S3和s4的初始化只用了一个单独的操作。在这些例子中,很容易判断s3的初始化是合法的:把一个string对象和一个字符串字面值连接起来。而s4的初始化试图将两个字符串字面值相加,因此是非法的。
s5的初始化方法显得有点不可思议,但这种用法和标准输入输出的串联效果是一样的(1.2节)。本例中,string标准库定义加操作返回一个string对象。这样,在对s5进行初始化时,子表达式s1 + ", "将返回一个新string对象,后者再和字面值“world\n”连接。整个初始化过程可以改写为:
string tmp = s1 + “, ”; // ok: + has a string operand s5 = tmp + “world”; // ok: + has a string operand |
而s6的初始化是非法的。依次来看每个子表达式,则第一个子表达式试图把两个字符串字面值连接起来。这是不允许的,因此这个语句是错误的。
7. 从string对象获取字符
string类型通过下标操作符([])来访问string对象中的单个字符。下标操作符需要取一个size_type类型的值,来标明要访问字符的位置。这个下标中的值通常被称为“下标”或“索引(index)”。
string对象的下标从0开始。如果s是一个string对象且s不空,则s[0]就是字符串的第一个字符,s[1]就表示第二个字符(如果有的话),而s[s.size()-1]则表示s的最后一个字符。
引用下标超出下标作用范围就会引起溢出错误。
可用下标操作符分别取出string对象的每个字符,分行输出:
string str("some string");for (string::size_type ix = 0; ix != str.size(); ++ix) cout << str[ix] << endl; |
每次通过循环,就从str对象中读取下一个字符,输出该字符并换行。
8. 下标操作可用作左值
前面说过,变量是左值(2.3.1节),且赋值操作的左操作数必须是左值。和变量一样,string对象的下标操作返回值也是左值。因此,下标操作可以放于赋值操作符的左边或右边。通过下面循环把str对象的每一个字符置为’*’:
for (string::size_type ix = 0; ix != str.size(); ++ix) str[ix] = '*'; |
9. 计算下标值
任何可产生整型数值的表达式都可用作下标操作符的索引。例如,假设someva l和someotherval是两个整型对象,可以这样写:
str[someotherval * someva l] = someva l;
|
虽然任何整型数值都可作为索引,但索引的实际数据类型却是unsigned类型string:: size_type。
前面讲过,要用string::size_type类型的变量接受size函数的返回值。在定义用作索引的变量时,出于同样的道理,string对象的索引变量最好也用string:: size_type类型。
在使用下标索引string对象时,必须保证索引值“在上下界范围内”。“在上下界范围内”就是指索引值是一个赋值为size_type类型的值,其取值范围在0到string对象长度减1之间。使用string::size_type类型或其他unsigned类型作为索引,就可以保证索引值不小于0。只要索引值是unsigned类型,就只需要检测它是否小于string对象的长度。
标准库不要求检查索引值,所用索引的下标越界是没有定义的,这样往往会导致严重的运行时错误。
【责任编辑:
董书 TEL:(010)68476606】