STL總結與常見面試題+資料

1 STL概述

爲了建立數據結構和算法的一套標準,並且降低他們之間的耦合關係,以提升各自的獨立性、彈性、交互操作性(相互合作性,interoperability),誕生了STL。

STL提供了六大組件,彼此之間可以組合套用,這六大組件分別是:容器、算法、迭代器、仿函數、適配器(配接器)、空間配置器

  • 容器:各種數據結構,如vector、list、deque、set、map等,用來存放數據,從實現角度來看,STL容器是一種class template。

  • 算法:各種常用的算法,如sort、find、copy、for_each。從實現的角度來看,STL算法是一種function tempalte.

  • 迭代器:扮演了容器與算法之間的膠合劑,共有五種類型,從實現角度來看,迭代器是一種將operator* , operator-> , operator++,operator–等指針相關操作予以重載的class template. 所有STL容器都附帶有自己專屬的迭代器,只有容器的設計者才知道如何遍歷自己的元素。原生指針(native pointer)也是一種迭代器。

  • 仿函數:行爲類似函數,可作爲算法的某種策略。從實現角度來看,仿函數是一種重載了operator()的class 或者class template

  • 適配器:一種用來修飾容器或者仿函數或迭代器接口的東西。

  • 空間配置器:負責空間的配置與管理。從實現角度看,配置器是一個實現了動態空間配置、空間管理、空間釋放的class tempalte.

STL六大組件的交互關係,容器通過空間配置器取得數據存儲空間,算法通過迭代器存儲容器中的內容,仿函數可以協助算法完成不同的策略的變化,適配器可以修飾仿函數。

2 STL的優點:

  1. STL 是 C++的一部分,因此不用額外安裝什麼,它被內建在你的編譯器之內。

  2. STL 的一個重要特性是將數據和操作分離。數據由容器類別加以管理,操作則由可定製的算法定義。迭代器在兩者之間充當“粘合劑”,以使算法可以和容器交互運作

  3. 程序員可以不用思考 STL 具體的實現過程,只要能夠熟練使用 STL 就 OK 了。這樣他們就可以把精力放在程序開發的別的方面。

  4. STL 具有高可重用性,高性能,高移植性,跨平臺的優點。

  5. 高可重用性:STL 中幾乎所有的代碼都採用了模板類和模版函數的方式實現,這相比於傳統的由函數和類組成的庫來說提供了更好的代碼重用機會。

  6. 高性能:如 map 可以高效地從十萬條記錄裏面查找出指定的記錄,因爲 map 是採用紅黑樹的變體實現的。

  7. 高移植性:如在項目 A 上用 STL 編寫的模塊,可以直接移植到項目 B 上。容器和算法之間通過迭代器進行無縫連接。STL 幾乎所有的代碼都採用了模板類或者模板函數,這相比傳統的由函數和類組成的庫來說提供了更好的代碼重用機會。

3 容器

STL容器就是將運用最廣泛的一些數據結構實現出來。

常用的數據結構:數組(array) , 鏈表(list), tree(樹),棧(stack), 隊列(queue), 集合(set),映射表(map), 根據數據在容器中的排列特性,這些數據分爲序列式容器和關聯式容器兩種。

序列式容器強調值的排序,序列式容器中的每個元素均有固定的位置,除非用刪除或插入的操作改變這個位置。Vector容器、Deque容器、List容器等。

關聯式容器是非線性的樹結構,更準確的說是二叉樹結構。各元素之間沒有嚴格的物理上的順序關係,也就是說元素在容器中並沒有保存元素置入容器時的邏輯順序。關聯式容器另一個顯著特點是:在值中選擇一個值作爲關鍵字key,這個關鍵字對值起到索引的作用,方便查找。Set/multiset容器 Map/multimap容器

容器 底層數據結構 時間複雜度 有無序 可不可重複
array 數組 隨機讀改 O(1) 無序 可重複
vector 數組 隨機讀改、尾部插入、尾部刪除 O(1)頭部插入、頭部刪除 O(n) 無序 可重複
deque 雙端隊列 頭尾插入、頭尾刪除 O(1) 無序 可重複
forward_list 單向鏈表 插入、刪除 O(1) 無序 可重複
list 雙向鏈表 插入、刪除 O(1) 無序 可重複
stack deque / list 頂部插入、頂部刪除 O(1) 無序 可重複
queue deque / list 尾部插入、頭部刪除 O(1) 無序 可重複
priority_queue vector /max-heap 插入、刪除 O(log2n) 有序 可重複
set 紅黑樹 插入、刪除、查找 O(log2n) 有序 不可重複
multiset 紅黑樹 插入、刪除、查找 O(log2n) 有序 可重複
map 紅黑樹 插入、刪除、查找 O(log2n) 有序 不可重複
multimap 紅黑樹 插入、刪除、查找 O(log2n) 有序 可重複
unordered_set 哈希表 插入、刪除、查找 O(1) 最差 O(n) 無序 不可重複
unordered_multiset 哈希表 插入、刪除、查找 O(1) 最差 O(n) 無序 可重複
unordered_map 哈希表 插入、刪除、查找 O(1) 最差 O(n) 無序 不可重複
unordered_multimap 哈希表 插入、刪除、查找 O(1) 最差 O(n) 無序 可重複

1 array

array 是固定大小的順序容器,它們保存了一個以嚴格的線性順序排列的特定數量的元素。

方法 說明
begin 返回指向數組容器中第一個元素的迭代器
end 返回指向數組容器中最後一個元素之後的理論元素的迭代器
rbegin 返回指向數組容器中最後一個元素的反向迭代器
rend 返回一個反向迭代器,指向數組中第一個元素之前的理論元素
cbegin 返回指向數組容器中第一個元素的常量迭代器(const_iterator)
cend 返回指向數組容器中最後一個元素之後的理論元素的常量迭代器(const_iterator)
crbegin 返回指向數組容器中最後一個元素的常量反向迭代器(const_reverse_iterator)
crend 返回指向數組中第一個元素之前的理論元素的常量反向迭代器(const_reverse_iterator)
size 返回數組容器中元素的數量
max_size 返回數組容器可容納的最大元素數
empty 返回一個布爾值,指示數組容器是否爲空
operator[] 返回容器中第 n(參數)個位置的元素的引用
at 返回容器中第 n(參數)個位置的元素的引用
front 返回對容器中第一個元素的引用
back 返回對容器中最後一個元素的引用
data 返回指向容器中第一個元素的指針
fill 用 val(參數)填充數組所有元素
swap 通過 x(參數)的內容交換數組的內容
get(array) 形如 std::get<0>(myarray);傳入一個數組容器,返回指定位置元素的引用
relational operators (array) 形如 arrayA > arrayB;依此比較數組每個元素的大小關係

測試代碼

#include<iostream>
#include<array>
using namespace std;

