GeekBand STL與泛型編程 第二周

5.容器(下)

Stack

Stack是一種先進后出(First In Last Out)的數據結構,只有一個出口:

  • 支持push、pop和top
  • 只能訪問頂層元素,不能遍歷
  • #include<stack>
template<class _Ty, class _Container = deque<_Ty>>
class stack{
    ...
};

默認使用的容器為deque。top范圍棧頂元素,但是不會彈出去,如果使用pop,則彈出棧頂元素。

Queue

Queue是一種先進先出(First In First Out)的數結構

  • 支持push,pop,front和back操作
  • 只能訪問最前或最后元素,不允許遍歷
  • #include<queue>
template<class _Ty, class _Container = deque<_Ty>>
class queue{
    ...
};

默認使用的容器為deque。不允許遍歷,沒有迭代器。

Map and Multimap

Map

  • 關聯式容器,存儲的對象是Key/Value pair
  • 不允許有重復的key
  • 對象必須具有可排序性能(Key)
template<class _Kty, class _Ty, class _Pr = less<_Kty>, 
    class _Alloc = allocator<pair<const _Kty, _Ty>>>
    class map{ ... };
  • 默認采用less排序
  • 可通過仿函數自定義排序
  • #include<map>
map.png

插入元素:

map1.insert(std::make_pair(4, Employee(L"Brown")));
map1[5] = Empolyee(L"Fisher");

刪除元素:

std::map<Person, PersonIdComparer>::iterator it = map1.begin();
map1.erase(it);

operator[]存取元素:

Employee& e = map1[2];
e.SetName(L"...");
...

Multimap

  • 類似map的容器
  • 允許key重復

Set and Multiset

Set

  • 關聯式容器,存儲的對象是即是Key又是Value
  • 不允許有重復的key
  • 對象必須具有可排序性能
template<class _Kty, class _Pr = less<_Kty>, 
    class _Alloc = allocator<_Kty>>
    class set{ ... };
  • 默認采用less排序,存儲對象必須具有operator<行為
  • 可通過仿函數自定義排序
  • #include<set>
set.png

插入元素:

ps1.insert(Person(L"Bill", 4));

刪除元素:

std::set<Person, PersonIdComparer>::iterator it = ps1.begin();
std::advance(it, 1); //it開始指向begin,通過advance增加1,之后指向第二個元素
ps1.erase(it);

相關算法:

set_union,合并兩個set(具有相同的存儲對象型別)

std::set<Person, PersonIdComparer> dest;
std::insert_iterator<std::set<Person, PersonIdComparer>> ii(dest, dest.begin());
std::set_union(ps1.begin(), ps1.end(), ps2.begin(), ps2.end(), ii, PersonIdComparer());
set_union.png

set_intersection,將連個set中相同的元素拿出來,放到新的set中。

std::set<Person, PersonIdComparer> dest;
std::insert_iterator<std::set<Person, PersonIdComparer>> ii(dest, dest.begin());
std::set_intersection(ps1.begin(), ps1.end(), ps2.begin(), ps2.end(), ii, PersonIdComparer());
set_intersection.png

set_difference,包含在[first1,last1)中而不包含在[first2,last2)中的元素。

std::set<Person, PersonIdComparer> dest;
std::insert_iterator<std::set<Person, PersonIdComparer>> ii(dest, dest.begin());
std::set_difference(ps1.begin(), ps1.end(), ps3.begin(), ps3.end(), ii, PersonIdComparer());
set_difference.png

特別注意:

  • 用于排序的成員(在Person中對象的Id,是真正的Key)不可改變
  • 出了真正的Key,其他成員可以改變但是需要特殊手法
std::set<Person, PersonIdCompare>::iterator it = ps1.find(Person(L"Bill", 4));
if(it != ps1.end()){
    it->SetName(L"Bill Gates"); //看上去沒有問題,但是錯誤,編譯不通過
}

set的實現方式不允許通過迭代器改變對象成員!

通過如下方式改變:

std::set<Person, PersonIdCompare>::iterator it = ps1.find(Person(L"Bill", 4));
if(it != ps1.end()){
    const_cast(Person&)<*it>.SetName(L"Bill Gates"); //通過const_cast將it轉化為Person 的引用,在修改Person的名字。
}

一定要cast為對象的引用,如下兩種方式可以編譯通過,但是無法改變對象的成員:

static_cast(Person)<*it>.SetName(L"Bill Gates");
((Person)(*it)).SetName(L"Bill Gates");

