C++ tuple元组、pair 比较、lower_bound和upper_bound
一、tuple元组
1.1、简介
C++11 标准新引入了一种类模板,命名为 tuple(元组)。tuple 最大的特点是:实例化的对象可以存储任意数量、任意类型的数据。
1.2、初始化
tuple 本质是一个以可变模板参数定义的类模板,它定义在
#include <tuple>
实例化 tuple 模板类对象常用的方法有两种,一种是借助该类的构造函数,另一种是借助 make_tuple() 函数。
- 类的构造函数
tuple 模板类提供有很多构造函数,包括:
1) 默认构造函数
constexpr tuple();
2) 拷贝构造函数
tuple (const tuple& tpl);
3) 移动构造函数
tuple (tuple&& tpl);
4) 隐式类型转换构造函数
template <class... UTypes>
tuple (const tuple<UTypes...>& tpl); //左值方式
template <class... UTypes>
tuple (tuple<UTypes...>&& tpl); //右值方式
5) 支持初始化列表的构造函数
explicit tuple (const Types&... elems); //左值方式
template <class... UTypes>
explicit tuple (UTypes&&... elems); //右值方式
6) 将pair对象转换为tuple对象
template <class U1, class U2>
tuple (const pair<U1,U2>& pr); //左值方式
template <class U1, class U2>
tuple (pair<U1,U2>&& pr); //右值方式
举个例子:
#include <iostream> // std::cout
#include <tuple> // std::tuple
using std::tuple;
int main()
{
std::tuple<int, char> first; // 1) first{}
std::tuple<int, char> second(first); // 2) second{}
std::tuple<int, char> third(std::make_tuple(20, 'b')); // 3) third{20,'b'}
std::tuple<long, char> fourth(third); // 4)的左值方式, fourth{20,'b'}
std::tuple<int, char> fifth(10, 'a'); // 5)的右值方式, fifth{10.'a'}
std::tuple<int, char> sixth(std::make_pair(30, 'c')); // 6)的右值方式, sixth{30,''c}
return 0;
}
- make_tuple()函数
上面程序中,我们已经用到了 make_tuple() 函数,它以模板的形式定义在头文件中,功能是创建一个 tuple 右值对象(或者临时对象)。
对于 make_tuple() 函数创建了 tuple 对象,我们可以上面程序中那样作为移动构造函数的参数,也可以这样用:
auto first = std::make_tuple (10,'a'); // tuple < int, char >
const int a = 0; int b[3];
auto second = std::make_tuple (a,b); // tuple < int, int* >
程序中分别创建了 first 和 second 两个 tuple 对象,它们的类型可以直接用 auto 表示。
1.3、访问
- 用get标准库模板进行访问其元素内容
- 用tuple_size访问其元素个数
- 用tuple_element访问其元素类型
#include<iostream>
#include<string>
#include<tuple>
using namespace std;
int main()
{
auto t1 = make_tuple(1, 2, 3, 4.4);
cout << "第1个元素:" << get<0>(t1) << endl;//和数组一样从0开始计
cout << "第2个元素:" << get<1>(t1) << endl;
cout << "第3个元素:" << get<2>(t1) << endl;
cout << "第4个元素:" << get<3>(t1) << endl;
typedef tuple<int, float, string>TupleType;
cout << tuple_size<TupleType>::value << endl;
tuple_element<1, TupleType>::type fl = 1.0;
return 0;
}
输出结果:
第1个元素:1
第2个元素:2
第3个元素:3
第4个元素:4.4
3
二、pair比较
2.1 pair的头文件
#include <utility>
2.2 pair的比较方式
pair的比较方式和元素的类型有关:
(1)当pair.first可以进行小于比较时,pair可以进行小于比较;
(2)当pair.first小于另一个pair的first时,pair小于另一个pair;
(3)当pair.first与另一个pair的first相等且pair.second小于另一个pair的second时,pair小于另一个pair。
因此,我们可以看到,在C++中比较对象,优先比较pair.first,如果相等再比较pair.second。
2.3 pair比较器的声明
当我们需要指定一组比较器来对pair对象进行排序或查找操作时,可以使用STL提供的std::pair_compare
下面是一个声明比较器的例子,针对以pair<int, string>为元素的vector进行按照第一个元素降序排列:
#include <vector>
#include <utility>
#include <algorithm>
#include <iostream>
bool compare(const std::pair<int, std::string>& p1, const std::pair<int, std::string>& p2)
{
return p1.first > p2.first;
}
int main()
{
std::vector<std::pair<int, std::string>> v{ {3, "hello"}, {1, "world"}, {2, "bye"} };
std::sort(v.begin(), v.end(), compare);
for (auto& i : v)
std::cout << i.first << " " << i.second << std::endl;
return 0;
}
在声明compare函数时,我们需要按照STL的Compare规范,在参数列表中声明两个输入参数:指向pair的指针或引用。函数体中,我们根据排列需求返回true或false。
上述代码中,我们将pair的第一个元素作为排序关键字,使用std::sort()对vector进行按照第一个元素降序排列,最终输出结果如下:
3 hello
2 bye
1 world
2.4 不同STL容器中pair的比较方法
在不同的STL容器中,pair的比较方法稍有不同。下面以set和map为例说明:
- set
在set中,要按照一定的规则对pair进行比较,我们可以在声明set时,指定一个比较器来进行排序。默认情况下,set会对pair进行键值比较,以pair中的第一个元素作为键值。
#include <set>
#include <utility>
#include <iostream>
int main()
{
std::set<std::pair<int, std::string>> s;
s.insert({ 3, "hello" });
s.insert({ 1, "world" });
s.insert({ 2, "bye" });
for (auto& i : s)
std::cout << i.first << " " << i.second << std::endl;
return 0;
}
在上述代码中,我们声明了一个set,并将三个pair对象插入到其中。由于默认情况下set按照pair的第一个元素进行排序,因此输出结果如下
1 world
2 bye
3 hello
如果我们需要按照pair的第二个元素进行排序,可以自定义比较器:
#include<set>
#include<utility>
#include<algorithm>
#include <iostream>
struct pair_comparer
{
bool operator() (const std::pair<int, std::string>& p1, const std::pair<int, std::string>& p2) const
{
return p1.second > p2.second;
}
};
int main()
{
std::set<std::pair<int, std::string>, pair_comparer> s;
s.insert({ 3, "hello" });
s.insert({ 1, "world" });
s.insert({ 2, "bye" });
for (auto& i : s)
std::cout << i.first << " " << i.second << std::endl;
return 0;
}
在上述代码中,我们自定义了一个pair_comparer结构体,并在其中定义了一个成员函数operator(),实现按照pair的第二个元素进行排序。在声明set时,我们将这个结构体作为第二个模板参数传递进去,标记该set需要按照pair_comparer进行排序。最终输出结果如下:
2 bye
3 hello
1 world
- map
与set相似,我们也可以对map中的pair进行按照键值排序。默认情况下,map以pair的第一个元素作为键值,因此不需要特定的比较器。
#include <map>
#include <utility>
#include <iostream>
int main()
{
std::map<std::pair<int, std::string>, double> m;
m.insert({ {3, "hello"}, 1.2 });
m.insert({ {1, "world"}, 3.4 });
m.insert({ {2, "bye"}, 5.6 });
for (auto& i : m)
std::cout << i.first.first << "," << i.first.second << "," << i.second << std::endl;
return 0;
}
在上述代码中,我们声明了一个map,并将三个pair对象作为键值对插入到其中。由于默认情况下map按照pair的第一个元素进行排序,因此输出结果如下:
1,world,3.4
2,bye,5.6
3,hello,1.2
如果我们需要按照pair的第二个元素进行排序,可以使用一个自定义的比较器:
#include <map>
#include <utility>
#include <iostream>
struct pair_comparer
{
bool operator() (const std::pair<int, std::string>& p1, const std::pair<int, std::string>& p2) const
{
return p1.second > p2.second;
}
};
int main()
{
std::map<std::pair<int, std::string>, double, pair_comparer> m;
m.insert({ {3, "hello"}, 1.2 });
m.insert({ {1, "world"}, 3.4 });
m.insert({ {2, "bye"}, 5.6 });
for(auto& i : m)
std::cout << i.first.first << "," << i.first.second << "," << i.second << std::endl;
return 0;
}
在上述代码中,我们自定义了一个pair_comparer结构体,并在其中定义了一个成员函数operator(),实现按照pair的第二个元素进行排序。在声明map时,我们将这个结构体作为第三个模板参数传递进去,标记该map需要按照pair_comparer进行排序。最终输出结果如下:
2,bye,5.6
1,world,3.4
3,hello,1.2
三、lower_bound和upper_bound
3.1 简介
find()、find_if()、search() 等函数的底层实现都采用的是顺序查找(逐个遍历)的方式,在某些场景中的执行效率并不高。
当指定区域内的数据处于有序状态时,如果想查找某个目标元素,更推荐使用二分查找的方法(相比顺序查找,二分查找的执行效率更高)。因此 lower_bound和upper_bound使用的前提是 **当前特定区域内数据处于有序状态**。
3.2 lower_bound()函数
lower_bound() 函数定义在
//在 [first, last) 区域内查找不小于 val 的元素
ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last,
const T& val);
//在 [first, last) 区域内查找第一个不符合 comp 规则的元素
ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last,
const T& val, Compare comp);
其中,first 和 last 都为正向迭代器,[first, last) 用于指定函数的作用范围;val 用于指定目标元素;comp 用于自定义比较规则,此参数可以接收一个包含 2 个形参(第二个形参值始终为 val)且返回值为 bool 类型的函数,可以是普通函数,也可以是函数对象。
实际上,第一种语法格式也设定有比较规则,只不过此规则无法改变,即使用 < 小于号比较 [first, last) 区域内某些元素和 val 的大小,直至找到一个不小于 val 的元素。这也意味着,如果使用第一种语法格式,则 [first,last) 范围的元素类型必须支持 < 运算符。
此外,该函数还会返回一个正向迭代器,当查找成功时,迭代器指向找到的元素;反之,如果查找失败,迭代器的指向和 last 迭代器相同。
再次强调,该函数仅适用于已排好序的序列。所谓“已排好序”,指的是 [first, last) 区域内所有令 element<val(或者 comp(element,val),其中 element 为指定范围内的元素)成立的元素都位于不成立元素的前面。
- lower_bound() 示例
#include <iostream> // std::cout
#include <algorithm> // std::lower_bound
#include <vector> // std::vector
#include <iostream>
using namespace std;
//以普通函数的方式定义查找规则
bool mycomp(int i, int j) { return i > j; }
//以函数对象的形式定义查找规则
class mycomp2 {
public:
bool operator()(const int& i, const int& j) {
cout << "=== i: " << i << " === j: " << j << "\n" ;
return i > j;
}
};
int main() {
int a[5] = { 1,2,3,4,5 };
//从 a 数组中找到第一个不小于 3 的元素
int* p = lower_bound(a, a + 5, 3);
cout << "*p = " << *p << endl;
vector<int> myvector{ 4,5,3,1,2 };
//根据 mycomp2 规则,从 myvector 容器中找到第一个违背 mycomp2 规则的元素
vector<int>::iterator iter = lower_bound(myvector.begin(), myvector.end(), 3, mycomp2());
cout << "*iter = " << *iter;
return 0;
}
- 底层实现
template <class ForwardIterator, class T>
ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last, const T& val)
{
ForwardIterator it;
iterator_traits<ForwardIterator>::difference_type count, step;
count = distance(first,last);
while (count>0)
{
it = first; step=count/2; advance (it,step);
if (*it<val) { //或者 if (comp(*it,val)),对应第 2 种语法格式
first=++it;
count-=step+1;
}
else count=step;
}
return first;
}
3.3 upper_bound()函数
upper_bound() 函数定义在
//查找[first, last)区域中第一个大于 val 的元素。
ForwardIterator upper_bound (ForwardIterator first, ForwardIterator last,
const T& val);
//查找[first, last)区域中第一个不符合 comp 规则的元素
ForwardIterator upper_bound (ForwardIterator first, ForwardIterator last,
const T& val, Compare comp);
其中,first 和 last 都为正向迭代器,[first, last) 用于指定该函数的作用范围;val 用于执行目标值;comp 作用自定义查找规则,此参数可接收一个包含 2 个形参(第一个形参值始终为 val)且返回值为 bool 类型的函数,可以是普通函数,也可以是函数对象。
实际上,第一种语法格式也设定有比较规则,即使用 < 小于号比较 [first, last) 区域内某些元素和 val 的大小,
直至找到一个大于 val 的元素,只不过此规则无法改变。这也意味着,如果使用第一种语法格式,则 [first,last) 范围的元素类型必须支持 < 运算符。
同时,该函数会返回一个正向迭代器,当查找成功时,迭代器指向找到的元素;反之,如果查找失败,迭代器的指向和 last 迭代器相同。
另外,由于 upper_bound() 底层实现采用的是二分查找的方式,因此该函数仅适用于“已排好序”的序列。注意,这里所说的“已排好序”,并不要求数据完全按照某个排序规则进行升序或降序排序,而仅仅要求 [first, last) 区域内所有令 element<val(或者 comp(val, element)成立的元素都位于不成立元素的前面(其中 element 为指定范围内的元素)。
- upper_bound()示例
#include <iostream> // std::cout
#include <algorithm> // std::upper_bound
#include <vector> // std::vector
#include <iostream>
using namespace std;
//以普通函数的方式定义查找规则
bool mycomp(int i, int j) { return i > j; }
//以函数对象的形式定义查找规则
class mycomp2 {
public:
bool operator()(const int& i, const int& j) {
cout << "=== i: " << i << " === j: " << j << "\n";
return i > j;
}
};
int main() {
int a[5] = { 1,2,3,4,5 };
//从 a 数组中找到第一个大于 3 的元素
int* p = upper_bound(a, a + 5, 3);
cout << "*p = " << *p << endl;
vector<int> myvector{ 4,5,3,1,2 };
//根据 mycomp2 规则,从 myvector 容器中找到第一个违背 mycomp2 规则的元素
vector<int>::iterator iter = upper_bound(myvector.begin(), myvector.end(), 3, mycomp2());
cout << "*iter = " << *iter;
return 0;
}
此程序中演示了 upper_bound() 函数的 2 种适用场景,其中 a[5] 数组中存储的为升序序列;而 myvector 容器中存储的序列虽然整体是乱序的,但对于目标元素 3 来说,所有符合 mycomp2(3, element) 规则的元素都位于其左侧,不符合的元素都位于其右侧,因此 upper_bound() 函数仍可正常执行。
- 底层实现
template <class ForwardIterator, class T>
ForwardIterator upper_bound (ForwardIterator first, ForwardIterator last, const T& val)
{
ForwardIterator it;
iterator_traits<ForwardIterator>::difference_type count, step;
count = std::distance(first,last);
while (count>0)
{
it = first; step=count/2; std::advance (it,step);
if (!(val<*it)) // 或者 if (!comp(val,*it)), 对应第二种语法格式
{ first=++it; count-=step+1; }
else count=step;
}
return first;
}
资料:
https://deepinout.com/cpp/cpp-questions/g_how-to-declare-comparator-for-set-of-pair-in-cpp.html
https://c.biancheng.net/view/8600.html
https://blog.csdn.net/qq_37529913/article/details/125139815
https://blog.csdn.net/m0_51913750/article/details/128368665