int main() 
{
	array<int, 8> myArr = {1,3,4,6,9};//固定大小爲8
	cout << "myArr元素序列:";
	for (auto i = 0; i < 8; ++i) 
	{
		cout << myArr[i] << " ";
	}
	cout << endl;

	array<int, 8> myArr1 = {2,3,4,7,8,9};//固定大小爲8
	cout << "myArr1元素序列:";
	for (auto i = 0; i < 8; ++i) 
	{
		cout << myArr1[i] << " ";
	}
	cout << endl;

	myArr.swap(myArr1);   //交換兩個容器的內容
	cout << "交換myArr與myArr1"<< endl;
	cout << endl;

	cout << "myArr.at(3) = " << myArr.at(3) << endl;//任意訪問
	cout << "myArr[3] = " << myArr[3] << endl;//任意訪問
	cout << "myArr.front() = " << myArr.front() << endl;//獲取第一個元素
	cout << "myArr.back() =  " << myArr.back() << endl;//獲取最後一個元素
	cout << "myArr.data() = " << myArr.data() << endl;//獲取第一個元素的指針
	cout << "*myArr.data() = " << *myArr.data() << endl;//獲取第一個元素的指針指向的元素

	cout << "正向迭代器遍歷容器:";
	for (auto it = myArr.begin(); it != myArr.end(); ++it) 
	{
		cout << *it << " ";
	}
	cout << endl;
	//逆向迭代器測試
	cout << "逆向迭代器遍歷容器:";
	for (auto rit = myArr.rbegin(); rit != myArr.rend(); ++rit) 
	{
		cout << *rit << " ";
	}
	cout << endl;
	//正向常迭代器測試
	cout << "正向常迭代器遍歷容器:";
	for (auto it = myArr.cbegin(); it != myArr.cend(); ++it) 
	{
		cout << *it << " ";
	}
	cout << endl;
	//逆向常迭代器測試
	cout << "逆向常迭代器遍歷容器:";
	for (auto rit = myArr.crbegin(); rit != myArr.crend(); ++rit) 
	{
		cout << *rit << " ";
	}
	cout << endl;
	if(myArr.empty())
		cout << "myArr爲空 " << endl;
	else
		cout << "myArr不爲空 " << endl;
	cout << "myArr.size() = " << myArr.size() << endl;
	cout << "myArr.max_size() = " << myArr.max_size() << endl;

	return 0;
}

運行結果

運行結果

vector

vector 是表示可以改變大小的數組的序列容器。

方法 說明
vector 構造函數
~vector 析構函數,銷燬容器對象
operator= 將新內容分配給容器,替換其當前內容,並相應地修改其大小
begin 返回指向容器中第一個元素的迭代器
end 返回指向容器中最後一個元素之後的理論元素的迭代器
rbegin 返回指向容器中最後一個元素的反向迭代器
rend 返回一個反向迭代器,指向中第一個元素之前的理論元素
cbegin 返回指向容器中第一個元素的常量迭代器(const_iterator)
cend 返回指向容器中最後一個元素之後的理論元素的常量迭代器(const_iterator)
crbegin 返回指向容器中最後一個元素的常量反向迭代器(const_reverse_iterator)
crend 返回指向容器中第一個元素之前的理論元素的常量反向迭代器(const_reverse_iterator)
size 返回容器中元素的數量
max_size 返回容器可容納的最大元素數
resize 調整容器的大小,使其包含 n(參數)個元素
capacity 返回當前爲 vector 分配的存儲空間(容量)的大小
empty 返回 vector 是否爲空
reserve 請求 vector 容量至少足以包含 n(參數)個元素
shrink_to_fit 要求容器減小其 capacity(容量)以適應其 size(元素數量)
operator[] 返回容器中第 n(參數)個位置的元素的引用
at 返回容器中第 n(參數)個位置的元素的引用
front 返回對容器中第一個元素的引用
back 返回對容器中最後一個元素的引用
data 返回指向容器中第一個元素的指針
assign 將新內容分配給 vector,替換其當前內容,並相應地修改其 size
push_back 在容器的最後一個元素之後添加一個新元素
pop_back 刪除容器中的最後一個元素,有效地將容器 size 減少一個
insert 通過在指定位置的元素之前插入新元素來擴展該容器,通過插入元素的數量有效地增加容器大小
erase 從 vector 中刪除單個元素(position)或一系列元素([first,last)),這有效地減少了被去除的元素的數量,從而破壞了容器的大小
swap 通過 x(參數)的內容交換容器的內容,x 是另一個類型相同、size 可能不同的 vector 對象
clear 從 vector 中刪除所有的元素(被銷燬),留下 size 爲 0 的容器
emplace 通過在 position(參數)位置處插入新元素 args(參數)來擴展容器
emplace_back 在 vector 的末尾插入一個新的元素,緊跟在當前的最後一個元素之後
get_allocator 返回與vector關聯的構造器對象的副本
swap(vector) 容器 x(參數)的內容與容器 y(參數)的內容交換。兩個容器對象都必須是相同的類型(相同的模板參數),儘管大小可能不同
relational operators (vector) 形如 vectorA > vectorB;依此比較每個元素的大小關係

測試代碼

#include <vector>
#include <iostream>
using namespace std;

