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 再去做对应的练习更合适。