pair对组:像双人搭档一样的数据组合
困难2成对数据的好搭档:认识C++ pair对组
你有没有遇到过这样的场景:老师让你记录每位同学的姓名和语文成绩,或者你玩游戏时要存储每个角色的生命值和攻击力,又或者你和朋友一起参加双人跳绳,需要记录两个人的名字和总得分?这些数据天然就是“成对”出现的。在C++中,有一个非常简单又强大的工具叫做 pair(对组),它能把两个数据捆绑在一起,就像双人自行车上的搭档一样,永远不分家。
1. 生活中的双人搭档——pair的直观理解
想象一下,你有一个好朋友叫“小红”,你们经常一起参加学校的双人比赛。无论是跳绳比赛、朗诵比赛还是科学实验,你们俩总是“绑定”在一起:你的名字和他(她)的名字、你们的得分、你们的排名……这些数据总是成对出现。在编程世界中,也有一种数据结构专门用来把两个数据“绑”在一起,它的名字叫 pair(对组)。
比如,你的名字是“小明”,你的好朋友叫“小红”,你们在数学竞赛中分别得了95分和90分。如果要把“小明-95”和“小红-90”这两组数据存储下来,或者要记录“小明和小红”这对组合的整体得分,pair就是最合适的工具。它就像一个双人背包,两个口袋可以装两种东西,但是它们永远在一起。
更具体地说,pair就像是一个简易的“双人座位”:第一个座位(first)放第一个数据,第二个座位(second)放第二个数据。你可以把任何两个东西放进去,不管它们是不是同一种类型。
2. pair的原理与使用方法——快速上手
pair 是C++标准模板库(STL)中的一个 工具组件,定义在 <utility> 头文件中。它非常简单:就是一个包含两个元素的“结构体”,这两个元素分别叫做 first 和 second。你可以把 first 和 second 想象成双人自行车的两个座位:第一个座位放第一样东西,第二个座位放第二样东西。
核心特点:
- 可以存储两个不同类型或相同类型的值。
- 有默认的构造函数,也可以直接初始化。
- 支持比较操作(先比较first,再比较second),常用于排序。
- 可以和容器(如vector、map)配合使用。
2.1 创建pair的几种方法
就像你可以用不同方式交朋友一样,创建pair也有很多种方法:
#include <iostream>
#include <utility> // 使用pair必须包含此头文件
using namespace std;
int main() {
// 方法1: 声明并直接用花括号初始化(C++11开始支持)
pair<string, int> student1 = {"Alice", 95}; // 学生的名字和成绩
// 方法2: 使用make_pair函数(自动推导类型)
pair<string, int> student2 = make_pair("Bob", 88); // 第二个学生
// 方法3: 先声明,再分别赋值
pair<string, int> student3; // 先声明一个空对组
student3.first = "Cathy"; // 给第一个座位放名字
student3.second = 92; // 给第二个座位放分数
// 输出看看
cout << "学生1: " << student1.first << " - " << student1.second << "分" << endl;
cout << "学生2: " << student2.first << " - " << student2.second << "分" << endl;
cout << "学生3: " << student3.first << " - " << student3.second << "分" << endl;
return 0;
}
2.2 访问pair中的元素
访问pair中的元素非常直接:用 .first 得到第一个元素,用 .second 得到第二个元素。注意不是用下标 [0] 或 [1],这是新手最容易搞错的地方。
pair<int, double> score_info = {5, 87.5}; // 5号同学,得分87.5
cout << "学号: " << score_info.first << ", 分数: " << score_info.second << endl;
2.3 pair的比较操作
pair支持 ==, !=, <, >, <=, >= 这些比较运算符。比较规则是“字典序”:先比较 first,如果相等再比较 second。这个特性非常有用,比如当你把多个pair放到一个容器中并排序时,默认就会先按第一个元素排,再按第二个元素排。
pair<int, int> p1 = {1, 5};
pair<int, int> p2 = {1, 3};
pair<int, int> p3 = {2, 0};
cout << boolalpha; // 让true/false显示为文字
cout << (p1 < p2) << endl; // false,因为first相等(1==1),但second: 5 > 3,所以p1 > p2
cout << (p1 < p3) << endl; // true,因为1 < 2,不需要比较second
重要提醒:如果两个pair的first不同,比较结果完全由first决定,根本不会看second。只有first相等时才会比较second。
2.4 pair与容器的配合
pair最常用的场景之一就是作为容器(比如vector、map)的元素。例如,你可以用 vector<pair<string, int>> 来存储一组“姓名-分数”数据。map的每个元素本身就是一个pair(key-value对)。
#include <vector>
#include <algorithm> // 用于sort
// ... 其他头文件
vector<pair<string, int>> class_list; // 班级名单:每个元素是一个学生的姓名和分数对
class_list.push_back({"Alice", 95}); // 添加学生
class_list.push_back({"Bob", 88});
2.5 C++17新语法:结构化绑定
从C++17开始,你可以用结构化绑定(structured binding)直接把pair的两个元素解包成独立变量,让代码更简洁:
pair<int, string> my_pair = {42, "hello"};
auto [num, msg] = my_pair; // num得到42,msg得到"hello"
cout << num << " " << msg << endl; // 输出: 42 hello
但作为初学者,先熟练掌握 .first 和 .second 也很重要,因为很多旧代码还在用传统方式。
3. 新手最容易犯的五个错误
错误1:忘记包含头文件
pair定义在 <utility> 中,但很多STL容器(比如 <map>、<vector>、<algorithm>)在内部已经间接包含了它。不过为了清晰,最好显式包含 #include <utility>。如果漏掉,编译器可能会报“pair未定义”错误。
错误2:用下标访问pair
有人会误写成 pair_var[0] 或 pair_var[1],但pair不是数组!正确方式是 pair_var.first 和 pair_var.second。
pair<int, double> p = {1, 2.5};
cout << p[0]; // ❌ 错误!pair没有下标操作
cout << p.first; // ✅ 正确
错误3:混淆pair和map
map的每个元素确实是一个pair(key-value对),但map本身是一个关联容器,而pair只是一个简单的对象。不能说“map就是pair”。map中的元素是 pair<const Key, Value> 类型。
错误4:排序时忘记自定义比较规则
如果你用 sort 对 vector<pair<...>> 排序,默认是按first升序,再按second升序。但如果你希望按second排序,就必须编写自己的比较函数(或lambda)。很多新手直接使用默认排序,发现结果不符合预期。
// 错误:默认排序先比较first(名字),但你可能想按分数(second)排序
sort(class_list.begin(), class_list.end());
// 正确:自定义lambda按分数升序
sort(class_list.begin(), class_list.end(),
[](const pair<string,int>& a, const pair<string,int>& b) {
return a.second < b.second;
});
错误5:在结构体/类中忽略pair的拷贝构造
pair本身支持拷贝,但如果你存储了动态分配的资源(如指针)到pair中,需要小心浅拷贝问题。不过初学者一般只存储基本类型或string,风险较小。
4. 完整示例:管理一个班级的考试成绩
下面是一个完整的程序,演示了从创建pair到存入vector、排序、查找、输出的全过程。每一行都加了中文注释,帮助你理解。
#include <iostream>
#include <vector> // 使用vector容器
#include <algorithm> // 使用sort和find_if
#include <utility> // 使用pair
#include <string> // 使用string
using namespace std;
int main() {
// 1. 创建几个学生(pair:姓名,分数)
pair<string, int> stu1 = {"Alice", 95}; // 第一个学生
pair<string, int> stu2 = {"Bob", 88}; // 第二个学生
pair<string, int> stu3 = {"Cathy", 92}; // 第三个学生
// 2. 把学生放进一个vector(班级名单)
vector<pair<string, int>> class_list; // 定义一个存放pair的vector
class_list.push_back(stu1); // 添加stu1
class_list.push_back(stu2); // 添加stu2
class_list.push_back(stu3); // 添加stu3
class_list.push_back(make_pair("David", 78)); // 直接创建并添加第四个
// 3. 遍历并打印原始名单
cout << "=== 原始班级名单 ===" << endl;
for (const auto& p : class_list) { // const auto&避免拷贝,提高效率
cout << p.first << " : " << p.second << "分" << endl;
}
// 4. 按分数(second)升序排序
sort(class_list.begin(), class_list.end(), // 排序范围
[](const pair<string,int>& a, const pair<string,int>& b) {
return a.second < b.second; // 按分数从小到大
});
cout << "\n=== 按分数升序排序后 ===" << endl;
for (const auto& p : class_list) {
cout << p.first << " : " << p.second << "分" << endl;
}
// 5. 查找分数为92的同学
int target_score = 92; // 要查找的目标分数
auto it = find_if(class_list.begin(), class_list.end(), // 按条件查找
[target_score](const pair<string,int>& p) {
return p.second == target_score;
});
if (it != class_list.end()) {
cout << "\n找到分数为 " << target_score << " 的同学: " << it->first << endl;
} else {
cout << "\n没有找到分数为 " << target_score << " 的同学。" << endl;
}
// 6. 使用结构化绑定(C++17)再次遍历
cout << "\n=== 使用结构化绑定的现代写法 ===" << endl;
for (const auto& [name, score] : class_list) { // 直接解包成name和score
cout << name << " -> " << score << "分" << endl;
}
return 0;
}
代码运行结果(可能因输入顺序不同略有差异):
=== 原始班级名单 ===
Alice : 95分
Bob : 88分
Cathy : 92分
David : 78分
=== 按分数升序排序后 ===
David : 78分
Bob : 88分
Cathy : 92分
Alice : 95分
找到分数为 92 的同学: Cathy
=== 使用结构化绑定的现代写法 ===
David -> 78分
Bob -> 88分
Cathy -> 92分
Alice -> 95分
5. Python中的等效实现:元组与namedtuple
Python标准库中没有直接叫 pair 的类,但我们可以用 元组(tuple) 来达到同样的效果。元组就是不可变的序列,也可以包含两个或多个元素,而且Python的元组支持解包,使用起来更方便。此外,也可以用 collections.namedtuple 命名元组,使代码更具可读性。
# Python 使用元组实现 pair 功能
from typing import List, Tuple
from operator import itemgetter
# 1. 创建 pair 的几种方式(用元组)
student1 = ("Alice", 95) # 直接写括号,第一个元素是名字,第二个是分数
student2 = tuple(["Bob", 88]) # 用tuple函数将列表转为元组
student3 = ("Cathy", 92)
# 访问元素:下标从0开始,t[0]相当于first,t[1]相当于second
print(f"学生1: {student1[0]} - {student1[1]}分")
# 2. 存入列表
class_list: List[Tuple[str, int]] = [] # 定义一个空列表,元素类型是(str,int)元组
class_list.append(student1) # 添加第一个学生
class_list.append(student2) # 添加第二个学生
class_list.append(student3) # 添加第三个学生
class_list.append(("David", 78)) # 直接添加新元组
# 3. 遍历并打印
print("\n全班名单(姓名-分数):")
for p in class_list:
print(f"{p[0]} : {p[1]}分")
# 4. 按分数排序(升序)
# 使用 sorted 并指定 key=itemgetter(1) 按第二个元素排序
sorted_list = sorted(class_list, key=itemgetter(1))
print("\n按分数升序排序后:")
for name, score in sorted_list: # 直接解包元组,name=p[0], score=p[1]
print(f"{name} : {score}分")
# 5. 查找某个成绩是否存在
target = 92
# 用列表推导式查找分数等于target的同学
result = [name for name, score in sorted_list if score == target]
if result:
print(f"\n找到分数为 {target} 的同学: {result[0]}")
else:
print(f"\n没有找到分数为 {target} 的同学。")
# 6. 使用 namedtuple(可选,增加可读性)
from collections import namedtuple
Student = namedtuple('Student', ['name', 'score']) # 创建一个Student类型
s1 = Student('Alice', 95)
s2 = Student('Bob', 88)
# 访问直接使用属性名,不再用下标
print(f"\nnamedtuple示例: {s1.name} - {s1.score}分")
# 可以将namedtuple放入列表操作
named_list = [s1, s2, Student('Cathy', 92)]
named_list.sort(key=lambda x: x.score) # 按分数排序
for s in named_list:
print(f"{s.name} : {s.score}分")
Python解释:
- 元组
(name, score)就是一个不可变的pair。 - 使用
itemgetter(1)可以方便地按第二个元素排序。 - 列表推导式
[name for ... if ...]完成查找。 namedtuple是Python标准库中的一个高级工具,允许像访问成员变量一样访问元组元素,代码更易读。
6. 总结与思考
C++ pair 要点回顾
- pair是一个模板类,需要包含头文件
<utility>,但很多STL容器(如map)已经间接包含了它。 - 访问成员用
.first和.second,注意不是下标。 - pair支持比较操作(
==,<等),比较规则是先比first,再比second,这在排序时非常有用。 - 可以用
make_pair简化创建,也支持花括号初始化(C++11起)。 - 在C++17及以上,可以使用结构化绑定
auto [a,b] = p;解包。
Python 等效总结
- 元组
(a,b)是Python中最简单的pair。 - 下标从0开始:
t[0]对应first,t[1]对应second。 - 元组是不可变的,如果需要修改内容,可以用列表
[a,b]代替。 - 当需要更语义化的访问时,推荐使用
namedtuple。
注意事项
- 在C++中,当pair作为容器元素且需要排序时,自定义比较函数要小心:如果缺省比较,则按字典序(先first后second)。
- Python的元组排序也是按字典序,所以如果第一个元素相同,才会比较第二个。这与C++的pair默认排序完全一致。
- 不要混淆“pair”和“map”:map的每个元素就是一个 pair,但 map本身是一个容器,而 pair只是一个存储两个值的对象。
- pair常用于函数返回多个值(比如返回一个错误码和结果),或者作为 map 的键值对。
小练习
- 写一个程序,输入5个学生的姓名和语文成绩,用pair存储并输出最高分和最低分的学生信息。
- 思考:如果希望按成绩降序排序,C++和Python分别怎么做?(提示:C++改为
return a.second > b.second,Python用reverse=True或取负值) - 尝试用pair来存储一个二维坐标点(x, y),并计算两点之间的距离。
通过pair的学习,你已经掌握了“成对数据”的打包方法。下一个知识点我们将学习更强大的“三合一”工具:tuple元组(C++的std::tuple和Python的tuple),它可以存储任意多个不同类型的数据。
例题精讲
在C++中,关于pair对组的定义,以下哪种方式是正确的?
pair对组中的两个元素类型必须相同。
以下代码创建了一个pair对象并输出其第一个元素,请填空:
#include <iostream>
#include <utility>
using namespace std;
int main() {
pair<string, int> student = ___({"Tom", 18});
cout << student.first;
return 0;
}已知pair<int, double> p = {3, 2.5};,下列哪个表达式可以正确访问p中的第二个元素?
下面的代码使用pair向map容器中插入一个键值对,请将空白处补充完整:
#include <iostream>
#include <map>
#include <utility>
using namespace std;
int main() {
map<string, int> score;
score.___("Alice", 95);
cout << score["Alice"];
return 0;
}