[C++]容器部分

順序容器

概述

vector

deque    雙端隊列

list     雙向鏈表

forward  單向鏈表

array    固定大小數組

string


容器的類型別名

iterator            迭代器類型

const_iterator      迭代器,可讀不可寫

size_type

difference_type     兩個迭代器之間的距離

value_type          元素類型

reference           value_type&

const_reference     const value_type&


begin與end的版本

rbegin,rend        反向遍歷

cbegin,cend        const_iterator



初始化

直接拷貝整個容器:要求容器類型和元素類型都相同

由一個迭代器指定元素範圍:元素類型能轉換即可

list<char*> list_char(vector_string.begin(), vector_string.end())

列表初始化、與大小相關的構造函數

array的初始化必須指定大小:array<int, 10> ay;


賦值和交換

c1=c2要求左右兩邊具有相同的類型


c1.assign(c2.begin(),c2.end());

其中c1,c2類型可以不同

賦值過後所有迭代器、指針和引用都會失效


swap(c1,c2)

c1.swap(c2)

不涉及拷貝動作,迭代器,指針和引用依然有效

相當於舊c1的迭代器變成了新c2的迭代器

兩個array交換會有拷貝過程


大小比較

只有容器其中的元素類型定義了比較運算符時纔可以比較

比較類似於char*的strcmp過程


添加元素

c.push_back(x);       //尾部添加
c.emplace_back(x);    //同上
c.push_front(x);
c.emplace_frone(x);
c.insert(p,t);        //在迭代器p前插入一個t,返回指向添加的t的迭代器
c.emplace(p,t);
c.insert(p,n,t);      //在迭代器p前插入n個t,返回指向新添加的第一個
c.insert(p,b,e);      //在迭代器p前插入迭代器b和e之間的元素,返回其中的首個
c.insert(p,il);       //il爲{}包圍的元素值列表

可以用insert在vector,deque,string插入,但效率低

emplace操作是直接在c的空間中使用構造函數構造了一個x,省去局部臨時對象,當x爲一個自定義類元素時比較好理解。

forward_list型只能在首部進行操作


訪問元素

c.back();        //返回尾部引用
c.front();       //返回首部引用
c[n];
c.at(n);

注意訪問過程中一直爲元素的引用

array<int, 5> ay = {1,2,3,4,5};
int &x = ay.at(0);
int y = ay.at(1);
x = 10;                            //改變了ay[0]
y = 10;                            //未改變ay[1]

back不適用於forward_list


刪除元素

c.pop_back();
c.pop_front();
c.erase(p);        //返回p後迭代器
c.erase(b,e);      //返回e後迭代器
c.clear();

vector和string在進行刪除操作後,其迭代器會失效


forward_list的特殊操作

flst.before_begin();        //指向鏈表首元素之前並不存在的元素的迭代器
flst.cbefore_begin();
lst.insert_after(p,t);      //返回一個指向插入的最後一個元素的迭代器
lst.insert_after(p,n,t);
lst.insert_after(p,b,e);
lst.insert_after(p,li);
lst.erase_after(p);         //返回最後一個被刪除的元素之後元素的迭代器
lst.erase_after(b,e);


改變容器大小

vt.resize(n);        //若n比原來大,添加新元素,初始化;否則刪除多餘的
vt.resize(n,t);      //調整vt,有新加元素的話均爲t

同樣不支持array


迭代器失效

迭代器指向的元素由於插入,刪除而導致其位置發生了改變,此時迭代器會失效
不要保存end返回的迭代器,用.end()


管理vector的大小

vt.shrink_to_fit();
vt.capacity();
vt.reserve(n);        //將capacity置爲n

使用resize()只是針對的都是以後元素

vector<int> vt(10, 1);
vt.resize(15,2);
vt.resize(5,2);

第一次resize後,在vt後又補了5個2

第二次resize後,vt中只剩下了5個2,capacity會與resize前一樣

capacity的預分配大小取決於編譯器


容器適配器

一種機制,使某種事物的行爲看起來像另一種事物

默認情況下,

stack:deque

queue:deque

priority_queue:vector

可以重載默認容器類型

stack<string, vector<string>> str_stk;

