C++:55---标准库特殊设施之tuple类型

一、tuple概述

  • tuple与pair类似,也是一个模板。pair接受两个成员,tuple接受任意数目的成员
  • 当我们希望将一些数据组合成单一对象时,tuple非常有用
  • 下面是tuple支持的操作,都定义在头文件tuple中:

二、定义和初始化tuple

  • 当我们定义一个tuple时,需要指出每个成员的类型:
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的构造函数是explicit的,因此必须使用直接初始化语法:
std::tuple<size_t, size_t, size_t> threeD1 = { 1,2,3 }; //错误
std::tuple<size_t, size_t, size_t> threeD2{ 1,2,3 };    //正确
  • 也可以使用make_pair()函数来生成一个tuple对象:
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返回指定成员的引用
  • 例如:
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; //返回最后一个成员,并将其除以cnt
std::get<2>(item) *= 0.8;             //打折20%(get返回的是item的引用)

tuple_size类模板、tuple_element类模板

  • tuple_size:其有一个名为value的public static数据成员,表示给定tuple中成员的数量
  • tuple_element:其接受一个索引和一个tuple类型,然后通过名为type的public成员,表示给定tuple指定成员的数据成员
//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为int
std::tuple_element<1, trans>::type cnt = get<1>(item);

四、关系和相等运算符

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

五、使用tuple返回多个值

  • tuple一个常见的用途是从一个函数返回多个值。下面我们一步一步介绍一个演示案例
  • Sales_data是一个自定义类,其中包含一本书的编号、价格、销售记录
#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变量,用来保存每一家书店的销售情况:
/*
    vector<Sales_data>代表一家书店的销售情况
    vector<vector>代表所有书店
*/
vector<vector<Sales_data>> files;
  • 编写一个返回tuple的函数:
    • 下面我们编写一个函数,对于一本给定的书,在files中搜索出售过这本书的书店
    • 对每家有销售记录的,返回一个tuple类型,保存这家书店的索引和两个迭代器(索引指出书店在files中的位置,迭代器分别标记书籍在此书店的vector<Sales_data>中第一条销售记录和最后一条销售记录之后的位置)
    • equal_range算法:
      • 在指定的迭代器区间内寻找参数3所指定的元素
      • 该算法默认使用<运算符来比较,因为Sales_data没有<运算符,因此我们传递给它一个名为compareIsbn函数的指针
      • 该算法返回一个pair,表示元素的范围。如果未找到,则两个迭代器相等,表示空范围
//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中
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;
            }
        }
    }
}
发布了1504 篇原创文章 · 获赞 1063 · 访问量 43万+

猜你喜欢

转载自blog.csdn.net/qq_41453285/article/details/104661451
今日推荐