因為上面兩個方法的行為等同于下面的語句:

Person tempCopy(*it);
tempCopy.SetName(L"Bill Gates"); //改變的只是臨時對象,并非set中的對象

擴展測試代碼

#include <iostream>
#include <algorithm>
#include <string>
#include <set>

class Person{
public:
    Person(int id, std::string name): id(id), name(name){}
    std::string GetName() const{ return name; }
    void SetName(std::string n){ name = n; }
    int GetId() const{ return id; }
    void SetId(int i){ id = i; }
private:
    int id;
    std::string name;
};

std::ostream& operator<< (std::ostream& os, const Person& p){
    return os << "Id: " << p.GetId() << ", Name: " << p.GetName();
}

template<typename T>
struct PrintContainer{
    PrintContainer(std::ostream& out, std::string partition = "\n") : os(out), partition(partition){}
    void operator()(const T& x){ os << x << partition;}
    std::ostream& os;
    std::string partition;
};

template<class _Kty, class _Pr, class _Alloc>
void PrintSet(std::set<_Kty, _Pr, _Alloc> s){
    std::for_each(s.begin(), s.end(), PrintContainer<_Kty>(std::cout));
}

struct PersonIdCompare : public std::binary_function<Person, Person, bool>{
    bool operator()(Person p1, Person p2){
        return (p1.GetId() < p2.GetId()) ? true : false;
    }
};

struct PersonNameCompare : public std::binary_function<Person, Person, bool>{
    bool operator()(Person p1, Person p2){
        return (p1.GetName() < p2.GetName()) ? true : false;
    }
};

int main(){
    Person personArray1[3]={
        Person(1, "z_name"),
        Person(2, "y_name"),
        Person(3, "x_name")
    };

    std::set<Person, PersonIdCompare> psid1(personArray1, personArray1 + 3);
    std::cout << "psid1:" << std::endl;
    PrintSet(psid1);
    std::set<Person, PersonNameCompare> psname1(personArray1, personArray1 + 3);
    std::cout << "psname1:" << std::endl;
    PrintSet(psname1);

    std::set<Person, PersonIdCompare>::iterator it = psid1.find(Person(1, "z_name"));
    if(it != psid1.end()){
        std::cout << "((Person)(*it)).SetName(\"123\");" << std::endl;
        ((Person)(*it)).SetName("123");
        PrintSet(psid1);
        std::cout << "static_cast<Person>(*it).SetName(\"123\");" << std::endl;
        static_cast<Person>(*it).SetName("123");
        PrintSet(psid1);
        std::cout << "const_cast<Person&>(*it).SetName(\"123\");" << std::endl;
        const_cast<Person&>(*it).SetName("123");
        PrintSet(psid1);
    }
    getchar();
    return 0;
}

輸出結果:

psid1:
Id: 1, Name: z_name
Id: 2, Name: y_name
Id: 3, Name: x_name
psname1:
Id: 3, Name: x_name
Id: 2, Name: y_name
Id: 1, Name: z_name
((Person)(*it)).SetName("123");
Id: 1, Name: z_name
Id: 2, Name: y_name
Id: 3, Name: x_name
static_cast<Person>(*it).SetName("123");
Id: 1, Name: z_name
Id: 2, Name: y_name
Id: 3, Name: x_name
const_cast<Person&>(*it).SetName("123");
Id: 1, Name: 123
Id: 2, Name: y_name
Id: 3, Name: x_name

6&7.STL整體結構,仿函數,仿函數適配器

STL整體結構

  • 內存分配器 Allocator
  • 容器 Containers
  • 算法 Algorithms
  • 迭代器 Iterators
  • 仿函數 Functors
  • 適配器 Adapters
stl組件關系.png

仿函數

又稱作函數對象(Function Object),其作用相當于一個函數指針。

std::remove_if(v.begin(), v.end(), ContainsString(L"C+="));

STL中將這種行為函數指針定義為所謂的仿函數,其實現是一個class,再以仿函數產生一個對象作為算法的參數。

仿函數與算法之間的關系

Algorithm(Iterator first, Iterator last, ..., Functor func){
    ...
    func(...)//其實相當于調用對象中的operator()
    ...
}

仿函數的類別定義中必須重載函數調用(function call)operator()。

為什么要用仿函數而不用普通函數指針?

  • 普通函數指針不能滿足STL的抽象要求
  • 函數指針無法和STL其他組件交互