int main()
{

	//構造函數,複製構造函數(元素類型要一致),
	vector<int> vecA;  //創建一個空的的容器
	vector<int> vecB(10,20); //創建一個10個元素,每個元素值爲20
	vector<int> vecC(vecB.begin(),vecB.end()); //使用迭代器,可以取部分元素創建一個新的容器
	vector<int> vecD(vecC); //複製構造函數,創建一個完全一樣的容器

	//重載=
	vector<int> vecE;
	vecE = vecB;

	//vector::begin(),返回的是迭代器

	vector<int> vecF(10); //創建一個有10個元素的容器
	cout << "vecF:";
	for (int i = 0; i < 10; i++)
	{
		vecF[i] = i;
		cout << vecF[i]<< " ";
	}
	cout << endl;

	//vector::begin() 返回迭代器
	vector<int>::iterator Beginit = vecF.begin();
	cout<< "vecF.begin():" << *Beginit << endl; 

	//vector::end() 返回迭代器
	vector<int>::iterator EndIter = vecF.end();
	EndIter--; //向後移一個位置
	cout <<"vecF.end():"<< *EndIter << endl; 

	//vector::rbegin() 返回倒序的第一個元素,相當於最後一個元素
	vector<int>::reverse_iterator ReverBeIter = vecF.rbegin();
	cout << "vecF.rbegin(): "<< *ReverBeIter << endl; 

	//vector::rend() 反序的最後一個元素下一個位置,也相當於正序的第一個元素前一個位置
	vector<int>::reverse_iterator ReverEnIter = vecF.rend();
	ReverEnIter--;
	cout << "vecF.rend():"<< *ReverEnIter << endl; 

	//vector::size() 返回元素的個數
	cout << "vecF.size():"<< vecF.size() << endl; 

	//vector::max_size()
	cout << "vecF.max_size():"<< vecF.max_size() << endl; 

	//vector::resize()
	cout<< "vecF.size():" << vecF.size() << endl; 
	vecF.resize(5);

	cout<< "調整vecF大小後重新賦值:"; 
	for(int k = 0; k < vecF.size(); k++)
		cout << vecF[k] << "  "; 
	cout << endl;

	//vector::capacity()
	cout<< "調整後vecF.size():"<< vecF.size() << endl; 
	cout<< "調整後vecF.capacity():" << vecF.capacity() << endl; 

	//vector::empty()
	vecB.resize(0);
	cout<< "vecB.resize(0)後"<< endl; 

	cout  << "vecB.size():" << vecB.size() << endl; 
	cout  << "vecB.capacity():" << vecB.capacity() << endl; 
	if(vecB.empty())
	    cout << "vecB爲空"<< endl; 
	else
		cout << "vecB不爲空"<< endl; 

	//vector::reserve() //重新分配存儲空間大小
	cout<< "vecC.capacity():" << vecC.capacity() << endl; //

	vecC.reserve(4);
	cout << "vecC.reserve(4)後vecC.capacity(): "<< vecC.capacity() << endl; //10
	vecC.reserve(14);
	cout << "vecC.reserve(14)後vecC.capacity(): "<< vecC.capacity() << endl; //14

	//vector::operator []
	cout << "vecF[0]:"<< vecF[0] << endl; //第一個元素是0

	//vector::at()
	try
	{
		cout << "vecF.size = " << vecF.size() << endl; //5
		cout << vecF.at(6) << endl; //拋出異常
	}
	catch(out_of_range)
	{	
		cout << "at()訪問越界" << endl;
	}

	//vector::front() 返回第一個元素的值
	cout << "vecF.front():"<< vecF.front() << endl; //0

	//vector::back()
	cout << "vecF.back():"<< vecF.back() << endl; //4

	//vector::assign()
	cout <<"vecA.size():"<< vecA.size() << endl; //0
	vector<int>::iterator First = vecC.begin();
	vector<int>::iterator End = vecC.end()-2;
	vecA.assign(First,End);
	cout << vecA.size() << endl; //8
	cout << vecA.capacity() << endl; //8

	vecA.assign(5,3); //將丟棄原來的所有元素然後重新賦值
	cout << vecA.size() << endl; //5
	cout << vecA.capacity() << endl; //8

	//vector::push_back()
	cout << *(vecF.end()-1) << endl; //4
	vecF.push_back(20);
	cout << *(vecF.end()-1) << endl; //20

	//vector::pop_back()
	cout << *(vecF.end()-1) << endl; //20
	vecF.pop_back();
	cout << *(vecF.end()-1) << endl; //4

	//vector::swap()
	cout << "vecF:";
	for (int i = 0; i < vecF.size(); i++)
	{
		vecF[i] = i;
		cout << vecF[i]<< " ";
	}
	cout << endl;
	cout << "vecD:";
	for (int d = 0; d < vecD.size(); d++)
	{
		vecD[d] = d;
		cout << vecD[d]<< " ";
	}
	cout << endl;

	vecF.swap(vecD); //交換這兩個容器的內容
	cout <<"vecD與vecF交換後:" <<endl;
	cout << "vecF:";
	for(int f = 0; f < vecF.size(); f++)
		cout << vecF[f] << " ";
	cout << endl;

	cout << "vecD:";
	for (int d = 0; d <vecD.size(); d++)
		cout << vecD[d] << " ";
	cout << endl;
	//vector::clear()
	vecF.clear();
	cout << "vecF.clear()後vecF.size():"<< vecF.size() << endl;     //0
	cout << "vecF.clear()後vecF.capacity():"<< vecF.capacity() << endl; //10

	return 0;
}

運行結果

運行結果

deque

deque容器爲一個給定類型的元素進行線性處理,像向量一樣,它能夠快速地隨機訪問任一個元素,並且能夠高效地插入和刪除容器的尾部元素。但它又與vector不同,deque支持高效插入和刪除容器的頭部元素,因此也叫做雙端隊列。

deque的中控器: deque是由一段一段的定量連續空間構成。一旦有必要在deque的前端或尾端增加新空間,便配置一段定量連續空間,串接在整個deque的頭端或尾端。deque的最大任務,便是在這些分段的定量連續空間上,維護其整體連續的假象,並提供隨機存取的接口。避開了“重新配置、複製、釋放”的輪迴,代價則是複雜的迭代器結構。

deque採用一塊所謂的map(不是STL的map容器)作爲主控。

map是一小塊連續空間,其中每個元素(此處稱爲一個節點,node)都是指針,指向另一段(較大的)連續線性空間,稱爲緩衝區。

緩衝區纔是deque的儲存空間主體。

template<class T, class Alloc = alloc, size_t BufSiz = 0>  
class deque{  
public :  
    typedef T value_type ;  
    typedef value_type* pointer ;  
    ...  
protected :  
    //元素的指針的指針(pointer of pointer of T)  
    // 其實就是T**,一個二級指針,維護一個二維數組
    typedef pointer* map_pointer ; 
  
protected :  
    map_pointer map ; //指向map,map是塊連續空間,其內的每個元素  
                      //都是一個指針(稱爲節點),指向一塊緩衝區  
    size_type map_size ;//map內可容納多少指針  
    ...  
};  

map其實是一個T**,也就是說它是一個指針,所指之物也是一個指針,指向型別爲T的一塊空間。

方法 說明
deque 構造函數
push_back 在當前的最後一個元素之後 ,在 deque 容器的末尾添加一個新元素
push_front 在 deque 容器的開始位置插入一個新的元素,位於當前的第一個元素之前
pop_back 刪除 deque 容器中的最後一個元素,有效地將容器大小減少一個
pop_front 刪除 deque 容器中的第一個元素,有效地減小其大小
emplace_front 在 deque 的開頭插入一個新的元素,就在其當前的第一個元素之前
emplace_back 在 deque 的末尾插入一個新的元素,緊跟在當前的最後一個元素之後

測試代碼

#include "stdafx.h"
#include<iostream>
#include<deque>
 
using namespace std;
int main()
{
	deque<int> d;
	d.push_back( 11 );//在 deque 容器的末尾添加一個新元素
	d.push_back(20);
	d.push_back(35);
	cout<<"初始化雙端隊列d:"<<endl;
	for(int i = 0; i < d.size(); i++)
	{
		cout<<d.at(i)<<"\t";
	}
	cout<<endl;

	d.push_front(10);//容器的開始位置插入一個新的元素,位於當前的第一個元素之前
	d.push_front(7);
	d.push_front(1);
 
	cout<<"隊列d向前陸續插入10、7、1:"<<endl;
	for(int i = 0;i < d.size();i++)
	{
		cout<<d.at(i)<<"\t";
	}
	cout<<endl;

	d.pop_back(); //刪除 deque 容器中的最後一個元素,有效地將容器大小減少一個
	d.pop_front(); //刪除 deque 容器中的第一個元素,有效地減小其大小
	cout<<"刪除deque最後一個和第一個元素後:"<<endl;
	for(int i = 0;i < d.size();i++)
	{
		cout<<d.at(i)<<"\t";
	}
	cout<<endl;
	return 0;
}

