順序容器
概述
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;
type | operation supported | basic type |
stack | push_back, pop_back, back | deque, vector, list |
queue | back, push_back, front, pop_front | deque, list |
priority_queue | front,push_back,pop_front,random visit | vector, 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)