跳转至

STL 和字符串处理

string 字符串类

STL 为我们提供了方便的 string 类型:

  • 允许简洁的拼接操作
1
string fullname = firstname + " " + lastname;
  • 也能够使用惯用的输入输出方法
1
std::cout << fullname << std::endl;
  • 构造方式
1
2
3
4
5
6
7
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
  • std::stringstd::vector<char> 的区别

    • string 可以 length() 或者 size() 来获取长度,但是 vector 只有 size() 函数。

    • string 可以通过 cin >> str 从标准输入输入字符串。

    • string 可以通过 str += "abc" 向字符串尾部添加字符。

    • 都可以使用 for (char c: str) 遍历字符串中的所有字符。

iostream 输入输出流

1
2
3
4
5
6
cout << str << endl;
cin >> str;
ostream& operator<<(ostream& out, const Test& src) {
    out << src.id << endl;
    return out;
} 

基类:istreamostream,而 iostream 是派生类。cincout 是具体的对象。ifstreamofstreamfstream 都是面向文件的输入输出流。也可以通过字符串来定义交互方式,比如 sstream

  • ostream 即 output stream,是 STL 库中所有输出流的基类

它重载了针对基础类型的输出流运算符,接受不同类型的数据,再调用系统函数进行输出。统一了输出接口,改善了 C 中输出方式混乱的状况,比如 C 中需要用户关心非常多的内容,手动设置输出类型,常常出错。

1
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" 调用第二个函数,返回 c1cout 的引用)。再执行 c1 << ' ' 调用第一个函数,返回 c2cout 的引用)。最后执行 c2 << "world" 调用第二个函数。

格式化输出

1
2
3
4
5
6
7
8
#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

1
2
ostream(const ostream&) = delete;
ostream(ostream&& x);

禁止复制、只允许移动,仅使用 cout 一个全局对象。

  • 为什么只能使用一个对象?
    • 减少复制开销
    • 一个对象对应一个标准输出,符合 OOP 思想
    • 多个对象之间无法同步输出状态
  • 是否能做得更好?
    • 全局对象往往引入初始化顺序问题
    • 单件模式(Singleton Pattern),在之后的设计模式中会介绍

文件输入输出流

以文件输入流作为例子:ifstreamistream 的子类,功能是从文件中读入数据。

打开文件:

1
2
3
4
5
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;
        }
    }
}

辅助函数:

1
2
3
4
5
6
7
8
getline(cin, str);

/* 其他操作 */
get(); // 读取一个字符
ignore(int n = 1, int delim = EOF); // 丢弃 n 个字符,或者直至遇到 delim 分隔符
peek(); // 查看下一个字符
putback(char c); // 返还一个字符
unget(); // 返还一个字符
  • 为什么 C++ 使用流输入取代了 scanf

scanf 不友好,不同类型要使用不同的标识符。

对比:

1
2
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 在编译期间已经解析完毕

字符串流

以输入输出流作为例子:

  • stringstreamiostream 的子类
  • iostream 继承于 istreamostream
  • stringstream 实现了输入输出流双方的接口
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#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
}
  • 什么是输入,什么是输出?

主体是你的程序,客体是你的 sstreamifstream。输入输出是相对程序而言的。

评论

作者: Ashitemaru (98.97%), c7w (1.03%)