forward_list

在頭文件<forward_list>中,與list類似,區別就是list時雙鏈表,forward_list是單鏈表,forward_list(單向鏈表)是序列容器,允許在序列中的任何地方進行恆定的時間插入和擦除操作。在鏈表的任何位置進行插入/刪除操作都非常快。

forward_list的特點

  • forward_list只提供錢箱迭代器,因此不支持反向迭代器,比如rbegin()等成員函數。

  • forward_list不提供size()成員函數。

  • forward_list沒有指向最末元素的錨點,因此不提供back()、push_back()和pop_back()。

  • forward_list不提供隨機訪問,這一點跟list相同。

  • 插入和刪除元素不會造成“指向至其他元素”的指針,引用和迭代器失效。

容器成員函數總結就不寫了,太多影響閱讀,感興趣小夥伴戳http://www.cplusplus.com/reference/stl/

list

list雙向鏈表,是序列容器,允許在序列中的任何地方進行常數時間插入和擦除操作,並在兩個方向上進行迭代,可以高效地進行插入刪除元素。

使用list容器之前必須加上頭文件:#include;

list容器的底層實現:

和 array、vector 這些容器迭代器的實現方式不同,由於 list 容器的元素並不是連續存儲的,所以該容器迭代器中,必須包含一個可以指向 list 容器的指針,並且該指針還可以藉助重載的 *、++、--、==、!= 等運算符,實現迭代器正確的遞增、遞減、取值等操作。

template<tyepname T,...>
struct __list_iterator{
    __list_node<T>* node;
    //...
    //重載 == 運算符
    bool operator==(const __list_iterator& x){return node == x.node;}
    //重載 != 運算符
    bool operator!=(const __list_iterator& x){return node != x.node;}
    //重載 * 運算符,返回引用類型
    T* operator *() const {return *(node).myval;}
    //重載前置 ++ 運算符
    __list_iterator<T>& operator ++(){
        node = (*node).next;
        return *this;
    }
    //重載後置 ++ 運算符
    __list_iterator<T>& operator ++(int){
        __list_iterator<T> tmp = *this;
        ++(*this);
        return tmp;
    }
    //重載前置 -- 運算符
    __list_iterator<T>& operator--(){
        node = (*node).prev;
        return *this;
    }
    //重載後置 -- 運算符
    __list_iterator<T> operator--(int){
        __list_iterator<T> tmp = *this;
        --(*this);
        return tmp;
    }
    //...
}

stack

stack沒有迭代器,是一種容器適配器,用於在LIFO(後進先出)的操作,其中元素僅從容器的一端插入和提取。

stack底層一般用list或deque實現,封閉頭部即可,不用vector的原因應該是容量大小有限制,擴容耗時

底層用deque實現

//deque<T> >中間有個空格是爲了兼容較老的版本
template <class T, class Sequence = deque<T> >
class stack {
    // 以㆘的 __STL_NULL_TMPL_ARGS 會開展爲 <>
    friend bool operator== __STL_NULL_TMPL_ARGS (const stack&, const stack&);
    friend bool operator< __STL_NULL_TMPL_ARGS (const stack&, const stack&);
public:
    typedef typename Sequence::value_type value_type;
    typedef typename Sequence::size_type size_type;
    typedef typename Sequence::reference reference;
    typedef typename Sequence::const_reference const_reference;
protected:
    Sequence c; // 底層容器
public:
    // 以㆘完全利用 Sequence c 的操作,完成 stack 的操作。
    bool empty() const { return c.empty(); }
    size_type size() const { return c.size(); }
    reference top() { return c.back(); }
    const_reference top() const { return c.back(); }
    // deque 是兩頭可進出,stack 是末端進,末端出(所以後進者先出)。
    void push(const value_type& x) { c.push_back(x); }
    void pop() { c.pop_back(); }
};

template <class T, class Sequence>
bool operator==(const stack<T, Sequence>& x, const stack<T, Sequence>& y) {
    return x.c == y.c;
}

template <class T, class Sequence>
bool operator<(const stack<T, Sequence>& x, const stack<T, Sequence>& y) {
    return x.c < y.c;
}

底層用list實現

  #include<stack>  
  #include<list>  
  #include<algorithm>  
  #include <iostream>  
  using namespace std;  
    
  int main(){  
      stack<int, list<int>> istack;  
      istack.push(1);  
      istack.push(3);  
      istack.push(5);  
        
      cout << istack.size() << endl; //3  
      cout << istack.top() << endl;//5  
      istack.pop();  
      cout << istack.top() << endl;//3  
      cout << istack.size() << endl;//2  
    
      system("pause");  
      return 0;  
  }  

queue

queue 是一種容器適配器,用於在FIFO(先入先出)的操作,其中元素插入到容器的一端並從另一端提取。

隊列不提供迭代器,不實現遍歷操作。

template <class T, class Sequence = deque<T> >
class queue {
  friend bool operator== __STL_NULL_TMPL_ARGS (const queue& x, const queue& y);
  friend bool operator< __STL_NULL_TMPL_ARGS (const queue& x, const queue& y);
public:
  typedef typename Sequence::value_type value_type;
  typedef typename Sequence::size_type size_type;
  typedef typename Sequence::reference reference;
  typedef typename Sequence::const_reference const_reference;
protected:
  Sequence c;
public:
  bool empty() const { return c.empty(); }
  size_type size() const { return c.size(); }
  reference front() { return c.front(); }
  const_reference front() const { return c.front(); }
  reference back() { return c.back(); }
  const_reference back() const { return c.back(); }
  void push(const value_type& x) { c.push_back(x); }
  void pop() { c.pop_front(); }
};

template <class T, class Sequence>
bool operator==(const queue<T, Sequence>& x, const queue<T, Sequence>& y) {
  return x.c == y.c;
}

template <class T, class Sequence>
bool operator<(const queue<T, Sequence>& x, const queue<T, Sequence>& y) {
  return x.c < y.c;
}

priority_queue

優先隊列,其底層是用堆來實現的。在優先隊列中,隊首元素一定是當前隊列中優先級最高的那一個。

set

set 是按照特定順序存儲唯一元素的容器。

template<class _Kty,
    class _Pr = less<_Kty>,
    class _Alloc = allocator<_Kty> >
class set
  1. set 的 底層數據結構是 紅黑樹,一種高效的平衡檢索二叉樹。

  2. set 容器中 每一個元素就是二叉樹的每一個節點,對於set容器的插入刪除操作,效率都比較高,原因是因爲二叉樹的刪除插入元素並不需要進行內存拷貝和內存移動,只是改變了指針的指向。

  3. 對 set 進行插入刪除操作 都不會引起iterator的失效,因爲迭代器相當於一個指針指向每一個二叉樹的節點,對set的插入刪除並不會改變原有內存中節點的改變, 但是vector的插入刪除操作一般會發生內存移動和內存拷貝,所以會發生迭代器的失效。

  4. set容器的檢索速度很快,因爲採用二分查找的方法 。

