- 内容提要
- 前言
- 第 1 章 预备知识
- 第 2 章 开始学习 C++
- 第 3 章 处理数据
- 第 4 章 复合类型
- 第 5 章 循环和关系表达式
- 第 6 章 分支语句和逻辑运算符
- 第 7 章 函数——C++的编程模块
- 第 8 章 函数探幽
- 第 9 章 内存模型和名称空间
- 第 10 章 对象和类
- 第 11 章 使用类
- 第 12 章 类和动态内存分配
- 第 13 章 类继承
- 第 14 章 C++中的代码重用
- 第 15 章 友元、异常和其他
- 第 16 章 string 类和标准模板库
- 第 17 章 输入、输出和文件
- 第 18 章 探讨 C++新标准
- 附录 A 计数系统
- 附录 B C++保留字
- 附录 C ASCII 字符集
- 附录 D 运算符优先级
- 附录 E 其他运算符
- 附录 F 模板类 string
- 附录 G 标准模板库方法和函数
- 附录 H 精选读物和网上资源
- 附录 I 转换为 ISO 标准 C++
- 附录 J 复习题答案
4.3 string 类简介
ISO/ANSI C++98 标准通过添加 string 类扩展了 C++库,因此现在可以 string 类型的变量(使用 C++的话说是对象)而不是字符数组来存储字符串。您将看到,string 类使用起来比数组简单,同时提供了将字符串作为一种数据类型的表示方法。
要使用 string 类,必须在程序中包含头文件 string。string 类位于名称空间 std 中,因此您必须提供一条 using 编译指令,或者使用 std::string 来引用它。string 类定义隐藏了字符串的数组性质,让您能够像处理普通变量那样处理字符串。程序清单 4.7 说明了 string 对象与字符数组之间的一些相同点和不同点。
程序清单 4.7 strtype1.cpp
下面是该程序的运行情况:
从这个示例可知,在很多方面,使用 string 对象的方式与使用字符数组相同。
- 可以使用 C-风格字符串来初始化 string 对象。
- 可以使用 cin 来将键盘输入存储到 string 对象中。
- 可以使用 cout 来显示 string 对象。
- 可以使用数组表示法来访问存储在 string 对象中的字符。
程序清单 4.7 表明,string 对象和字符数组之间的主要区别是,可以将 string 对象声明为简单变量,而不是数组:
类设计让程序能够自动处理 string 的大小。例如,str1 的声明创建一个长度为 0 的 string 对象,但程序将输入读取到 str1 中时,将自动调整 str1 的长度:
这使得与使用数组相比,使用 string 对象更方便,也更安全。从理论上说,可以将 char 数组视为一组用于存储一个字符串的 char 存储单元,而 string 类变量是一个表示字符串的实体。
4.3.1 C++11 字符串初始化
正如您预期的,C++11 也允许将列表初始化用于 C-风格字符串和 string 对象:
4.3.2 赋值、拼接和附加
使用 string 类时,某些操作比使用数组时更简单。例如,不能将一个数组赋给另一个数组,但可以将一个 string 对象赋给另一个 string 对象:
string 类简化了字符串合并操作。可以使用运算符+将两个 string 对象合并起来,还可以使用运算符+=将字符串附加到 string 对象的末尾。继续前面的代码,您可以这样做:
程序清单 4.8 演示了这些用法。可以将 C-风格字符串或 string 对象与 string 对象相加,或将它们附加到 string 对象的末尾。
程序清单 4.8 strtype2.cpp
转义序列\"表示双引号,而不是字符串结尾。该程序的输出如下:
4.3.3 string 类的其他操作
在 C++新增 string 类之前,程序员也需要完成诸如给字符串赋值等工作。对于 C-风格字符串,程序员使用 C 语言库中的函数来完成这些任务。头文件 cstring(以前为 string.h)提供了这些函数。例如,可以使用函数 strcpy( ) 将字符串复制到字符数组中,使用函数 strcat( ) 将字符串附加到字符数组末尾:
程序清单 4.9 对用于 string 对象的技术和用于字符数组的技术进行了比较。
程序清单 4.9 strtype3.cpp
下面是该程序的输出:
处理 string 对象的语法通常比使用 C 字符串函数简单,尤其是执行较为复杂的操作时。例如,对于下述操作:
使用 C-风格字符串时,需要使用的函数如下:
另外,使用字符数组时,总是存在目标数组过小,无法存储指定信息的危险,如下面的示例所示:
函数 strcat( ) 试图将全部 12 个字符复制到数组 site 中,这将覆盖相邻的内存。这可能导致程序终止,或者程序继续运行,但数据被损坏。string 类具有自动调整大小的功能,从而能够避免这种问题发生。C 函数库确实提供了与 strcat( ) 和 strcpy( ) 类似的函数—strncat( ) 和 strncpy( ),它们接受指出目标数组最大允许长度的第三个参数,因此更为安全,但使用它们进一步增加了编写程序的复杂度。
下面是两种确定字符串中字符数的方法:
函数 strlen( ) 是一个常规函数,它接受一个 C-风格字符串作为参数,并返回该字符串包含的字符数。函数 size( ) 的功能基本上与此相同,但句法不同:str1 不是被用作函数参数,而是位于函数名之前,它们之间用句点连接。与第 3 章介绍的 put( ) 方法相同,这种句法表明,str1 是一个对象,而 size( ) 是一个类方法。方法是一个函数,只能通过其所属类的对象进行调用。在这里,str1 是一个 string 对象,而 size( ) 是 string 类的一个方法。总之,C 函数使用参数来指出要使用哪个字符串,而 C++ string 类对象使用对象名和句点运算符来指出要使用哪个字符串。
4.3.4 string 类 I/O
正如您知道的,可以使用 cin 和运算符<<来将输入存储到 string 对象中,使用 cout 和运算符<<来显示 string 对象,其句法与处理 C-风格字符串相同。但每次读取一行而不是一个单词时,使用的句法不同,程序清单 4.10 说明了这一点。
程序清单 4.10 strtype4.cpp
下面是一个运行该程序时的输出示例:
在用户输入之前,该程序指出数组 charr 中的字符串长度为 27,这比该数组的长度要大。这里要两点需要说明。首先,为初始化的数组的内容是未定义的;其次,函数 strlen( ) 从数组的第一个元素开始计算字节数,直到遇到空字符。在这个例子中,在数组末尾的几个字节后才遇到空字符。对于未被初始化的数据,第一个空字符的出现位置是随机的,因此您在运行该程序时,得到的数组长度很可能与此不同。
另外,用户输入之前,str 中的字符串长度为 0。这是因为未被初始化的 string 对象的长度被自动设置为 0。
下面是将一行输入读取到数组中的代码:
这种句点表示法表明,函数 getline( ) 是 istream 类的一个类方法(还记得吗,cin 是一个 istream 对象)。正如前面指出的,第一个参数是目标数组;第二个参数数组长度,getline( ) 使用它来避免超越数组的边界。
下面是将一行输入读取到 string 对象中的代码:
这里没有使用句点表示法,这表明这个 getline( ) 不是类方法。它将 cin 作为参数,指出到哪里去查找输入。另外,也没有指出字符串长度的参数,因为 string 对象将根据字符串的长度自动调整自己的大小。
那么,为何一个 getline( ) 是 istream 的类方法,而另一个不是呢?在引入 string 类之前很久,C++就有 istream 类。因此 istream 的设计考虑到了诸如 double 和 int 等基本 C++数据类型,但没有考虑 string 类型,所以 istream 类中,有处理 double、int 和其他基本类型的类方法,但没有处理 string 对象的类方法。
由于 istream 类中没有处理 string 对象的类方法,因此您可能会问,下述代码为何可行呢?
像下面这样的代码使用 istream 类的一个成员函数:
但前面处理 string 对象的代码使用 string 类的一个友元函数。有关友元函数及这种技术为何可行,将在第 11 章介绍。另外,您可以将 cin 和 cout 用于 string 对象,而不用考虑其内部工作原理。
4.3.5 其他形式的字符串字面值
本书前面说过,除 char 类型外,C++还有类型 wchar_t;而 C++11 新增了类型 char16_t 和 char32_t。可创建这些类型的数组和这些类型的字符串字面值。对于这些类型的字符串字面值,C++分别使用前缀 L、u 和 U 表示,下面是一个如何使用这些前缀的例子:
C++11 还支持 Unicode 字符编码方案 UTF-8。在这种方案中,根据编码的数字值,字符可能存储为 1~4 个八位组。C++使用前缀 u8 来表示这种类型的字符串字面值。
C++11 新增的另一种类型是原始(raw)字符串。在原始字符串中,字符表示的就是自己,例如,序列\n 不表示换行符,而表示两个常规字符—斜杠和 n,因此在屏幕上显示时,将显示这两个字符。另一个例子是,可在字符串中使用",而无需像程序清单 4.8 中那样使用繁琐的\"。当然,既然可在字符串字面量包含",就不能再使用它来表示字符串的开头和末尾。因此,原始字符串将"(和)"用作定界符,并使用前缀 R 来标识原始字符串:
上述代码将显示如下内容:
如果使用标准字符串字面值,将需编写如下代码:
在上述代码中,使用了\来显示\,因为单个\表示转义序列的第一个字符。
输入原始字符串时,按回车键不仅会移到下一行,还将在原始字符串中添加回车字符。
如果要在原始字符串中包含)",该如何办呢?编译器见到第一个)"时,会不会认为字符串到此结束?会的。但原始字符串语法允许您在表示字符串开头的"和(之间添加其他字符,这意味着表示字符串结尾的"和) 之间也必须包含这些字符。因此,使用 R"+*(标识原始字符串的开头时,必须使用)+*"标识原始字符串的结尾。因此,下面的语句:
将显示如下内容:
总之,这使用"+*(和)+*"替代了默认定界符"(和)"。自定义定界符时,在默认定界符之间添加任意数量的基本字符,但空格、左括号、右括号、斜杠和控制字符(如制表符和换行符)除外。
可将前缀 R 与其他字符串前缀结合使用,以标识 wchar_t 等类型的原始字符串。可将 R 放在前面,也可将其放在后面,如 Ru、UR 等。
下面介绍另一种复合类型—结构。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论