C++ Prime 笔记十一 初识 string
标准库类型 string
表示可变长字符序列,定义在命名空间 std
中,使用时必须首先包含 string
头文件。
定义和初始化
表达式 | 效果 |
---|---|
string(); | 生成一个空的 string s |
string s(str); | 直接初始化,创建一个 str 的拷贝 |
string s = str; | 拷贝初始化,等价于 s(str),s 是 str 的副本 |
string s("value") | 将C风格字符串的值作为 s 的初始值(不包括结尾的空字符) |
string s = "value" | 等价于 s("value") |
string s(n, 'c') | 生成一个字符串,包含 n 个 'c' 字符 |
这里涉及到两种初始化的方式,一种是拷贝初始化(copy initialization),使用等号初始化一个变量;一种是不使用等号的直接初始化(direct initialization)。当只有一个初始值时,使用直接初始化或者拷贝初始化都可以。多个初始值一般选择直接初始化,因为使用拷贝初始化,必须显示创建一个临时对象用于拷贝,可读性会比较差:
string s = string(10, 'c');
// 等价于
string temp(10, 'c');
string s = temp;
读写string 对象
操作 | 效果 |
---|---|
os<<s | 将 s 写到输出流 os 中,返回 os |
is>>s | 从 is 中读取字符串赋给 s, 字符串以空白分隔,返回 is |
getline(is, s) | 从 is 中读取一行赋值给 s,保留空白,遇换行符结束,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 中所含的字符一样,则相等;相等性判断对字母大小写敏感 |
s1 != s2 | 同上 |
<, <=, >, >= | 利用字符在字典中的顺序进行比较,且对字母大小写敏感 |
执行 string 对象的读写操作时,string 对象会自动忽略开头的空白:
// rw.cc
#include <iostream>
int main() {
std::string s;
std::cin >> s;
std::cout << s << std::endl;
return 0;
}
$ ./rw.out
Hello World!
Hello
程序输入“ Hello World!”,string 对象 s 自动忽略开头的空白(空格符、换行符、制表符等)并从第一个真正的字符开始读起,直到遇到下一处空白为止,因此 s 中保存的内容为 Hello,没有包含任何空格。
和内置类型的输入输出操作一样,string 对象的输入输出操作也是返回运算符左侧的运算对象作为其结果,因此可以这样写:
string s1, s2;
cin >> s1 >> s2;
cout << s1 << s2 << endl;
如果希望保留字符串中的空白符,应该使用 getline
代替原来的 >>
运算符:
// getline.cc
#include <iostream>
int main() {
std::string line;
// 反复读取直到遇到文件结束标记或非法输入结束
while (getline(std::cin, line)) {
std::cout << line << std::endl;
}
return 0;
}
$ ./getline.out
When you are old and grey and full of sleep,
When you are old and grey and full of sleep,
And nodding by the fire, take down this book,
And nodding by the fire, take down this book,
And slowly read, and dream of the soft look,
And slowly read, and dream of the soft look,
Your eyes had once, and of their shadows deep;^C
getline
从给定的输入流中读取内容(在上面的例子中是标准输入流 std::cin
),直到遇到换行符为止,换行符也被读进来,只不过在存入到 string 对象 line 的时候被丢弃掉,换行符的作用是触发 getline
函数返回。如果在一开始输入一个换行符,将得到一个空 string 。
empty 和 size
empyt
函数根据 string 对象是否为空返回一个对应的布尔值。由于 empty() 是 string 的一个成员函数,需要使用点操作符 (.
) 来调用。size
函数返回 string 对象的长度:
// noempty.cc
// 输出非空行及其长度
#include <iostream>
int main() {
std::string line;
while (getline(std::cin, line)) {
if (!line.empty()) {
std::cout << line.size()
<< ":\t"
<< line
<< std::endl;
}
}
return 0;
}
$ ./noempty.out
When you are old and grey and full of sleep,
44: When you are old and grey and full of sleep,
And nodding by the fire, take down this book,
45: And nodding by the fire, take down this book,
And slowly read, and dream of the soft look,
44: And slowly read, and dream of the soft look,
^C
回顾之前学习过的,C风格字符串采用 strlen(p)
来计算字符串的长度,其中 p 是指向以空字符作为结束的数组。
size
函数返回的是一个 string::size_type
类型的值,该类型在类 string
中定义,无符号且足够大,与机器无关。在含有 size() 的表达式中尽量避免使用 int —— 混用 int 和 unsigned 可能带来问题:
s.size() < n; // 如果 n 是一个具有负值的 int,就容易引发错误
string 对象的赋值操作
string 支持直接使用等号进行赋值操作:
string s1(10, 'c'), s2;
s2 = s1; // 同类型复制
s2 = "C-string"; // 用 C 风格字符串赋值
s2 = 'c'; // 用单一字符来赋值
如果需要多个实参来描述新值,可以改用成员函数 assign()
:
const string s1("Hello"), s2;
s2.assign(s1); // 将 s1 的值赋给 s2
s2.assign(s1, 1, 3); // s2 的值为 "ell"
s2.assign("the", 4); // 用字符数组赋值:'t' 'h' 'e' '\0'
s2.assign(5, 'x'); // 赋给五个字符,s2 的值为 "xxxxx"
string 对象的加法运算
- 对 string 对象使用加法运算符(
+
)的结果是一个新的 string 对象; - 加法运算符(
+
)两侧至少有一个是 string 类型的对象; - 复合赋值运算符(
+=
)右侧必须是 string 对象,左侧可以是 string 对象或字符(/字符串)字面值。
string s1 = "Hello", s2 = "world";
string s3 = s1 + ", " + s2 + '\n'; // OK
string s4 = s1 + ", " + "world"; // OK
// 等价于
// string tmp = s1 + ", ";
// s4 = tmp + "world";
// 下面操作将引发错误
// error: invalid operands of types ‘const char [6]’ and
// ‘const char [3]’ to binary ‘operator+’
string s5 = "hello" + ", " + s2;
字符串字面值与 string 是不同的类型,只是标准库允许把字符字面值和字符串字面值转换成 string 对象。
一个练习:
练习 3.5:编写一段程序,从标准输入中读入多个字符串并将它们连接在一起,输出连接成的大字符串。然后修改上述程序,用空格把输入的多个字符串分隔开来。
// p81_3_5.cc
#include <iostream>
int main() {
std::string str_in;
std::string str_res1;
std::string str_res2;
while (std::cin >> str_in) {
// 结束读取字符串
if (str_in == "#") {
break;
}
str_res1 += str_in; // 连接多个字符串
str_res2 += (str_in + ' '); // 连接多个字符串并用空格隔开
}
std::cout << "str_res1:\t" << str_res1 << std::endl;
std::cout << "str_res2:\t" << str_res2 << std::endl;
return 0;
}
$ ./p81_3_5.out
When you are old and grey and full of sleep,
And nodding by the fire, take down this book,
And slowly read, and dream of the soft look,
Your eyes had once, and of their shadows deep;
#
str_res1: Whenyouareoldandgreyandfullofsleep,Andnoddingbythefire,takedownthisbook,Andslowlyread,anddreamofthesoftlook,Youreyeshadonce,andoftheirshadowsdeep;
str_res2: When you are old and grey and full of sleep, And nodding by the fire, take down this book, And slowly read, and dream of the soft look, Your eyes had once, and of their shadows deep;
$
处理字符
- 如何获取字符本身;
- 要知道能改变某个字符的特性
下表列出了主要用的函数,这些函数定义在 cctype 头文件中
函数 | 含义 |
---|---|
isalnum© | 当 c 是字母或数字时为真 |
isalpha© | 当 c 是字母时为真 |
iscntrl© | 当 c 是控制字符时为真 |
isdigit© | 当 c 是数字时为真 |
isgraph© | 当 c 不是空格但可打印时为真 |
islower© | 当 c 是小写字母时为真 |
isprint© | 当 c 是可打印字符时为真 |
ispunct© | 当 c 是标点符号时为真 |
isspace© | 当 c 是空白字符时为真 |
isupper© | 当 c 是大写字母时为真 |
isxdigit© | 当 c 是十六进制时为真 |
tolower© | 如果 c 是大写字母,输出对应的小写字母;否则原样输出 c |
toupper© | 如果 c 是小写字母,输出对应的大写字母;否则原样输出 c |
cname 表示一个属于 C 语言标准库的头文件,同时被 C++ 兼容,定义在 cname 中的名字从属于命名空间 std 。
使用范围 for 可以处理 string 对象中的每个字符,如果只处理 string 中的一部分字符,可以采用下标或者是使用迭代器。
下标运算符([]
)接收 string::size_type 类型的值,该值表示访问的字符的位置,返回值为该位置上的字符的引用。不同与数组的下标,内置的下标运算符可以处理负值,而 string 对象的下标必须大于 0 而小于 s.size(),也不可使用下标访问空的 string 。
由于 C++ 标准不要求标准库检查下标的合法性,因此必须确保在合理范围之内使用下标。
一个练习:
编写一段程序,读入一个包含标点符号的字符串,将标点符号去除后,将剩余字符用 ’X‘ 代替,输出处理后的字符串。
// exer.cc
#include <iostream>
int main() {
std::string str;
std::cin >> str;
// 不能通过范围 for 来添加或减少对象中的元素
for (int i = 0; i != str.size(); ++i) {
if (ispunct(str[i])) {
str.erase(i, 1);
--i;
}
}
std::cout << str.size() << ":\t" << str << std::endl;
// 改变了 str 中的字符,必须使用引用
for (char &c : str) {
c = 'X';
}
std::cout << str.size() << ":\t" << str << std::endl;
return 0;
}
$ ./exer.out
when,you,are,old~andgrey.!!andfull%of*,sleep....
34: whenyouareoldandgreyandfullofsleep
34: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
拖了很多天的笔记终于是写完了 ~ 啦 啦 啦 ~~
拖延是病~(╯#-_-)╯~~~~~~~~~~~~~~~~~╧═╧