第三章 标准库类型(part 1) 标准库 string 类型

除基本数据类型外,C++ 还定义了一个内容丰富的抽象数据类型标准库。

其中最重要的标准库类型是 stringvector,它们分别定义了大小可变的字符串和集合。

stringvector 往往将迭代器用作配套类型(companion type),用于访问string 中的字符,或者vector 中的元素。

这些标准库类型是语言组成部分中更基本的那些数据类型(如数组和指针)的抽象。

另一种标准库类型 bitset,提供了一种抽象方法来操作位的集合。与整型值上的内置位操作符相比,bitset 类类型提供了一种更方便的处理位的方式。

两种最重要的标准库类型是stringvectorstring 类型支持长度可变的字符串,vector 可用于保存一组指定类型的对象。说它们重要,是因为它们在 C++ 定义的基本类型基础上作了一些改进。后续还将学习类似于标准库中stringvector 类型的语言级构造,但标准库的 stringvector 类型可能更灵活,且不易出错。

另一种标准库类型提供了更方便和合理有效的语言级的抽象设施,它就是bitset 类。通过这个类可以把某个值当作们的集合来处理。


3.1. 命名空间的 using 声明

需要从标准输入读取数据时,就用 std::cin。这些名字都用了 :: 操作符,该操作符是作用域操作符。它的含义是右操作数的名字可以在左操作数的作用域中找到。因此, std::cin 的意思是说所需要名字 cin 是在命名空间 std 中定义的。显然,通过这种符号引用标准库名字的方式是非常麻烦的。

幸运的是,C++ 提供了更简洁的方式来使用命名空间成员。介绍一种最安全的机制:using 声明

使用 using 声明可以在不需要加前缀namespace_name:: 的情况下访问命名空间中的名字。using 声明的形式如下:

     using namespace::name;

一旦使用了 using 声明,我们就可以直接引用名字,而不需要再引用该名字的命名空间。

     #include <string>
     #include <iostream>
     // using declarations states our intent to use these names from the namespace std
     using std::cin;
     using std::string;
     int main()
     {
      string s;       // ok: string is now a synonym for std::string
      cin >> s;       // ok: cin is now a synonym for std::cin
      cout << s;      // error: no using declaration; we must use full name
      std::cout << s; // ok: explicitly use cout from namepsace std
     }

没有 using 声明,而直接使用命名空间中名字的未限定版本是错误的,尽管有些编译器也许无法检测出这种错误。

每个名字都需要一个 using 声明
一个 using 声明一次只能作用于一个命名空间成员。 using 声明可用来明确指定在程序中用到的命名空间中的名字,如果希望使用 std(或其他的命名空间)中的几个名字,则必须为要用到的每个名字都提供一个 using 声明。
     #include <iostream>
     // using declarations for names from the standard library
     using std::cin;
     using std::cout;
     using std::endl;
     int main()
     {
         cout << "Enter two numbers:" << endl;
         int v1, v2;
         cin >> v1 >> v2;
         cout << "The sum of " << v1
              << " and " << v2
              << " is " << v1 + v2 << endl;
         return 0;
     }

cincoutendl 进行 using 声明,就意味着以后可以省前缀std::,直接使用命名空间中的名字,这样代码可以更易读。

使用标准库类型的类定义

有一种情况下,必须总是使用完全限定的标准库名字:在头文件中。

理由是头文件的内容会被预处理器复制到程序中。用 #include 包含文件时,相当于头文件中的文本将成为我们编写的文件的一部分。如果在头文件中放置 using 声明,就相当于在包含该头文件using 的每个程序中都放置了同一using,不论该程序是否需要using 声明。

通常,头文件中应该只定义确实必要的东西。请养成这个好习惯。


3.2. 标准库 string 类型

string 类型支持长度可变的字符串,C++ 标准库将负责管理与存储字符相关的内存,以及提供各种有用的操作。标准库 string 类型的目的就是满足对字符串的一般应用。

  与其他的标准库类型一样,用户程序要使用 string 类型对象,必须包含相关头文件。如果提供了合适的 using 声明,那么编写出来的程序将会变得简短些:

     #include <string>
     using std::string;
string 对象的定义和初始化
表 3.1. 几种初始化 string 对象的方式

string s1;

Default constructor; s1 is the empty string

默认构造函数 s1 为空串

string s2(s1);

Initialize s2 as a copy of s1

s2 初始化为 s1 的一个副本

string s3("value");

Initialize s3 as a copy of the string literal

s3 初始化为一个字符串字面值副本

