从零开始的简单函数式C++(一)pair
STL 中的主要工具
STL 中不仅提供了很多容器,同时也提供了两个基本的数据单位供大家使用。之所以叫数据单位,是因为本质上
pair
和
tuple
是两个通用的数据类型模板。更简单的讲,关键在于两点,其一是通用,也就是可以随用随写,其二是模板,也就是他的具体实现方法。
包装两个数据的元组
pair
每个人都会遇到如此简单的问题,数据的绑定。比如你有一个学习小组,每个人的姓名和他的学习能力这两个数据构成了这个小组里面所有成员的实体。假设姓名用
string
类型,学习能力用
int
类型,最传统的方法就是构造一个只有数据的结构体。而现在有了
pair
之后你只需要直接用
pair<string, int>
就可以代替前面冗长的结构体声明,而且使用起来也十分简单。具体而言请参考如以下代码所示。
#include <iostream>
#include <string>
#include <utility>
#include <functional>
int main()
std::string names[3] = { "alice", "bob", "carl" };
int score[3] = { 5, 6, 7 };
// build a pair from string and int
std::pair<std::string, int> p0 = std::make_pair(names[0], score[0]);
std::cout << "The value of p0 is "
<< "(" << p0.first << ", " << p0.second << ")\n";
// => The value of p0 is (alice, 5)
// build a pair from string and std::ref(int)
auto p1 = std::make_pair(names[1], std::ref(score[1]));
std::cout << "The value of p1 is "
<< "(" << p1.first << ", " << p1.second << ")\n";
// => The value of p1 is (bob, 6)
score[1] += 10;
std::cout << "After score[1] += 10, the value of p1 is "
<< "(" << p1.first << ", " << p1.second << ")\n";
// => After score[1] += 10, the value of p1 is (bob, 16)
p1.second += 100;
std::cout << "After p1.second += 100, the value of p1 is "
<< "(" << p1.first << ", " << p1.second << "), "
<< "while score[1] is " << score[1] << "\n";
// => After p1.second += 100, the value of p1 is (bob, 116), while score[1] is 116
names[1] += "bi";
std::cout << "After names[1] += \"bi\", the value of p1 is "
<< "(" << p1.first << ", " << p1.second << "), "
<< "while names[1] is " << names[1] << "\n";
// => After names[1] += "bi", the value of p1 is (bob, 116), while names[1] is bobbi
return 0;
// filename: ch1-pair-example-1.cpp
// compile this> g++ ch1-pair-example-1.cpp -o ch1-pair-example-1.exe
在这份代码当中,有以下几点需要额外注明:
-
第 3-4 行添加了必要的头文件,其中
std::pair<>
以及std::make_pair()
在头文件<utility>
当中,std::ref()
在头文件<functional>
当中; -
第 8-9 行预定义了一些数据,第 12 行显式的定义了一个元组
p0
,并在第 13-14 行分别使用p0.first
和p0.second
选择其数据并输出; -
第 18 行的前半段利用编译时的类型推导功能同样定义了一个元组
p1
,关键字auto
表示使用类型推导; -
第 18 行的后半段用
std::ref()
制造了一个score[1]
的引用类型,可以注意到在第 22 行对其进行修改后,元组p1
的值也发生了改变,而在第 26 行直接对p1.second
进行修改之后,原始数据score[1]
也随之发生了改变; -
第 31 行虽然我们对
names[1]
的值进行了修改,但是并没有影响到p1
的内部取值。
从上面这个代码中,我们不难推测出一些关于
pair
简单的特性。
-
pair
是一个仅包含 两个元素 的一个模板类,使用时需要通过 显式 的声明或者 隐式 的推导对其进行模板特化,从而制造出我们需要的特定类型,换言之,任何的类型推导错误都会导致其出现编译时错误; -
pair
只包含两个数据成员,分别是first
与second
,其类型和数据值按照声明时的 代码书写顺序 依次对应; -
auto
在进行类型推导时会默认使用 基础类型 ,而非指针与引用,只有在初始化时使用了显式的引用(如std::ref()
)时才会将类型变更为引用类型。
在有以上思考的基础上,我们就不难阅读
pair
的定义声明了。
namespace std{
template<class T1, class T2>
struct pair
using first_type = T1;
using second_type = T2;
T1 first;
T2 second;
pair(const pair&) = default;
pair(pair&&) = default;
constexpr pair();
constexpr pair(const T1& x, const T2& y);
template<class U, class V> constexpr pair(U&& x, V&& y);
template<class U, class V> constexpr pair(const pair<U, V>& p);
template<class U, class V> constexpr pair(pair<U, V>&& p);
pair& operator=(const pair& p);
template<class U, class V> pair& operator=(const pair<U, V>& p);
pair& operator=(pair&& p) noexcept();
template<class U, class V> pair& operator=(pair<U, V>&& p);
void swap(pair& p) noexcept();
// filename: ch1-pair-defination-simplified.cpp
借助这份定义我们进一步的了解一下
pair
内建的其他方法。
#include <iostream>
#include <string>
#include <utility>
#include <functional>
using namespace std;
template<class T1, class T2>
ostream& operator<<(ostream &out, const pair<T1, T2> &_)
return out << "(" << _.first << ", " << _.second << ")";
void display_separator() { cout << "--------" << endl; }
int main()
pair<int, double> p1; // => (0, 0)
cout << "Default constructor: " << p1 << endl;
pair<int, double> p2(65, 1.1); // => (65, 1.1)
cout << "Initialized with two values: " << p2 << endl;
pair<int, double> p3(p2); // => (65, 1.1)
cout << "Copy constructor: " << p3 << endl;
pair<char, int> p4(p2); // => (A, 1)
cout << "Copy then convert: " << p4 << endl;
display_separator();
pair<char, int> p5 = make_pair('B', 2);
cout << "Before swap: p4 is " << p4 << " , p5 is " << p5 << endl;
// => Before swap: p4 is (A, 1) , p5 is (B, 2)
p5.swap(p4);
cout << "After swap: p4 is " << p4 << " , p5 is " << p5 << endl;
// => After swap: p4 is (B, 2) , p5 is (A, 1)
display_separator();
pair<char, int> p6 = make_pair('B', 1);
pair<char, int> p7 = make_pair('A', 2);
cout << "p4 is " << p4 << " , p5 is " << p5 << ", "
<< "p6 is " << p6 << " , p7 is " << p7 << endl;
// => p4 is (B, 2) , p5 is (A, 1), p6 is (B, 1) , p7 is (A, 2)
cout << std::boolalpha;
cout << (p4 == p5) << endl; // => false
cout << (p4 != p5) << endl; // => true
cout << (p5 < p7) << endl; // => true
cout << (p4 < p6) << endl; // => false
cout << (p5 < p6) << endl; // => true
cout << (p6 < p7) << endl; // => false