C++ STL之算法與適配器

本文預(yù)覽:

  • 迭代器的分類
  • 不同迭代器對算法的影響
  • 算法舉例及源碼剖析
  • 仿函數(shù)
  • 適配器

概覽

前面說的都是關(guān)于容器的東西烤蜕,容器占到了STL大概百分之八十的內(nèi)容,數(shù)據(jù)結(jié)構(gòu)的地位是如此重要讽营,程序只有數(shù)據(jù)結(jié)構(gòu)還是不夠的,這次來說說算法橱鹏。STL提供了大概八九十個算法,包含在<algrithms>頭文件里莉兰,數(shù)據(jù)結(jié)構(gòu)是算法的底層基礎(chǔ),數(shù)據(jù)結(jié)構(gòu)提供了算法操作的對象杉辙,而算法怎么去操作數(shù)據(jù)結(jié)構(gòu),這個是由迭代器來完成的蜘矢。也就是說迭代器在算法和容易之間起到了一個粘合劑的作用泉孩。

迭代器的分類

迭代器分為五個類型:

struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag       : public input_iterator_tag {};   //單向迭代器硼端,單向列表
struct bidirectional_iterator_tag : public forward_iterator_tag {};    //雙向迭代器寓搬,紅黑樹和雙向鏈表用到的類型
struct random_access_iterator_tag : public bidirectional_iterator_tag {}; //隨機訪問型县耽,連續(xù)內(nèi)存vector镣典、array等

每一種迭代器都是一個class

迭代器關(guān)系圖

我們可以通過代碼來測試每一種容器對應(yīng)迭代器的類型:

void _display_category(random_access_iterator_tag)
{
    cout<<"random_access_iterator_tag"<<endl;
}

void _display_category(bidirectional_iterator_tag)
{
    cout<<"bidirectional_iterator_tag"<<endl;
}

void _display_category(forward_iterator_tag)
{
    cout<<"forward_iterator_tag"<<endl;
}

void _display_category(output_iterator_tag)
{
    cout<<"output_iterator_tag"<<endl;
}

void _display_category(input_iterator_tag)
{
    cout<<"input_iterator_tag"<<endl;
}

template <typename T>
void display_category(T ite) {
    typename iterator_traits<T>::iterator_category cagy;
    _display_category(cagy);
    cout<<"typeid(ite).name() = "<<typeid(ite).name()<<endl;
};

int main(int argc, const char * argv[]) {
    display_category(array<int, 1>::iterator());
    display_category(vector<int>::iterator());
    display_category(list<int>::iterator());
    display_category(map<int, int>::iterator());
    display_category(set<int>::iterator());
    display_category(istream_iterator<int>());
    display_category(ostream_iterator<int>(cout, ""));
    return 0;
}

不同迭代器對算法的影響

舉一個簡單的例子:

迭代器對算法的影響

算法distance計算迭代器的距離唾琼,如果是random_acess_iterator_tag類型的,我們看到锡溯,直接一次計算,時間復(fù)雜度O(1)祭饭,可忽略不計;但是如果是input_iterator_tag類型的倡蝙,那么時間復(fù)雜度是O(n),也就是說猪钮,迭代器對算法實現(xiàn)復(fù)雜度是有影響的。

再舉一個例子advance:

advance對迭代器做移動操作

算法advance對迭代器執(zhí)行遷建或者后退操作烤低。都是根據(jù)迭代器類型笆载,來決定進行那種類型的操作拂玻。

算法舉例及源碼剖析

1宰译、 count_if()
返回滿足條件的元素個數(shù)

    vector<int> a = {1,3,2,8,7,4,6};
    cout<<count_if(a.begin(), a.end(), bind2nd(less<int>(), 4));

可能需要對bind2nd做出解釋一下,這一句的意思是對仿函數(shù)less的第二個參數(shù)綁定為40沿侈,整句的意思是:輸出vector a中小于4的元素個數(shù)。

剛剛接觸bind2nd不是很懂什么意思缀拭,這個剛開始我也不太懂,那就分心下源碼:

//第一步:開始找源代碼
template <class __Operation, class _Tp>
binder2nd<__Operation>    //返回類型
bind2nd(const __Operation& __op, const _Tp& __x)
    {return binder2nd<__Operation>(__op, __x);}    //臨時對象binder2nd咙好,倆參數(shù)傳進去,構(gòu)造對象初始值