typeoperation supportedbasic type
stackpush_back, pop_back, backdeque, vector, list
queueback, push_back, front, pop_frontdeque, list
priority_queuefront,push_back,pop_front,random visitvector, deque

其它操作

stk.top();                //返回棧頂元素
que.front();              //返回隊首元素
prique.top();             //返回優先級最高元素



泛型算法

一些經典算法的通用接口;可用於不同類型的元素和多種容器類型。

auto result = find(vec.begin(), vec.end(), value);

查找成功,返回首個的迭代器;查找失敗,返回第二個參數,即查找範圍的尾後。

元素本身必須保證支持比較運算符<=>

頭文件:algorithm, numeric


只讀算法


count(vt.cbegin(), vt.cend(), val);               //統計目標範圍中val的數量
accumulate(vt.cbegin(), vt.cend(), sum);          //sum+目標範圍求和,可以是任何定義了+的類型,const char*就不行
equal(vt1.cbegin(), vt1.cend(), vt2.cbegin());    //逐個比較對應元素是否相等,假定vt2比vt1長

寫算法

算法不檢查寫操作,所以要保證目標範圍確實存在

fill(vt.begin(), vt.end(), val);        //目標範圍中元素重置爲零
fill_n(vt.begin(), n, val);     //起始位置後連續n個置爲val

back_inserter

頭文件iterator

接受一個容器的引用,返回與其綁定的插入迭代器,使用它賦值時會自動調用push_back()

vector<int> vt;
fill_n(back_inserter(vt),10,1);

拷貝算法

auto ret = copy(vt1.begin(), vt1.end(), vt2.begin());    //保證vt2不比源範圍小
replace(vt.begin(), vt.end(), s_val, d_val);             //在vt中將s_val替換爲d_val
replace_copy(vt1.begin(), vt1.end(), back_inserter(vt2), s_val, d_val);
//將替換後的結果保存在vt2中

排序算法

sort(words.begin(), words.end());                        //升序排列words
auto end_unique = unique(words.begin(), words.end());
//將不重複的元素按照原順序放在最前面,後面的按原順序被覆蓋
words.erase(end_unique, word.end());

stable_sort(words.begin(), words.end())爲排序的穩定版本


定製操作

向算法中傳遞函數

sort(words.begin(), words.end(), isShorter());

bool isShorter(const string &s1, const string &s2)爲比較words中的元素大小,排序結果按照爲真的情況排列


lambda表達式

一個可調用對象,類似於一個未命名的內聯函數。

相當於可以傳參的函數指針,必須使用尾置返回

結構

[capture list](parameter list) -> return type {function body}

int num = 1;
auto fun = [num](const int &a, const int &b){ return a + b + num;};
cout << fun(2, 3) << endl;

替換函數指針時效果更明顯


捕獲和返回

值捕獲

引用捕獲

使用&符號,保證在lambda執行時變量是存在的

隱式捕獲

=表示全部爲值捕獲,&表示全部爲引用捕獲

例子可以直接改寫爲

auto fun = [=](const int &a, const int &b){ return a + b + num;};

混合捕獲

[&, identifier_list]:除identifier_list中的各項,其餘的都是是引用捕獲

[=, identifier_list]:除identifier_list中的各項,其餘的都是是值捕獲

identifier_list由逗號隔開


可變lambda

對於值捕獲,lamda內部的使用相當於拷貝,但要在參數列表後加上mutable關鍵字纔可以修改

int num = 1;
auto fun = [num]() mutable { return ++num;};
num = 0;
cout << fun() << endl;        //輸出爲2
cout << num << endl;          //輸出爲0
cout << fun() << endl;        //輸出爲3

對於引用捕獲,取決於引用指向的是否爲const類型

int num = 1;
auto fun = [&num]{ return ++num;};
num = 0;
cout << fun() << endl;        //num爲1
cout << num;                  //num爲1


lambda的返回類型

如果一個lambda體包含return之外的任何語句,編譯器會默認假定其返回類型爲void

只有一條return時

auto lb = [](int i) {
    return i < 0 ? -i : i;
};
transform(vt.begin(), vt.end(), vt.begin(), lb);