multiset

multiset允許元素重複而set不允許

template<class _Kty,
	class _Pr = less<_Kty>,
	class _Alloc = allocator<_Kty> >
class multiset

map

map 是關聯容器,按照特定順序存儲由 key value (鍵值) 和 mapped value (映射值) 組合形成的元素。

由於 RB-tree 是一種平衡二叉搜索樹,自動排序的效果很不錯,所以標準的STL map 即以 RB-tree 爲底層機制。又由於 map 所開放的各種操作接口,RB-tree 也都提供了,所以幾乎所有的 map 操作行爲,都只是轉調 RB-tree 的操作行爲。

方法 說明
map 構造函數
begin 返回引用容器中第一個元素的迭代器
key_comp 返回容器用於比較鍵的比較對象的副本
value_comp 返回可用於比較兩個元素的比較對象,以獲取第一個元素的鍵是否在第二個元素之前
find 在容器中搜索具有等於 k的鍵的元素,如果找到返回一個迭代器,否則返回 map::end
count 在容器中搜索具有等於 k(參數)的鍵的元素,並返回匹配的數量
lower_bound 返回一個非遞減序列 [first, last)中的第一個大於等於值 val的位置的迭代器
upper_bound 返回一個非遞減序列 [first, last)中第一個大於 val的位置的迭代器
equal_range 獲取相同元素的範圍,返回包含容器中所有具有與 k等價的鍵的元素的範圍邊界

multimap

multimap 的特性以及用法與 map 完全相同,唯一的差別在於它允許鍵值重複,因此它的插入操作採用的是底層機制 RB-tree 的 insert_equal() 而非 insert_unique。

unordered_set

unordered_set是基於哈希表,因此要了解unordered_set,就必須瞭解哈希表的機制。哈希表是根據關鍵碼值而進行直接訪問的數據結構,通過相應的哈希函數(也稱散列函數)處理關鍵字得到相應的關鍵碼值,關鍵碼值對應着一個特定位置,用該位置來存取相應的信息,這樣就能以較快的速度獲取關鍵字的信息。

template < class Key,  
    class Hash = hash<Key>,  
    class Pred = equal_to<Key>,  
    class Alloc = allocator<Key>  
> class unordered_set;  

4 算法

  1. 簡單查找算法,要求輸入迭代器(input iterator)

find(beg, end, val); // 返回一個迭代器,指向輸入序列中第一個等於 val 的元素,未找到返回 end
find_if(beg, end, unaryPred); // 返回一個迭代器,指向第一個滿足 unaryPred 的元素,未找到返回 end
find_if_not(beg, end, unaryPred); // 返回一個迭代器,指向第一個令 unaryPred 爲 false 的元素,未找到返回 end
count(beg, end, val); // 返回一個計數器,指出 val 出現了多少次
count_if(beg, end, unaryPred); // 統計有多少個元素滿足 unaryPred
all_of(beg, end, unaryPred); // 返回一個 bool 值,判斷是否所有元素都滿足 unaryPred
any_of(beg, end, unaryPred); // 返回一個 bool 值,判斷是否任意(存在)一個元素滿足 unaryPred
none_of(beg, end, unaryPred); // 返回一個 bool 值,判斷是否所有元素都不滿足 unaryPred
  1. 查找重複值的算法,傳入向前迭代器(forward iterator)

adjacent_find(beg, end); // 返回指向第一對相鄰重複元素的迭代器,無相鄰元素則返回 end
adjacent_find(beg, end, binaryPred); // 返回指向第一對相鄰重複元素的迭代器,無相鄰元素則返回 end
search_n(beg, end, count, val); // 返回一個迭代器,從此位置開始有 count 個相等元素,不存在則返回 end
search_n(beg, end, count, val, binaryPred); // 返回一個迭代器,從此位置開始有 count 個相等元素,不存在則返回 end
  1. 查找子序列算法,除 find_first_of(前兩個輸入迭代器,後兩個前向迭代器) 外,都要求兩個前向迭代器

search(beg1, end1, beg2, end2); // 返回第二個輸入範圍(子序列)在爹一個輸入範圍中第一次出現的位置,未找到則返回 end1
search(beg1, end1, beg2, end2, binaryPred); // 返回第二個輸入範圍(子序列)在爹一個輸入範圍中第一次出現的位置,未找到則返回 end1
find_first_of(beg1, end1, beg2, end2); // 返回一個迭代器,指向第二個輸入範圍中任意元素在第一個範圍中首次出現的位置,未找到則返回end1
find_first_of(beg1, end1, beg2, end2, binaryPred); // 返回一個迭代器,指向第二個輸入範圍中任意元素在第一個範圍中首次出現的位置,未找到則返回end1
find_end(beg1, end1, beg2, end2); // 類似 search,但返回的最後一次出現的位置。如果第二個輸入範圍爲空,或者在第一個輸入範圍爲空,或者在第一個輸入範圍中未找到它,則返回 end1
find_end(beg1, end1, beg2, end2, binaryPred); // 類似 search,但返回的最後一次出現的位置。如果第二個輸入範圍爲空,或者在第一個輸入範圍爲空,或者在第一個輸入範圍中未找到它,則返回 end1
  1. 其他只讀算法,傳入輸入迭代器

for_each(beg, end, unaryOp); // 對輸入序列中的每個元素應用可調用對象 unaryOp,unaryOp 的返回值被忽略
mismatch(beg1, end1, beg2); // 比較兩個序列中的元素。返回一個迭代器的 pair,表示兩個序列中第一個不匹配的元素
mismatch(beg1, end1, beg2, binaryPred); // 比較兩個序列中的元素。返回一個迭代器的 pair,表示兩個序列中第一個不匹配的元素
equal(beg1, end1, beg2); // 比較每個元素,確定兩個序列是否相等。
equal(beg1, end1, beg2, binaryPred); // 比較每個元素,確定兩個序列是否相等。
  1. 二分搜索算法,傳入前向迭代器或隨機訪問迭代器(random-access iterator),要求序列中的元素已經是有序的

lower_bound(beg, end, val); // 返回一個非遞減序列 [beg, end) 中的第一個大於等於值 val 的位置的迭代器,不存在則返回 end
lower_bound(beg, end, val, comp); // 返回一個非遞減序列 [beg, end) 中的第一個大於等於值 val 的位置的迭代器,不存在則返回 end
upper_bound(beg, end, val); // 返回一個非遞減序列 [beg, end) 中第一個大於 val 的位置的迭代器,不存在則返回 end
upper_bound(beg, end, val, comp); // 返回一個非遞減序列 [beg, end) 中第一個大於 val 的位置的迭代器,不存在則返回 end
equal_range(beg, end, val); // 返回一個 pair,其 first 成員是 lower_bound 返回的迭代器,其 second 成員是 upper_bound 返回的迭代器
binary_search(beg, end, val); // 返回一個 bool 值,指出序列中是否包含等於 val 的元素。對於兩個值 x 和 y,當 x 不小於 y 且 y 也不小於 x 時,認爲它們相等。
  1. 只寫不讀算法,要求輸出迭代器(output iterator)

