std::pair 是 C++ 标准库里最简单、出镜率又极高的“小工具类型”之一。在 <utility> 头文件中定义。
它不仅是“存两个数”的结构体,实际上,也常联动STL 各大容器使用。它是 std::map 的底层细胞,是 std::vector 排序的默认规则载体,也是算法中状态记录的神器。
本文旨在梳理 std::pair 的基础用法,并重点剖析它和 map / vector / stack / queue / priority_queue 等容器之间的深度联动。
一、std::pair 是什么?
核心理解: std::pair 本质上就是一个“把两个值绑在一起”的轻量级模板结构体。
你可以把它简单理解为标准库预先定义好的通用结构体,它的成员变量名是固定的,就叫 first 和 second:
// std::pair 的简化模型
template<class T1, class T2>
struct pair {
T1 first; // 第一个元素
T2 second; // 第二个元素
// ... 加上了构造函数、比较运算符重载等
};
这意味着,最原始、最基础的用法,就是通过 .first 和 .second 来存取数据:
std::pair<int, std::string> p;
p.first = 1; // 访问第一个元素
p.second = "Test"; // 访问第二个元素
那么,它比自己写的 struct 强在哪里?
-
通用性:它是模板,什么类型都能装。
-
内置特性:自带了构造、赋值、
swap以及字典序比较(这个很重要)。 -
生态位:STL 的其他容器(如 map)原生支持它。
二、创建与初始化:从 C++98 到 C++17
1. 基础创建
#include <utility>
#include <string>
// 1. 默认构造:基础类型初始化为0或空
std::pair<int, int> p1; // first=0, second=0
// 2. 带参构造
std::pair<int, std::string> p2(1, "C++");
// 3. 拷贝构造
std::pair<int, std::string> p3 = p2;
2. 辅助函数与现代写法
// [C++98/11] make_pair:自动推导类型,无需写模板参数
auto p4 = std::make_pair(1024, "Megabytes");
// [C++11] 初始化列表:最简洁,推荐!
std::pair<int, int> p5 = {10, 20};
// [C++17] CTAD (类模板实参推导):直接写 pair
std::pair p6(3.14, 50); // 自动推导为 pair<double, int>
三、访问与解包:告别繁琐的 first/second
1. 传统访问
std::pair<int, double> p = {10, 3.14};
// 语义不够直观,容易搞混 first 和 second 代表什么
int id = p.first;
double val = p.second;
2. 结构化绑定 (Structured Binding, C++17) —— 核心
现代 C++ 允许直接给 pair 的成员起别名,极大提高了代码可读性。
std::pair<int, int> point = {100, 200};
// 自动将 point 解包
auto [x, y] = point;
std::cout << "X: " << x << ", Y: " << y << std::endl;
// 引用解包(修改 rx 会改变 point.first)
auto& [rx, ry] = point;
rx = 0;
3. std::tie (C++11)
主要用于“只想要其中一个值”或者“批量赋值”。
// 1. 先定义好一个变量用来接数据
int id;
std::string name;
// 2. 模拟一个 pair 数据
std::pair<int, std::string> p = {1001, "Admin"};
// 3. 使用 std::tie 解包
// 把 p.first 的值“拷贝”给 id
// ignore代表忽略second 也可以新建一个变量来承载second的值
std::tie(id, std::ignore) = p;
std::cout << "解包出的 id: " << id << std::endl; // 输出: 1001
// 4. 修改 id 不会影响原来的 pair ,因为 id 只是拷贝过来的一个副本。
id = 999;
std::cout << "修改后的 id: " << id << std::endl; // 输出: 999
std::cout << "原来的 p.first: " << p.first << std::endl; // 输出: 1001 (依然没变)
核心对比总结: 虽然 std::tie 是经典写法,但在现代 C++ 开发中,强烈推荐直接使用结构化绑定。
四、比较运算:天然的排序规则
std::pair 重载了所有比较运算符(<, >, == 等)。 规则:字典序比较。
-
先比较
first。 -
如果
first相等,再比较second。
std::pair<int, int> a = {1, 5};
std::pair<int, int> b = {1, 7};
std::pair<int, int> c = {2, 0};
// a < b 为真 (first相等,5 < 7)
// b < c 为真 (first不等,1 < 2,不需要看second)
意义: 这意味着 vector<pair> 排序时,无需写自定义比较函数,它天然会按照“第一关键字优先,第二关键字次之”的规则排序。
五、联动:pair 在容器中的角色
1. 联动 std::map / std::unordered_map
Map 的基础元素就是 Pair。 当你遍历 std::map<Key, Value> 时,你拿到的就是 std::pair<const Key, Value>。
-
Key 是 const 的:
pair.first是只读的,防止你修改 Key 破坏树结构。 -
Insert 返回值:
map::insert返回的也是一个 pair:pair<iterator, bool>,第二个 bool 告诉你插入是否成功。
std::map<std::string, int> scores;
scores.insert({"Alice", 95}); // 隐式构造 pair
// 遍历 (C++17 风格)
for (const auto& [name, score] : scores) {
std::cout << name << ": " << score << std::endl;
}
2. 联动 std::vector (算法常用)
std::vector<std::pair<T1, T2>> 是处理多维属性或区间问题的神器。
场景: 很多个区间 [start, end],需要合并重叠区间。
做法: 把它们存入 vector,直接 sort,它会自动按 start 从小到大排;start 一样的按 end 排。
std::vector<std::pair<int, int>> intervals = {{2,3}, {1,5}, {1,2}};
std::sort(intervals.begin(), intervals.end());
// 排序后: {1,2}, {1,5}, {2,3}
3. 联动 Priority Queue (Dijkstra 最短路)
在 Dijkstra 算法中,我们需要一个优先队列来取出“当前距离最短的点”。 pair<int, int> (距离, 点ID) 是最佳选择,因为 pair 默认按 first 比较,配合 greater 即可实现最小堆。
// 定义小根堆:pair<距离, 节点ID>
std::priority_queue<
std::pair<int,int>,
std::vector<std::pair<int,int>>,
std::greater<> // 默认 pair 是比较 first 大的在前,用 greater 变成小的在前
> pq;
4. Pair 作为 Map 的 Key(注意)
有时候 Key 本身就是二元的,比如坐标 (x, y)。
-
std::map:完美支持。因为
std::map只需要比较大小(<),而 pair 已经实现了<。 -
std::unordered_map:❌ 默认不支持!
-
原因:
unordered_map依赖哈希函数,而标准库没有为pair提供默认的std::hash实现。 -
解决办法:必须手动写一个哈希仿函数(Struct Hash),通常涉及复杂的位运算(如
hash_combine)。 -
工程建议:虽然有办法实现,但比较麻烦。除非对查找性能有极致要求,否则建议直接用
std::map,或者把两个 int 通过位运算拼成一个long long作为 Key。
-
std::map<std::pair<int, int>, std::string> matrix_map;
matrix_map[{0, 1}] = "Wall"; // ✅ 编译通过
// std::unordered_map<std::pair<int, int>, int> umap; // ❌ 编译报错
六、什么时候用 Pair,什么时候用 Struct?
虽然 pair 很方便,但不要滥用。
-
推荐使用 Pair 的场景:
-
通用容器交互:需要放入 Map、需要利用 Vector 的默认排序。
-
临时返回值:函数需要临时返回“结果+状态”(如
{true, index})。 -
刷题/算法:追求编码速度,逻辑简单的场景。
-
-
推荐定义 Struct 的场景:
-
语义复杂:如果代码里到处是
p.first和p.second,没人知道这代表“坐标”还是“分数”。 -
长期维护:如果是核心业务逻辑对象,定义
struct Point { int x, y; };可读性远高于pair<int, int>。 -
超过两个字段:千万不要写
pair<int, pair<int, int>>,请原地定义结构体。
-
七、实例
练习 1:区间合并 (Vector + Pair 排序联动)
题目:给定一组区间,合并所有重叠的区间。
#include <iostream>
#include <vector>
#include <algorithm>
using Interval = std::pair<int, int>;
void mergeIntervals(std::vector<Interval>& intervals) {
if (intervals.empty()) return;
// 1. 关键:利用 pair 的默认排序 (先按左端点,再按右端点)
std::sort(intervals.begin(), intervals.end());
std::vector<Interval> merged;
merged.push_back(intervals[0]);
for (size_t i = 1; i < intervals.size(); ++i) {
// 获取 merged 中最后一个区间的引用
auto& last = merged.back();
// 当前区间
auto [currStart, currEnd] = intervals[i];
if (currStart <= last.second) {
// 有重叠,合并:右端点取最大值
last.second = std::max(last.second, currEnd);
} else {
// 无重叠,直接推入
merged.push_back(intervals[i]);
}
}
// 输出结果
for (const auto& [start, end] : merged) {
std::cout << "[" << start << ", " << end << "] ";
}
std::cout << endl;
}
int main() {
std::vector<Interval> data = {{1, 3}, {2, 6}, {8, 10}, {15, 18}};
std::cout << "Merged: ";
mergeIntervals(data);
// 输出: [1, 6] [8, 10] [15, 18]
}
练习 2:函数多返回值 (返回状态 + 数据)
#include <iostream>
#include <vector>
#include <utility> // for std::pair
// 返回类型:pair<是否找到, 索引>
std::pair<bool, int> findTarget(const std::vector<int>& nums, int target) {
for (int i = 0; i < nums.size(); ++i) {
if (nums[i] == target) {
return {true, i}; // 自动构造 pair 返回
}
}
return {false, -1};
}
int main() {
std::vector<int> v = {10, 20, 30, 40};
// 使用结构化绑定接收结果
auto [found, index] = findTarget(v, 30);
if (found) {
std::cout << "Found at index: " << index << std::endl;
} else {
std::cout << "Not found." << std::endl;
}
}
练习 3:BFS 广度优先搜索 (Queue + Pair 状态绑定)
题目:模拟在网格中记录坐标。
#include <iostream>
#include <queue>
#include <vector>
void bfsStepDemo() {
// 队列存储坐标 <x, y>
std::queue<std::pair<int, int>> q;
q.push({0, 0});
// 方向数组:上下左右
int dx[] = {0, 0, 1, -1};
int dy[] = {1, -1, 0, 0};
std::cout << "BFS Traversal Order: \n";
while (!q.empty()) {
// 1. 取出队首并解包
auto [cx, cy] = q.front();
q.pop();
std::cout << "(" << cx << "," << cy << ") -> ";
// 模拟只走一步作为演示
if (cx > 1) break;
// 2. 拓展邻居
for (int i = 0; i < 4; ++i) {
int nx = cx + dx[i];
int ny = cy + dy[i];
// 实际逻辑中这里需要判断边界和 visited 数组
q.push({nx, ny});
}
}
std::cout << "End" << std::endl;
}
1131

被折叠的 条评论
为什么被折叠?