仿函數可以作為模板實參用于定義對象的某種默認行為

class Person{...};
std::set<Person, std::less<Person>> set1, set2; //operator< 排序
std::set<Person, std::greater<Person>> set1, set2; //operator> 排序
...
if(set1 === set2)...//正確,相同的型別
if(set1 === set3)...//錯誤,不同的型別!

仿函數適配器

將無法匹配的仿函數“套接”成可以匹配的型別

  • binder1st/binder2nd
  • mem_fun/mem_fun_ref

binder1st

給定一個vector,其元素為[0, 0, 0, 0, 0, 0, 0, 0, 10, 0],通過not_equal_to來匹配第一個非0元素。

std::not_equal_to<int> f; //適配前的仿函數

typename std::not_equal_to<int>::first_argument_type nonZeroElement(0);
    //本例中實際為int,在not_equal_to<T> 中,typedef first_argument_type為T類型,并賦初值0

std::vector<int>::iterator it = std::find_if(v.begin(), b.end(),
    std::binder1st<std::not_equal_to<int>>(f,nonZeroElement));
    //注意此處為binder1st

在實際使用時,標準庫封裝了一個模板函數,bind1st,這個函數把定義nonZeroElement和f的過程封裝掉了,因此以上三條語句可以簡化為一條:

std::find_if(v.begin(), b.end(),
    std::bind1st(std::not_equal_to<int>(),0));
    //注意此處為bind1st

binder2nd

和binder1st有所區別的是,binder2nd綁定的是右值,而binder1st綁定的是左值,因此以下兩條語句等價:

std::bind1st(std::less<int>(), 0);
std::bing2nd(std::greater<int>(), 0);

mem_fun

這個適配器用來適配對象的成員函數。

class Person{
public:
    Person(int id, std::string name): id(id), name(name){}
    std::string GetName() const{ return name; }
    void SetName(std::string n){ name = n; }
    int GetId() const{ return id; }
    void SetId(int i){ id = i; }
    void Print() const{ std::cout << "Id: " << id << ", Name: " << name << std::endl; }
private:
    int id;
    std::string name;
};
std::vector<Person*> v;
    v.push_back(new Person(1, "z_name"));
    v.push_back(new Person(2, "y_name"));
    v.push_back(new Person(3, "x_name"));
    
std::for_each(v.begin(), v.end(), &Person::Print);//編譯錯誤,for_each不接受這種形式的參數

通過如下方式可以正常調用:

std::for_each(v.begin(), v.end(), std::mem_fun(&Person::Print));

mem_fun_ref

與mem_fun不同的是,mem_fun_ref采用引用,調用成員函數時,使用reference而不是指針。

std::vector<Person> v;
    v.push_back(Person(1, "z_name"));
    v.push_back(Person(2, "y_name"));
    v.push_back(Person(3, "x_name"));
    
    std::for_each(v.begin(), v.end(), std::mem_fun_ref(&Person::Print));

其他需要注意的問題

  • std::string/std::wstring與vector<char>/vector<wchar_t>
    • 單線程情況下首選std::string/std::wstring
    • 多線程需注意string是否帶reference count,多線程下,避免分配和拷貝的reference count省下的開銷轉到了并發控制上,因此可考慮使用vector<char>/vector<wchar_t>,vector不帶reference conut
  • new出的對象放入容器后,要在銷毀容器前delete那些對象
  • 盡量用算法來替代手寫循環
  • 容器的size(大小)和capacity(容量)是不一樣的,可以使用swap為容器縮水
  • 在有對象繼承的情況下,建立指針的容器而不是對象的容器

8.泛型算法 非變易算法

非變易算法

非變易算法是一系列模板函數,在不改變操作對象的前提下對元素進行處理。查找、子序列搜索、統計、匹配等等。

  • for_each
template<class _InIt, class _Fn1> inline
_Fn1 for_each(_InIt _First, _InIt _Last, _Fn1 _Func)

在區間[First,_Last)上對每一個元素應用_Func函數。

  • find
template<class _InIt, class _Ty> inline
_InIt find(_InIt _First, _InIt _Last, const _Ty& _Val)

在區間[First,_Last)中,如果*it=Value則返回該it,沒有找到則返回_Last。

  • find_if
template<class _InIt, class _Pr> inline
_InIt find_if(_InIt _First, _InIt _Last, _Pr _Pred)