fill(beg, end, val); // 將 val 賦予每個元素,返回 void
fill_n(beg, cnt, val); // 將 val 賦予 cnt 個元素,返回指向寫入到輸出序列最有一個元素之後位置的迭代器
genetate(beg, end, Gen); // 每次調用 Gen() 生成不同的值賦予每個序列,返回 void
genetate_n(beg, cnt, Gen); // 每次調用 Gen() 生成不同的值賦予 cnt 個序列,返回指向寫入到輸出序列最有一個元素之後位置的迭代器

7.使用輸入迭代器的寫算法,讀取一個輸入序列,將值寫入到一個輸出序列(dest)中

copy(beg, end, dest); // 從輸入範圍將元素拷貝所有元素到 dest 指定定的目的序列
copy_if(beg, end, dest, unaryPred); // 從輸入範圍將元素拷貝滿足 unaryPred 的元素到 dest 指定定的目的序列
copy_n(beg, n, dest); // 從輸入範圍將元素拷貝前 n 個元素到 dest 指定定的目的序列
move(beg, end, dest); // 對輸入序列中的每個元素調用 std::move,將其移動到迭代器 dest 開始始的序列中
transform(beg, end, dest, unaryOp); // 調用給定操作(一元操作),並將結果寫到dest中
transform(beg, end, beg2, dest, binaryOp); // 調用給定操作(二元操作),並將結果寫到dest中
replace_copy(beg, end, dest, old_val, new_val); // 將每個元素拷貝到 dest,將等於 old_val 的的元素替換爲 new_val
replace_copy_if(beg, end, dest, unaryPred, new_val); // 將每個元素拷貝到 dest,將滿足 unaryPred 的的元素替換爲 new_val
merge(beg1, end1, beg2, end2, dest); // 兩個輸入序列必須都是有序的,用小於號運算符將合併後的序列寫入到 dest 中
merge(beg1, end1, beg2, end2, dest, comp); // 兩個輸入序列必須都是有序的,使用給定的比較操作(comp)將合併後的序列寫入到 dest 中

8.劃分算法,要求雙向選代器(bidirectional iterator)

is_partitioned(beg, end, unaryPred); // 如果所有滿足謂詞 unaryPred 的元素都在不滿足 unarypred 的元素之前,則返回 true。若序列爲空,也返回 true
partition_copy(beg, end, dest1, dest2, unaryPred); // 將滿足 unaryPred 的元素拷貝到到 dest1,並將不滿足 unaryPred 的元素拷貝到到 dest2。返回一個迭代器 pair,其 first 成員表示拷貝到 dest1 的的元素的末尾,second 表示拷貝到 dest2 的元素的末尾。
partitioned_point(beg, end, unaryPred); // 輸入序列必須是已經用 unaryPred 劃分過的。返回滿足  unaryPred 的範圍的尾後迭代器。如果返回的迭代器不是 end,則它指向的元素及其後的元素必須都不滿足 unaryPred
stable_partition(beg, end, unaryPred); // 使用 unaryPred 劃分輸入序列。滿足 unaryPred 的元素放置在序列開始,不滿足的元素放在序列尾部。返回一個迭代器,指向最後一個滿足 unaryPred 的元素之後的位置如果所有元素都不滿足 unaryPred,則返回 beg
partition(beg, end, unaryPred); // 使用 unaryPred 劃分輸入序列。滿足 unaryPred 的元素放置在序列開始,不滿足的元素放在序列尾部。返回一個迭代器,指向最後一個滿足 unaryPred 的元素之後的位置如果所有元素都不滿足 unaryPred,則返回 beg
  1. 排序算法,要求隨機訪問迭代器(random-access iterator)

sort(beg, end); // 排序整個範圍
stable_sort(beg, end); // 排序整個範圍(穩定排序)
sort(beg, end, comp); // 排序整個範圍
stable_sort(beg, end, comp); // 排序整個範圍(穩定排序)
is_sorted(beg, end); // 返回一個 bool 值,指出整個輸入序列是否有序
is_sorted(beg, end, comp); // 返回一個 bool 值,指出整個輸入序列是否有序
is_sorted_until(beg, end); // 在輸入序列中査找最長初始有序子序列,並返回子序列的尾後迭代器
is_sorted_until(beg, end, comp); // 在輸入序列中査找最長初始有序子序列,並返回子序列的尾後迭代器
partial_sort(beg, mid, end); // 排序 mid-beg 個元素。即,如果 mid-beg 等於 42,則此函數將值最小的 42 個元素有序放在序列前 42 個位置
partial_sort(beg, mid, end, comp); // 排序 mid-beg 個元素。即,如果 mid-beg 等於 42,則此函數將值最小的 42 個元素有序放在序列前 42 個位置
partial_sort_copy(beg, end, destBeg, destEnd); // 排序輸入範圍中的元素,並將足夠多的已排序元素放到 destBeg 和 destEnd 所指示的序列中
partial_sort_copy(beg, end, destBeg, destEnd, comp); // 排序輸入範圍中的元素,並將足夠多的已排序元素放到 destBeg 和 destEnd 所指示的序列中
nth_element(beg, nth, end); // nth 是一個迭代器,指向輸入序列中第 n 大的元素。nth 之前的元素都小於等於它,而之後的元素都大於等於它
nth_element(beg, nth, end, comp); // nth 是一個迭代器,指向輸入序列中第 n 大的元素。nth 之前的元素都小於等於它,而之後的元素都大於等於它
  1. 使用前向迭代器的重排算法。普通版本在輸入序列自身內部重拍元素,_copy 版本完成重拍後寫入到指定目的序列中,而不改變輸入序列

remove(beg, end, val); // 通過用保留的元素覆蓋要刪除的元素實現刪除 ==val 的元素,返回一個指向最後一個刪除元素的尾後位置的迭代器
remove_if(beg, end, unaryPred); // 通過用保留的元素覆蓋要刪除的元素實現刪除滿足 unaryPred 的元素,返回一個指向最後一個刪除元素的尾後位置的迭代器
remove_copy(beg, end, dest, val); // 通過用保留的元素覆蓋要刪除的元素實現刪除 ==val 的元素,返回一個指向最後一個刪除元素的尾後位置的迭代器
remove_copy_if(beg, end, dest, unaryPred); // 通過用保留的元素覆蓋要刪除的元素實現刪除滿足 unaryPred 的元素,返回一個指向最後一個刪除元素的尾後位置的迭代器
unique(beg, end); // 通過對覆蓋相鄰的重複元素(用 == 確定是否相同)實現重排序列。返回一個迭代器,指向不重複元素的尾後位置
unique (beg, end, binaryPred); // 通過對覆蓋相鄰的重複元素(用 binaryPred 確定是否相同)實現重排序列。返回一個迭代器,指向不重複元素的尾後位置
unique_copy(beg, end, dest); // 通過對覆蓋相鄰的重複元素(用 == 確定是否相同)實現重排序列。返回一個迭代器,指向不重複元素的尾後位置
unique_copy_if(beg, end, dest, binaryPred); // 通過對覆蓋相鄰的重複元素(用 binaryPred 確定是否相同)實現重排序列。返回一個迭代器,指向不重複元素的尾後位置
rotate(beg, mid, end); // 圍繞 mid 指向的元素進行元素轉動。元素 mid 成爲爲首元素,隨後是 mid+1 到到 end 之前的元素,再接着是 beg 到 mid 之前的元素。返回一個迭代器,指向原來在 beg 位置的元素
rotate_copy(beg, mid, end, dest); // 圍繞 mid 指向的元素進行元素轉動。元素 mid 成爲爲首元素,隨後是 mid+1 到到 end 之前的元素,再接着是 beg 到 mid 之前的元素。返回一個迭代器,指向原來在 beg 位置的元素
  1. 使用雙向迭代器的重排算法

