STL 和字符串处理
string
字符串类
STL 为我们提供了方便的 string
类型:
| string fullname = firstname + " " + lastname;
|
| std::cout << fullname << std::endl;
|
| string s0("Initial string"); // 从 C 风格字符串构造
string s1; // 默认空字符串
string s2(s0, 8, 3); // 截取,index 从 8 开始,长度为 3
string s3("Another character sequence", 12); // 截取
string s4(10, 'x'); // 复制字符
string s5(s0.begin(), s0.begin() + 7); // 复制截取
str.c_str(); // 转换为 C 风格字符串
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | // 方式一
#include <string>
#include <iostream>
int main() {
std::string sample = "Kanashi koto wa kitto owaru darou";
for (auto it = sample.begin(); it != sample.end(); it++) {
std::cout << *it << std::endl;
}
return 0;
}
// 方式二
for (char c: sample) {
std::cout << c << std::endl;
}
|
1
2
3
4
5
6
7
8
9
10
11
12 | cout << str[1]; // 访问元素
str[1] = 'a'; // 修改元素
str.size() // 查询长度
str.clear() // 清空
str.empty() // 查询是否为空
str.push_back('a');
str.append(s2); // 向尾部增加
// 直接按照字典序来比较
string a = "alice", b = "bob";
a == b // false
a < b // true
|
1
2
3
4
5
6
7
8
9
10
11
12
13 | // 数值类型字符串化
to_string(1); // "1"
to_string(3.14); // "3.14"
to_string(3.1415926); // "3.141593",注意精度损失
to_string(1 + 2 + 3); // "6"
// 字符串转数值类型
int a = stoi("2001"); // a = 2001
std::string::size_type sz; // size_t alias
int b = stoi("50 cats", &sz); // b = 50 sz = 2
int c = stoi("40c3", nullptr, 16); // c = 0x40c3,十六进制
int d = stoi("0x7f", nullptr, 0); // d = 0x7f,自动检查进制
double e = stod("34.5"); // e = 34.5
|
iostream
输入输出流
| cout << str << endl;
cin >> str;
ostream& operator<<(ostream& out, const Test& src) {
out << src.id << endl;
return out;
}
|
基类:istream
和 ostream
,而 iostream
是派生类。cin
、cout
是具体的对象。ifstream
、ofstream
、fstream
都是面向文件的输入输出流。也可以通过字符串来定义交互方式,比如 sstream
。
ostream
即 output stream,是 STL 库中所有输出流的基类
它重载了针对基础类型的输出流运算符,接受不同类型的数据,再调用系统函数进行输出。统一了输出接口,改善了 C 中输出方式混乱的状况,比如 C 中需要用户关心非常多的内容,手动设置输出类型,常常出错。
| printf("%d %f %s", 1, 2.3, "hello");
|
cout
是 STL 中内建的一个 ostream
对象,它会将数据送到标准输出流(一般是屏幕)。
1
2
3
4
5
6
7
8
9
10
11
12 | class ostream {
public:
ostream& operator<<(char c) {
printf("%c", c);
return *this;
}
ostream& operator<<(const char* str) {
printf("%s", str);
return *this;
}
} cout;
|
语句 cout << "hello" << ' ' << "world"
先执行 cout << "hello"
调用第二个函数,返回 c1
(cout
的引用)。再执行 c1 << ' '
调用第一个函数,返回 c2
(cout
的引用)。最后执行 c2 << "world"
调用第二个函数。
格式化输出
| #include <iomanip>
cout << fixed << 2018.0 << " " << 0.0001 << endl; // 固定小数位数
cout << scientific << 2018.0 << " " << 0.0001 << endl; // 科学计数法
cout << defaultfloat; // 还原默认输出格式
cout << setprecision(2) << 3.1415926 << endl; // 输出精度设置为 2
cout << oct << 12 << " " << hex << 12 << endl; // 八进制输出与十六进制输出
cout << dec; // 还原十进制
cout << setw(3) << setfill('*') << 5 << endl; // 设置对齐长度为 3,对齐字符为 *
|
自己实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | class setprecision {
private:
int precision;
public:
setprecision(int p): precision(p) { }
friend class ostream;
};
// 函数类
class ostream {
private:
int precision; // 记录流的状态
public:
ostream& operator<<(const setprecision &m) {
precision = m.precision;
return *this;
}
} cout;
cout << setprecision(2);
|
借助辅助类,设置成员变量。这种类叫流操纵算子。
为什么重载流运算符要返回引用,而不是直接返回 cout
?
始终在用同一个输出流对象 cout
,没有构造新的 ostream
。
| ostream(const ostream&) = delete;
ostream(ostream&& x);
|
禁止复制、只允许移动,仅使用 cout
一个全局对象。
- 为什么只能使用一个对象?
- 减少复制开销
- 一个对象对应一个标准输出,符合 OOP 思想
- 多个对象之间无法同步输出状态
- 是否能做得更好?
- 全局对象往往引入初始化顺序问题
- 单件模式(Singleton Pattern),在之后的设计模式中会介绍
文件输入输出流
以文件输入流作为例子:ifstream
是 istream
的子类,功能是从文件中读入数据。
打开文件:
| ifstream ifs("input.txt");
ifstream ifs("binary.bin", ifstream::binary); // 以二进制形式打开文件
ifstream ifs; // 先创建一个 sfstream 类的对象
ifs.open("file");
ifs.close();
|
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 | #include <iostream>
#include <string>
#include <cctype>
#include <fstream>
using namespace std;
int main() {
ifstream ifs("input.txt");
while (ifs) { // 判断文件是否到末尾,利用了重载的 bool 运算符
ifs >> ws; // 除去前导空格 ws 也是流操纵算子
int c = ifs.peek(); // 检查下一个字符,但不读取
if (c == EOF) break;
if (isdigit(c)) {
int n;
ifs >> n;
cout << "Read a number: " << n << endl;
} else {
string str;
ifs >> str;
cout << "Read a word: " << str << endl;
}
}
}
|
辅助函数:
| getline(cin, str);
/* 其他操作 */
get(); // 读取一个字符
ignore(int n = 1, int delim = EOF); // 丢弃 n 个字符,或者直至遇到 delim 分隔符
peek(); // 查看下一个字符
putback(char c); // 返还一个字符
unget(); // 返还一个字符
|
scanf
不友好,不同类型要使用不同的标识符。
对比:
| scanf("%d %hd %f %lf %s", &i, &s, &f, &d, name);
cin >> i >> s >> f >> d >> name;
|
其中:
- 安全性:
scanf("%d %d", &a)
可能写入非法内存;
- 可拓展性:
cin >> obj
可重载流操作运算符
- 性能:
scanf
在运行期间需要对格式字符串进行解析,istream
在编译期间已经解析完毕
字符串流
以输入输出流作为例子:
stringstream
是 iostream
的子类
iostream
继承于 istream
和 ostream
stringstream
实现了输入输出流双方的接口
| #include <sstream>
using namespace std;
int main() {
stringstream ss;
ss << "10"; // 将 10 存入 ss
ss << "0 200";
int a, b;
ss >> a >> b; // a = 100, b = 200
}
|
主体是你的程序,客体是你的 sstream
、ifstream
。输入输出是相对程序而言的。