報錯

auto lb = [](int i) {
    if(i < 0)
        return -i;
    else
        return i;
};
transform(vt.begin(), vt.end(), vt.begin(), lb);

後置返回類型

auto lb = [](int i) -> int
{
    if(i < 0)
        return -i;
    else
        return i;
};
transform(vt.begin(), vt.end(), vt.begin(), lb);


參數綁定

bind函數頭文件functional中

auto newCallable = bind(callable, arg_list);

arg_list由逗號隔開

其中類似於"_n"形式的參數,表示callable中的第n個參數,在命名空間std::placeholders中

using namespace std;
using namespace std::placeholders;
bool loc(int num, int boundary)
{
    return num >= boundary;
}
int main()
{
    int ay[] = {-3,-2,-1,0,1,2,3};
    vector<int> vt(begin(ay), end(ay));
    auto loc0 = bind(loc, _1, 0);
    auto it = find_if(vt.begin(), vt.end(), loc0);
    for( ; it != vt.end(); it++)
    {
        cout << *it << endl;
    }
}

還可以用bind重新對參數進行排序,實現一些有趣的功能

sort(words.bedin(), words.end(), isShorter);
//調整isShorter參數位置,變成由長到短進行排序
sort(words.bedin(), words.end(), bind(isShorter, _2, _1));


綁定引用參數

bind的過程默認是由“拷貝”實現的,對於需要以引用方式傳遞的參數,比如ostream類型,使用ref函數

void print(ostream &os, const int num, const char splitChar)
{
    os << num << splitChar;
}
int main()
{
    int ay[] = {-3,-2,-1,0,1,2,3};
    vector<int> vt(begin(ay), end(ay));
    auto loc0 = bind(loc, _1, 0);
    auto it = find_if(vt.begin(), vt.end(), loc0);
    for_each(vt.begin(), vt.end(), bind(print, ref(cout), _1, ' '));
}


迭代器詳解

插入迭代器

back_inserter

front_inserter

inserter

int ay[] = {-3,-2,-1,0,1,2,3};
vector<int> vt(begin(ay), end(ay));
list<int> lt;
copy(vt.cbegin(), vt.cend(), inserter(lt, lt.begin()));    //與vt相同
copy(vt.cbegin(), vt.cend(), front_inserter(lt));    //前面多了32..-3


iostream迭代器

直接讀取輸入流、像輸出流寫

vector<int> vt;
istream_iterator<int> int_it(cin);
istream_iterator<int> end;
while(int_it != end)
{
    vt.push_back(*int_it++);
}

或者直接寫成

istream_iterator<int> int_it(cin);
istream_iterator<int> end;
vector<int> vt(int_it, end);

也可以直接用於算法

綁定時不讀取,直到使用迭代器時才真正讀取


ostream_iterator<T> out(os);
ostream_iterator<T> out(os, str);    //str爲一個C風格的字符串,會跟在每次打印的後面
out = val;                           //將val寫入到out對應的輸出流
*out, ++out, out++;                  //實際沒有任何動作

例子,把vt中的元素都打出來

ostream_iterator<int> int_it(cout, "\t");
for(auto it = vt.begin(); it != vt.end(); it++)
{
    *int_it++ = *it;
}

*int_it++只是爲了與其它迭代器保持一致

使用copy更爲簡潔

copy(vt.begin(), vt.end(), int_it);

只要類元素支持輸入(>>)和輸出(<<)運算符,都可以會用流迭代器


反向迭代器

rbegin, rend

crbegin, crend

按實際位置來看,rbegin++相當於end--

所以可以使用反向迭代器來做遞減排序

sort(vec.rbegin(), vec.rend())

base成員函數可以將反向迭代器轉變正向迭代器,即其真實位置向右移一位,且++運算變向。


泛型算法結構

迭代器類別

輸入迭代器,輸出迭代器,前向迭代器,雙向迭代器,隨機訪問迭代器


算法形參模式

alg(beg, end, other args)

alg(beg, end, dest, other args)

alg(beg, end, beg2, other args)

alg(beg, end, beg2, end2 other args)

單個目標迭代器