reverse(beg, end); // 翻轉序列中的元素,返回 void
reverse_copy(beg, end, dest);; // 翻轉序列中的元素,返回一個迭代器,指向拷貝到目的序列的元素的尾後位置
  1. 使用隨機訪問迭代器的重排算法

random_shuffle(beg, end); // 混洗輸入序列中的元素,返回 void
random_shuffle(beg, end, rand); // 混洗輸入序列中的元素,rand 接受一個正整數的隨機對象,返回 void
shuffle(beg, end, Uniform_rand); // 混洗輸入序列中的元素,Uniform_rand 必須滿足均勻分佈隨機數生成器的要求,返回 void
  1. 最小值和最大值

min(val1, va12); // 返回 val1 和 val2 中的最小值,兩個實參的類型必須完全一致。參數和返回類型都是 const的引引用,意味着對象不會被拷貝。下略
min(val1, val2, comp);
min(init_list);
min(init_list, comp);
max(val1, val2);
max(val1, val2, comp);
max(init_list);
max(init_list, comp);
minmax(val1, val2); // 返回一個 pair,其 first 成員爲提供的值中的較小者,second 成員爲較大者。下略
minmax(vall, val2, comp);
minmax(init_list);
minmax(init_list, comp);
min_element(beg, end); // 返回指向輸入序列中最小元素的迭代器
min_element(beg, end, comp); // 返回指向輸入序列中最小元素的迭代器
max_element(beg, end); // 返回指向輸入序列中最大元素的迭代器
max_element(beg, end, comp); // 返回指向輸入序列中最大元素的迭代器
minmax_element(beg, end); // 返回一個 pair,其中 first 成員爲最小元素,second 成員爲最大元素
minmax_element(beg, end, comp); // 返回一個 pair,其中 first 成員爲最小元素,second 成員爲最大元素
  1. 字典序比較,根據第一對不相等的元素的相對大小來返回結果。如果第一個序列在字典序中小於第二個序列,則返回 true。否則,返回 fa1se。如果個序列比另一個短,且所有元素都與較長序列的對應元素相等,則較短序列在字典序中更小。如果序列長度相等,且對應元素都相等,則在字典序中任何一個都不大於另外一個。

lexicographical_compare(beg1, end1, beg2, end2);
lexicographical_compare(beg1, end1, beg2, end2, comp);

5 如何選擇合適的容器

需要根據容器的特點和使用場景而定,可能滿足需求的不止一種容器。

按是否有序關聯性分爲:

  1. 序列式容器:array、vector、deque、list 和 forward_list;

  2. 關聯式容器:map、multimap、set 和 multiset;

  3. 無序關聯式容器:unordered_map、unordered_multimap、unordered_set 和 unordered_multiset;

  4. 容器適配器:stack、queue 和 priority_queue。 根據容器底層採用是否是連續的存儲空間分爲:

  5. 採用連續的存儲空間:array、vector、deque;

  6. 採用分散的存儲空間:list、forward_list 以及所有的關聯式容器和哈希容器。

注意:deque 容器歸爲使用連續存儲空間的這一類,是存在爭議的。因爲 deque 容器底層採用一段一段的連續空間存儲元素,但是各段存儲空間之間並不一定是緊挨着的。

選擇容器流程圖(來源於網絡)

選擇容器的幾點建議:

  • 如果只是存儲確定或不確定的對象,而不去刪除它們,可以選用vector。就是因爲vector是數組的替代品,是連續內存的,不適合頻繁的刪除。

  • 如果在容器的指定位置插入新元素,則只能選擇序列式容器,不選擇關聯式容器和哈希容器。

  • 如果頻繁的插入和刪除,可以選用list(鏈表),內存不是連續的,可以方便的插入和刪除,但是不支持索引訪問。

  • 數據量很大,不在乎他們的排序,要求效率,對容器中各元素的存儲位置沒有要求,可以考慮使用哈希容器,反之就要避免使用哈希容器。

  • 如果是隨機訪問迭代器,選擇 array、vector、deque。

  • 如果是雙向迭代器,考慮 list 序列式容器以及所有的關聯式容器。

  • 如果必須是前向迭代器,考慮 forward_list序列式容器以及所有的哈希容器。

  • 如果插入或刪除操作時,容器中的其它元素不移動?選擇不是array、vector、deque的其它容器。

6 面試中常出現的STL問題

  1. vector的底層原理

vector底層是一個動態數組,包含三個迭代器,start和finish之間是已經被使用的空間範圍,end_of_storage是整塊連續空間包括備用空間的尾部。

當空間不夠裝下數據(vec.push_back(val))時,會自動申請另一片更大的空間(1.5倍或者2倍),然後把原來的數據拷貝到新的內存空間,接着釋放原來的那片空間【vector內存增長機制】。

當釋放或者刪除(vec.clear())裏面的數據時,其存儲空間不釋放,僅僅是清空了裏面的數據。

