C++ 竞赛入门
什么是 STL
从“给一组成绩排序”出发,讲清容器、算法、迭代器各负责什么。
什么是 STL
读完能做什么
- 能说出一组成绩被
sort排序前后发生了什么变化,并解释为什么输出是60 70 80 90。 - 能指出一段程序里,哪个对象保存数据、哪个函数处理数据、处理的是哪一段范围。
- 能用「容器、算法、迭代器」三个词各说一句话,说清它们各自负责什么。
- 能解释
#include <bits/stdc++.h>和using namespace std;做了什么,以及它们有什么副作用。
为什么需要它
老师给你四个成绩 80 60 90 70,要你按从小到大输出。用已经学过的数组,得自己写排序:两层循环、比较、交换。程序不长,但每换一道题就要重写一遍;如果换成给字符串排序、给一堆记录排序,还得再写一套。
这些“排序”“查找”“去重”的套路早就有人写过,C++ 把其中常用的做成了一套现成的工具,放在标准库里。这套工具就是 STL(Standard Template Library,标准模板库)。本课要学的,就是怎么直接用它们,而不是每次从零手写。
它是什么
一句话:STL 是 C++ 标准库中一套现成的容器与算法,你可以把它们像积木一样拼起来用。
要读懂这段积木,先认识三个角色:
| 角色 | 负责什么 | 本课的例子 |
|---|---|---|
| 容器(container) | 保存一组数据 | vector、string、set、map |
| 算法(algorithm) | 对一段数据做处理 | sort、find、unique |
| 迭代器(iterator) | 表示数据里的位置,把容器和算法连起来 | v.begin()、v.end() |
#include <bits/stdc++.h>。 它把这门课用到的标准库头文件一次全部包含进来。正规写法是按需 #include <vector>、#include <algorithm>;竞赛代码里常用 bits/stdc++.h 图省事。副作用:它不是 C++ 标准规定的头文件,只有 GCC 这类编译器提供(本机 GCC 可以),换到别的编译器可能编不过,而且因为它包含的头文件多,编译会稍慢一点。本课统一采用 bits/stdc++.h。
using namespace std;。 标准库的名字都放在命名空间 std 里。写上这句之后,vector、sort、cout 可以直接写,不用每次写成 std::vector、std::sort、std::cout。副作用:如果自己定义的名字刚好和标准库重名,可能产生冲突;大型项目里通常避免全局 using。竞赛代码为了少打字,本课统一采用 using namespace std;。
模板尖括号。 vector<int> 里的 <int> 表示“这个容器里放什么类型”,vector<double>、vector<string> 同理。这里只需要照着填类型,不需要自己会写模板(详见“容易错”一节)。
六个组件只做定位。 网上常说 STL 有六大组件:容器、算法、迭代器是主线,本课重点讲;仿函数(写比较规则)和适配器(stack、queue 这类)后面会陆续遇到;空间配置器(allocator)是底层内存管理细节,不需要学,本课只标为选读。
最小可运行示例
把四个成绩存进容器,排序,再打印出来:
#include <bits/stdc++.h>
using namespace std;
int main() {
vector<int> score = {80, 60, 90, 70};
cout << "排序前:";
for (int x : score) cout << x << ' ';
cout << '\n';
sort(score.begin(), score.end());
cout << "排序后:";
for (int x : score) cout << x << ' ';
cout << '\n';
return 0;
}
输出:
排序前:80 60 90 70
排序后:60 70 80 90
逐项标注这段调用里的三个东西:
- 对象:
score,一个vector<int>,里面按顺序放着 4 个成绩。 - 函数:
sort,它会把一段数据按从小到大重新排好。 - 范围:从
score.begin()到score.end()。begin()指向第一个元素,end()指向最后一个元素的后面一个位置,两个位置合起来表示整个容器。这种“左闭右开”的范围写法在本课反复出现,细节见容器、迭代器与区间。
sort 不是“黑箱”:它真的会移动容器里的元素。调用之后 score 的内容从 80 60 90 70 变成了 60 70 80 90,这就是第二行输出的来源。
一步步看变化
四个成绩在下标 0~3 的格子里,sort 把它们按从小到大重新摆放:
| 下标 | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| 排序前 | 80 | 60 | 90 | 70 |
| 排序后 | 60 | 70 | 80 | 90 |
对照输出:
- 第一行
排序前:80 60 90 70:按原顺序打印容器里的四个元素。 - 第二行
排序后:60 70 80 90:sort把最小的60放到下标 0,最大的90放到下标 3,中间依次是70、80。
for (int x : score) 是范围 for:把容器里的元素逐个取出来,每次放到 x 里,不用自己写下标。cout << x << ' ' 就是打印 x 再跟一个空格。
逐项扩展核心操作
换一组数据,程序不用改
同一套容器与算法,换数据就能用:
#include <bits/stdc++.h>
using namespace std;
int main() {
vector<int> score = {55, 90, 70, 60};
cout << "排序前:";
for (int x : score) cout << x << ' ';
cout << '\n';
sort(score.begin(), score.end());
cout << "排序后:";
for (int x : score) cout << x << ' ';
cout << '\n';
return 0;
}
输出:
排序前:55 90 70 60
排序后:55 60 70 90
只有 vector<int> score = {...} 这一行变了,sort 那一行原样不动。这正是“可复用”的含义:算法写一次,数据随便换。
只处理一段范围
sort 处理的是你给的两个位置之间的部分。把起点从 begin() 改成 begin() + 1,就只排后面三个:
#include <bits/stdc++.h>
using namespace std;
int main() {
vector<int> score = {80, 60, 90, 70};
sort(score.begin() + 1, score.end()); // 只排下标 1 到末尾这一段
for (int x : score) cout << x << ' ';
cout << '\n';
return 0;
}
输出:
80 60 70 90
score 里下标 0 的 80 没被动,下标 1~3 的 60 90 70 被排成了 60 70 90。这说明了三件事里的“范围”:同一段数据,你指哪一段,算法就只处理哪一段。
什么时候选它、哪里容易错
会使用容器,不等于必须先会写模板。 这是初学者最容易吓到自己的地方:vector<int>、sort 的名字听起来都和“模板”有关,但使用者不需要自己写模板。你只要会照着写 vector<int>、vector<string>(尖括号里填类型),会让一个函数处理 vector<int> 和 vector<double>,就已经够用了。模板怎么写,是标准库实现者的事,不是本课的前提。
调用算法不是隐藏输入输出的黑箱。 sort(score.begin(), score.end()) 没有打印任何东西,也没有从键盘读入任何东西;它做的只有一件事——把 score 里的元素重新排好。数据怎么进来、结果怎么出去,仍然由你自己写 cin / cout 决定。
bits/stdc++.h 不是标准头文件。 它只是 GCC 的方便写法。本课统一用它,但要知道换编译器可能失效;正式工程里应按需包含 <vector>、<algorithm> 等。
选读:历史与内存配置。 STL 最早由 Alexander Stepanov 等人在惠普实验室设计,后来被纳入 C++ 标准;容器背后的内存分配由“空间配置器”负责。这些是实现背景,不影响你使用 vector 和 sort,本课不要求掌握。
操作速查表
| 写法 | 作用 | 本课对应文章 |
|---|---|---|
#include <bits/stdc++.h> |
一次包含全部标准库头文件(GCC 专用) | 本页 |
using namespace std; |
省略 std:: 前缀 |
本页 |
vector<int> v = {...} |
用一个容器保存一组数据 | vector 动态数组 |
v.begin() / v.end() |
表示首元素位置 / 尾后位置 | 容器、迭代器与区间 |
sort(first, last) |
把 [first, last) 区间排成从小到大 |
sort 排序 |
for (int x : v) |
范围 for,逐个取出元素 | vector 动态数组 |
动手检查
第 1 题:预测输出
把上面最小示例里的初值改成 {80, 60, 90, 70, 75}(多了一个 75),先手推两行输出分别是什么,再展开下面的折叠核对:
vector<int> score = {80, 60, 90, 70, 75};
sort(score.begin(), score.end());
for (int x : score) cout << x << ' ';
第 2 题:修改代码
把最小示例的初值改成 {55, 90, 70, 60},让程序按从小到大输出。验证条件:先说出排序后下标 0 和下标 3 分别是哪个数,再运行核对。
参考结果与解释
第 1 题:第一行是 排序前:80 60 90 70 75 ,第二行是 排序后:60 70 75 80 90 。75 插入后共 5 个元素,sort 仍按从小到大排。完整程序与实测输出:
#include <bits/stdc++.h>
using namespace std;
int main() {
vector<int> score = {80, 60, 90, 70, 75};
cout << "排序前:";
for (int x : score) cout << x << ' ';
cout << '\n';
sort(score.begin(), score.end());
cout << "排序后:";
for (int x : score) cout << x << ' ';
cout << '\n';
return 0;
}
输出:
排序前:80 60 90 70 75
排序后:60 70 75 80 90
第 2 题:把初值改成 {55, 90, 70, 60} 即可,其余代码不动。排序后下标 0 是 55(最小),下标 3 是 90(最大)。
#include <bits/stdc++.h>
using namespace std;
int main() {
vector<int> score = {55, 90, 70, 60};
cout << "排序前:";
for (int x : score) cout << x << ' ';
cout << '\n';
sort(score.begin(), score.end());
cout << "排序后:";
for (int x : score) cout << x << ' ';
cout << '\n';
return 0;
}
输出:
排序前:55 90 70 60
排序后:55 60 70 90
立即练习
本页是概念课,重点是把“容器保存数据、算法处理范围、迭代器表示位置”这三件事讲清楚,所以不安排 OJ 题目;等你学过 vector 和 sort 再去做对应的练习更合适。