这一章从类模板进入 STL 和 string。模板解决“同一套类逻辑适配多种类型”,string 则是 C++ 中最常用的字符串工具。安全复盘时,重点看模板实例化、分文件编写、字符串边界、输入读取和编码问题。

2.0 类模板成员函数定义

类模板的成员函数通常也要写在头文件里,因为编译器需要看到完整模板定义才能实例化。

1
2
3
4
5
6
7
8
9
template <typename T>
class Box {
public:
explicit Box(T value) : value(value) {}
T get() const { return value; }

private:
T value;
};

2.1 类模板继承中的规则

类模板可以继承普通类,也可以被普通类或模板类继承。关键是派生类必须知道基类模板参数。

1
2
3
4
5
6
7
8
9
template <typename T>
class Base {
public:
T value;
};

template <typename T>
class Derived : public Base<T> {
};

2.2 类模板对象参数传递

类模板对象作为参数时,可以指定具体类型,也可以继续写成模板函数。

1
2
3
4
template <typename T>
void print_box(const Box<T> &box) {
std::cout << box.get() << std::endl;
}

2.3 类模板友元函数定义

模板友元比较容易写乱。复盘时先分清:友元是普通函数,还是函数模板。

1
2
3
4
5
6
7
8
9
10
11
12
template <typename T>
class Pair {
public:
Pair(T a, T b) : a(a), b(b) {}

template <typename U>
friend std::ostream &operator<<(std::ostream &os, const Pair<U> &p);

private:
T a;
T b;
};

2.4 类模板分文件编写

普通类可以 .h 声明、.cpp 实现;模板类通常要把实现放在头文件,或者使用 .hpp / .tpp 包含实现。否则链接阶段可能找不到实例化后的函数。

2.5 STL 概念

STL 可以按四块理解:

组件 作用
容器 保存数据,如 vectorlistmap
迭代器 像指针一样遍历容器
算法 排序、查找、拷贝等通用逻辑
函数对象 自定义比较、过滤、处理规则

2.6 string 初始化

1
2
3
4
5
6
#include <string>

std::string a;
std::string b = "hello";
std::string c(5, 'x');
std::string d(b);

std::string 会自动维护长度和容量,比 C 字符数组更安全,但仍然要注意输入长度和编码。

2.7 string 字符串数据

方法 作用
size() 字符串长度
empty() 是否为空
c_str() 转为 C 风格字符串
data() 访问底层数据

安全复盘:c_str() 返回的指针不要长期保存,字符串修改后指针可能失效。

2.8 string 字符串比较

string 支持 ==!=< 等比较,也可以用 compare

1
2
3
if (name == "admin") {
// ...
}

复盘时注意大小写、空格、编码和不可见字符。

2.9 string 读取和输入

operator>> 读取到空白为止;getline 读取整行。

1
2
std::string line;
std::getline(std::cin, line);

如果前面用过 cin >> value,再用 getline 时要处理残留换行。

3.0 string 访问和修改

1
2
3
std::string s = "hello";
s[0] = 'H';
char c = s.at(1);

[] 不做边界检查,at() 越界会抛异常。安全复盘优先考虑 at() 或先检查范围。

3.1 string 查找和替换

1
2
3
4
5
std::string s = "hello world";
auto pos = s.find("world");
if (pos != std::string::npos) {
s.replace(pos, 5, "C++");
}

永远检查 npos,不要默认一定能找到。

3.2 string 插入和删除

1
2
s.insert(0, "[");
s.erase(0, 1);

插入和删除会改变长度,也可能导致内部缓冲区重新分配。

3.3 string 常见操作

操作 注意点
拼接 大量拼接关注性能
子串 检查起点和长度
查找 判断 npos
替换 确认目标范围
转 C 字符串 指针生命周期短

本章最小复盘代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include <string>

template <typename T>
class Holder {
public:
explicit Holder(T value) : value(value) {}
const T &get() const { return value; }

private:
T value;
};

int main() {
Holder<std::string> holder("hello world");
std::string text = holder.get();

auto pos = text.find("world");
if (pos != std::string::npos) {
text.replace(pos, 5, "C++");
}

std::cout << text << std::endl;
return 0;
}

学习检查清单

检查项 状态
能说明类模板为什么通常写在头文件 待复盘
能写出简单类模板和模板函数参数 待复盘
能区分 cin >>getline 的读取差异 待复盘
能安全使用 findreplaceerase 待复盘
能说明 c_str() 指针的生命周期风险 待复盘