//第二步勾效,找到binder2nd
template <class __Operation>
class _LIBCPP_TYPE_VIS_ONLY binder2nd
    : public unary_function<typename __Operation::first_argument_type,
                            typename __Operation::result_type>
{
protected:
    __Operation                                op;  //定義了操作類型,這里是less<int>
    typename __Operation::second_argument_type value;    //定義了操作的第二參數(shù)层宫,這里是 4
public:
    _LIBCPP_INLINE_VISIBILITY
    binder2nd(const __Operation& __x, const typename __Operation::second_argument_type __y)
        : op(__x), value(__y) {}      //構(gòu)造函數(shù)啊,在這里給上面定義的倆變量賦值
    _LIBCPP_INLINE_VISIBILITY typename __Operation::result_type operator()      //重載了()萌腿,一定是在哪里調(diào)到了
        (      typename __Operation::first_argument_type& __x) const
            {return op(__x, value);}    //這里就很明了了,調(diào)用的時候傳入了第一參數(shù)毁菱,我們?nèi)タ纯丛谀恼{(diào)用的。
    _LIBCPP_INLINE_VISIBILITY typename __Operation::result_type operator()
        (const typename __Operation::first_argument_type& __x) const
            {return op(__x, value);}
};

//第三步贮庞,找在哪里調(diào)用了__Operation(typename __Operation::first_argument_type& __x)
template <class _InputIterator, class _Predicate>
typename iterator_traits<_InputIterator>::difference_type
count_if(_InputIterator __first, _InputIterator __last, _Predicate __pred)
{
    typename iterator_traits<_InputIterator>::difference_type __r(0);
    for (; __first != __last; ++__first)
        if (__pred(*__first))    //這一句就是了,仿函數(shù)調(diào)用勘天,我們看到傳入第一參數(shù)。
            ++__r;
    return __r;
}

在STL里面的源碼我都加了注釋脯丝,一步一步找到調(diào)用的地方,簡單來說就是宠进,bind2nd通過binder2nd返回它的臨時對象,這個臨時對象記錄了操作(less<int>())和參數(shù)4材蹬,并重載了()吝镣,看到了沒堤器,這個就是仿函數(shù)了末贾,在count_if源代碼調(diào)用了(),傳入一個參數(shù)拱撵。這就是整個調(diào)用過程。我們這里實際上有兩處是仿函數(shù)拴测,一次是less<int>一次是binder2nd<less<int>>。這里我們并沒有看less的源代碼集索,有興趣可以看看汇跨,雖然很簡單渺鹦。

在C++11中bind2nd這個已經(jīng)被有了更好了用法扰法,那就是bind和lamda表達式毅厚。是因為這個太難理解了嗎浦箱?或許吧。

2酷窥、 accumulate()
累加計算

#include <iostream>
#include <algorithm>
#include <numeric>
#include "Measurement.h"
using namespace std;

struct myClass{
    int operator()(int x, int y){
        return 2*x+y;
    }
} myobj;

int main(int argc, const char * argv[]) {
    int init = 0;
    int arr[] = {10,20,30};
    cout<<accumulate(arr, arr+2, init)<<endl;
    
    cout<<accumulate(arr, arr+3, init, myobj);

    return 0;
}

3、 count()

count本身是一個算法蓬推,不是每一種容器都提供count,其中線性容器沒有count算法沸伏,而關(guān)聯(lián)容器由于本身的特性,它是已經(jīng)排好序的紅黑樹毅糟,所以本身提供count接口。


count

4喇肋、 find()

find需要注意的是一個問題是,在算法里提供了find算法蝶防,它的內(nèi)部實現(xiàn)是全遍歷,時間復(fù)雜度是O(n)间学,那么在所有的線性容器find都可以使用algorithms提供的算法贺喝,不需自己再寫菱鸥,但是set和map等關(guān)聯(lián)容器就不行了躏鱼,由于紅黑樹是一個高度平衡二叉樹,它自己內(nèi)部實現(xiàn)了更加效率的find算法染苛,其時間復(fù)雜度是O(log(n))主到,這也是為什么線性容器沒有find躯概,而關(guān)聯(lián)容器有find的原因登钥。

find()

5娶靡、 sort()

sort方法有個特例,就是list姿锭,list內(nèi)部實現(xiàn)了自己的sort,而其他的容器沒有自己的sort呻此,關(guān)聯(lián)容器本身不需要實現(xiàn)sort,因其本身就是按順序排列的焚鲜,其他的線性容器可以統(tǒng)一使用算法提供的sort,而list由于自身的特殊性糯彬,不需要移動每一個元素,因此自身做了優(yōu)化情连。

sort()

仿函數(shù)

仿函數(shù)的已經(jīng)提過很多了览效,STL里面到處都是仿函數(shù)却舀,這也是我們唯一可以修改的地方了吧锤灿,仿函數(shù)寫起來還是比較簡單的,一是小但校,二是比較容易擴容,但是想要和標準庫兼容還是需要繼承通用的父類状囱,負責(zé)是不能和標準庫兼容的。