在區間[First,_Last)中,如果_Pred(*it)=true 則返回該it,沒有找到則返回_Last。

  • adjacent_find(1)
template<class _FwdIt> inline
_FwdIt adjacent_find(_FwdIt _First, _FwdIt _Last)

在區間[First,_Last)中,如果*it==*(it + 1)則返回該it,沒有找到則返回_Last。

  • adjacent_find(2)
template<class _FwdIt, class _Pr> inline
_FwdIt adjacent_find(_FwdIt _First, _FwdIt _Last, _Pr _Pred)

在區間[First,_Last)中,如果_Pred(*it, *(it + 1))==true 則返回該it,沒有找到則返回_Last。相當于adjacent_find(1)的_Pred默認使用的是equal_to。

  • find_first_of(1)
template<class _FwdIt1, class _FwdIt2> inline
_FwdIt1 find_first_of(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _FwdIt2 _Last2)

在區間[First1,_Last1)中的it1,使得對于區間[First2,_Last2)中某個it2,滿足 *it1 == *it2 則返回it1,如果沒有找到則返回_Last1。

  • find_first_of(2)
template<class _FwdIt1, class _FwdIt2, class _Pr> inline
_FwdIt1 find_first_of(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _FwdIt2 _Last2, _Pr _Pred)

在區間[First1,_Last1)中的it1,使得對于區間[First2,_Last2)中某個it2,滿足 _Pred(*it1, *it2) == true, 則返回it1,如果沒有找到則返回_Last1。

  • count
template<class _InIt, class _Ty> inline
typename iterator_traits<_InIt>::difference_type count(_InIt _First, _InIt _Last, const _Ty& _Val)

返回在區間[First,_Last)中滿足 *it==—Val 的迭代器的個數。

  • count_if
template<class _InIt, class _Pr> inline
typename iterator_traits<_InIt>::difference_type count_if(_InIt _First, _InIt _Last, _Pr _Pred

返回在區間[First,_Las1)中滿足 _Pred(*it)==true 的迭代器的個數。

  • mismatch(1)
template<class _InIt1, class _InIt2> inline pair<_InIt1, _InIt2>
mismatch(_InIt1 _First1, _InIt1 _Last1, _InIt2 _First2, _InIt2 _Last2)

在區間[First1,_Last1)中的it1,滿足 *it1 != *(_First2 + (it1 - _First1)), 返回pair<it1, _First2 + (it1 - _First1)>,如果找不到則返回pair<_Last1, _First2 + (_Last1 - _First1)>。

  • mismatch(2)
template<class _InIt1, class _InIt2, class _Pr> inline pair<_InIt1, _InIt2>
mismatch(_InIt1 _First1, _InIt1 _Last1, _InIt2 _First2, _InIt2 _Last2, _Pr _Pred)

在區間[First1,_Last1)中的it1,滿足 _Pred(*it1 ,*(_First2 + (it1 - _First1)))==false, 返回pair<it1, _First2 + (it1 - _First1)>,如果找不到則返回pair<_Last1, _First2 + (_Last1 - _First1)>。

  • equal(1)
template<class _InIt1, class _InIt2> inline 
bool equal(_InIt1 _First1, _InIt1 _Last1, _InIt2 _First2, _InIt2 _Last2)

在區間[First1,_Last1)中滿足*it1 == *(_First2 + (it1 - _First1))則返回true,否則返回false。

  • equal(2)
template<class _InIt1, class _InIt2, class _Pr> inline 
bool equal(_InIt1 _First1, _InIt1 _Last1, _InIt2 _First2, _InIt2 _Last2, _Pr _Pred)

在區間[First1,_Last1)中滿足_Pred(*it1, *(_First2 + (it1 - _First1)))==true,則返回true,否則返回false。

  • search(1)
template<class _FwdIt1, class _FwdIt2> inline
_FwdIt1 search(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _FwdIt2 _Last2)

在區間[First1,_Last1)中的it1,對于每一個在區間[First2,_Last2)中的it2,滿足*(it1 + (it2 - _First2)) == *it2; 則返回it1,否在返回_Last1。

  • search(2)
template<class _FwdIt1, class _FwdIt2, class _Pr> inline
_FwdIt1 search(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _FwdIt2 _Last2, _Pr _Pred)

在區間[First1,_Last1)中的it1,對于每一個在區間[First2,_Last2)中的it2,滿足_Pred(*(it1 + (it2 - _First2)), *it2) == true; 則返回it1,否在返回_Last1。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容