string s4(n, 'c');

Initialize s4 with n copies of the character'c'

s4 初始化为字符 'c'n 个副本


警告:标准库 string 类型和字符串字面值

因为历史原因以及为了与 C 语言兼容,字符串字面值与标准库string 类型不是同一种类型。这一点很容易引起混乱,编程时一定要注意区分字符串字面值和string 数据类型的使用,这很重要。


string 对象的读写
读入未知数目的 string 对象

和内置类型的输入操作一样,string 的输入操作符也会返回所读的数据流。因此,可以把输入操作作为判断条件。下面的程序将从标准输入读取一组string 对象,然后在标准输出上逐行输出:

     int main()
     {
         string word;
         // read until end-of-file, writing each word to a new line
         while (cin >> word)
             cout << word << endl;
         return 0;
     }

上例中,用输入操作符来读取 string 对象。该操作符返回所读的 istream 对象,并在读取结束后,作为while 的判断条件。如果输入流是有效的,即还未到达文件尾且未遇到无效输入,则执行while 循环体,并将读取到的字符串输出到标准输出。如果到达了文件尾,则跳出while 循环。

使用 getline 读取整行文本
另外还有一个有用的 string IO 操作: getline。这个函数接受两个参数:一个输入流对象和一个 string 对象。 getline函数从输入流的下一行读取,并保存读取的内容到 不包括换行符。和输入操作符不一样的是, getline 并不忽略行开头的换行符。只要 getline 遇到换行符,即便它是输入的第一个字符, getline 也将停止读入并返回。如果第一个字符就是换行符,则 string 参数将被置为空 string

getline 函数将 istream 参数作为返回值,和输入操作符一样也把它用作判断条件。例如,重写前面那段程序,把每行输出一个单词改为每次输出一行文本:

     int main()
     {
         string line;
         // read line at time until end-of-file
         while (getline(cin, line))
             cout << line << endl;
         return 0;
     }

由于 line 不含换行符,若要逐行输出需要自行添加。照常,我们用 endl 来输出一个换行符并刷新输出缓冲区。

《注解》:

由于 getline 函数返回时丢弃换行符,换行符将不会存储在 string 对象中。


string 对象的操作
string Operations

s.empty()

Returns true if s is empty; otherwise returnsfalse

如果 s 为空串,则返回 true,否则返回 false

s.size()

Returns number of characters in s

返回 s 中字符的个数

s[n]

Returns the character at position n in s; positions start at 0.

返回 s 中位置为 n 的字符,位置从 0 开始计数

s1 + s2

Returns a string equal to the concatenation of s1 ands2

s1s2 连接成一个新字符串,返回新生成的字符串

s1 = s2

Replaces characters in s1 by a copy of s2

s1 内容替换为 s2 的副本

v1 == v2

Returns true if v1 and v2 are equal;false otherwise

比较 v1v2的内容,相等则返回 true,否则返回 false

!=, <, <=, >, and >=

Have their normal meanings

保持这些操作符惯有的含义


stringsizeempty 操作

     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,如果 string 对象为空则返回true 否则返回false


string::size_type 类型

从逻辑上来讲,size() 成员函数似乎应该返回整形数值,或无符号整数。但事实上,size 操作返回的是string::size_type 类型的值。我们需要对这种类型做一些解释。

string 类类型和许多其他库类型都定义了一些配套类型(companion type)。通过这些配套类型,库类型的使用就能与机器无关(machine-independent)。

       size_type 就是这些配套类型中的一种。它定义为与unsigned 型(unsigned intunsigned long)具有相同的含义,而且可以保证足够大能够存储任意string 对象的长度。为了使用由 string 类型定义的size_type 类型是由 string 类定义。

《注解》:任何存储 stringsize 操作结果的变量必须为string::size_type 类型。特别重要的是,还要把 size 的返回值赋给一个int 变量。

虽然我们不知道string::size_type 的确切类型,但可以知道它是unsigned 型。对于任意一种给定的数据类型,它的 unsigned 型所能表示的最大正数值比对应的 signed 型要大倍。这个事实表明size_type 存储的 string 长度是 int 所能存储的两倍。

使用 int 变量的另一个问题是,有些机器上 int 变量的表示范围太小,甚至无法存储实际并不长的 string 对象。如在有 16 位 int 型的机器上, int 类型变量最大只能表示 32767 个字符的 string 个字符的 string 对象。而能容纳一个文件内容的 string 对象轻易就会超过这个数字。因此,为了避免溢出,保存一个 stirng 对象 size 的最安全的方法就是使用标准库类型 string::size_type

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 对象;

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

