前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++(STL):02---tuple容器

C++(STL):02---tuple容器

作者头像
用户3479834
发布2021-02-03 12:32:14
1.2K0
发布2021-02-03 12:32:14
举报
文章被收录于专栏:游戏开发司机

一、tuple的历史概述

  • Tuple是TR1引入的东西,它扩展了pair的概念,拥有任意数量的元素。在C++11标准之前,tuple最多带有10个类型不同的元素
  • C++11,tuple被重新定义,采用variadic template概念,被设计为可用于任意大小的异质集合

二、tuple概述

  • tuple与pair类似,也是一个模板。pair接受两个成员,tuple接受任意数目的成员
  • 当我们希望将一些数据组合成单一对象时,tuple非常有用

tuple的实现

  • TR1标准时(C++11之前),tuple最多带有10个实参,因此tuple被定义为如下的形式
  • 到了C++11之后,有了variadic template概念,tuple被重新定义,格式如下,其可以接受任意数量的实参

支持的操作

  • tuple定义在头文件<tuple>中

三、定义和初始化tuple

  • 当我们定义一个tuple时,需要指出每个成员的类型:
代码语言:javascript
复制
std::tuple<size_t, size_t, size_t> threeD; //使用默认构造函数
std::tuple<std::string, std::vector<double>, int, std::list<int>>someVal("constants", { 3.14,2.718 }, 42, { 0,1,2,3,4,5 });
  • tuple的元素类型可以是reference的。例如:
代码语言:javascript
复制
string s;tuple<string&> t(s);get<0>(t) = "hello"; //s变为hello

Tuple和初值列

  • tuple的构造函数是explicit的
  • 这么做是为了避免单一值被隐式转换为“带有一个元素”的tuple。例如:
代码语言:javascript
复制
template<typename... Args>void foo(const std::tuple<Args...> t);
int main(){foo(42);             //错误,禁止隐式转换foo(make_tuple(42)); //正确return 0;}
  • 因此必须使用直接初始化语法:
代码语言:javascript
复制
std::tuple<size_t, size_t, size_t> threeD1 = { 1,2,3 }; //错误,不能进行隐式类型转换了std::tuple<size_t, size_t, size_t> threeD2{ 1,2,3 };    //正确
  • 因此不可以将初值列传值“期望获得一个tuple”的地方。例如:
代码语言:javascript
复制
std::vector<std::tuple<int, float>> v{ { 1,1,0 },{2,2,0} }; //错误
std::tuple<int, int, int> foo() { //错误	return{ 1,2,3 };}
  • 也可以使用make_tuple()函数来生成一个tuple对象:
代码语言:javascript
复制
auto item = std::make_tuple("0-999-78345-X", 3, 20.00);
//item类型为tuple<const char*, int, double>

四、访问tuple成员

get()函数

  • 我们可以使用first和second来访问pair容器的第一个元素和第二个元素。但是tuple容器成员数目是不限制的,因此我们必须使用get标准库函数模板来访问tuple中的元素
  • 为了使用get,我们必须指定一个显式模板实参,用来指出要访问第几个成员,成员索引从0开始
  • get返回指定成员的引用
  • 例如:
代码语言:javascript
复制
auto item = std::make_tuple("0-999-78345-X", 3, 20.00);
auto book = std::get<0>(item);        //返回item第一个成员auto cnt = std::get<1>(item);         //返回item第二个成员auto price = std::get<2>(item) / cnt; //返回最后一个成员,并将其除以cntstd::get<2>(item) *= 0.8;             //打折20%(get返回的是item的引用)
  • tuple不是寻常的容器,不允许迭代元素 。对于tuple可以使用其成员函数来处理元素,因此必须在编译期知道你打算处理的元素的索引值。例如:
代码语言:javascript
复制
std::tuple<int,double> t1;get<0>(t1); //正确
int i;get<i>(t1); //错误,运行期才传入一个索引值
get<3>(t1); //错误,索引错误,t1只有两个元素

tuple_size类模板、tuple_element类模板、tuple_cat类模板

  • tuple_size:其有一个名为value的public static数据成员,表示给定tuple中成员的数量
  • tuple_element:其接受一个索引和一个tuple类型,然后通过名为type的public成员,表示给定tuple指定成员的数据成员
代码语言:javascript
复制
//item的类型为tuple<const char*, int, double>auto item = std::make_tuple("0-999-78345-X", 3, 20.00);
//trans为item的数据类型typedef decltype(item) trans;
//返回item中的数据成员数量size_t sz = std::tuple_size<trans>::value;
//type为intstd::tuple_element<1, trans>::type cnt = get<1>(item);
  • tuple_cat:可以将多个tuple串接为一个tuple
代码语言:javascript
复制
int n;
//tt的类型为tuple<int,double,std::string,int>auto tt = std::tuple_cat(std::make_tuple(42, 7.7, "hello"), std::tie(n));

五、关系和相等运算符

  • 下面是STL容器的比较规则:
    • 如果两个容器具有相同大小且所有元素都两两对应相等,则两容器相等;否则不相等
    • 如果两个容器大小不相同,但较小容器中每个元素都等于较大容器中对应元素,则较小容器小于较大容器
    • 如果两个容器都不是另一个容器的前缀子序列,则它们的比较结果取决于第一个不相等元素的比较结果
  • tuple与STL容器的比较规则类似,但是:
    • 只有两个tuple具有相同数量的成员时才可以进行比较
    • 为了使用相等或不等运算符,对每对成员相等或不等运算符都必须是合法的
    • 类似的为了使用关系运算符,对每对成员使用关系运算符也必须是合法的
  • 例如:
代码语言:javascript
复制
std::tuple<std::string, std::string> duo("1", "2");


std::tuple<size_t, size_t> twoD(1, 2);
bool b = (duo == twoD);     //错误,string与size_t类型不一致无法比较


std::tuple<size_t, size_t, size_t> threeD(1, 2, 3);
b = (twoD < threeD);        //错误,tuple的成员数量不同


std::tuple<size_t, size_t> origin(0, 0);
b = (origin > twoD);        //正确,b为true

六、make_tuple()函数、tie()函数

make_tuple()

  • 用来创建一个tuple()对象。例如:
代码语言:javascript
复制
//三个元素的类型是int、int、const char*make_tuple(22, 44, "nico");
//其实第三个元素的类型为const char[5]。当使用type trait std::decay()时,第三个元素的类型衰退为const char*
  • 可以借助函数对象reference_wrapper<>以及便捷函数ref()和cref()(全部都定义于<functional>中)来影响make_tuple()产生的类型
  • 例如:
代码语言:javascript
复制
std::string s;
auto x = std::make_tuple(s); //以s的非引用创建一个tuplestd::get<0>(x) = "my value"; //未改变s的值
auto y = make_tuple(ref(s)); //以s的引用创建一个tuplestd::get<0>(y) = "my value"; //改变s的值

tie()

  • tie()用来创建一个tuple对象,其实参都是引用类型
  • 例如:
代码语言:javascript
复制
std::tuple<int, float, std::string> t(77, 1.1, "more light");
int i;float f;std::string s;std::tie(i, f, s) = t;//将t赋值给一个tuple对象(tie()创建返回的tuple),其中都使用i,f,s的引用来创建//因此,创建之后,i=77,f=1.1,s="more light"
  • 使用tie()时,允许使用std::ignore忽略tuple的某些元素。例如:
代码语言:javascript
复制
std::tuple<int, float, std::string> t(77, 1.1, "more light");
int i;std::string s;//使用t创建一个tuple,tuple的类型为tuple<int,std::string>,其中忽略了t的第二个元素std::tie(i, std::ignore, s) = t;

七、tuple的输入/输出

  • 通过文章上面tuple支持的操作可以看出,可以用一个pair初始化一个双元素tuple,也可以将一个pair赋值给一个双元素tuple
  • pair<>提供了一个特殊的构造函数,以tuple为初值(可以参阅前面的pair文章)

八、使用tuple返回多个值

  • tuple一个常见的用途是从一个函数返回多个值。下面我们一步一步介绍一个演示案例
  • Sales_data是一个自定义类,其中包含一本书的编号、价格、销售记录
代码语言:javascript
复制
#include <iostream>
#include <ostream>
#include <sstream>
#include <fstream>
#include <string>
#include <iterator>
#include <vector>
#include <tuple>
#include <algorithm>
#include <numeric>
using namespace std;


struct Sales_data {
std::string isbn()const { return bookNo; }
Sales_data(const string& s) :bookNo(s), units_sold(0), revenue(0) {}


Sales_data &operator+=(const Sales_data &rhs) {
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}


std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};


ostream &operator<<(ostream &os, const Sales_data &item)
{
os << item.isbn() << " " << item.units_sold << " " << item.revenue << ;
return os;
}


Sales_data operator+(const Sales_data &lhs, const Sales_data &rhs) {
Sales_data sum = lhs;
sum += rhs;
return sum;
}
  • 现在我们定义一个files变量,用来保存每一家书店的销售情况:
代码语言:javascript
复制
/*vector<Sales_data>代表一家书店的销售情况vector<vector>代表所有书店*/vector<vector<Sales_data>> files;
  • 编写一个返回tuple的函数:
    • 在指定的迭代器区间内寻找参数3所指定的元素
    • 该算法默认使用<运算符来比较,因为Sales_data没有<运算符,因此我们传递给它一个名为compareIsbn函数的指针
    • 该算法返回一个pair,表示元素的范围。如果未找到,则两个迭代器相等,表示空范围
    • 下面我们编写一个函数,对于一本给定的书,在files中搜索出售过这本书的书店
    • 对每家有销售记录的,返回一个tuple类型,保存这家书店的索引和两个迭代器(索引指出书店在files中的位置,迭代器分别标记书籍在此书店的vector<Sales_data>中第一条销售记录和最后一条销售记录之后的位置)
    • equal_range算法:
代码语言:javascript
复制
//equal_range算法比较函数
bool compareIsbn(const Sales_data& lhs, const Sales_data& rhs)
{
return lhs.isbn() < rhs.isbn();
}


vector<vector<Sales_data>> files;


//声明一个别名
typedef tuple<vector<Sales_data>::size_type,
vector<Sales_data>::const_iterator,
vector<Sales_data>::const_iterator> matches;


//返回一个类型为tuple的vector
vector<matches> findBook(const vector<vector<Sales_data>> &files, const string &book)
{
vector<matches> ret;


//遍历files
for (auto it = files.cbegin(); it != files.cend(); ++it)
{
//在it所在的书店中寻找名为book的书籍,返回值见上面介绍
auto found = equal_range(it->cbegin(), it->cend(), book, compareIsbn);


//如果找到了,添加到ret中
if (found.first != found.second)
ret.push_back(std::make_tuple(it - files.cbegin(), found.first, found.second));
}


return ret;
}
  • 使用函数返回tuple:
    • while循环从in中读取名为s的书籍,然后调用上面的findBook函数来查找是否有名为s的书籍
    • 如果没有,则findBook返回的vector为空,那么使用continue继续循环
    • 如果查找到了,使用for循环遍历trans的vector,其中每个元素都是一个tuple
    • 然后使用get得到tuple中的0、1、2三种元素并打印
    • 其中使用了accumulate算法(由于我们定义了Sales_data的加法运算符,因此可以使用这个算法)。accumulate以参数3为初始值,其中使用Sales_data的参数为string的构造函数来构造。又由于我们定义了Sales_data的<<运算符,因此可以输出到ostream中
代码语言:javascript
复制
void reportResults(istream &in, ostream &os, const vector<vector<Sales_data>> &files)
{
std::string s; //要查找的书籍


while (in >> s)
{
auto trans = findBook(files, s);


if (trans.empty()) {
std::cout << s << "not found in any stores" << std::endl;
continue;
}
else {
for (const auto &store : trans) {
os << "store: " << std::get<0>(store) << std::endl;
os << "sales: " << std::accumulate(std::get<1>(store), std::get<2>(store), Sales_data(s)) << std::endl;
}
}
}
}

九、自定义打印tuple

  • tuple最初定义于Boost程序库,其提供将tuple的元素输出值output stream。但是C++标准库不支持,因此我们可以自己定义一些打印tuple元素的代码。如下所示:
    • 其中运用了模板超编程,在编译器递归迭代tuple的所有元素
    • 每次调用PRINT_TUPLE<>::print()就可以打印一个元素
    • 一个偏特化版本(其“当前所以你IDX”和“tuple内的元素个数MAX”相等)用来终结递归调用

演示案例:

代码语言:javascript
复制
#include "printtuple.hpp"
#include <tuple>
#include <iostream>
#include <string>
using namespace std;
int main()
{
    tuple <int,float ,string> t(77,1.1, "more light");
    cout<< "io: " <<t << endl;
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-12-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 游戏开发司机 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、tuple的历史概述
  • 二、tuple概述
    • tuple的实现
      • 支持的操作
      • 三、定义和初始化tuple
        • Tuple和初值列
        • 四、访问tuple成员
          • get()函数
            • tuple_size类模板、tuple_element类模板、tuple_cat类模板
            • 五、关系和相等运算符
            • 六、make_tuple()函数、tie()函数
              • make_tuple()
                • tie()
                • 七、tuple的输入/输出
                • 八、使用tuple返回多个值
                • 九、自定义打印tuple
                相关产品与服务
                容器服务
                腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档