通用工具之Pair和Tuple-《C++标准库(第二版)》读书笔记
寫在前面:本文是閱讀《C++標(biāo)準(zhǔn)庫(第二版)》的讀書筆記。
文章目錄
- 5.1 Pair 和Tuple
- 5.1.1 Pair
- 元素訪問
- 構(gòu)造函數(shù)和賦值
- 逐塊式構(gòu)造
- 便捷函數(shù)make_pair()
- Pair 之間的比較
- 5.1.2 Tuple(不定數(shù)的值組)
- Tuple的操作
- 便捷函數(shù)make_tuple()和tie()
- Tuple和初值列
5.1 Pair 和Tuple
5.1.1 Pair
Class pair 可將兩個(gè)value 視為一個(gè)單元。C++標(biāo)準(zhǔn)庫中多處用到了這個(gè)class。尤其容器map、multimap,unordered_map和unordered_multimap就是使用pair來管理其以key/value pair形式存在的元素。 任何函數(shù)如果需要返回兩個(gè)value,也需要用到pair,例如minmax().
struct pair 定義于< utility> ,下表中的各項(xiàng)操作它都支持。原則上可以對(duì)pair< > 執(zhí)行create、copy/assign/swap 及compare操作 。此外它還提供first_type和second_type 類型定義式,用來表示第一value和第二value的類型。
元素訪問
為了讓程序能夠處理pair 的兩個(gè)值,它提供了”直接訪問對(duì)應(yīng)數(shù)據(jù)成員“的能力。事實(shí)上,由于它是個(gè)struct 而不是class,以至于所有成員都是public:
namespace std{template<typename T1, typename T2>struct pair {//memberT1 first;T2 second;...}; }舉個(gè)例子,想要實(shí)現(xiàn)一個(gè)泛型函數(shù)模板,用以將一個(gè)value pair 寫入一個(gè)stream內(nèi),必須這樣做:
template <typename T1, typename T2> std:: ostream& operator<< (std::ostream & strm,const std::pair<T1,T2>& p) {return strm <<"["<< p.first<<"," << p.second <<"]"; }另外,自C++11起,可以對(duì)pair使用一份tuple-like 接口。因此,你可以使用tuple_size< > ::value 獲得元素個(gè)數(shù),使用tuple_element < > ::type 獲得某指定元素的類型,也可以使用get() 獲得first 或second:
typedef std::pair<int,float> IntFloatPair;IntFloatPair p(42,3.14);std::get<0>(p) // yields p.first std::get<1>(p) // yields p.second std::tuple_size<IntFloatPair> ::value //yields 2 std::tuple_element<0,IntFloatPair>::type //yields int構(gòu)造函數(shù)和賦值
Default構(gòu)造函數(shù)生成一個(gè)pair時(shí),以兩個(gè)”被default構(gòu)造函數(shù)個(gè)別初始化“的元素作為初值。根據(jù)語言規(guī)則,基礎(chǔ)類型(如int)的default析構(gòu)函數(shù)也可以引起適當(dāng)?shù)某跏蓟瘎?dòng)作,所以
std::pair<int,float> p; //initialize p.first and p.second with zero就是以int() 和float() 來初始化p。 這兩個(gè)構(gòu)造函數(shù)都傳回零值。
Copy構(gòu)造函數(shù)同時(shí)存在兩個(gè)版本,版本1接受相同類型的pair,版本2是個(gè)member template,在”構(gòu)造過程中需要隱式類型轉(zhuǎn)換“時(shí)被調(diào)用。如果pair對(duì)象被復(fù)制,調(diào)用的是被隱式合成的那個(gè)copy構(gòu)造函數(shù)。 例如
void f(std::pair<int,const char*>); void g(std::pair<const int,std::string>);void foo(){std::pair<int ,const char*> p(42,"hello");f(p); //OK:calls implicitly generated copy constructorg(p); //OK:calls template constructor }逐塊式構(gòu)造
Class pair< > 提供三個(gè)構(gòu)造函數(shù),用以初始化first 和second成員:
namespace std{template <typename T1, typename T2>struct pair{...pair(const T1&x ,const T2& y);template<typename U, typename V> pair(U&& x, V&& y);template<typename ... Args1,typename ... Args2>pair( piecewise_construct_t,tuple<Args1...> first_args,tuple<Args2...> second_args);...}; }前兩個(gè)構(gòu)造函數(shù)提供的是慣常行為:傳遞一個(gè)實(shí)參給first,另一個(gè)實(shí)參給second,并且涵蓋對(duì)move semantic 和隱式類型轉(zhuǎn)換的支持。第三個(gè)構(gòu)造函數(shù)有點(diǎn)特別,它允許傳遞兩個(gè)tuple(那是一種”擁有不定個(gè)數(shù)的元素且元素類型各不相同“的對(duì)象),但以另外一種方式處理它們。正常而言,如果傳遞1個(gè)或2個(gè)tuple,最前面兩個(gè)構(gòu)造函數(shù)允許初始化一個(gè)pair,其first和/或 second是tuple。但是第三個(gè)構(gòu)造函數(shù)使用tuple,將其元素傳遞給first和second的構(gòu)造函數(shù)。為了強(qiáng)迫執(zhí)行這樣的行為,你必須傳遞std::piecewise_construct 作為額外的第一實(shí)參。
舉個(gè)例子
#include<bits/stdc++.h> using namespace std;class Foo{public: Foo(tuple<int,float>){cout<<"Foo::Foo(tuple)"<<endl;}template <typename ...Args>Foo(Args... args){cout<<"Foo::Foo(args...)"<<endl;} }; int main(){//create tuple t:tuple<int,float> t(1,2.22);//pass the tuple as a whole to the constructor of Foo:pair<int,Foo> p1(42, t);// pass the elements of the tuple to the constructor of Foo:pair<int,Foo> p2(piecewise_construct,make_tuple(42),t); }執(zhí)行結(jié)果
只有當(dāng)std::piecewise_construct被當(dāng)作第一實(shí)參時(shí),class Foo 才會(huì)被迫使用那個(gè)”接受tuple的元素(一個(gè)int和一個(gè)float)而非tuple整體“的構(gòu)造函數(shù)。這意味著,此例中被調(diào)用的是Foo那個(gè)”實(shí)參數(shù)量不定的構(gòu)造函數(shù)“。如果提供了Foo::Foo(int ,float)的話,被調(diào)用的將是它。
如你所見,兩個(gè)實(shí)參都必須是tuple才會(huì)強(qiáng)迫導(dǎo)致這個(gè)行為。因此,第一實(shí)參42被顯式轉(zhuǎn)化為一個(gè)tuple,用的是make_tuple().
便捷函數(shù)make_pair()
Template函數(shù)make_pair()使你無需寫出類型就能生成一個(gè)pair對(duì)象。舉個(gè)例子,不用這樣寫;
std::pair<int,char>(42,'@')可以這樣寫
std::make_pair(42,'@')自C++11起,make_pair()聲明如下
namespace std{//create value pair only by providing the valuestemplate<template T1, template T2>pair<V1,V2> make_pair(T1&&x,T2&& y); }其中返回值的細(xì)節(jié)和它們的類型V1,V2 ,取決于x和y的類型。標(biāo)準(zhǔn)明確指出,如果可能的話make_pair()使用move語義,否則就是用copy語義。 此外,它會(huì)蛀蝕(decay)實(shí)參,致使make_pair(“a”,“xy”)產(chǎn)生一個(gè)pair< const char* ,const char*> 而非一個(gè) pair< const char[2], const char[3] >
當(dāng)我們必須對(duì)一個(gè)”接受pair為實(shí)參“的函數(shù)傳遞兩個(gè)value時(shí),make_pair()就會(huì)特別方便,請(qǐng)看下列:
void f(pair<int,const char*>); void g(pair<const int,string>);void foo(){f(make_pair(42,"empty")); //pass 2 values as pairg(make_pair(42,"chair")); //pass 2 values as pair with type conversions }從本例可以看出,make_pair()即使在類型并不準(zhǔn)確吻合的情況下也能借由template構(gòu)造函數(shù)提供的支持順利運(yùn)行。當(dāng)你使用map和multimap時(shí),經(jīng)常需要這樣的能力。
注意,自C++11開始,可以使用初值列
f({42,"empty"}); g({42,"chair"});然而一個(gè)表達(dá)式如果明白指出類型,便帶有一個(gè)優(yōu)勢(shì):產(chǎn)生出來的pair將有絕對(duì)明確的類型。例如
pair<int, float>(42,7.77)所得到的結(jié)果和下面不同
make_pair(42,7.77)后者所產(chǎn)生的pair的第二個(gè)元素的類型是double(因?yàn)闊o任何限定符的浮點(diǎn)字面常量的類型被視為double)。當(dāng)我們使用重載函數(shù)或template的時(shí)候,確切的類型非常重要。例如,為了提高效能,程序員可能同時(shí)提供分別針對(duì)float和double的重載函數(shù)或template,這時(shí)候確切的類型就非常重要。
Pair 之間的比較
為了比較兩個(gè)pair對(duì)象,C++標(biāo)準(zhǔn)庫提供了大家慣用的操作符。兩個(gè)pair對(duì)象內(nèi)的所有元素都相等,這兩個(gè)pair對(duì)象才被視為相等。
namespace std{template<typename T1,typename T2>bool operator ==(const pair<T1,T2> &x, const pair<T1,T2> &y){return x.first==y.first && x.second==y.second;} }兩個(gè)pair相互比較時(shí),第一元素具有較高的優(yōu)先級(jí)。所以如果兩個(gè)pair的第一元素不相等,其比較結(jié)果就成為整個(gè)比較的結(jié)果。如果first相等,才繼續(xù)比較second,并把比較結(jié)果當(dāng)作整體結(jié)果
namespace std{template<typename T1,typename T2>bool operator< (const pair<T1,T2>&x, const pair<T1,T2> &y){return x.first <y.first ||(!(y.first< x.first) && x.second < y.second);} }5.1.2 Tuple(不定數(shù)的值組)
Tuple 擴(kuò)展了pair的概念,擁有任意數(shù)量的元素。也就是說,tuple呈現(xiàn)出一個(gè)異質(zhì)元素列,其中每個(gè)類型都可以被指定,或來自編譯期推導(dǎo)。
C++11,variadic template 被引入進(jìn)來,使template得以接受任何數(shù)量的template實(shí)參,于是,出現(xiàn)在< tuple> 中的class tuple 聲明式 現(xiàn)在就被簡(jiǎn)化為如下:
namespace std{template< typename ... Types>class tuple; }Tuple的操作
原則上,tuple接口十分直觀:
- 通過明白的聲明,或使用便捷函數(shù)make_tuple(),你可以創(chuàng)建一個(gè)tuple。
- 通過get<>() function template,你可以訪問tuple的元素。
下面是其接口的一個(gè)基本示例
#include<bits/stdc++.h> using namespace std;int main(){tuple<string,int, int ,complex<double>> t;//create adn initialize a tuple explicitlytuple<int,float,string> t1(41, 6.3, "nico");cout<< get<0> (t1)<<" ";cout<< get<1> (t1)<<" ";cout<<get<2> (t1) <<" ";cout<<endl;//create tuple with make_tuple()auto t2 = make_tuple(22,43.1,"hello");//assign second value in t2 to t1get<1>(t1) = get<1>(t2);cout<< get<1> (t1)<<endl;if(t1< t2)t1=t2;return 0; }測(cè)試結(jié)果
下面是分析
該條語句建立起了一個(gè)異質(zhì)的四元素tuple,每個(gè)元素的內(nèi)容由default構(gòu)造函數(shù)初始化。基礎(chǔ)類型都被初始化為0.
下面的語句 tuple<int, float, string> t1(41, 6.3 ,“nico”); 建立并初始化一個(gè)異質(zhì)的三元素tuple。
你也可以使用make_tuple()建立tuple ,其所有元素類型都自動(dòng)推導(dǎo)自它的初值。
注意, tuple的元素類型可以是reference。例如
Tuple不是尋常的容器,不允許迭代元素。對(duì)于tuple可以使用其成員函數(shù)來處理元素,因此必須在編譯期知道你打算處理的元素的索引值。 例如你可以這樣處理tuple t1 的第一元素:
get<0>(t1)運(yùn)行期才傳入一個(gè)索引值是不被允許的:
int i; get<i> (t1) // compile-time error: i is no compile-time value好消息是,萬一傳入無效索引,編譯器會(huì)報(bào)錯(cuò):
get<3> (t1) //compile-time error if t1 has only three elements此外,tuple 還提供常用的拷貝,賦值和比較操作,它們身上都允許發(fā)生隱式類型轉(zhuǎn)換(因?yàn)椴捎胢ember template),但元素個(gè)數(shù)必須絕對(duì)吻合。 如果兩個(gè)tuple 的所有元素都相等,它們就整體相等。檢查某個(gè)tuple是否小于另一個(gè)tuple,采用的是lexicographical(字典序)比較法則。
便捷函數(shù)make_tuple()和tie()
make_tuple()函數(shù)會(huì)根據(jù)value建立tuple,不需要明確指出元素的類型。
auto t2 = make_tuple(22,43.1,"hello");這句話建立并初始化了一個(gè)tuple,其對(duì)應(yīng)的三個(gè)元素類型是int ,double 和 const char* 。
借用特別的函數(shù)對(duì)象reference_wrapper < > 及 便捷函數(shù) ref() 和 cref() ,可以影響make_tuple()產(chǎn)生的類型,例如以下表達(dá)式產(chǎn)出的tuple 帶有一個(gè)reference 指向變量或?qū)ο髎:
string s; make_tuple(ref(s)); //yields type tuple<string&> ,where the element refers to s如果你打算改動(dòng)tuple內(nèi)的一個(gè)既有值,上述就很重要:
std::string s;auto x= std:: make_tuple(s); // x is of type tuple <string>std:: get<0>(x) = "my values"; // modifies x but not sauto y =std::make_tuple(ref(s)); // y is of type tuple<string&>, thus y refers to sstd::get<0> (y) = "my values"; //modifies y運(yùn)用reference搭配make_tuple() ,就可以提取tuple的元素值,將某些變量值設(shè)給它們,例如以下例子:
tuple<int, float, string> t(77, 1.1, "more light"); int i; float f;string s;//assign values of t to i, f and s: make_tuple( ref(i), ref(f), ref(s))=t;然后分別輸出 i,f,s
如果想最方便地在tuple中使用reference,可選擇tie(),它可以建立一個(gè)內(nèi)含reference的tuple:
tuple<int,float, string> t(77,1.1, "more light"); int i; float f; string s; tie(i,f,s)=t; //assigns values of t to i , f and s這里的tie(i,f,s)=t 會(huì)以i,f,s的reference 建立起一個(gè)tuple,因此上述賦值操作其實(shí)就是將t內(nèi)的元素分別賦值為i,f和s。
使用tie()時(shí), ignore允許我們忽略tuple的某些元素,也就是我們可以用它來局部提取tuple 的元素值:
tuple<int,float, string> t(77,1.1, "more light"); int i; string s; tie(i,std::ignore,s)=t; //assigns first and third value of t to i and sTuple和初值列
不可以使用賦值語法將某個(gè)tuple初始化,因?yàn)槟菚?huì)被視為一個(gè)隱式轉(zhuǎn)換
tuple<int ,double> t1(42,3.14); // OK ,old syntax tuple<int,double> t2{42, 3.14};//OK: new syntax tuple<int, double> t3={42, 3.14}; //ERROR此外,你不可以將初值列傳到“期望獲得一個(gè)tuple”的地方:
vector< tuple<int,float>> v{{1,1.0},{2, 2.0}};//ERRORtuple<int,int,int> foo(){return {1, 2, 3};//ERROR }注意,上述做法對(duì)于pair< > 和容器(除了array < >外) 是行得通的:
vector< pair<int, float> > v1{{1,1.0},{2, 2.0}}; //OK vector<vector<float>> v2{{1,1.0},{2, 2.0}}; //OKvector<int> foo2(){return {1, 2, 3}; //OK }但是對(duì)于tuple ,必須明確地將初值轉(zhuǎn)換為一個(gè)tuple(比如運(yùn)用make_tuple()):
vector< tuple<int,float>> v{ make_tuple(1,1.0),make_tuple(2, 2.0)};//OKtuple<int,int,int> foo(){return make_tuple(1, 2, 3);//OK }總結(jié)
以上是生活随笔為你收集整理的通用工具之Pair和Tuple-《C++标准库(第二版)》读书笔记的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Leetcode1713. 得到子序列的
- 下一篇: Leetcode1694. 重新格式化电