C++ pair

在编写程序时,我们经常会遇到 “两个数据成对出现” 的情况,比如学生的姓名和成绩、商品的名称和价格,或者平面上的横坐标和纵坐标。如果只是临时把这两个值组合在一起,专门去定义一个结构体未免有些繁琐。

在 C++ 中,标准库专门提供了轻便的 std::pair 来解决这类需求。pair 在英文中是 “一对” 的意思,说白了就是把两个数据打包在一起的组合类型。如果需要打包 3 个及以上的数据,则可以使用元组 std::tuple。

C++ pair 的创建与访问

std::pair 定义在 <utility> 头文件中,内部保存的两个值既可以是相同类型,也可以是不同类型。

语法:

#include <utility>

std::pair<类型1, 类型2> 变量名 = {值1, 值2};

说明:

尖括号 <> 里依次指定第 1 个值和第 2 个值的数据类型。如果只声明变量而不写初始值,比如 std::pair<std::string, int> p;,std::pair 会自动对两个成员执行零初始化,把字符串设为空串 ""、整数设为 0,不会产生随机垃圾值。

对于 std::pair 来说,内部的两个值有着固定的公开成员名称:第 1 个值叫做 first,第 2 个值叫做 second。需要注意,它们都是普通的成员变量而不是成员函数,读写时直接写 p.first 和 p.second 即可,不要像调用函数那样在后面加小括号。

示例 1:创建与修改 pair

#include <iostream>
#include <string>
#include <utility>

int main() {
    std::pair<std::string, int> stu = {"Jack", 95};
    std::cout << stu.first << ": " << stu.second << '\n';

    stu.second = 98;    // 修改第 2 个成员的值
    std::cout << stu.first << ": " << stu.second << '\n';
    return 0;
}

运行结果如下。

Jack: 95
Jack: 98

分析:

在这个例子中,stu 是一个 std::pair<std::string, int> 类型的变量,其中 stu.first 保存字符串 "Jack",stu.second 保存整数 95。只要这个 pair 变量没有加 const 修饰,我们就可以随时通过 stu.second = 98; 修改里面的值。

除了直接写明类型并使用花括号初始化之外,<utility> 头文件还提供了一个辅助函数 std::make_pair(),可以根据传入的值自动推导类型。对于这个例子来说,下面 2 种创建方式是等价的:

// 方式 1(推荐)
std::pair<std::string, int> stu = {"Jack", 95};

// 方式 2
auto stu = std::make_pair(std::string("Jack"), 95);

细心的小伙伴可能会发现,方式 2 在传入 "Jack" 时专门包了一层 std::string("Jack")。这是因为双引号字符串字面量的默认类型是 const char*,如果直接写 std::make_pair("Jack", 95),第 1 个成员就会被推导成字符指针而不是 std::string。

而在方式 1 中,尖括号里已经明确写出了目标类型,花括号里的 "Jack" 会自动转为 std::string。这种写法既简短又不容易推错类型,因此在实际开发中我们更推荐使用方式 1。

比较两个 pair 的大小

只要 pair 内部保存的元素本身支持比较运算,两个同类型的 pair 就可以直接使用 ==、!=、<、> 等运算符进行比较。它们的比较规则如下:

  • 判断相等(== 与 !=):只有当两个 pair 的 first 和 second 分别相等时,== 的结果才为 true,否则为 false。
  • 比较大小(<、> 等):先比较两个 pair 的 first,只要 first 分出了大小,就不再看后面的 second。只有当 first 相等时,才会继续比较 second。

下面我们来看一个简单的例子。

示例 2:比较两个 pair 的大小

#include <iostream>
#include <utility>

int main() {
    std::pair<int, int> p1 = {10, 20};
    std::pair<int, int> p2 = {10, 35};
    std::pair<int, int> p3 = {15, 5};

    if (p1 < p2) {
        std::cout << "p1 小于 p2\n";
    }
    if (p1 < p3) {
        std::cout << "p1 小于 p3\n";
    }
    return 0;
}

运行结果如下。

p1 小于 p2
p1 小于 p3

分析:

判断 p1 < p2 时,程序先对比两者的 first,发现都是 10。接着继续对比 second,因为 20 小于 35,所以判定 p1 < p2 成立。

而在判断 p1 < p3 时,虽然 p3.second 的 5 比 p1.second 的 20 小,但第一步对比 first 时 10 已经小于 15 了,此时直接得出 p1 < p3 成立的结果,根本不会再去比后面的 second。对于这一点,小伙伴们别搞混了。

注意 只有当两个 std::pair 对应位置的成员类型能够相互比较时,才能使用 == 或 < 进行判断。如果一个是 std::pair<int, int>,另一个是 std::pair<std::string, int>,直接比较会在编译阶段报错。

C++ pair 的常见应用

在实际开发中,std::pair 最常见的使用场景主要有 2 种:一种是 “让函数一次返回两个结果”,另一种是 “配合容器保存成对的数据”。

1. 让函数一次返回两个结果

前面学习函数返回值时我们知道,一条 return 语句一次只能返回一个值。如果一个函数需要同时带回两个计算结果,比如做整数除法时既想算出 “商”、又想算出 “余数”,使用 std::pair 打包返回就非常合适。

示例 3:函数返回两个计算结果

#include <iostream>
#include <utility>

// 同时返回商和余数
std::pair<int, int> divide(int a, int b) {
    return {a / b, a % b};
}

int main() {
    std::pair<int, int> res = divide(17, 5);

    std::cout << "商:" << res.first << '\n';
    std::cout << "余数:" << res.second << '\n';
    return 0;
}

运行结果如下。

商:3
余数:2

分析:

因为 divide() 函数开头已经写明了返回类型为 std::pair<int, int>,所以函数体内的 return {a / b, a % b}; 可以直接使用花括号把商 3 和余数 2 打包返回。在 main() 中接收返回值后,通过 res.first 和 res.second 就能分别取出这两个结果。

2. 配合 vector 保存成对数据

当我们手头有一批成对出现的数据需要按顺序存放时,可以把 std::pair 作为元素类型放进 std::vector 中,比如写成 std::vector<std::pair<std::string, int>>。后面学习 std::map 映射容器时,里面的每一组键值对本质上也是一个 std::pair。

示例 4:在 vector 中保存 pair

#include <iostream>
#include <string>
#include <utility>
#include <vector>

int main() {
    std::vector<std::pair<std::string, int>> items = {
        {"apple", 5},
        {"banana", 3}
    };

    items.push_back({"orange", 8});    // 向尾部追加一组数据

    for (const auto& item : items) {
        std::cout << item.first << ": " << item.second << '\n';
    }
    return 0;
}

运行结果如下。

apple: 5
banana: 3
orange: 8

分析:

items 是一个存放 std::pair<std::string, int> 元素的动态数组。初始化和调用 push_back() 时,像 {"orange", 8} 这样的每一对内层花括号都会自动构造出一个 pair 元素,通过 item.first 和 item.second 即可分别读取名称与数量。

另外可以看到,这里范围 for 循环写成了 const auto& item。因为每个元素的完整类型名比较长,配合前面学过的 auto 关键字写成 const auto&,既能自动推导类型,又能避免每次循环都额外拷贝一份 pair。

上一篇:

下一篇:

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号