《C++高级编程》20.7 元组 Tuple

0.引入

第17章介绍的且在<utility>中定义的 std::pair类可保存两个值,每个值都有特定的类型。
每个值的类型都应该在编译时确定。下面是一个简单的例子:

pair<int, string> p1 (16,"Hello World");
pair<bool, float> p2(true, 0.123f);
cout <<"p1 =("< p1.first << "," << p1.secocond <<")" << endl;
cout <<"p2 =("<< p2.first << "," << p2.secocond <<")" << endl;

输出如下所示:
p1 =(16,Hello World)
p2 =(1,0.123)

还有std::tuple类,这个类定义在<tuple>头文件中. tuple(元组)是pair的泛化,允许存储任意数量
的值,每个值都有自己特定的类型。和pair一样,tuple的大小和值类型都是编译时确定的,都是固定
的。

tuple可通过tuple构造函数创建,需要指定模板类型和实际值。例如,下面的代码创建了一个tuple,
其第一个元素是一个整数,第二个元素是一个字符串,最后一个元素是一个布尔值:
using MyTuple = tuple<int, string, bool>;
MyTuple t1 { 16, "Test", true };

stdget<i>()从 tuple中获得第i个元素,i是从0开始的索引;因此<个>表示tuple的第一个元素,<1>
表示tuple的第二个元素,依此类推。返回值的类型是 tuple中那个索引位置的正确类型:

cout << format("t1 = ({}, {}, {})", get<0>(t1), get<1>(t1), get<2>(t1)) << endl;
// Outputs: t1 = (16, Test, 1)

可通过<typeinfo>头文件中的 typeid()检查 get<i>()是否返回了正确的类型. 下面这段代码的输
出表明,get<1>(t1)返回的值确实是std::string:

cout << "Type of get<1>(t1) = " << typeid(get<1>(t1)).name() << endl;
// Outputs: Type of get<1>(t1) = class std::basic_string<char,
// struct std::char_traits<char>,class std::allocator<char> >

注意: typeid()返回的准确字符串与编译器相关. 上面例子中的输出来自Visual C++ 2017.

也可根据类型使用std:get<T>()从 tuple中提取元素,其中T是要提取的元素(而不是索引)的类型.
如果tuple有几个所需类型的元素,编译器会生成错误. 例如,可从t1中提取字符串元素:

cout << "String = " << get<string>(t1) << endl;
// Outputs: String = Test

遗憾的是,迭代tuple的值并不简单. 无法编写简单循环或调用get<i>(mytuIple)等,因为i的值在
编译时必须是已知的. 一种可能的解决方案是使用模板元编程,详见第22章,其中举了一个打印tuple
值的示例. 可通过std::tuple_size模板来查询tuple的大小. 注意,tuple_size要求指定tuple的
类型(在这个例子中为MyTuple),而不是实际的tuple实例,例如t1:

cout << "Tuple Size = " << tuple_size<MyTuple>::value << endl;
// Outputs: Tuple Size = 3

如果不知道准确的tuple类型,始终可以使用decltype(),如下所示:

cout << "Tuple Size = " << tuple_size<decltype(t1)>::value << endl;
// Outputs: Tuple Size = 3

在C++17中,提供了构造函数的模板参数推导规则. 在构造tuple时,可忽略模板类型形参,让编译器
根据传递给构造函数的实参类型, 自动进行推导. 例如,下面定义同样的t1元组,它包含一个整数、一
个字符串和一个布尔值. 注意,必须指定"Test"s,以确保它是std:string.
tuple t1 { 16, "Test"s, true };

缘于类型的自动推导,不能通过&来指定引用. 如果需要通过构造函数的模板参数推导方式,生成一个
包含引用或常量引用的tuple, 那么需要分别使用ref()和cref(). ref()和cref()辅助函数在
<functional>头文件中定义. 例如,下面的构造会生成一个类型为
tuple<int, double&,const double&, string&>的tuple:

double d { 3.14 };
string str1 { "Test" };
tuple t2 { 16, ref(d), cref(d), ref(str1) };

为测试元组t2中的 double引用,下面的代码首先将double变量的值写入控制台. 然后调用
get<1>(t2),这个函数实际上返回的是对d的引用,因为第二个tuple(索引1)元素使用了ref(d).
第二行修改引用的变量的值,最后一行展示了d的值的确通过保存在tuple中的引用修改了.
注意,第三行未能编译,因为cref(d)用于第三个tuple元素,也就是说,它是d的常量引用.
cout << "d = " << d << endl;
get<1>(t2) *= 2;
//get<2>(t2) *= 2; // ERROR because of cref().
cout << "d = " << d << endl;
// Outputs: d = 3.14
// d = 6.28

如果不使用构造函数的模板参数推导方法,可以使用std::make_tuple(工具函数创建一个tuple.
利用这个辅助函数模板,只需要指定实际值,即可创建一个tuple。在编译时自动推导类型,例如:

auto t2 { make_tuple(16, ref(d), cref(d), ref(str1)) };

1.分解元组

可采用两种方法,将一个元组分解为单独的元素: 结构化绑定(C++17)以及 std::tie()。

1.1 结构化绑定

C++17引入了结构化绑定,允许方便地将一个元组分解为多个变量。例如,下面的代码定义了一个
tuple,这个tuple包括一个整数、一个字符串和一个布尔值;此后,使用结构化绑定,将这个tuple
分解为三个独立的变量:

#include <tuple>    
#include <iostream>    
using namespace std;    
int main()    
{    
   tuple t1 { 16, "Test"s, true };    
   auto [i, str, b] { t1 };    
   cout << "i = " << i << ", str = " << str << ", b = " << b << endl;    
}    

使用结构化绑定,无法在分解时忽略特定元素。如果tuple包含三个元素,则结构化绑定需要三个变量。
如果想忽略元素,则必须使用tie()。

1.2 tie()

如果在分解元组时不使用结构化绑定,可使用std:tie()工具函数,它生成一个引用tuple。
下例首先创建一个tuple,这个 tuple包含一个整数、一个字符串和一个布尔值;
然后创建三个变量,即整型变量、字符串变量和布尔变量,将这些变量的值写入控制台。
tie(i, str,b)调用会创建一个tuple,其中包含对i的引用、对 str的引用以及对b的引用。
使用赋值运算符,将tuple tl赋给tie()的结果。由于tie()的结果是一个引用tuple,赋值
实际上更改了三个独立变量中的值。

#include <iostream>    
#include <tuple>    
using namespace std;    
int main()    
{    
    tuple t1 { 16, "Test"s, true };    
    int i { 0 };    
    string str;    
    bool b { false };    
    cout << "Before: i = " << i <<", str = " << str << ", b = "<< b << endl;    
    tie(i, str, b) = t1;    
    cout << "After: i = " << i <<", str = " << str << ", b = "<< b << endl;    
    i = 0;    
    str = "";    
    b = false;    
    tie(i, std::ignore, b) = t1;    
    cout << "After: i = " << i <<", str = " << str << ", b = "<< b << endl;      
}    

2.串联

通过 std:tuple_cat()可将两个tuple串联为一个tuple. 在下面的例子中,t3的类型为 
tuple<int, string, bool,double, string>:

#include <tuple>    
#include <iostream>    
using namespace std;    
int main()    
{    
    tuple t1{16, "Test"s, true};    
    tuple t2{3.14, "string 2"s};    
    auto t3{tuple_cat(t1, t2)};    
    int i = 0;    
    string str1 = "";    
    bool j = false;    
    float k = 0.0;    
    string str2 = "";    
    tie(i, str1, j, k, str2) = t3;    
    cout << " i = " << i << ", str1 = " << str1 << ", j = " << j << ", k = " << k << ", str2 = " << str2 << endl;    
}   

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值