先导课:STL 是什么、三大核心组件、所有容器共享的通用操作 —— 在认识每个具体容器之前,先建立整体的知识地图。
11.0
想象一下,你有一大堆乐高积木,每次想搭一个城堡都要自己从零开始做每一块砖——这太累了!如果有人提前给你准备好了各种形状的积木块:长方形的基础块、可以转动的轮子、连接用的铰链……你只需要把它们拼起来就行了。
STL 就是 C++ 给程序员准备的"积木工具箱"。它里面装好了编程时最常用的一些数据结构:可以自动变长的数组、可以两头操作的队列、可以快速查找的字典……我们不再需要从零开始写这些复杂的东西,直接用就好。
begin() 指向第一个元素,end() 指向最后一个元素之后的位置(标记"结束了")。
| 1 | vector<int> v = {10, 20, 30}; |
| 2 | auto it = v.begin(); // it 指向第一个元素(10) |
| 3 | cout << *it; // *it 解引用,访问 it 指向的值,输出 10 |
| 4 | ++it; // it 往后移动一位,现在指向 20 |
| 5 | cout << *it; // 输出 20 |
整个过程就像伸出一只"智能手",先用 begin() 把它放到容器的第一个元素上,用 *it(解引用)看一眼手指着的值,再用 ++it 把手往后挪一位,指向下一个元素——这只"手"知道该怎么在你选的这种容器里移动,不用你自己操心底层是怎么存储的。
auto it = v.begin(); 这种"让编译器自动推断类型"的写法,是 C++11 才引入的语法。有些编辑器(比如老版本的 Dev-C++、Code::Blocks)默认使用的还是更早的 C++98 标准,直接写 auto 会编译报错,需要手动在编译选项里加上 -std=c++11(或更新的 -std=c++14/c++17 等)才能启用。如果一时半会儿改不了编译设置,也可以老老实实写出完整的迭代器类型来代替 auto,比如下面要讲到的 vector<int>::iterator it = v.begin();——这种写法从 C++98 起就一直支持,不受标准版本限制。好消息是,几乎所有 STL 容器都共享一套相同的操作。就像一个工具箱里的所有工具都有"拿起来"和"放下去"这两个动作一样,无论你用 vector 还是 list,下面这些操作你都能用:
| 函数 / 类型 | 功能 | 返回值 |
|---|---|---|
| .size() | 返回容器中元素的个数 | size_t(无符号整数) |
| .empty() | 判断容器是否为空 | bool(空为 true) |
| .clear() | 清空所有元素 | 无 |
| .begin() | 返回指向第一个元素的迭代器 | 迭代器 |
| .end() | 返回指向尾后(最后一个元素之后) | 迭代器 |
| .front() | 返回第一个元素的引用 | 元素的引用 |
| .back() | 返回最后一个元素的引用 | 元素的引用 |
| .swap(b) | 交换两个同类容器的全部内容 | 无(void) |
| ::iterator | 容器的迭代器类型 | 类型名 |
front() / back() 在 stack、queue 等容器适配器中会有所限制(比如 stack 没有 front()),具体细节会在对应章节里说明。这张表概括的是大多数序列容器和关联容器共有的接口。:: 叫作用域解析符,现阶段可以暂时把 容器类型::iterator 整体理解为"这个容器专属的迭代器标记类型"——每一种容器都有自己配套的迭代器类型,写代码时要用哪个容器,就配哪个容器的 ::iterator。之后会经常看到类似 vector<int>::iterator it; 这样的写法,它和前面代码里用 auto it = v.begin(); 效果完全一样,只是 auto 让编译器自动推断出这个较长的类型名,写代码时更省事,因此更常用。下面五大类一共 18 个专题,建议按顺序学习——序列容器是最基础的,后面的容器适配器、关联容器都会用到序列容器的概念。