string类和标准模板库 --- 完整知识点详解
一、string类
1.1 为什么要学习string类?
在C语言中,我们使用字符数组(char[])来处理字符串,但字符数组有很多"坑":
- 容易造成缓冲区溢出
- 需要手动管理内存
- 字符串拼接、复制等操作繁琐(需要调用
strcpy、strcat等函数)
string类 是C++标准库提供的字符串管理类,它把字符串当作一个对象来处理,就像管理一个"智能的字符容器",自动处理内存,提供丰富的操作方法。
通俗比喻: 如果说字符数组是"手工记账",那string类就是"Excel自动计算"------你只需要告诉它你想做什么,它帮你处理底层细节。
cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
string s1 = "Hello"; // 方式1:直接赋值
string s2("World"); // 方式2:用构造函数
string s3 = s1 + " " + s2; // 方式3:直接拼接!
cout << s3 << endl; // 输出:Hello World
cout << "长度:" << s3.size() << endl; // 输出:长度:11
return 0;
}
对比C语言,字符串拼接在C语言中需要:
c
char buf[100];
strcpy(buf, s1);
strcat(buf, " ");
strcat(buf, s2); // 很繁琐,还容易写错
1.2 构造字符串
string类提供了多种构造方式,适用于不同的场景:
| 构造方式 | 示例 | 说明 |
|---|---|---|
| 默认构造 | string s; |
创建空字符串 |
| C风格字符串构造 | string s("hello"); |
用字符数组初始化 |
| 拷贝构造 | string s2(s1); |
用另一个string初始化 |
| 部分拷贝构造 | string s(str, pos, len); |
从str的pos位置开始拷贝len个字符 |
| 填充构造 | string s(5, 'A'); |
创建包含5个'A'的字符串 |
cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
string s1; // 空字符串
string s2 = "C++"; // "C++"
string s3(s2); // 拷贝s2,得到"C++"
string s4("Hello World", 5); // 从"Hello World"取前5个字符 -> "Hello"
string s5(s2, 1); // 从s2的第1个位置开始取到末尾 -> "++"
string s6(4, '#'); // "####"
cout << "s1: \"" << s1 << "\"" << endl;
cout << "s2: " << s2 << endl;
cout << "s3: " << s3 << endl;
cout << "s4: " << s4 << endl;
cout << "s5: " << s5 << endl;
cout << "s6: " << s6 << endl;
return 0;
}
输出:
s1: ""
s2: C++
s3: C++
s4: Hello
s5: ++
s6: ####
1.3 string类输入
string类支持多种输入方式:
方式一:使用cin >>(遇到空白停止)
cpp
string word;
cin >> word; // 输入 "Hello World",只会读取 "Hello"
方式二:使用getline()(读取整行)
cpp
string line;
getline(cin, line); // 输入 "Hello World",读取完整的 "Hello World"
方式三:使用getline()的自定义分隔符版本
cpp
string line;
getline(cin, line, ':'); // 以':'作为分隔符
// 输入 "name:Tom",line的值为 "name"(遇到:就停止)
方式四:使用cin.getline()(读入char数组后赋给string)
cpp
char buf[100];
cin.getline(buf, 100);
string s(buf);
方式五:使用cin.get()逐字符读取
cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
cout << "请输入你的名字:";
string name;
getline(cin, name); // 推荐:可以读取包含空格的名字
cout << "你好," << name << "!" << endl;
cout << "请输入一句话(用#结束):";
string sentence;
getline(cin, sentence, '#'); // 以'#'结束
cout << "你说的是:" << sentence << endl;
return 0;
}
注意:
cin >> s遇到空格、Tab、回车都会停止读取,而getline(cin, s)会读取整行(遇到回车才停止)。当你需要读取包含空格的输入时,必须用getline()。
1.4 使用字符串
1.4.1 基本操作
cpp
string s = "Hello";
// 获取长度
s.size(); // 返回 5
s.length(); // 和 size() 一样,返回 5
// 访问单个字符
s[0]; // 'H'
s.at(1); // 'e'(带越界检查,更安全)
// 判断是否为空
s.empty(); // 返回 false(因为s不为空)
1.4.2 字符串拼接
cpp
string first = "Hello";
string second = "World";
string full = first + " " + second; // "Hello World"
first += ", "; // 追加到first末尾 -> "Hello, "
first += second; // -> "Hello, World"
first.append("!"); // 也可以用append -> "Hello, World!"
1.4.3 字符串查找
cpp
string s = "Hello World, Hello C++";
// find():查找子串第一次出现的位置
size_t pos = s.find("Hello"); // pos = 0
pos = s.find("Hello", 1); // 从位置1开始找 -> pos = 13
// rfind():从后往前查找
pos = s.rfind("Hello"); // pos = 13
// find_first_of():查找字符集中任意一个字符第一次出现的位置
pos = s.find_first_of("aeiou"); // 找第一个元音字母 -> pos = 1
// find_last_of():查找最后一个
pos = s.find_last_of("aeiou"); // pos = 17
if (pos != string::npos) {
cout << "找到了,位置是:" << pos << endl;
} else {
cout << "没找到" << endl;
}
注意:
string::npos是一个特殊常量,表示"没有找到"。
1.4.4 字符串比较
cpp
string a = "Apple";
string b = "Banana";
// 直接用比较运算符,按字典序比较
a == b; // false
a != b; // true
a < b; // true('A' < 'B')
a > b; // false
// compare()方法,返回值类似strcmp
int result = a.compare(b); // 返回负数(a < b时)
1.4.5 字符串截取与替换
cpp
string s = "Hello World";
// substr(pos, len):截取子串
string sub = s.substr(6, 5); // 从位置6开始取5个字符 -> "World"
string sub2 = s.substr(6); // 从位置6取到末尾 -> "World"
// replace(pos, len, str):替换
s.replace(6, 5, "C++"); // "Hello C++"
// insert(pos, str):在指定位置插入
s.insert(6, "Beautiful "); // "Hello Beautiful C++"
// erase(pos, len):删除
s.erase(6, 10); // 从位置6开始删除10个字符
1.4.6 字符串与其他类型的转换
cpp
// 数字转字符串
string num = to_string(42); // "42"
string pi = to_string(3.14159); // "3.141590"
// 字符串转数字
int n = stoi("42"); // 42
double d = stod("3.14159"); // 3.14159
long l = stol("1234567890"); // 1234567890
1.5 string还提供了哪些功能?
| 功能 | 方法 | 说明 |
|---|---|---|
| 反转字符串 | reverse(s.begin(), s.end()) |
需要<algorithm> |
| 清空 | s.clear() |
清空字符串 |
| 交换两个字符串 | s1.swap(s2) |
交换s1和s2的内容 |
| 获取C风格字符串 | s.c_str() |
返回const char*,用于与C函数交互 |
| 分配空间 | s.reserve(100) |
预分配100个字符的空间 |
| 字符统计 | s.find_first_not_of(" ") |
找第一个不是空格的字符 |
cpp
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int main() {
string s = "Hello";
// 反转
reverse(s.begin(), s.end());
cout << "反转后:" << s << endl; // "olleH"
// 交换
string a = "AAA", b = "BBB";
a.swap(b);
cout << "a=" << a << " b=" << b << endl; // a=BBB b=AAA
// 清空
s.clear();
cout << "清空后长度:" << s.size() << endl; // 0
// c_str()用法:传递给需要char*的C函数
string filename = "test.txt";
FILE* fp = fopen(filename.c_str(), "r"); // fopen需要const char*
return 0;
}
1.6 字符串种类
C++实际上提供了多种字符串类型:
| 字符串类型 | 头文件 | 说明 |
|---|---|---|
std::string |
<string> |
基于char,最常用 |
std::wstring |
<string> |
基于wchar_t,宽字符 |
std::u16string |
<string> |
基于char16_t(C++11) |
std::u32string |
<string> |
基于char32_t(C++11) |
cpp
#include <iostream>
#include <string>
using namespace std;
int main() {
string s1 = "Hello"; // 普通字符串
wstring s2 = L"你好世界"; // 宽字符字符串(中文常用)
u16string s3 = u"Hello"; // UTF-16字符串
u32string s4 = U"Hello"; // UTF-32字符串
cout << "普通字符串:" << s1 << endl;
// wcout << "宽字符串:" << s2 << endl; // 需要设置locale
return 0;
}
为什么要了解多种字符串? 处理中文、日文等多字节字符时,普通
string可能不够用,需要使用宽字符版本。但在大多数日常编程中,std::string就足够了。
二、智能指针模板类
2.1 为什么需要智能指针?
先来看一个常见的内存泄漏问题:
cpp
void leakyFunction() {
int* p = new int(42); // 在堆上分配内存
// ... 做一些事情 ...
// 忘记 delete p 了!
// 函数结束,p被销毁,但堆上的内存没有释放 -> 内存泄漏!
}
智能指针 就像一个"自动管家"------它会在你不再需要某块内存时自动释放它,避免内存泄漏。
通俗比喻: 智能指针就像图书馆的借书系统。你借书(分配内存)后,系统会自动记录。当你忘记还书时,系统会在合适的时机自动帮你处理。而普通指针就像手工记录,容易忘记还。
C++11提供了三种智能指针:
| 智能指针 | 特点 | 类比 |
|---|---|---|
unique_ptr |
独占所有权,同一时刻只有一个指针拥有对象 | 一物一主 |
shared_ptr |
共享所有权,可以有多个指针拥有同一对象 | 共同拥有 |
weak_ptr |
弱引用,不拥有对象,只是"观察" | 借来看看 |
2.2 unique_ptr --- 独占型智能指针
cpp
#include <iostream>
#include <memory>
using namespace std;
int main() {
// 创建unique_ptr
unique_ptr<double> p1(new double(3.14));
cout << *p1 << endl; // 3.14
// C++14推荐用make_unique
unique_ptr<double> p2 = make_unique<double>(2.718);
// p1可以"接管"p2的所有权
p1 = move(p2);
// 此时p2变为null,p1拥有2.718
if (p2 == nullptr) {
cout << "p2现在是空的" << endl;
}
// 当p1离开作用域时,自动释放内存
return 0;
}
unique_ptr的重要规则:
- 不能复制 :
unique_ptr<int> p2 = p1;❌ 编译错误 - 可以移动 :
unique_ptr<int> p2 = move(p1);✅ 所有权转移
cpp
unique_ptr<int> make_int(int n) {
return make_unique<int>(n); // 可以从函数返回
}
int main() {
unique_ptr<int> p = make_int(42);
cout << *p << endl; // 42
// 数组版本
unique_ptr<int[]> arr = make_unique<int[]>(5);
arr[0] = 10;
arr[1] = 20;
cout << arr[0] << " " << arr[1] << endl; // 10 20
return 0;
}
2.3 shared_ptr --- 共享型智能指针
cpp
#include <iostream>
#include <memory>
#include <string>
using namespace std;
class Dog {
public:
string name;
Dog(string n) : name(n) {
cout << name << " 出生了!" << endl;
}
~Dog() {
cout << name << " 离开了..." << endl;
}
};
int main() {
// 共享所有权
shared_ptr<Dog> p1 = make_shared<Dog>("旺财");
cout << "p1引用计数:" << p1.use_count() << endl; // 1
{
shared_ptr<Dog> p2 = p1; // p2也拥有旺财
cout << "p2引用计数:" << p1.use_count() << endl; // 2
shared_ptr<Dog> p3 = p1; // p3也拥有旺财
cout << "p3引用计数:" << p1.use_count() << endl; // 3
}
// p2、p3离开作用域,引用计数减1
cout << "作用域结束后:" << p1.use_count() << endl; // 1
return 0;
// p1离开作用域,引用计数变0,自动释放Dog
}
输出:
旺财 出生了!
p1引用计数:1
p2引用计数:2
p3引用计数:3
作用域结束后:1
旺财 离开了...
关键概念:引用计数
- 每当一个新的shared_ptr指向对象时,计数+1
- 当一个shared_ptr被销毁时,计数-1
- 当计数变为0时,对象被自动删除
2.4 weak_ptr --- 弱引用
问题场景: 如果两个对象互相用shared_ptr引用对方,会形成循环引用,导致引用计数永远不为0,内存泄漏。
cpp
// 循环引用问题(反面教材)
class B; // 前置声明
class A {
public:
shared_ptr<B> b_ptr; // A拥有B
~A() { cout << "A被销毁" << endl; }
};
class B {
public:
shared_ptr<A> a_ptr; // B拥有A -> 循环引用!
~B() { cout << "B被销毁" << endl; }
};
// 解决方案:用weak_ptr打破循环
class B_fixed;
class A_fixed {
public:
shared_ptr<B_fixed> b_ptr;
~A_fixed() { cout << "A被销毁" << endl; }
};
class B_fixed {
public:
weak_ptr<A_fixed> a_ptr; // 用weak_ptr!不增加引用计数
~B_fixed() { cout << "B被销毁" << endl; }
};
cpp
#include <iostream>
#include <memory>
using namespace std;
int main() {
weak_ptr<int> wp; // weak_ptr不能直接创建对象
{
shared_ptr<int> sp = make_shared<int>(42);
wp = sp; // weak_ptr从shared_ptr赋值
cout << "sp引用计数:" << sp.use_count() << endl; // 1(weak_ptr不增加计数)
// 使用前需要先"锁定"为shared_ptr
if (auto temp = wp.lock()) {
cout << "锁定成功,值为:" << *temp << endl; // 42
}
}
// sp已销毁
if (wp.expired()) {
cout << "对象已经被销毁了" << endl;
}
return 0;
}
通俗比喻: shared_ptr是"主人",weak_ptr是"访客"。主人决定对象的生死,访客只是来看看。当所有主人都走了,对象就销毁了,即使还有访客在看。
2.5 使用智能指针的注意事项
注意事项1:不要混用普通指针和智能指针
cpp
int* raw = new int(42);
unique_ptr<int> smart(raw);
delete raw; // ❌ 错误!smart已经拥有了内存,再delete就是双重释放
注意事项2:不要用同一个指针创建两个智能指针
cpp
int* raw = new int(42);
unique_ptr<int> p1(raw);
unique_ptr<int> p2(raw); // ❌ p1和p2都会尝试释放内存
注意事项3:不要用new返回的指针赋值给auto_ptr以外再手动delete
cpp
// 错误示范
unique_ptr<int> p(new int(42));
delete p.get(); // ❌ 不要手动删除!
// 正确做法:让智能指针自动管理
unique_ptr<int> p = make_unique<int>(42); // ✅ 自动管理
注意事项4:使用make_shared/make_unique代替new
cpp
// 不推荐
shared_ptr<int> p1(new int(42));
// 推荐(异常安全,且效率更高)
shared_ptr<int> p2 = make_shared<int>(42);
unique_ptr<int> p3 = make_unique<int>(42);
为什么推荐make_shared?
假设执行
shared_ptr<int> p(new int(42), custom_deleter);时,如果custom_deleter的分配抛出异常,那么new int(42)分配的内存就泄漏了。而make_shared只分配一次内存,更安全。
2.6 unique_ptr为何优于auto_ptr
auto_ptr是C++98引入的早期智能指针,C++11已经将其废弃 (deprecated),用unique_ptr替代。
| 对比项 | auto_ptr | unique_ptr |
|---|---|---|
| 复制语义 | 会偷偷转移所有权 | 必须用move()显式转移 |
| 与STL容器 | 不能放入容器 | 可以放入容器 |
| 数组支持 | 不支持 | 支持unique_ptr<T[]> |
| 可用性 | C++17已移除 | C++11起推荐使用 |
cpp
// auto_ptr的坑(不要这样做!)
auto_ptr<string> p1(new string("hello"));
auto_ptr<string> p2 = p1; // 所有权转移!p1变成null了!
// 之后如果用p1,可能导致程序崩溃
// unique_ptr的安全做法
unique_ptr<string> p3(new string("hello"));
// unique_ptr<string> p4 = p3; // ❌ 编译错误!
unique_ptr<string> p4 = move(p3); // ✅ 显式移动,意图清晰
核心区别:
auto_ptr的复制操作会"偷偷"转移所有权,你可能不知道原来的指针已经失效了。而unique_ptr要求你必须明确用move()来转移,代码意图更清晰,不容易犯错。
2.7 选择智能指针的决策指南
你需要管理动态分配的对象吗?
├── 不需要 → 用普通变量,不要用智能指针
└── 需要 → 谁拥有这个对象?
├── 只有当前作用域需要 → unique_ptr
├── 多个地方共享 → shared_ptr
├── 需要观察但不拥有 → weak_ptr
├── 需要在容器中存储 → unique_ptr(容器支持move语义)
└── 需要共享所有权但有循环引用 → shared_ptr + weak_ptr
cpp
// 场景1:函数内部创建对象返回给调用者
unique_ptr<int> createNumber() {
return make_unique<int>(42); // 所有权交给调用者
}
// 场景2:多个对象共享同一资源
class ResourceManager {
shared_ptr<Resource> resource;
public:
ResourceManager(shared_ptr<Resource> r) : resource(r) {}
};
// 场景3:观察者模式
class Observer {
weak_ptr<Subject> subject; // 观察但不拥有
public:
void update() {
if (auto s = subject.lock()) {
// 安全使用
}
}
};
三、标准模板库(STL)
3.1 什么是STL?
STL(Standard Template Library)是C++标准库的核心组成部分,它提供了通用的、可复用的数据结构和算法。
通俗比喻: 如果C++是乐高积木的基础砖块,那STL就是预先搭好的各种"组件"------轮子、门、窗户。你不需要自己从头造轮子,直接用现成的就好了。
STL的四大组件:
| 组件 | 作用 | 举例 |
|---|---|---|
| 容器 | 存储数据 | vector, list, map |
| 迭代器 | 遍历容器 | begin(), end() |
| 算法 | 操作数据 | sort(), find() |
| 函数对象 | 自定义行为 | lambda, 函数符 |
3.2 模板类vector --- 动态数组
3.2.1 为什么用vector?
普通数组大小固定,定义时就必须确定大小:
cpp
int arr[100]; // 只能存100个元素,多了就溢出了
vector就像一个可以自动伸缩的数组,你往里面加元素,它会自动扩容。
通俗比喻: 普通数组像固定大小的书架,vector像可以自动生长的书架------书多了,它自己变大。
3.2.2 创建vector
cpp
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main() {
// 各种创建方式
vector<int> v1; // 空vector
vector<int> v2(10); // 10个元素,初始值为0
vector<int> v3(10, 42); // 10个元素,初始值为42
vector<int> v4 = {1, 2, 3, 4, 5}; // C++11初始化列表
vector<string> v5 = {"hello", "world"};
// 用另一个vector初始化
vector<int> v6(v4); // 拷贝v4
vector<int> v7(v4.begin(), v4.end()); // 用迭代器范围
// 查看大小
cout << "v4的大小:" << v4.size() << endl; // 5
cout << "v4的容量:" << v4.capacity() << endl; // 至少5(可能更多)
return 0;
}
3.3 可对矢量执行的操作
3.3.1 添加和删除元素
cpp
vector<int> v = {1, 2, 3};
// 尾部添加
v.push_back(4); // {1, 2, 3, 4}
// emplace_back(C++11):直接在末尾构造对象,效率更高
v.emplace_back(5); // {1, 2, 3, 4, 5}
// 插入
v.insert(v.begin() + 1, 10); // {1, 10, 2, 3, 4, 5}
// 在位置1插入10
// 删除末尾
v.pop_back(); // {1, 10, 2, 3, 4}
// 删除指定位置
v.erase(v.begin() + 1); // {1, 2, 3, 4}(删除位置1的元素)
// 删除范围
v.erase(v.begin(), v.begin() + 2); // {3, 4}(删除前两个)
// 清空所有
v.clear(); // {}
3.3.2 访问元素
cpp
vector<int> v = {10, 20, 30, 40, 50};
// 下标访问
v[0]; // 10
v[2]; // 30
// at()访问(带越界检查)
v.at(3); // 40
// v.at(10); // 抛出 std::out_of_range 异常
// 首尾元素
v.front(); // 10(第一个元素)
v.back(); // 50(最后一个元素)
// 原始指针
int* p = v.data(); // 获取底层数组的指针
3.3.3 容量相关
cpp
vector<int> v;
v.reserve(100); // 预分配100个元素的空间
v.push_back(1);
v.push_back(2);
cout << "大小:" << v.size() << endl; // 2(实际元素数量)
cout << "容量:" << v.capacity() << endl; // 100(已分配空间)
cout << "是否为空:" << v.empty() << endl; // 0(false)
v.resize(5); // 调整大小为5,多出的元素初始化为0
v.shrink_to_fit(); // 释放多余的内存(C++11)
3.3.4 二维vector
cpp
// 创建3行4列的二维数组
vector<vector<int>> matrix(3, vector<int>(4, 0));
matrix[0][0] = 1;
matrix[1][2] = 5;
// 遍历
for (int i = 0; i < matrix.size(); i++) {
for (int j = 0; j < matrix[i].size(); j++) {
cout << matrix[i][j] << " ";
}
cout << endl;
}
3.4 对矢量可执行的其他操作
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> v = {5, 2, 8, 1, 9, 3};
// 排序
sort(v.begin(), v.end());
// v: {1, 2, 3, 5, 8, 9}
// 逆序
reverse(v.begin(), v.end());
// v: {9, 8, 5, 3, 2, 1}
// 查找
auto it = find(v.begin(), v.end(), 5);
if (it != v.end()) {
cout << "找到了5,位置:" << (it - v.begin()) << endl;
}
// 随机打乱
random_shuffle(v.begin(), v.end()); // 需要<algorithm>
// 判断是否包含(C++20有contains,之前用find)
bool has8 = (find(v.begin(), v.end(), 8) != v.end());
cout << "有8吗?" << (has8 ? "有" : "没有") << endl;
// unique:去除相邻重复元素(需要先排序)
sort(v.begin(), v.end());
auto last = unique(v.begin(), v.end());
v.erase(last, v.end()); // 真正删除重复元素
return 0;
}
3.5 基于范围的for循环(C++11)
传统for循环遍历vector需要管理索引,容易出错:
cpp
vector<int> v = {1, 2, 3, 4, 5};
// 传统方式(容易写错索引)
for (int i = 0; i < v.size(); i++) {
cout << v[i] << " ";
}
// C++11 基于范围的for循环(简洁安全!)
for (int x : v) {
cout << x << " "; // 只读访问
}
// 使用引用修改元素
for (int& x : v) {
x *= 2; // 每个元素翻倍
}
// 使用auto自动推导类型
vector<string> words = {"Hello", "World", "C++"};
for (const auto& word : words) { // const引用,高效且不修改
cout << word << " ";
}
// 遍历初始化列表
for (int x : {1, 2, 3, 4, 5}) {
cout << x << " ";
}
关键点:
for (int x : v)--- 拷贝每个元素,只读for (int& x : v)--- 引用每个元素,可修改for (const int& x : v)--- const引用,只读且不拷贝(推荐!)
3.6 泛型编程
泛型编程(Generic Programming)是编写与数据类型无关的代码。
通俗比喻: 你不会为"红色的苹果"写一个切法,为"绿色的苹果"写另一个切法。你写一个通用的"切水果"方法,不管什么颜色的水果都能用。
cpp
// 不使用模板:为每种类型写一遍
void swapInts(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
void swapDoubles(double& a, double& b) {
double temp = a;
a = b;
b = temp;
}
// 使用模板:一个函数搞定所有类型
template <typename T>
void mySwap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
int main() {
int x = 1, y = 2;
mySwap(x, y); // 自动推导T为int
double a = 1.5, b = 2.5;
mySwap(a, b); // 自动推导T为double
}
STL的核心思想就是泛型编程------容器、算法、迭代器都是模板,可以处理任何类型的数据。
3.7 为何使用迭代器
问题:如何用统一的方式遍历不同容器?
vector可以用下标遍历,但list、map等容器没有下标 。迭代器提供了一种统一的遍历方式。
通俗比喻: 迭代器就像一个"遥控器"。不管你用的是电视机(vector)、收音机(list)还是投影仪(map),遥控器的操作方式都差不多------按"下一个"就到下一个,按"上一个"就返回。
cpp
#include <iostream>
#include <vector>
#include <list>
#include <map>
using namespace std;
int main() {
// vector使用迭代器
vector<int> v = {1, 2, 3, 4, 5};
vector<int>::iterator it; // 声明迭代器
for (it = v.begin(); it != v.end(); ++it) {
cout << *it << " "; // 解引用获取元素
}
cout << endl;
// list使用迭代器(和vector一样的写法!)
list<string> lst = {"apple", "banana", "cherry"};
for (auto it2 = lst.begin(); it2 != lst.end(); ++it2) {
cout << *it2 << " "; // 一样用*it获取元素
}
cout << endl;
// map使用迭代器
map<string, int> ages = {{"Tom", 25}, {"Jerry", 23}};
for (auto it3 = ages.begin(); it3 != ages.end(); ++it3) {
cout << it3->first << ": " << it3->second << endl;
// map的迭代器指向pair,用->first和->second访问
}
return 0;
}
3.8 迭代器类型
| 迭代器类型 | 支持操作 | 对应容器 |
|---|---|---|
| 输入迭代器 | 读取、单次前进 | istream_iterator |
| 输出迭代器 | 写入、单次前进 | ostream_iterator |
| 前向迭代器 | 读写、多次前进 | forward_list, unordered_map |
| 双向迭代器 | 读写、前进和后退 | list, map, set |
| 随机访问迭代器 | 读写、跳跃、比较 | vector, deque, string |
cpp
vector<int> v = {1, 2, 3, 4, 5};
// 随机访问迭代器(vector的迭代器)
auto it = v.begin();
it += 3; // 跳到第4个元素
it -= 2; // 回退2个位置
cout << *it; // 2
// 双向迭代器(list的迭代器)
list<int> lst = {1, 2, 3, 4, 5};
auto it2 = lst.end();
--it2; // 可以后退
cout << *it2; // 5
// ++it2; // 不能跳多个位置(没有+=操作)
3.9 迭代器层次结构
输入迭代器 → 前向迭代器 → 双向迭代器 → 随机访问迭代器
↓
随机访问迭代器(最强大)
理解关键: 每个高级迭代器都包含低级迭代器的所有功能。随机访问迭代器最强大,可以做所有操作;输入迭代器最基础,只能读取和前进。
cpp
// 随机访问迭代器可以做的事
vector<int> v = {1, 2, 3, 4, 5};
auto it = v.begin();
// 比较
it < v.end() // ✅ 随机访问迭代器支持
it - v.begin() // ✅ 可以算距离
// 跳跃
it + 3 // ✅ 前进3个位置
it - 2 // ✅ 后退2个位置
// list的双向迭代器不能做的事
list<int> lst = {1, 2, 3};
auto it2 = lst.begin();
// it2 + 2 // ❌ 编译错误!双向迭代器不支持+操作
// it2 < lst.end() // ❌ 编译错误!双向迭代器不支持<
3.10 概念、改进和模型
概念(Concept): 对迭代器功能需求的描述。例如"随机访问迭代器"就是一个概念,它要求支持+、-、<等操作。
模型(Model): 概念的具体实现。例如vector<int>::iterator就是"随机访问迭代器"概念的一个模型。
改进(Refinement): 一个概念对另一个概念的增强。例如"双向迭代器"是对"前向迭代器"的改进,它增加了--操作。
cpp
// 概念和模型的关系
// 概念:随机访问迭代器(要求:支持 +, -, <, [], *= 等)
// 模型:vector<int>::iterator 是随机访问迭代器概念的一个模型
// string::iterator 也是随机访问迭代器概念的一个模型
// list<int>::iterator 是双向迭代器概念的模型(不是随机访问的)
// 一个算法可能要求特定的概念
// sort() 要求随机访问迭代器
vector<int> v = {3, 1, 4, 1, 5};
sort(v.begin(), v.end()); // ✅ vector迭代器满足要求
list<int> lst = {3, 1, 4, 1, 5};
// sort(lst.begin(), lst.end()); // ❌ list迭代器只是双向的,不满足要求
lst.sort(); // ✅ list有自己的sort成员函数
为什么这很重要? 使用算法时,你需要确保你的迭代器满足算法要求的概念,否则编译会报错或运行出错。
3.11 容器种类
C++ STL容器分为三大类:
序列容器(按位置顺序存储)
| 容器 | 特点 | 适用场景 |
|---|---|---|
vector |
连续内存,尾部操作O(1),随机访问O(1) | 大多数情况的首选 |
deque |
双端队列,头部和尾部操作O(1) | 需要频繁在两端操作 |
list |
双向链表,任意位置插入删除O(1) | 频繁在中间插入删除 |
forward_list |
单向链表,C++11 | 内存受限场景 |
array |
固定大小数组,C++11 | 需要确定大小的数组 |
cpp
#include <iostream>
#include <vector>
#include <deque>
#include <list>
#include <array>
#include <forward_list>
using namespace std;
int main() {
// vector:最常用
vector<int> v = {1, 2, 3, 4, 5};
// deque:双端操作
deque<int> dq = {1, 2, 3};
dq.push_front(0); // 前面加元素
dq.push_back(4); // 后面加元素
// list:双向链表
list<int> lst = {1, 2, 3};
lst.push_front(0);
lst.push_back(4);
lst.insert(lst.begin() + 2, 10); // ❌ list不支持+操作!
// 正确做法:
auto it = lst.begin();
advance(it, 2); // 用advance移动迭代器
lst.insert(it, 10);
// array:固定大小数组(C++11)
array<int, 5> arr = {5, 4, 3, 2, 1};
// forward_list:单向链表
forward_list<int> fl = {1, 2, 3};
fl.push_front(0);
// fl.push_back(4); // ❌ 不支持push_back!
return 0;
}
容器选择速查
需要随机访问? → vector(首选)或 deque
需要频繁在两端插入? → deque
需要频繁在中间插入删除? → list
需要固定大小? → array
需要单向遍历且内存紧张? → forward_list
需要自动排序? → set 或 map
需要快速查找? → unordered_set 或 unordered_map
3.12 关联容器
关联容器使用键(key)来快速查找元素,底层通常用红黑树实现,查找效率为O(log n)。
| 容器 | 说明 | 键是否唯一 | 是否有序 |
|---|---|---|---|
set |
集合 | 是 | 有序 |
multiset |
允许重复的集合 | 否 | 有序 |
map |
键值对 | 是 | 有序 |
multimap |
允许重复键的映射 | 否 | 有序 |
cpp
#include <iostream>
#include <set>
#include <map>
#include <string>
using namespace std;
int main() {
// ===== set:自动去重和排序 =====
set<int> s = {5, 3, 1, 4, 2, 3};
// s: {1, 2, 3, 4, 5} (自动排序,重复的3只保留一个)
s.insert(6); // 添加元素
s.erase(2); // 删除元素
// 查找
if (s.find(3) != s.end()) {
cout << "3存在" << endl;
}
// 或者用count
cout << "1的数量:" << s.count(1) << endl; // 1(存在)
// 遍历
for (int x : s) {
cout << x << " "; // 1 3 4 5 6(有序!)
}
cout << endl;
// ===== map:键值对 =====
map<string, int> ages;
ages["Tom"] = 25;
ages["Jerry"] = 23;
ages["Alice"] = 28;
// 访问
cout << "Tom的年龄:" << ages["Tom"] << endl; // 25
// 查找
auto it = ages.find("Jerry");
if (it != ages.end()) {
cout << it->first << " -> " << it->second << endl;
// Jerry -> 23
}
// 遍历(按键的字典序)
for (const auto& pair : ages) {
cout << pair.first << ": " << pair.second << endl;
}
return 0;
}
什么时候用关联容器? 当你需要快速查找的时候。例如:学生成绩查询系统(用学号作为键)、电话簿(用名字作为键)、统计词频(用单词作为键)。
3.13 无序关联容器(C++11)
无序关联容器使用哈希表实现,查找效率平均为O(1),比有序关联容器的O(log n)更快。
| 容器 | 说明 |
|---|---|
unordered_set |
无序集合 |
unordered_multiset |
允许重复的无序集合 |
unordered_map |
无序映射 |
unordered_multimap |
允许重复键的无序映射 |
cpp
#include <iostream>
#include <unordered_map>
#include <string>
using namespace std;
int main() {
// 统计词频
unordered_map<string, int> wordCount;
string words[] = {"apple", "banana", "apple", "cherry", "banana", "apple"};
for (const string& word : words) {
wordCount[word]++; // 不存在则自动创建并初始化为0,然后+1
}
for (const auto& pair : wordCount) {
cout << pair.first << ": " << pair.second << endl;
}
// 输出(顺序可能不同,因为是无序的):
// apple: 3
// banana: 2
// cherry: 1
return 0;
}
有序 vs 无序的选择:
- 需要按键排序 遍历 → 用有序的(
map,set) - 只需要快速查找 ,不需要排序 → 用无序的(
unordered_map,unordered_set) - 无序容器查找平均O(1),但最坏情况可能退化到O(n)
- 有序容器查找始终O(log n),稳定可预测
3.14 函数对象(函数符)
函数对象(Function Object / Functor)是重载了operator()的类的对象,可以像函数一样被调用。
通俗比喻: 函数对象就像一个"带记忆的函数"。普通函数执行完就忘了之前的计算,而函数对象可以"记住"一些状态。
3.14.1 函数符概念
cpp
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
// 最简单的函数符
class Adder {
int value;
public:
Adder(int v) : value(v) {}
// 重载(),使对象可以像函数一样调用
int operator()(int x) const {
return x + value;
}
};
// 无参函数符
class Printer {
public:
void operator()() const {
cout << "Hello from Functor!" << endl;
}
};
int main() {
Adder add5(5);
cout << add5(10) << endl; // 15(像函数一样调用!)
Printer print;
print(); // "Hello from Functor!"
// 在STL算法中使用函数符
vector<int> v = {1, 2, 3, 4, 5};
Adder add10(10);
// transform:对每个元素应用函数符
vector<int> result(v.size());
transform(v.begin(), v.end(), result.begin(), add10);
// result: {11, 12, 13, 14, 15}
for (int x : result) {
cout << x << " ";
}
cout << endl;
return 0;
}
3.14.2 预定义的函数符
C++在<functional>头文件中提供了很多预定义函数符:
cpp
#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> v = {5, 3, 8, 1, 9, 2};
// less<int>():小于比较(默认排序规则)
sort(v.begin(), v.end(), less<int>());
// v: {1, 2, 3, 5, 8, 9}
// greater<int>():大于比较
sort(v.begin(), v.end(), greater<int>());
// v: {9, 8, 5, 3, 2, 1}
// plus<int>():加法
plus<int> add;
cout << add(3, 4) << endl; // 7
// negate<int>():取负
negate<int> neg;
cout << neg(5) << endl; // -5
// 多参数函数符
// bind1st / bind2d 已在C++11中弃用,用bind替代
return 0;
}
常用预定义函数符:
| 函数符 | 功能 | 示例 |
|---|---|---|
less<T> |
小于 | less<int>()(3, 5) → true |
greater<T> |
大于 | greater<int>()(5, 3) → true |
plus<T> |
加法 | plus<int>()(3, 4) → 7 |
minus<T> |
减法 | minus<int>()(5, 3) → 2 |
multiplies<T> |
乘法 | multiplies<int>()(3, 4) → 12 |
negate<T> |
取负 | negate<int>()(5) → -5 |
3.14.3 自适应函数符和函数适配器
自适应函数符携带了类型信息(参数类型和返回类型),可以与函数适配器配合使用。
cpp
#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// bind:绑定参数(C++11推荐替代bind1st/bind2nd)
// 找到第一个大于5的元素
auto it = find_if(v.begin(), v.end(),
bind(greater<int>(), placeholders::_1, 5));
// 等价于 lambda:[](int x) { return x > 5; }
if (it != v.end()) {
cout << "第一个大于5的元素:" << *it << endl; // 6
}
// 统计大于3的元素个数
int count = count_if(v.begin(), v.end(),
bind(greater<int>(), placeholders::_1, 3));
cout << "大于3的元素个数:" << count << endl; // 7
// 将每个元素乘以2
vector<int> result(v.size());
transform(v.begin(), v.end(), result.begin(),
bind(multiplies<int>(), placeholders::_1, 2));
// result: {2, 4, 6, 8, 10, 12, 14, 16, 18, 20}
for (int x : result) {
cout << x << " ";
}
cout << endl;
return 0;
}
现代C++推荐: 在大多数情况下,使用lambda表达式比bind和函数符更简洁:
cpp// 用lambda替代bind find_if(v.begin(), v.end(), [](int x) { return x > 5; }); count_if(v.begin(), v.end(), [](int x) { return x > 3; });
四、算法
4.1 算法组
STL算法分为几大类:
非修改序列操作(不改变容器内容)
cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
using namespace std;
int main() {
vector<int> v = {1, 2, 3, 4, 5};
// find:查找
auto it = find(v.begin(), v.end(), 3);
cout << "3在位置:" << (it - v.begin()) << endl; // 2
// count:计数
vector<int> v2 = {1, 2, 2, 3, 2};
cout << "2出现的次数:" << count(v2.begin(), v2.end(), 2) << endl; // 3
// accumulate:求和(需要<numeric>)
int sum = accumulate(v.begin(), v.end(), 0);
cout << "求和:" << sum << endl; // 15
// min_element / max_element
auto minIt = min_element(v.begin(), v.end());
auto maxIt = max_element(v.begin(), v.end());
cout << "最小值:" << *minIt << " 最大值:" << *maxIt << endl;
// all_of / any_of / none_of
bool allPositive = all_of(v.begin(), v.end(), [](int x) { return x > 0; });
cout << "全部为正?" << (allPositive ? "是" : "否") << endl; // 是
// equal:比较两个范围是否相等
vector<int> v3 = {1, 2, 3, 4, 5};
cout << "v和v3相等?" << (equal(v.begin(), v.end(), v3.begin()) ? "是" : "否") << endl;
return 0;
}
修改序列操作
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> v = {1, 2, 3, 4, 5};
// copy:复制
vector<int> v2(5);
copy(v.begin(), v.end(), v2.begin());
// transform:转换
vector<int> v3(5);
transform(v.begin(), v.end(), v3.begin(), [](int x) { return x * x; });
// v3: {1, 4, 9, 16, 25}
// replace:替换
replace(v.begin(), v.end(), 3, 100); // 把所有3替换为100
// fill:填充
fill(v.begin(), v.end(), 0); // 全部设为0
// generate:生成
int counter = 0;
generate(v.begin(), v.end(), [&counter]() { return counter++; });
// v: {0, 1, 2, 3, 4}
// remove + erase:删除特定元素(remove只是移动元素到末尾)
v = {1, 2, 3, 2, 4, 2, 5};
v.erase(remove(v.begin(), v.end(), 2), v.end());
// v: {1, 3, 4, 5}
return 0;
}
排序和相关操作
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
// sort:排序
vector<int> v = {5, 2, 8, 1, 9, 3};
sort(v.begin(), v.end()); // 升序
sort(v.begin(), v.end(), greater<int>()); // 降序
// stable_sort:稳定排序(保持相等元素的相对顺序)
// partial_sort:部分排序
// nth_element:第n个元素就位
// 二分查找(要求已排序)
sort(v.begin(), v.end(), greater<int>()); // 降序
bool found = binary_search(v.begin(), v.end(), 8, greater<int>());
cout << "找到8:" << (found ? "是" : "否") << endl;
// lower_bound / upper_bound
v = {1, 2, 2, 3, 4, 4, 5};
auto lb = lower_bound(v.begin(), v.end(), 2); // 第一个>=2的位置
auto ub = upper_bound(v.begin(), v.end(), 2); // 第一个>2的位置
cout << "2的范围:" << (lb - v.begin()) << " 到 " << (ub - v.begin()) << endl;
// 输出:2的范围:1 到 3
// next_permutation:下一个排列
vector<int> p = {1, 2, 3};
do {
for (int x : p) cout << x << " ";
cout << endl;
} while (next_permutation(p.begin(), p.end()));
// 输出所有排列:
// 1 2 3
// 1 3 2
// 2 1 3
// 2 3 1
// 3 1 2
// 3 2 1
return 0;
}
4.2 算法的通用特征
通用特征1:算法通过迭代器操作容器
cpp
// 算法不知道底层是什么容器
// 它只通过迭代器来"读写"数据
sort(v.begin(), v.end()); // vector
sort(lst.begin(), lst.end()); // 如果迭代器满足要求
通用特征2:算法可以使用函数对象自定义行为
cpp
// 默认升序
sort(v.begin(), v.end());
// 自定义降序
sort(v.begin(), v.end(), greater<int>());
// 自定义比较规则
struct Student {
string name;
int score;
};
vector<Student> students = {{"Tom", 85}, {"Jerry", 92}, {"Alice", 78}};
// 按分数降序排列
sort(students.begin(), students.end(), [](const Student& a, const Student& b) {
return a.score > b.score;
});
通用特征3:部分算法有多个版本
cpp
// 默认版本:使用<运算符
sort(v.begin(), v.end());
// 自定义版本:使用比较函数
sort(v.begin(), v.end(), greater<int>());
// 部分算法有_if版本:接受谓词
find_if(v.begin(), v.end(), [](int x) { return x > 5; });
五、STL和string类
5.1 函数和容器方法
STL算法和容器的成员函数有时可以完成同样的任务,但效率可能不同:
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> v = {5, 3, 8, 1, 9};
// 用STL算法查找
auto it1 = find(v.begin(), v.end(), 8);
// 用STL算法排序
sort(v.begin(), v.end());
// 用STL算法反转
reverse(v.begin(), v.end());
// vector自己的方法
v.push_back(10); // 只有vector有push_back
v.size(); // 每个容器都有size
v.empty(); // 每个容器都有empty
v.clear(); // 每个容器都有clear
// 选择原则:
// 1. 有成员方法就优先用成员方法(可能更高效)
// 2. 需要通用算法就用STL算法
// 3. 成员方法和STL算法结合使用
return 0;
}
对比表:
| 操作 | STL算法 | 容器方法 |
|---|---|---|
| 查找 | find() |
list::find() |
| 排序 | sort() |
list::sort() |
| 反转 | reverse() |
无(用算法) |
| 计数 | count() |
无(用算法) |
| 插入 | 不适用 | insert() |
| 删除 | remove() |
erase() |
注意:
list的成员方法sort()比STL的sort()更高效,因为sort()需要随机访问迭代器,而list只有双向迭代器,所以list的sort()是成员方法。
六、使用STL
6.1 实战示例:学生成绩管理系统
cpp
#include <iostream>
#include <vector>
#include <map>
#include <algorithm>
#include <string>
#include <numeric>
using namespace std;
struct Student {
string name;
int score;
};
int main() {
// 1. 用vector存储学生信息
vector<Student> students = {
{"Tom", 85},
{"Jerry", 92},
{"Alice", 78},
{"Bob", 95},
{"Charlie", 88}
};
// 2. 按成绩降序排序
sort(students.begin(), students.end(),
[](const Student& a, const Student& b) {
return a.score > b.score;
});
cout << "=== 成绩排名 ===" << endl;
for (int i = 0; i < students.size(); i++) {
cout << "第" << i + 1 << "名:" << students[i].name
<< " " << students[i].score << "分" << endl;
}
// 3. 计算平均分
int total = accumulate(students.begin(), students.end(), 0,
[](int sum, const Student& s) {
return sum + s.score;
});
cout << "\n平均分:" << (double)total / students.size() << endl;
// 4. 找出成绩最高的学生
auto top = max_element(students.begin(), students.end(),
[](const Student& a, const Student& b) {
return a.score < b.score;
});
cout << "最高分:" << top->name << " " << top->score << "分" << endl;
// 5. 统计90分以上的人数
int count90 = count_if(students.begin(), students.end(),
[](const Student& s) { return s.score >= 90; });
cout << "90分以上的人数:" << count90 << endl;
// 6. 用map建立名字到成绩的映射
map<string, int> scoreMap;
for (const auto& s : students) {
scoreMap[s.name] = s.score;
}
// 7. 查询某个学生的成绩
string queryName = "Alice";
if (scoreMap.find(queryName) != scoreMap.end()) {
cout << queryName << "的成绩:" << scoreMap[queryName] << "分" << endl;
}
return 0;
}
七、其他库
7.1 vector、valarray和array
| 容器 | 头文件 | 特点 | 适用场景 |
|---|---|---|---|
vector |
<vector> |
通用动态数组,支持迭代器和STL算法 | 通用场景 |
array |
<array> |
固定大小数组(C++11),栈上分配 | 大小已知且固定 |
valarray |
<valarray> |
数学运算优化,支持向量化操作 | 科学计算/数学运算 |
cpp
#include <iostream>
#include <vector>
#include <array>
#include <valarray>
#include <numeric>
using namespace std;
int main() {
// vector:通用,最常用
vector<double> v = {1.0, 2.0, 3.0, 4.0, 5.0};
// array:固定大小,比vector效率略高
array<double, 5> arr = {1.0, 2.0, 3.0, 4.0, 5.0};
cout << "array大小:" << arr.size() << endl; // 5
// valarray:数学运算
valarray<double> va = {1.0, 2.0, 3.0, 4.0, 5.0};
// valarray支持逐元素数学运算!
valarray<double> vb = {10.0, 20.0, 30.0, 40.0, 50.0};
valarray<double> vc = va + vb; // {11, 22, 33, 44, 55}
valarray<double> vd = va * vb; // {10, 40, 90, 160, 250}
valarray<double> ve = vb - va; // {9, 18, 27, 36, 45}
valarray<double> vf = vb / va; // {10, 10, 10, 10, 10}
// 求和
double sum = 0;
for (int i = 0; i < 5; i++) sum += vc[i];
cout << "vc求和:" << sum << endl; // 165
// 子数组
valarray<double> sub = va[slice(1, 3, 1)]; // 从位置1取3个,步长1
// sub: {2, 3, 4}
return 0;
}
选择指南:
- 日常编程 → vector(支持STL算法,最灵活)
- 大小固定的小数组 → array(栈上分配,无内存开销)
- 数学密集计算 → valarray(编译器可能做向量化优化)
7.2 模板initializer_list(C++11)
initializer_list允许函数接受花括号包围的初始化列表作为参数。
cpp
#include <iostream>
#include <vector>
#include <initializer_list>
using namespace std;
// 接受initializer_list的函数
void printList(initializer_list<int> il) {
for (const auto& x : il) {
cout << x << " ";
}
cout << endl;
}
// 使用initializer_list实现可变参数求和
int sum(initializer_list<int> il) {
int total = 0;
for (auto x : il) {
total += x;
}
return total;
}
// 在类中使用initializer_list
class IntArray {
vector<int> data;
public:
IntArray(initializer_list<int> il) : data(il) {}
void print() const {
for (int x : data) {
cout << x << " ";
}
cout << endl;
}
};
int main() {
// 直接用花括号列表调用
printList({1, 2, 3, 4, 5});
printList({10, 20, 30});
cout << "求和:" << sum({1, 2, 3, 4, 5}) << endl; // 15
// 用初始化列表构造对象
IntArray arr = {10, 20, 30, 40, 50};
arr.print(); // 10 20 30 40 50
// vector也可以用初始化列表
vector<int> v = {5, 4, 3, 2, 1};
return 0;
}
7.3 使用initializer_list的注意事项
cpp
#include <iostream>
#include <initializer_list>
using namespace std;
class Safe {
public:
Safe(initializer_list<int> il) {
cout << "创建了包含" << il.size() << "个元素的Safe" << endl;
// 注意:initializer_list中的元素是const的!
for (auto& x : il) {
cout << x << " ";
// x = 100; // ❌ 错误!initializer_list元素是const
}
cout << endl;
}
};
int main() {
Safe s = {1, 2, 3, 4, 5};
return 0;
}
重要注意事项:
initializer_list中的元素是只读的(const)initializer_list对象本身只是一个"视图",底层数据在花括号结束后可能失效- 应该立即 把
initializer_list的内容拷贝到容器中保存initializer_list的大小在编译时确定
八、总结
知识体系总览
第16章 知识脉络
│
├── string类
│ ├── 构造方式(默认、C字符串、拷贝、填充)
│ ├── 输入方式(cin>>、getline、自定义分隔符)
│ ├── 基本操作(拼接、查找、比较、截取、替换)
│ ├── 类型转换(to_string、stoi、stod)
│ └── 字符串种类(string、wstring、u16string、u32string)
│
├── 智能指针
│ ├── unique_ptr:独占所有权(推荐首选)
│ ├── shared_ptr:共享所有权(引用计数)
│ ├── weak_ptr:弱引用(解决循环引用)
│ ├── auto_ptr:已废弃(不要用)
│ └── 选择策略:独占→unique,共享→shared,观察→weak
│
├── STL
│ ├── 容器
│ │ ├── 序列容器:vector、deque、list、array、forward_list
│ │ ├── 关联容器:set、map、multiset、multimap
│ │ └── 无序容器:unordered_set、unordered_map等
│ ├── 迭代器
│ │ ├── 类型层次:输入→前向→双向→随机访问
│ │ └── 统一遍历不同容器的接口
│ ├── 算法
│ │ ├── 非修改:find、count、accumulate
│ │ ├── 修改:copy、transform、replace
│ │ └── 排序:sort、binary_search、next_permutation
│ └── 函数对象
│ ├── functor:重载operator()的类
│ ├── 预定义函数符:less、greater、plus等
│ └── lambda表达式(现代C++首选)
│
└── 其他
├── valarray:数学运算优化
├── array:固定大小数组
└── initializer_list:统一初始化语法
核心使用建议
- 字符串处理 :优先使用
string,需要与C库交互时用c_str() - 内存管理 :优先使用
unique_ptr,需要共享时用shared_ptr,永远不要用auto_ptr - 容器选择 :大多数情况用
vector,需要键值查找用map/unordered_map - 遍历容器 :优先使用基于范围的
for循环和auto - 自定义行为:优先使用lambda表达式,复杂的用函数对象
- 算法使用:优先使用STL算法代替手写循环
本章是C++标准库的核心内容,掌握string、智能指针和STL的使用,将极大提升你的编程效率和代码质量。这些工具让你专注于解决问题本身,而不是重复造轮子。