HOME/CPP Primer/

3.2 标准库类型string

Article Outline
TOC
Collection Outline

3.2 标准库类型string

标准库类型string表示可变长的字符序列,使用string类型必须首先包含string头文件。作为标准库的一部分,string定义在命名空间std中:

#include <string>
using std::string;

3.2.1 定义和初始化string对象

如何初始化类的对象是由类本身决定的。一个类可以定义很多种初始化对象的方式,这些方式之间必须有所区别:或者是初始值的数量不同,或者是初始值的类型不同。

string s1;          // 默认初始化,s1是一个空字符串
string s2 = s1;     // s2是s1的副本
string s3 = "hiya"; // s3是该字符串字面值的副本
string s4(10, 'c'); // s4的内容是cccccccccc

直接初始化和拷贝初始化

如果使用等号初始化一个变量,实际上执行的是拷贝初始化,编译器把等号右侧的初始值拷贝到新创建的对象中去。 如果不使用等号,执行的是直接初始化。

当初始值只有一个时,使用直接初始化或拷贝初始化都行,如果像s4需要用到多个值,一般来说智能使用直接初始化。

string s5 = "hiya"; // 拷贝初始化
string s6 ("hiya"); // 直接初始化
string s7(10, 'c'); // 直接初始化

3.2.2 string对象上的操作

一个类除了要规定初始化其对象的方式外,还要定义对象上所能执行的操作。其中,类既能定义通过函数名调用的操作,也能定义<<、+等各种运算符在该类对象上的新含义。

os<<s 将s写到输出流os当中,返回os
is>>s 从is中读取字符串赋给s,字符串以孔板分割,返回is
getline(is, s) 从is中读取一行赋给s,返回is
s.empty() s为空返回true,否则返回false
s.size() 返回s中字符的个数
s[n] 返回s中第n个字符的引用,位置n从0计起
s1+s2 返回s1和s2连接后的结果
s1=s2 用s2的副本代替s1中原来的字符
s1==s2 如果s1和s2中所含的字符完全一样,则它们相等;string对象的相等性判断对字母的大小写敏感
s1!=s2 如果s1和s2中所含的字符不完全一样,则它们不相等;string对象的相等性判断对字母的大小写敏感
<, <=, >, >= 利用字符在字典中的顺序进行比较,且对字母的大小写敏感

读写string对象

int main()
{
    string s;
    cin >> s;
    cout << s << endl;
    return 0;
}

在执行读取操作时,string对象会自动忽略开头的空白字符并从第一个真正的字符开始读起,直到遇见下一处空白为止。如果程序的输入是“ Hello World! ”则输出将是“Hello”。

读取未知数量的string对象

int main()
{
    string word;
    while(cin >> word)
        cout << word << endl;
    return 0;
}

使用getline读取一整行

有时候我们希望能在最终得到的字符串中保留输入时的空白符,这时应该用getline函数代替原来的>>运算符。getline函数的参数是一个输入流和一个string对象,函数从给定的输入流中读取内容,直到遇到换行符为止(注意换行符也被读进来了),然后把所读的内容存入到那个string对象中去(注意不存换行符)。getline只要一遇到换行符就结束读取操作并返回结果,哪怕输入的一开始就是换行符也是如此。如果输入真的一开始就是换行符,那么所得的结果是个空string。

和输入运算符一样,getline也会返回它的流参数,也能用getline的结果作为条件。

int main()
{
    string line;
    while(getline(cin,line));
        cout << line << endl;
    return 0;
}

string的empty和size操作

只输出非空的行:

int main()
{
    string line;
    while(getline(cin,line));
        if(!line.empty())
            cout << line << endl;
    return 0;
}

size函数返回string对象的长度(即string对象中字符的个数),可以使用size函数只输出长度超过80个字符的行:

int main()
{
    string line;
    while(getline(cin,line));
        if(line.size() > 80)
            cout << line << endl;
    return 0;
}

string::size_type类型

对于size函数来说,返回的是string::size_type类型的值。

string类及其他大多数标准类型都定义了几种配套的类型都定义了几种配套的类型。这些配套类型体现了标准库类型与机器无关的特性。在具体使用的时候,通过作用域操作符来表明名字size_type是在类string中定义的。

由于size函数返回的是一个无符号整型数,因此如果在表达式中混用了带符号数和无符号数将可能产生意想不到的结果。

比较string对象

string类定义了几种用于比较字符串的运算符。这些比较运算符逐一比较string对象中的字符,并且对大小写敏感。

相等性运算符分别检验两个string对象相等或不相等,string对象相等意味着它们的长度相同而且所包含的字符也全都相同。关系运算符<、<=、>、>=分别检验一个string对象是否小于、小于等于、大于、大于等于另外一个string对象。这些运算符都依照(大小写敏感的)字典顺序:

  1. 如果两个string对象的长度不同,而且较短string对象的每个字符都与较长string对象对应位置上的字符相同,就说较短string对象小于较长string对象。

  2. 如果两个string对象在某些对应位置上不一致,则string对象比较的假爱国其实是string对象中第一对相异字符比较的结果。