then substr is less than phrase, and slang is greater than either substr or phrase.

substr 小于 phrase,而 slang 则大于 substrphrase


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 占用的相关内存释放掉,然后再分配给 st2 足够存放 st2 副本的内存空间,最后把 st2 中的所有字符复制到新分配的内存空间。

两个 string 对象相加

string 对象的加法被定义为连接(concatenation)。也就是说,两个(或多个)string 对象可以通过使用加操作符 + 或者复合赋值操作符 +=连接起来。给定两个 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
和字符串字面值的连接

上面的字符串对象 s1s2 直接包含了标点符号。也可以通过将 string 对象和字符串字面值混合连接得到同样的结果:

     string s1("hello");
     string s2("world");

     string s3 = s1 + ", " + s2 + "\n";
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 对象中读取下一个字符,输出该字符并换行。
下标操作可用作左值

前面说过,变量是左值,且赋值操作的左操作的必须是左值。

和变量一样,string 对象的下标操作返回值也是左值。因此,下标操作可以放于赋值操作符的左边或右边。

通过下面循环把 str 对象的每一个字符置为 ‘*’:

     	for (string::size_type ix = 0; ix != str.size(); ++ix)
         str[ix] = '*';
string 对象中字符的处理

我们经常要对 string 对象中的单个字符进行处理,例如,通常需要知道某个特殊字符是否为空白字符、字母或数字。表 3.3 列出了各种字符操作函数,适用于 string 对象的字符(或其他任何 char 值)。这些函数都在 cctype 头文件中定义。

Table 3.3. cctype Functions

isalnum(c)

True if c is a letter or a digit.

如果 c 是字母或数字,则为 True

isalpha(c)

true if c is a letter.

如果 c 是字母,则为 true

iscntrl(c)

true if c is a control character.

如果 c 是控制字符,则为 true

isdigit(c)

true if c is a digit.

如果 c 是数字,则为 true

isgraph(c)

true if c is not a space but is printable.

如果 c 不是空格,但可打印,则为 true

islower(c)

true if c is a lowercase letter.

如果 c 是小写字母,则为 true

isprint(c)

True if c is a printable character.

如果 c 是可打印的字符,则为 true

ispunct(c)

True if c is a punctuation character.

如果 c 是标点符号,则 true

isspace(c)

true if c is whitespace.

如果 c 是空白字符,则为 true

isupper(c)

True if c is an uppercase letter.

如果 c 是大写字母,则 true

isxdigit(c)

true if c is a hexadecimal digit.

如果是 c 十六进制数,则为 true

tolower(c)

If c is an uppercase letter, returns its lowercase equivalent; otherwise returns c unchanged.

如果 c 大写字母,返回其小写字母形式,否则直接返回 c

toupper(c)

If c is a lowercase letter, returns its uppercase equivalent; otherwise returns c unchanged.

如果 c 是小写字母,则返回其大写字母形式,否则直接返回 c

表中的大部分函数是测试一个给定的字符是否符合条件,并返回一个 int 作为真值。如果测试失败,则该函数返回 0 ,否则返回一个(无意义的)非 0 ,表示被测字符符合条件。

表中的这些函数,可打印的字符是指那些可以表示的字符,空白字符则是空格、制表符、垂直制表符、回车符、换行符和进纸符中的任意一种;标点符号则是除了数字、字母或(可打印的)空白字符(如空格)以外的其他可打印字符

建议:采用 C 标准库头文件的 C++ 版本

C++ 标准库除了定义了一些选定于 C++ 的设施外,还包括 C 标准库。C++ 中的头文件 cctype 其实就是利用了 C 标准库函数,这些库函数就定义在 C 标准库的 ctype.h 头文件中。
	C 标准库头文件命名形式为 name 而 C++ 版本则命名为 cname ,少了后缀,.h 而在头文件名前加了 c 表示这个头文件源自 C 标准库。因此,cctypectype.h 文件的内容是一样的,只是采用了更适合 C++程序的形式。特别地,cname 头文件中定义的名字都定义在命名空间 std 内,而 .h 版本中的名字却不是这样。

通常,C++ 程序中应采用 cname 这种头文件的版本,而不采用 name.h 版本,这样,标准库中的名字在命名空间 std 中保持一致。使用 .h 版本会给程序员带来负担,因为他们必须记得哪些标准库名字是从 C 继承来的,而哪些是 C++ 所特有的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值