如帶有dest的算法,假定目標空間足夠容納寫入的數據,更常見的是dest爲ostream_iterator

接受第二個序列範圍

沒有end2時,假定第二個範圍不比第一個範圍小


命名規範

使用重載形式傳遞一個謂詞

比如用來代替<=>

unique(beg, end);

unique(beg, end, cmp);

cmp提供判斷兩個元素是否相等的謂詞


_if版本的算法

find(beg, end, val);

find_if(beg, end, pred);

pred查找提供判斷範圍中的元素是否爲真的謂詞


拷貝與否的版本

reverse(beg, end):

reverse_copy(beg, end, dest):

將處理後的元素保存到dest中


特定容器算法

list, forward_list定義了獨有的sort, merge, remove, reverse和unique

splice成員

參數列表有三種

(p, lst2)  (p, lst2, p2)  (p, lst2, b, e)


lst.splice(args)flst.splice_after(args)
插入位置p之前p之後
插入元素p2指向的元素
p2之後的那一個元素


鏈表特有操作的核心是:改變底層容器


關聯容器


兩個主要的關聯容器:map和set。map是“關鍵字-值”,set只有“關鍵字”。

8種容器主要在三個維度上有所區分:

map or set

關鍵字可否重複

是否有序保存:頭文件對應爲unordered_map和unordered_set


使用

map<string, size_t> word_count;

可以直接使用下標訪問

++word_count[word];

word如果不在map中,下標運算符會自動創建一個新元素,初始值爲0

每個key-value對是一個"pair"類型,可以通過迭代器來遍歷,內部元素分別對應爲first和second

int main()
{
    map<string, size_t> word_count;
    string word;
    while(cin >> word)
    {
        ++word_count[word];
    }
    for(auto it = word_count.begin(); it != word_count.end(); it++)
    {
        cout << (*it).first << ' ' << it -> second << endl;
    }
}

在vs中不支持列表初始化,可以用相應類型的數組

map<string, size_t>::value_type init[] = {
    map<string, size_t>::value_type("b", 1),
    map<string, size_t>::value_type("b", 1)
};
map<string, size_t> word_count(begin(init), end(init));


set<string>

高效的關鍵字查詢操作,檢查一個給定關鍵字是否在set中

string str[] = {"a", "an", "the"};
set<string> exclude;
exclude.insert(begin(str), end(str));
while(cin >> word)
{
    if(exclude.find(word) == exclude.end())
    {
        ++word_count[word];
    }
}


關鍵字類型的要求

對於有序容器而言,關鍵字必須定義元素比較的方法,默認會採用<

關鍵字類型上必須有“嚴格弱序”的定義

自定義比較函數


struct personInfo{
    string name;
    int age;
};
bool comparePerson(const personInfo &p1, const personInfo &p2)
{
    return p1.name < p2.name;
}
int main()
{
    map<personInfo, size_t, bool (*) (const personInfo &, const personInfo &)> word_count(comparePerson);
    //等價於map<personInfo, size_t, decltype(comparePerson) *> word_count(comparePerson);
}


pair類型

頭文件utility,用來生成特定類型的模板

定義及初始化

pair<string, int> pi("tom", 12);

pair<string, int> pie("tom", 12);    //空的pair

數據成員是public的,通過.first和.second訪問

比較

兩個元素都相等時才相等,判斷大小按照先比較first,再比較second的原則


基本操作

額外類型

key_type:關鍵字類型

mapped_type:每個關鍵字關聯的類型

value_type:對於set,與key_type相同;對於map,相應的pair類型

關鍵字部分是const的,不能改變


迭代器

set的迭代器類型是const的,不能修改關鍵字

使用迭代器遍歷關聯容器時,會按關鍵字升序遍歷元素


泛型算法

關鍵字是const,不能將關聯容器傳遞給修改或重排容器元素的算法

對於只讀取元素的算法,關聯容器中的元素不能通過它們的關鍵字進行快速查找,使用其專用的算法會快得多。

可以將關聯容器當做源序列目的位置來做。


添加元素

set.insert(vt.begin(), vt.end());
set.insert({2,4,6});                //vs2010不支持
person.insert({"han", 34});           //vs2010不支持
person.insert(make_pair("tom", 23));
person.insert(pair<string, int>("jack", 33));
person.insert(map<string, int>::value_type("jack", 33));