下面是string对象比较的一个示例:

string str = "Hello";
string phrase = "Hello World";
string slang = "Hiya";

根据规则1可判断,对象str小于对象phrase;根据规则2可判断,对象slang既大于str也大于phrase。

为string对象赋值

一般来说,在设计标准库类型时都力求在易用性上向内置类型看齐,因此大多数库类型都支持赋值操作。对于string类而言,允许把一个对象的值赋给另外一个对象:

string st1(10, 'c'), st2;   // st1的内容是cccccccccc;st2是一个空字符串
st1 = st2;                  // 赋值:用st2的副本替换st1的内容,此时st1和st2都是空字符串

两个string对象相加

两个string对象相加得到一个新的string对象其内容是把左侧的运算对象与右侧的运算对象串接而成。

string s1 = "hello, ", s2 = "world\n";
string s3 = s1 + s2;    // s3的内容是hello,world\n
s1 += s2;               // 等价于s1 = s1 + s2

字面值和string对象相加

string s1 = "hello", s2 = "world";  // 在s1和s2中都没有标点符号
string s3 = s1 + ", " + s2 + '\n';

当把string对象和字符字面值及字符串字面值混在一条语句中使用时,必须确保每个加法运算符的两侧的运算对象至少有一个是string:

string s4 = s1 + ", ";              // 正确:把一个string对象和一个字面值相加
string s5 = "hello" + ", ";         // 错误:两个运算对象都不是string
string s6 = s1 + ", " + "world";    // 正确:每个加法运算符都有一个运算对象是string
string s7 = "hello" + ", " + s2;    // 错误:不能把字面值直接相加

3.2.3 处理string对象中的字符

cctype头文件中的函数

函数 说明
isalnum(c) 当c是字母或数字时为真
isalpha(c) 当c是字母时为真
iscntrl(c) 当c是控制字符时为真
isdigit(c) 当c是数字时为真
isgraph(c) 当c不是空格但可打印时为真
islower(c) 当c是小写字母时为真
isprint(c) 当c是可打印字符时为真(即c是空格或c具有可视形式)
ispunct(c) 当c是标点符号时为真(即c不是控制字符、数字、字母、可打印空白中的一种)
isspace(c) 当c是空白时为真(即c是空格、横向制表符、纵向制表符、回车符、换行符、进纸符中的一种)
isupper(c) 当c是大写字母时为真
isxdigit(c) 当c是十六进制数字时为真
tolower(c) 如果c是大写字母,输出对应的小写字母,否则原样输出c
toupper(c) 如果c是小写字母,输出对应的大写字母,否则原样输出c

处理每个字符?使用基于范围的for语句

范围for语句遍历给定序列中的每个元素并对序列中的每个值执行某种操作,其语法形式是:

for(declaratioin: expression)
    statement

使用范围for语句把string对象中的字符每行一个输出出来:

string str("some string");
for(auto c : str)
    cout << c << endl;

使用范围for语句和ispunct函数来统计string对象中标点符号的个数:

string s("Hello World!!!");
decltype(s.size()) punct_cnt = 0;
for(auto c : s)
    if(ispunct(c))
        ++punct_cnt;
cout << punct_cnt << " punctuation characters in " << s << endl;

程序输出的结果将是:

3 punctuation characters in Hello World!!!

使用范围for语句改变字符串中的字符

如果想要改变string对象中字符的值,必须把循环变量定义成引用类型。

把字符串改写为大写字母的形式,可以使用标准库函数toupper,对其中的每个字符调用toupper函数并将结果再赋给原字符就可以了:

string s("Hello World!!!")
for(auto &c : s)
    c = toupper(c);
cout << s << endl;

只处理一部分字符?

想要访问string对象中的单个字符有两种方式:一种是使用下标,另外一种是使用迭代器。

下标运算符接收的输入参数是string::size_type类型的值,这个参数表示要访问的字符的位置,返回值是该位置上字符的引用。

string对象的下表从0计起。如果string对象s至少包含两个字符,则s[0]是第一个字符,s[1]是第二个字符、s[s.size()-1]是最后一个字符。

下面的程序使用下标运算符输出string对象中的第一个字符:

if(!s.empty())
    cout << s[0] << endl;

将字符串首字母改写成大写形式:

string s("some string");
if(!s.empty())
    s[0] = toupper(s[0]);

程序的输出结果是:

Some string

使用下标执行迭代

把s中的第一个词改写成大写:

for(decltype(s.size()) index = 0; index != s.size() && isspace(s[index]); ++index)
    s[index] = toupper(s[index]);

程序输出的结果是:

SOME string

使用下标执行随机访问

编写一个程序把0到15之间的十进制数转换成对应的十六进制形式,只需初始化一个字符串令其存放16个十六进制“数字”:

const string hexdigits = "0123456789ABCDEF";
cout << "Enter a series of number between 0 and 15"
    << " seperate by space. Hit ENTER when finished: "
    << endl;
string result;
string::size_type n;
while(cin >> n)
    if(n < hexdigits.size())
        result += hexdigits[n];
cout << "Your hex number is: " << result << endl;