pair(c++)

1. 基本概念

什么是 std::pair

std::pair 是一个 模板类,专门用来存储两个数据项(firstsecond),这些数据项可以是相同类型,也可以是不同类型。


2. pair 的底层设计

模板声明

pair 的模板声明如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
template<class T1, class T2>
struct pair {
T1 first; // 第一个元素
T2 second; // 第二个元素

// 构造函数
constexpr pair();
constexpr pair(const T1& a, const T2& b);
template<class U1, class U2>
constexpr pair(U1&& a, U2&& b);

// 拷贝和移动构造
constexpr pair(const pair& p);
constexpr pair(pair&& p) noexcept;

// 赋值运算符
pair& operator=(const pair& p);
pair& operator=(pair&& p) noexcept;

// 交换
void swap(pair& p) noexcept;

// 比较运算符
bool operator==(const pair& p) const;
bool operator!=(const pair& p) const;
bool operator<(const pair& p) const;
bool operator<=(const pair& p) const;
bool operator>(const pair& p) const;
bool operator>=(const pair& p) const;
};

从以上声明可以看出,pair 提供了:

  1. 基本的构造函数(默认构造、拷贝构造、移动构造)。
  2. 赋值运算符和交换方法。
  3. 比较运算符用于比较两个 pair 对象。

3. pair 的创建与初始化

常见的初始化方式

以下是创建 pair 的常见方法:

(1)显式构造

直接通过构造函数传入两个值:

1
std::pair<int, std::string> p1(10, "Hello");

(2)std::make_pair

使用 make_pair 辅助函数,类型可以自动推导:

1
auto p2 = std::make_pair(20, "World");

(3)初始化列表

{} 初始化:

1
std::pair<int, double> p3 = {30, 3.14};

4. pair 的常用方法与操作

(1)访问成员变量

pair 提供了两个公开成员变量 firstsecond,可以直接访问:

1
2
std::pair<int, std::string> p(42, "Answer");
std::cout << "First: " << p.first << ", Second: " << p.second << std::endl;

(2)交换两个 pair

交换两个 pair 的内容:

1
2
3
std::pair<int, std::string> p1(1, "A"), p2(2, "B");
p1.swap(p2); // 或 std::swap(p1, p2);
std::cout << p1.first << ", " << p1.second << std::endl;

(3)比较运算符

规则:

  • 字典序 比较。
  • 先比较 first,如果相等,则比较 second

示例:

1
2
3
4
std::pair<int, int> p1(1, 2), p2(1, 3);
if (p1 < p2) {
std::cout << "p1 is less than p2" << std::endl;
}

(4)std::make_pair

make_pair 是一个辅助函数,自动推导 pair 的模板参数:

1
2
auto p = std::make_pair(10, "Test");
std::cout << p.first << ", " << p.second << std::endl;

5. pair 的应用场景

(1)与关联容器结合

std::map 插入数据

pair 常用作 std::map 的键值对:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <map>
#include <iostream>

int main() {
std::map<int, std::string> myMap;

// 插入 pair
myMap.insert(std::make_pair(1, "One"));
myMap[2] = "Two";

// 遍历
for (const auto &p : myMap) {
std::cout << p.first << ": " << p.second << std::endl;
}

return 0;
}

std::set 结合 pair

可以用 pair 构成复合键:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <set>
#include <iostream>

int main() {
std::set<std::pair<int, int>> s;

s.insert({1, 2});
s.insert({2, 3});

for (const auto &p : s) {
std::cout << "(" << p.first << ", " << p.second << ")" << std::endl;
}

return 0;
}

(2)作为函数的返回值

当需要返回两个值时,pair 是一个非常好的选择:

1
2
3
4
5
6
7
8
9
10
11
12
#include <utility>
#include <iostream>

std::pair<int, double> getValues() {
return {42, 3.14};
}

int main() {
auto result = getValues();
std::cout << "Int: " << result.first << ", Double: " << result.second << std::endl;
return 0;
}

(3)作为临时数据结构

pair 轻量、简单,适合作为临时存储数据的结构:

1
2
3
std::vector<std::pair<std::string, int>> students;
students.push_back({"Alice", 90});
students.push_back({"Bob", 85});

6. 进阶技巧

使用 pair 创建多层嵌套

pair 支持嵌套,可以存储复杂结构:

1
2
std::pair<int, std::pair<string, double>> nested(1, {"Test", 3.14});
cout << nested.first << ", " << nested.second.first << ", " << nested.second.second << endl;

7. 注意事项

(1)是否适合长期存储

pair 通常用作临时的简单数据结构,如果需要清晰表达含义或长期存储,推荐使用 struct

1
2
3
4
struct Student {
std::string name;
int age;
};

(2)pair 的比较规则

比较是字典序,且不能直接比较不同类型的 pair


总结:

pair 是一个轻量级的、非常实用的 C++ 模板类,适用于简单的键值对存储、临时数据传递、函数返回值等场景。通过结合 std::make_pair 和 STL 容器,可以极大提高开发效率。

8. pair 的内存布局与性能

(1)内存布局