對應的同樣有emplace函數

返回值

對於不包括重複關鍵字的容器,返回一個pair,first是一個指向對應關鍵字的迭代器,second是一個bool值,表示插入是否成功。

若關鍵字已在容器中,second爲false。

對於包括重複關鍵字的容器,每次insert都會成功,返回指向新元素迭代器。


刪除元素

函數參數類型返回值操作
c.erase(k)關鍵字刪除元素的個數刪除關鍵字值爲k的元素
c.erase(p)迭代器void刪除p指定的元素
c.erase(b, e)迭代器對void刪除指定範圍內的元素


map的下標操作(include unordered)

word_count["a"] = 1;

若word_count中不含有關鍵字值爲"a"的元素會默認創建一個,首先初始化值爲0,再將1賦予它

c.at(k)

訪問關鍵字爲k的元素,帶參數檢查,若不在c中,會拋出一個“out_of_range”異常

返回值

注意,map迭代器解引用操作和下標操作所得到的結果是不同的,前者返回value_type類型,後者返回mapped_type類型


訪問元素

c.find(k);        //返回第一個指向關鍵字爲k的元素迭代器,若不在c中,返回c.end()

c.count(k);       //返回關鍵字爲k的元素數量,對於不允許關鍵字重複的容器,只有0或1

c.lower_bound(k); //返回第一個關鍵字不小於k的元素的迭代器

c.upper_bound(k); //返回第一個關鍵字大於k的元素的迭代器

c.equal_range(k); //返回一個迭代器pair,表示關鍵字等於k的元素的範圍,若不存在,則兩個都爲c.end()

在multi中查找元素

(1)在multimap及multiset之中,具有相同關鍵字的元素會相鄰存儲,所以利用find查找到第一個的迭代器,再根據count鎖定範圍。

(2)使用lower_bound和upper_bound確定範圍

(3)直接使用equal_bound



無序容器

不是用比較來組織元素,而是使用哈希函數

在關鍵字類型的元素沒有明顯的序關係情況下很有效

使用無序容器遍歷並輸出,關鍵字未必會有序

根據關鍵字計算出哈希值,一個哈希值對應一個桶,相當於“拉鍊法”


桶接口

c.bucket_count();        //正在使用的桶數目
c.max_bucket_count();    //能容納的最多的桶數量
c.bucket_size();         //第n個桶中有多少元素
c.bucket(k);             //關鍵字爲k的元素在哪個桶中


桶迭代器

local_iterator

const_local_iterator

c.begin(n), c.end(n)    桶n的首和尾後

c.cbegin(n), c.cend(n)    


哈希策略

c.load_factor();        //每個桶的平均元素數量,返回float
c.max_load_factor();    //c試圖維護的平均桶大小
c.rehash(n);            //重新存儲,保證bucket_count>=n且bucket_count>size/max_load_factor
c.reserve(n);           //重新存儲,使得c可以保存n個元素且不必rhash

reserve相當於預先分配了n個元素的空位,保證不引起rehash++


對關鍵字類型的要求

默認情況會用==來比較關鍵字值,使用hash<key_type>來生成hash值。標準庫爲內置類型,string,指針以及智能指針提供了hash模板

對於自定義類型需要提供hash模板

bool equalPerson(const PersonInfo &p1, const PersonInfo &p2)
{
    return p1.name == p2.name;
}
size_t hasher(const PersonInfo &person)
{
    return hash<string>()(person.name);
}
int main()
{
    unordered_multimap<string, int, decltype(hasher) *, decltype(equalPerson) *> person(40, hasher, equalPerson);
//40是桶大小
}

如果類中重載了==運算符,可以省去equal函數。


關聯容器特點總結

(1)map和set內部實現是採用非線性的二叉樹結構,具體的說是紅黑樹的結構原理;

(2)關聯容器對元素的插入和刪除操作比vector 要快,比list 要慢,因爲list 是線性的,而關聯容器是二叉樹結構

(3)map和set的查找的複雜度基本是Log(N)


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章