因此,對vector的任何操作一旦引起了空間的重新配置,指向原vector的所有迭代器會都失效了

  1. vector中的reserve和resize的區別

  • reserve是直接擴充到已經確定的大小,可以減少多次開闢、釋放空間的問題(優化push_back),就可以提高效率,其次還可以減少多次要拷貝數據的問題。reserve只是保證vector中的空間大小(capacity)最少達到參數所指定的大小n。reserve()只有一個參數。

  • resize()可以改變有效空間的大小,也有改變默認值的功能。capacity的大小也會隨着改變。resize()可以有多個參數。

  1. vector中的size和capacity的區別

  • size表示當前vector中有多少個元素(finish - start);

  • capacity函數則表示它已經分配的內存中可以容納多少元素(end_of_storage - start);

  1. vector中erase方法與algorithn中的remove方法區別

  • vector中erase方法真正刪除了元素,迭代器不能訪問了

  • remove只是簡單地將元素移到了容器的最後面,迭代器還是可以訪問到。因爲algorithm通過迭代器進行操作,不知道容器的內部結構,所以無法進行真正的刪除。

  1. vector迭代器失效的情況

  • 當插入一個元素到vector中,由於引起了內存重新分配,所以指向原內存的迭代器全部失效。

  • 當刪除容器中一個元素後,該迭代器所指向的元素已經被刪除,那麼也造成迭代器失效。erase方法會返回下一個有效的迭代器,所以當我們要刪除某個元素時,需要it=vec.erase(it);。

  1. 正確釋放vector的內存(clear(), swap(), shrink_to_fit())

  • vec.clear():清空內容,但是不釋放內存。

  • vector<int>().swap(vec):清空內容,且釋放內存,想得到一個全新的vector。

  • vec.shrink_to_fit():請求容器降低其capacity和size匹配。

  • vec.clear();vec.shrink_to_fit();:清空內容,且釋放內存。

  1. list的底層原理

  • ist的底層是一個雙向鏈表,使用鏈表存儲數據,並不會將它們存儲到一整塊連續的內存空間中。恰恰相反,各元素佔用的存儲空間(又稱爲節點)是獨立的、分散的,它們之間的線性關係通過指針來維持,每次插入或刪除一個元素,就配置或釋放一個元素空間。

  • list不支持隨機存取,如果需要大量的插入和刪除,而不關心隨即存取

  1. 什麼情況下用vector,什麼情況下用list,什麼情況下用deque

  • vector可以隨機存儲元素(即可以通過公式直接計算出元素地址,而不需要挨個查找),但在非尾部插入刪除數據時,效率很低,適合對象簡單,對象數量變化不大,隨機訪問頻繁。除非必要,我們儘可能選擇使用vector而非deque,因爲deque的迭代器比vector迭代器複雜很多。

  • list不支持隨機存儲,適用於對象大,對象數量變化頻繁,插入和刪除頻繁,比如寫多讀少的場景。

  • 需要從首尾兩端進行插入或刪除操作的時候需要選擇deque。

  1. priority_queue的底層原理

  • priority_queue:優先隊列,其底層是用堆來實現的。在優先隊列中,隊首元素一定是當前隊列中優先級最高的那一個。

  1. map 、set、multiset、multimap的底層原理

map 、set、multiset、multimap的底層實現都是紅黑樹,epoll模型的底層數據結構也是紅黑樹,linux系統中CFS進程調度算法,也用到紅黑樹。

紅黑樹的特性:

  • 每個結點或是紅色或是黑色;

  • 根結點是黑色;

  • 每個葉結點是黑的;

  • 如果一個結點是紅的,則它的兩個兒子均是黑色;

  • 每個結點到其子孫結點的所有路徑上包含相同數目的黑色結點。

  1. 爲何map和set的插入刪除效率比其他序列容器高

  • 因爲不需要內存拷貝和內存移動

  1. 爲何map和set每次Insert之後,以前保存的iterator不會失效?

  • 因爲插入操作只是結點指針換來換去,結點內存沒有改變。而iterator就像指向結點的指針,內存沒變,指向內存的指針也不會變。

  1. 當數據元素增多時(從10000到20000),map的set的查找速度會怎樣變化?

  • RB-TREE用二分查找法,時間複雜度爲logn,所以從10000增到20000時,查找次數從log10000=14次到log20000=15次,多了1次而已。

  1. map 、set、multiset、multimap的特點

  • set和multiset會根據特定的排序準則自動將元素排序,set中元素不允許重複,multiset可以重複。

  • map和multimap將key和value組成的pair作爲元素,根據key的排序準則自動將元素排序(因爲紅黑樹也是二叉搜索樹,所以map默認是按key排序的),map中元素的key不允許重複,multimap可以重複。

  • map和set的增刪改查速度爲都是logn,是比較高效的。

  1. 爲何map和set的插入刪除效率比其他序列容器高,而且每次insert之後,以前保存的iterator不會失效?

  • 存儲的是結點,不需要內存拷貝和內存移動。

  • 插入操作只是結點指針換來換去,結點內存沒有改變。而iterator就像指向結點的指針,內存沒變,指向內存的指針也不會變。

  1. 爲何map和set不能像vector一樣有個reserve函數來預分配數據?

  • 在map和set內部存儲的已經不是元素本身了,而是包含元素的結點。也就是說map內部使用的Alloc並不是map<Key, Data, Compare, Alloc>聲明的時候從參數中傳入的Alloc。

  1. set的底層實現實現爲什麼不用哈希表而使用紅黑樹?

  • set中元素是經過排序的,紅黑樹也是有序的,哈希是無序的

  • 如果只是單純的查找元素的話,那麼肯定要選哈希表了,因爲哈希表在的最好查找時間複雜度爲O(1),並且如果用到set中那麼查找時間複雜度的一直是O(1),因爲set中是不允許有元素重複的。而紅黑樹的查找時間複雜度爲O(lgn)

  1. hash_map與map的區別?什麼時候用hash_map,什麼時候用map? 構造函數:hash_map需要hash function和等於函數,而map需要比較函數(大於或小於)。

存儲結構:hash_map以hashtable爲底層,而map以RB-TREE爲底層。

總的說來,hash_map查找速度比map快,而且查找速度基本和數據量大小無關,屬於常數級別。而map的查找速度是logn級別。但不一定常數就比log小,而且hash_map還有hash function耗時。

如果考慮效率,特別當元素達到一定數量級時,用hash_map。

考慮內存,或者元素數量較少時,用map。

  1. 迭代器失效的問題

插入操作:

  • 對於vector和string,如果容器內存被重新分配,iterators,pointers,references失效;如果沒有重新分配,那麼插入點之前的iterator有效,插入點之後的iterator失效;

  • 對於deque,如果插入點位於除front和back的其它位置,iterators,pointers,references失效;當我們插入元素到front和back時,deque的迭代器失效,但reference和pointers有效;

  • 對於list和forward_list,所有的iterator,pointer和refercnce有效。

刪除操作:

  • 對於vector和string,刪除點之前的iterators,pointers,references有效;off-the-end迭代器總是失效的;

  • 對於deque,如果刪除點位於除front和back的其它位置,iterators,pointers,references失效;當我們插入元素到front和back時,off-the-end失效,其他的iterators,pointers,references有效;

  • 對於list和forward_list,所有的iterator,pointer和refercnce有效。

  • 對於關聯容器map來說,如果某一個元素已經被刪除,那麼其對應的迭代器就失效了,不應該再被使用,否則會導致程序無定義的行爲。

  1. 線程不安全的情況

  • 在對同一個容器進行多線程的讀寫、寫操作時;

  • 在每次調用容器的成員函數期間都要鎖定該容器;

  • 在每個容器返回的迭代器(例如通過調用begin或end)的生存期之內都要鎖定該容器;

  • 在每個在容器上調用的算法執行期間鎖定該容器。

參考資料

http://www.cplusplus.com/reference/stl/ https://blog.csdn.net/qq_23350817/article/details/87930715 https://blog.csdn.net/bizhu12/article/details/6769976             http://c.biancheng.net/view/7385.html
https://blog.csdn.net/daaikuaichuan/article/details/80717222

備註:公衆號回覆STL獲取相關的電子書和視頻資料

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