std::pair 使用指南

std::pair 是 C++ 标准库里最简单、出镜率又极高的“小工具类型”之一。在 <utility> 头文件中定义。

它不仅是“存两个数”的结构体,实际上,也常联动STL 各大容器使用。它是 std::map 的底层细胞,是 std::vector 排序的默认规则载体,也是算法中状态记录的神器。

本文旨在梳理 std::pair 的基础用法,并重点剖析它和 map / vector / stack / queue / priority_queue 等容器之间的深度联动。


一、std::pair 是什么?

核心理解: std::pair 本质上就是一个“把两个值绑在一起”的轻量级模板结构体。

你可以把它简单理解为标准库预先定义好的通用结构体,它的成员变量名是固定的,就叫 firstsecond

// 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 强在哪里?

  1. 通用性:它是模板,什么类型都能装。

  2. 内置特性:自带了构造、赋值、swap 以及字典序比较(这个很重要)。

  3. 生态位: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 重载了所有比较运算符(<, >, == 等)。 规则:字典序比较

  1. 先比较 first

  2. 如果 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.firstp.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;
}
(gdb) bt #0 0x005cd5ff in std::_Rb_tree_decrement(std::_Rb_tree_node_base*) () from /usr/lib/libstdc++.so.6 #1 0x080e3b15 in std::_Rb_tree_iterator<std::pair<std::basic_string<char, std::char_traits<char>, std::allocator<char> > const, STDTM::AlarmElement> >::operator-- (this=0xbfbacdc4) at /usr/lib/gcc/i686-redhat-linux/4.4.7/../../../../include/c++/4.4.7/bits/stl_tree.h:199 #2 0x080e3873 in std::_Rb_tree<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::basic_string<char, std::char_traits<char>, std::allocator<char> > const, STDTM::AlarmElement>, std::_Select1st<std::pair<std::basic_string<char, std::char_traits<char>, std::allocator<char> > const, STDTM::AlarmElement> >, std::less<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::basic_string<char, std::char_traits<char>, std::allocator<char> > const, STDTM::AlarmElement> > >::_M_insert_unique (this=0x89b53a0, __v=...) at /usr/lib/gcc/i686-redhat-linux/4.4.7/../../../../include/c++/4.4.7/bits/stl_tree.h:1179 #3 0x080e31df in std::_Rb_tree<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::pair<std::basic_string<char, std::char_traits<char>, std::allocator<char> > const, STDTM::AlarmElement>, std::_Select1st<std::pair<std::basic_string<char, std::char_traits<char>, std::allocator<char> > const, STDTM::AlarmElement> >, std::less<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::basic_string<char, std::char_traits<char>, std::allocator<char> > const, STDTM::AlarmElement> > >::_M_insert_unique_ (this=0x89b53a0, __position=..., __v=...) at /usr/lib/gcc/i686-redhat-linux/4.4.7/../../../../include/c++/4.4.7/bits/stl_tree.h:1217 #4 0x080e2ee7 in std::map<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, STDTM::AlarmElement, std::less<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::basic_string<char, std::char_traits<char>, std::allocator<char> > const, STDTM::AlarmElement> > >::insert (this=0x89b53a0, __position=..., __x=...) at /usr/lib/gcc/i686-redhat-linux/4.4.7/../../../../include/c++/4.4.7/bits/stl_map.h:540
06-13
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值