元组简介
C++11 标准新引入了一种类模板,命名为 tuple(中文可直译为元组)。tuple 最大的特点是:实例化的对象可以存储任意数量、任意类型的数据。
tuple 的应用场景很广泛,例如当需要存储多个不同类型的元素时,可以使用 tuple;当函数需要返回多个数据时,可以将这些数据存储在 tuple 中,函数只需返回一个 tuple 对象即可。
tuple对象的创建
tuple 本质是一个以可变模板参数定义的类模板,它定义在 头文件并位于 std 命名空间中。因此要想使用 tuple 类模板,程序中需要首先引入以下代码:
#include <tuple>
using std::tuple;
实例化 tuple 模板类对象常用的方法有两种,一种是借助该类的构造函数,另一种是借助 make_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); //右值方式
make_tuple()函数
上面程序中,我们已经用到了 make_tuple() 函数,它以模板的形式定义在 <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 表示。
创建一个空的元组, 创建时,需要指定元组的数据类型
std::tuple<int, float, double, long, long long> first;
创建一个元组并初始化元组
std::string str_second_1("_1");
std::string str_second_2("_2");
// 指定了元素类型为引用 和 std::string, 下面两种方式都是可以的,只不过第二个参数不同而已
std::tuple<std::string, std::string> second_1(str_second_1, std::string("_2"));
std::tuple<std::string, std::string> second_2(str_second_1, str_second_2);
创建一个元素是引用的元组
//3、创建一个元组,元组的元素可以被引用, 这里以 int 为例
int i_third = 3;
std::tuple<int&> third(std::ref(i_third));
使用make_tuple创建元组
int i_fourth_1 = 4;
int i_fourth_2 = 44;
// 下面的两种方式都可以
std::tuple<int, int> forth_1 = std::make_tuple(i_fourth_1, i_fourth_2);
auto forth_2 = std::make_tuple(i_fourth_1, i_fourth_2);
创建一个类型为引用的元组, 对元组的修改。 这里以 std::string为例
std::string str_five_1("five_1");
// 输出原址值
std::cout << "str_five_1 = " << str_five_1.c_str() << "\n";
std::tuple<std::string&, int> five(str_five_1, 5);
// 通过元组 对第一个元素的修改,str_five_1的值也会跟着修改,因为元组的第一个元素类型为引用。
// 使用get访问元组的第一个元素
std::get<0>(five) = "five_2";
// 输出的将是: five_2
std::cout << "str_five_1 = " << str_five_1.c_str() << "\n";
计算元组的元素个数
需要函数: std::tuple_size。 下面是一个例子
std::tuple<char, int, long, std::string> first('A', 2, 3, "4");
// 使用std::tuple_size计算元组个数
int i_count = std::tuple_size<decltype(first)>::value;
std::cout << "元组个数=" << i_count << "\n"; //4个
访问元素
访问元组的元素,需要函数: std::get<index>(obj)
。其中:[index]是元组中元素的下标,0,1,2,3,4,… [obj]-元组变量。
std::tuple<char, int, long, std::string> second('A', 2, 3, "4");
int index = 0;
std::cout << index++ << " = " << std::get<0>(second) << "\n"; //0=A
std::cout << index++ << " = " << std::get<1>(second) << "\n"; //1=2
std::cout << index++ << " = " << std::get<2>(second) << "\n"; //2=3
std::cout << index++ << " = " << std::get<3>(second).c_str() << "\n"; //3=4
元组不支持迭代访问,且只能通过索引(或者tie解包:将元组的中每一个元素提取到指定变量中)访问,且索引不能动态传入。上面的代码中,索引都是在编译器编译期间就确定了。下面的演示代码将会在编译期间出错。
for (int i = 0; i < 3; i++)
std::cout << index++ << " = " << std::get<i>(second) << "\n"; // 无法通过编译
获取元素的类型
获取元组中某个元素的数据类型,需要用到另外一个类型:std::tuple_element 。 语法:std::tuple_element<index, tuple> 。 [index]-元组中元素的索引,[tuple]哪一个元组。
std::tuple<int, std::string> third(9, std::string("ABC"));
// 得到元组第1个元素的类型,用元组第一个元素的类型声明一个变量
std::tuple_element<1, decltype(third)>::type val_1;
// 获取元组的第一个元素的值
val_1 = std::get<1>(third);
std::cout << "val_1 = " << val_1.c_str() << "\n"; //val_1 = ABC
使用 std::tie解包
元组,可以看作一个包,类比结构体。 需要访问元组的元素时,2 种方法: A、索引访问,B、std::tie。
元组包含一个或者多个元素,使用std::tie解包:首先需要定义对应元素的变量,再使用tie。 比如,元素第0个元素的类型时 char,第1个元素类型时int,那么,需要定义一个 char的变量和int的变量,用来储存解包元素的结果。
std::tuple<char, int, long, std::string> fourth('A', 2, 3, "4");
// 定义变量,保存解包结果
char tuple_0 = '0';
int tuple_1 = 0;
long tuple_2 = 0;
std::string tuple_3("");
// 使用std::tie, 依次传入对应的解包变量
std::tie(tuple_0, tuple_1, tuple_2, tuple_3) = fourth;
// 输出解包结果
std::cout << "tuple_0 = " << tuple_0 << "\n"; //tuple_0 = A
std::cout << "tuple_1 = " << tuple_1 << "\n"; //tuple_1 = 2
std::cout << "tuple_2 = " << tuple_2 << "\n"; //tuple_2 = 3
std::cout << "tuple_3 = " << tuple_3.c_str() << "\n"; //tuple_3 = 4
std::tie的结构定义如下:
template<class... _Types> inline
constexpr tuple<_Types&...>
tie(_Types&... _Args) _NOEXCEPT
{ // make tuple from elements
typedef tuple<_Types&...> _Ttype;
return (_Ttype(_Args...));
}
接着 std::tie 解包。 如果一个元组,只需要取出其中特定位置上的元素,不用把每一个元素取出来。比如:只要索引为偶数的元素。元组提供了类似占位符的功能:std::ignore。满足上面的需求,只需要在索引为奇数的位置填上std::ignore。一个例子:
std::tuple<char, int, long, std::string> fourth('A', 2, 3, "4");
// 定义变量,保存解包结果
char tuple_0 = '0';
int tuple_1 = 0;
long tuple_2 = 0;
std::string tuple_3("");
// 使用占位符
std::tie(tuple_0, std::ignore, tuple_2, std::ignore) = fourth;
// 输出解包结果
std::cout << "tuple_0 = " << tuple_0 << "\n"; //tuple_0 = A
std::cout << "tuple_1 = " << tuple_1 << "\n"; //tuple_1 = 0
std::cout << "tuple_2 = " << tuple_2 << "\n"; //tuple_2 = 3
std::cout << "tuple_3 = " << tuple_3.c_str() << "\n"; //tuple_3 =
元组连接(拼接)
使用 std::tuple_cat 执行拼接。
std::tuple<char, int, double> first('A', 1, 2.2f);
// 组合到一起, 使用auto, 自动推导
auto second = std::tuple_cat(first, std::make_tuple('B', std::string("-=+")));
// 组合到一起,可以知道每一个元素的数据类型时什么 与 auto推导效果一样
std::tuple<char, int, double, char, std::string> third = std::tuple_cat(first, std::make_tuple('B', std::string("-=+")));
// 输出合并后的元组内容
int index = 0;
std::cout << index++ << " = " << std::get<0>(second) << "\n"; //0 = A
std::cout << index++ << " = " << std::get<1>(second) << "\n"; //1 = 1
std::cout << index++ << " = " << std::get<2>(second) << "\n"; //2 = 2.2
std::cout << index++ << " = " << std::get<3>(second) << "\n"; //3 = B
std::cout << index++ << " = " << std::get<4>(second).c_str() << "\n"; //4 = -=+
std::cout << index++ << " = " << std::get<0>(second) << "\n"; //0 = A
std::cout << index++ << " = " << std::get<1>(second) << "\n"; //1 = 1
std::cout << index++ << " = " << std::get<2>(second) << "\n"; //2 = 2.2
std::cout << index++ << " = " << std::get<3>(second) << "\n"; //3 = B
std::cout << index++ << " = " << std::get<4>(second).c_str() << "\n"; //4 = -=+
遍历
这里将采用的时 递归遍历,需要注意,考虑爆栈的情况。其实,tuple也是基于模板的STL容器。 因为其可以容纳多个参数,且每个参数类型可不相同,遍历输出则涉及到参数展开的情况,这里以递归的方式实现遍历,核心代码:
#include <iostream>
#include <tuple>
template<typename Tuple, size_t N>
struct tuple_show
{
static void show(const Tuple& t, std::ostream& os)
{
tuple_show<Tuple, N - 1>::show(t, os);
os << ", " << std::get<N - 1>(t);
}
};
// 偏特性,可以理解为递归的终止
template<typename Tuple>
struct tuple_show < Tuple, 1>
{
static void show(const Tuple& t, std::ostream& os)
{
os << std::get<0>(t);
}
};
// 自己写个函数,调用上面的递归展开,
template<typename... Args>
std::ostream& operator << (std::ostream& os, const std::tuple<Args...>& t)
{
os << "[";
tuple_show<decltype(t), sizeof...(Args)>::show(t, os);
os << "]";
return os;
}
int main()
{
auto t1 = std::make_tuple(1, 'A', "-=+", 2);
std::cout << t1;
return 0;
}