STL仿函數(shù)使用舉例

仿函數(shù)的適配條件:

每種仿函數(shù)都有對應(yīng)的適配條件

這個跟函數(shù)適配器是有關(guān)系的袭艺。

適配器

適配器分很多類型:

  • 容器適配器
  • 函數(shù)適配器
  • 迭代器適配器

容器適配器
stack和queue是容器,但是他們在本質(zhì)上是適配器猾编,他們本身并沒有實現(xiàn)什么結(jié)構(gòu)和算法,而是把deque拿過來答倡,接口改造一下,實現(xiàn)了自己需要的功能瘪撇。

容器適配器

函數(shù)適配器
我們前面分析的bind2nd就是一個函數(shù)適配器,我們使用一下C++11提供的bind適配器设江。
bind返回一個函數(shù)對象ret攘轩,調(diào)用ret,相當(dāng)于調(diào)用function

int main(int argc, const char * argv[]) {
    using namespace std::placeholders;
    
    //綁定函數(shù)
    auto fn_five = bind(my_divide, 10, 2);
    cout<<fn_five()<<endl;
    
    auto fn_half = bind(my_divide, _1, 2);
    cout<<fn_half(10)<<endl;
    
    auto fn_invert = bind(my_divide, _2, _1);
    cout<<fn_invert(2, 10)<<endl;
    
    auto fn_rounding = bind<int>(my_divide, _1, _2);
    cout<<fn_rounding(10, 3)<<endl;
    
    //綁定成員函數(shù)和成員變量
    myPair ten_two {10,2};
    
    auto bound_memfn = bind(&myPair::multiply, _1);
    cout<<bound_memfn(ten_two)<<endl;
    
    auto bound_memdata = bind(&myPair::a, _1);
    cout<<bound_memdata(ten_two)<<endl;
    
    //舉例
    vector<int> v {1,2,3,4,5,6,7};
    
    auto fn = bind(less<int>(), _1, 5);
    cout<<count_if(v.begin(), v.end(), fn);
    cout<<count_if(v.begin(), v.end(), not1(bind2nd(less<int>(), 5)));
    
    return 0;
}

迭代器適配器

    list<int> foo, bar;
    for (int i = 1; i <= 5; i++) {
        foo.push_back(i);
        bar.push_back(i*10);
    }
    
    list<int>::iterator it = foo.begin();
    advance(it, 3);
    
    copy(bar.begin(), bar.end(), inserter(foo, it));
    
    for (auto x: foo) {
        cout<<x<<' ';
    }

輸出結(jié)果:
1 2 3 10 20 30 40 50 4 5 

inserter迭代器適配器度帮,在copy的代碼寫的很清楚,但是怎么執(zhí)行插入操作的瞳秽?迭代器適配器重載了=操作符,使得每次賦值的時候都會執(zhí)行插入操作率翅。


ostream_iterator
向控制臺輸出

    std::ostream_iterator<int> out_it(std::cout, ",");
    
    copy(foo.begin(), foo.end(), out_it);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末冕臭,一起剝皮案震驚了整個濱河市腺晾,隨后出現(xiàn)的幾起案子辜贵,更是在濱河造成了極大的恐慌,老刑警劉巖托慨,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異厚棵,居然都是意外死亡,警方通過查閱死者的電腦和手機婆硬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來哈误,“玉大人哩至,你說我怎么就攤上這事蜜自。” “怎么了重荠?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長戈鲁。 經(jīng)常有香客問我,道長婆殿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任怕磨,我火速辦了婚禮,結(jié)果婚禮上肠鲫,老公的妹妹穿的比我還像新娘。我一直安慰自己导饲,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布渣锦。 她就那樣靜靜地躺著浓体,像睡著了一般泡挺。 火紅的嫁衣襯著肌膚如雪命浴。 梳的紋絲不亂的頭發(fā)上娄猫,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天生闲,我揣著相機與錄音,去河邊找鬼碍讯。 笑死,一個胖子當(dāng)著我的面吹牛捉兴,可吹牛的內(nèi)容都是我干的录语。 我是一名探鬼主播禾乘,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼始藕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起伍派,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎诉植,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體倍踪,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了椒惨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡康谆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出沃暗,到底是詐尸還是另有隱情,我是刑警寧澤孽锥,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站惜辑,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏盛撑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一抵卫、第九天 我趴在偏房一處隱蔽的房頂上張望胎撇。 院中可真熱鬧殖氏,春花似錦创坞、人聲如沸受葛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至闰渔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間冈涧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工督弓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人愚隧。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像狂塘,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子荞胡,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內(nèi)容