std::pair 是一个轻量级的数据结构,由两个成员组成:firstsecond。这两个成员变量的类型分别是 T1T2,因此它的内存布局是:

1
2
3
4
struct pair {
T1 first; // 第一个元素
T2 second; // 第二个元素
};
  • 内存对齐:由于 pair 是一个简单的结构体,它会遵循内存对齐规则,确保每个元素存储在合适的内存地址上。这可能会导致额外的内存开销,特别是当 T1T2 的类型大小差异较大时。
  • 内存占用:如果 T1T2 是相同大小的类型(如两个 int),那么 pair 只会占用两倍于一个元素的空间。如果它们是不同类型(例如 intdouble),则内存占用可能会有额外的填充,具体取决于系统的对齐规则。

(2)性能优化

  • 构造和拷贝效率pair 的构造非常高效,因为它只是简单地将两个元素放入一个结构中。如果 T1T2 是基本数据类型(如 intchar 等),那么它的拷贝和移动成本非常低。
  • 使用 std::make_pair 的好处:使用 std::make_pair 可以减少模板参数的显式指定,有助于提高代码的可读性。编译器能够推导出正确的类型,并确保代码简洁。

(3)与其他标准库容器结合的性能

  • 在与其他容器(如 std::vectorstd::map)结合使用时,pair 不会带来显著的性能瓶颈。相反,它的轻量级设计使得它非常适合用于这些容器中存储键值对或其他复合数据。

9. pair 与 C++11 及以后的特性

(1)pairstd::tuple 的比较

在某些情况下,std::pairstd::tuple 都可以用来存储多个值。那么它们之间有何异同呢?

  • **std::pair**:只能存储两个元素,且这两个元素的类型在编译时确定。
  • **std::tuple**:可以存储任意数量的元素,并且这些元素的类型也可以是不同的。tuple 可以更灵活,但在性能上稍逊色于 pair

示例:

1
2
3
4
std::pair<int, double> p1(10, 3.14);
std::tuple<int, double, std::string> t1(10, 3.14, "Hello");

std::cout << std::get<0>(t1) << ", " << std::get<1>(t1) << ", " << std::get<2>(t1) << std::endl;

在需要返回多个值或者存储不同数量的元素时,tuple 是更合适的选择,而当只涉及两个值时,pair 更加简洁高效。

(2)结构化绑定(C++17)

C++17 引入了结构化绑定(structured bindings),这使得从 pair 中解构元素变得非常简单:

1
2
3
4
5
std::pair<int, std::string> p1(10, "Hello");

// 结构化绑定
auto [num, str] = p1;
std::cout << "Number: " << num << ", String: " << str << std::endl;

这种方式使得代码更加简洁、易读,也更符合直觉。通过结构化绑定,pair 的使用变得更为方便,尤其在遍历容器时。

(3)std::tiepair 结合

std::tie 是一个非常强大的工具,可以将多个变量与一个 pair(或 tuple)进行绑定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <tuple>
#include <iostream>

std::pair<int, double> getPair() {
return {1, 3.14};
}

int main() {
int a;
double b;
std::tie(a, b) = getPair();
std::cout << a << ", " << b << std::endl;
return 0;
}

这种方式特别有用,尤其在函数返回多个值时,std::tie 使得 pairtuple 的解构变得更加简洁。


10. pair 的应用限制与注意事项

(1)类型不兼容时的使用

pair 强制要求 firstsecond 的类型在编译时确定。如果需要存储不同类型的元素,但它们的类型在运行时才能确定,那么 pair 并不是最合适的选择。在这种情况下,std::variantstd::any 等类型可能更加合适。

示例:

1
2
3
std::pair<int, std::string> p1(10, "Hello");
// 编译失败,如果尝试不同类型的组合时类型不匹配
// std::pair<int, std::string> p2("Hello", 10); // 错误

(2)无名结构体

在某些情况下,我们希望存储的数据有明确的字段名。尽管 pair 提供了 firstsecond,但这些名字并不总是能够准确描述存储的内容。如果需要更加清晰的字段命名,structclass 可能更为合适。

示例:

1
2
3
4
5
6
7
struct Student {
std::string name;
int age;
};

Student s = {"Alice", 20};
std::cout << "Name: " << s.name << ", Age: " << s.age << std::endl;

(3)pair 和多线程

在多线程环境下,pair 是一个轻量级的数据结构,并且可以方便地用于共享数据,但在多个线程访问时需要确保线程安全。对于常见的多线程场景,通常会结合使用锁(如 std::mutex)来保护 pair 的成员,避免竞争条件。


11. 总结

  • std::pair 是一个非常简单且高效的数据结构,适用于存储两个相关的值,尤其是在需要返回多个值、或者使用 map 等容器时非常实用。
  • std::make_pair 是一个简化创建 pair 的工厂函数,能够自动推导类型。
  • pair 在内存布局上非常简单,但需要注意其内存对齐问题以及与其他容器的结合使用。
  • C++11 引入的 std::tie 和 C++17 的结构化绑定使得 pair 的使用更加简洁。
  • 在需要返回多个值时,pair 是一个非常有效的工具,但对于更多元素或复杂的数据结构,std::tuple 更为合适。