c++基礎複習(5)---STL

STL簡介

STL(Standard Template Library),即標準模板庫。該庫提供了常用的數據結構和算法。

STL三種基本組件:

1、容器(container):容器是容納、包含一組元素的對象。容器類庫包括7種基本容器:向量(vector)、雙端隊列(deque)、列表(list)、集合(set)、多重集合(multiset)、映射(map)和多重映射(multimap)。其中容器又分爲順序容器和關聯容器。

順序容器:將一組具有相同的類型的元素已嚴格的線性形式組織起來(vector、deque、list)。

關聯容器:具有根據一組索引來快速提取元素的能力(set、,multiset、map、multimsp)。

2、迭代器(iterator):迭代器提供了訪問容器種每個元素的方法。對迭代器可以使用“++”運算符來獲得指向下一個元素的迭代器,可以使用“*”運算符訪問一個迭代器所指向的元素,也可以使用“->”運算符來訪問類的成員,和指針的使用比較相似。指針本身也是一種迭代器,迭代器就是泛化的指針。

3、算法(algorithm):STL種包含七十多種算法,包括查找、排序、消除、計數等算法。

除了上面的三種基本組件,STL還包括三個輔助組件。

1、函數對象(function object):函數對象又被稱爲“仿函數”,可以像使用函數那樣去使用它。重載“()”運算符的類的對象都可以被當作函數對象使用,函數對象是泛化的函數。用來擴展算法。

2、適配器(adapter):用於爲已有對象提供新的接口的對象,適配器本身一般並不提供新的功能,只是爲了改變對象的接口而存在的。

3、分配器(allocator):負責空間的配置和管理。

一、STL容器的底層實現和適用的場景

(1)vector(向量)

vector的底層實現爲數組,和一般的數組不同的是,一般的數組在需要在定義時指定的數組的大小,一旦定義好之後數組的大小就不能發生改變,所以需要程序員在定義時,就確定數組的大小。而vector的大小是動態的,即可以在適用時自動擴容(只能增大,不能減少,即一旦容量升上去,就不會在降下來。)因爲vector的底層是數組實現的,所以要求內存必須連續。但內存分配時,並不是在原數組的地址上向後擴容,而是會重新選擇一塊空間,將原來的數組拷貝過來(原因是:該數組原來地址的後面不一定有足夠的空間)。所以vector在擴容上就比較浪費時間,所以在使用vector時,最好先指定數組的大小,以避免頻繁的擴容浪費時間。在刪除容器中元素時,和普通的數組的處理方式相同,將從該元素後面的元素全部向前移動一個位置,所以如果vector的元素類型時類時,就會導致對象的構造和析構,就會浪費大量的時間。所以當需要使用vector存儲類時,一般建議使用對象的指針。

總結:適用於數據的頻繁數據隨機訪問,且不需要頻繁的插入和刪除。

(2)list(鏈表)

list的底層實現爲雙向鏈表,和普通的鏈表相同,即插入和刪除的時間複雜度都是O(1),但不支持數據元素的隨機訪問。

總結:適用於數據的頻繁插入和刪除,且不需要頻繁的隨機訪問。

(3)deque(雙向隊列)

deque是一種雙向開口(先入先出,即:頭刪尾插)的連續性空間。所謂的連續性,不過時讓用戶感覺爲連續的,實際上是不連續的。duque的底層採用了“中央控制器”和緩衝區的結合方式,對外造成了整體連續的假象。“中央控制器”實際上就是使用了map。map佔用一小段連續的空間,其中每個元素都是一個指針,用來記錄每個緩衝區的地址,而且緩衝區的大小是固定的,默認爲512b。所以每一塊中存儲的元素個數是相同的。

deque支持[]運算符,支持數據隨機訪問,在隊尾插入和隊頭刪除的效率都是O(1)。

總結:適用於需要隨機存取,而且兩端的數據需要插入和刪除。

(4)set(集合)和 multiset(多重集合)

set的底層的數據結構是採用insert_unique方式插入紅黑樹(自平衡二叉查找樹)

set總結:適用於去除數據中重複的元素的情況。

multiset的底層數據結構是採用insert_equal方式插入的紅黑樹。

(5)map(映射)和multimap(多重映射)

map的底層數據結構和set相同使用的是insert_unique方式插入紅黑樹,和set不同map的元素是鍵值對,鍵值對由鍵值(key)和實值(value)構成的,set的元素的只有一個實值。

map的用法很多:容器內元素頻繁的查找,且數據的下標不一定是整形的情況。

這裏需要注意的map和set內部的採用的結構是紅黑樹,所以會對加入容器的數據自動排序。

(6)hash_set(哈希集合)和hash_multiset(哈希多重集合)

hash_set和hash_multiset的底層實現是哈希表,由於哈希表是通過散列函數對數據進行分配空間的,所以在數據的查找上的速度爲常數級。它和set和multiset的區別是它對存入容器的數據不排序。

(7)hash_map(哈希映射)和hash_multimap(哈希多重映射)

hash_map和hash_multimap的底層實現是哈希表,它和map和multimap的區別也是對容器中的數據不排序。

這裏需要注意一點:現在的c++11規定無序容器的同一命名規則爲unordered_xx,所以hash_set,hash_multimap,hash_map和hash_multimap在現在c++11中的名稱爲:unordered_set,unordered_multiset,unordered_map和unordered_multimap。

二、適配器(adapter

適配器的底層並沒有具體數據結構實現,而是對其他STL的組件進行的一定的修改和封裝。適配器的優點就在於可以讓程序員選擇最合適的容器來使用。

適配器分爲三類:1、容器適配器 2、迭代器適配器 3、仿函數適配器

1、容器適配器

(1)stack(棧,默認基於deque(雙向隊列)實現)

(2)queue(隊,默認基於deque(雙向隊列)實現)

(3)priority_queue(優先隊列,默認基於vector(向量)實現)

三、分配器(allocaotr)

負責空間配置與管理。從實現的角度來看,配置器是一個實現動態空間配置、空間管理、空間釋放的模板類。

STL分配器將內存管理中的分配和釋放分開,其中內存分配交給alloc::allocate()負責,內存釋放由alloc::dealloccate()負責;對像的構造由::construct()負責,對象的析構::destroy()負責。

同時爲了提高內存管理的效率,減少申請內存造成的內存碎片問題,STL採用了兩級配置器,當分配的內存大於等於128kb時,會使用第一級的空間配置器,在第一級空間適配器中內存是利用malloc()、realloc(),free()函數進行分配和釋放的。當空間小於128k時,採用第二級空間配置器,第二級空間配置器採用了內存池技術,通過空閒鏈表來管理內存。

四、迭代器失效

迭代器失效有兩種表現形式:

(1)無法通過迭代器++,--,操作遍歷整個容器(第一層失效)。

(2)無法通過迭代器存取迭代器所指向的內存(第二層失效)。

因爲迭代器適合容器中的元素綁定的,而是和內存綁定的,所以在刪除容器中元素時,幾乎都會導致迭代器失效,需要重新定位的情況(第一層失效),而對於部分容器來說,插入操作可能也會導致迭代器失效(第二層失效)。

(注意:在使用的erase刪除容器的元素時,erase函數會返回下一個有效的迭代器)

迭代器是失效對於不同的容器有不同的表現形式。

(1)vector、deque

對於這兩種順序容器來說,在刪除元素時會導致當前未知之後的迭代器都會失效。vector和deque中的元素在刪除之後,當前位置之後的元素的位置會向前移動(第一層失效)。而對於插入操作來說,由於vector、deque的存在空間時連續的,所以在插入元素時可能會導致內存重新分配的問題(第二層失效),如果內存沒有重新分配,迭代器就不會失效。

(2)list

list的空間時動態分配的,空間本身就不連續,所以插入一般不會產生迭代器失效問題,在刪除時會導致第一層迭代器失效問題。

(3)map、set

刪除map和set的元素,會導致當前的迭代器失效,但不會影響下一個元素的迭代器。插入也不會導致迭代器失效。

五、map和set又什麼區別,分別是如何實現的?

map和set都是c++的關聯容器,其底層都是紅黑樹(RB-Tree)。由於map和set所開放的各種操作接口,紅黑數也都提供了,索引幾乎所有的map和set的操作行爲,都只是轉調紅黑數的操作行爲。

map和set的區別在於:

(1)map中的元素使key-value(關鍵字-值)對,關鍵字起到索引的作用,值則表示與索引相關聯的數據;set與之相對的就是關鍵字的簡單集合,set中每個元素只包含一個關鍵字。

(2)set的迭代器使const的,不允許修改元素的值;map允許修改value,但不允許修改key。其原因是因爲map和set是根據關鍵字來保證其有序性,如果允許修改key值的,就會破壞底層紅黑樹的結構,而這樣就會導致迭代器失效,所以STL不允許修改關鍵字,而map的數據是k-v鍵值對,是通過k值來索引v的值,所有v的修改,並不會影響到底層紅黑樹的結構,所以STL允許修改map的value值

(3)map支持下標操作,set不支持下表操作。map可以用key做下標,map的下標運算符[]將關鍵字作爲下標去查找。(注意:如果map索引的下標並不存在時,map並不會報錯,而是會在自動添加該關鍵字,併爲該關鍵字對應的value賦予一個默認的值)

六、vector和list的區別?

(1)vector的底層實現是數組;而list的底層實現是雙向鏈表。

(2)vector的內存的空間時連續的;而list的內存空間時不連續的。

(3)vector支持數據的隨機訪問;而list不支持數據的隨機訪問。

(4)vector在插入時如果空間不夠纔會自動申請新的空間的擴容,但容器中數據刪除時不會釋放空間;而list在每次插入和刪除都會申請和釋放空間。

(5)vector在數據的插入或刪除都可能會導致迭代器失效(插入或刪除位置後面額迭代器全部失效);而list的在插入時不會導致迭代器失效,而在刪除時會導致當前迭代器失效。

(6)vector在刪除和插入元素時會導致內存拷貝(插入位置之後的元素需要全部後移或刪除位置的元素需要全部前移);而list不會。

(7)vector適用於對容器中元素進行頻繁的訪問的情況;而list適用於對容器中元素進行頻繁插入和刪除的情況。

七、vector和deque的區別?

(1)vector的底層實現時數組;而deque的底層實現時“中央控制區”和緩衝的結構。

(2)vector的內存空間時連續的;而deque的內存空間是一種假的連續,實際上是不連續的。

(3)vecotr的支持數組的隨機訪問;而deque雖然也是支持使用[]的運算符,但實際上是需要通過中央控制器二次尋址的,效率比vector略低。

(4)vector在空間不夠重新申請新空間策略是先申請一塊比原來空間大的空間(1.5倍或兩倍)將原來的數據拷貝一份到新的空間中,然後將原來的空間的釋放;而deque的策略是申請一塊固定大小(默認大小:512bytes)的空間,然後將該空間的地址存放在中央控制器中,並沒有數據拷貝和空間的釋放的過程,所以擴容時deque的效率要高於vector

(5)vector在對尾部進行插入和刪除的效率時O(1);而deque在尾部和頭部的插入和刪除的效率都是O(1)。

(6)vector適用於對容器中元素進行頻繁的訪問的情況;而deque的適用於元素的頻繁訪問,並且在數據需要在頭部和尾部進行插入和刪除的情況。

八、容器容量和容器大小(capacity和size)

(1)容器容量:容器所能容納元素的個數,通常大於容器的實際存儲元素的個數。

s.capacity();//查看容器容量
s.reserve(len);//擴展當前容器的容量,如果len大於當前容器容量,那麼將會將容器的容量擴展爲len,且不會對新擴展的空間進行初始化;當len小於等於當前容器容量時,什麼也不做。

(2)容器大小:容器中當前存在元素的個數。

s.size();//查看當前容器的中元素的個數
s.resize(len);//設置當前容器的容量,如個len大於當前容器中元素的個數,新增的元素將並初始化爲0。len小於容器的實際大小時,會將len之後的元素的全部清除爲0。

通過情況向容器的容量是大於等於容器的大小的,就像杯子和杯子中的水的關係。一般需要容器的大小不會引起內存的重新分配(向杯子中加水),但修改容器的容量就會導致容器內存的重新分配(原來的杯子太小了,需要換新的杯子)。

#include <iostream>
#include <vector>
using namespace std;
int main()
{
	vector<int> intArr;
	intArr.push_back(1);
	intArr.push_back(2);
	intArr.push_back(3);
	intArr.push_back(4);
	intArr.push_back(5);
	intArr.push_back(6);
	cout << "1、修改前:容器size:" << intArr.size() << ",容器capacity:" << intArr.capacity() << endl;
	vector<int>::iterator viter = intArr.begin();
	for (; viter != intArr.end(); ++viter)
	{
		cout << *viter << ",";
	}
	cout << endl;
	intArr.resize(3);
	intArr.reserve(3);
	cout << "2、修改後、容器size:" << intArr.size() << ",容器capacity:" << intArr.capacity() << endl;
	viter = intArr.begin();
	for (; viter != intArr.end();++viter)
	{
		cout << *viter << ",";
	}
	cout << endl;
	intArr.resize(6);
	intArr.reserve(10);
	cout << "3、修改後、容器size:" << intArr.size() << ",容器capacity:" << intArr.capacity() << endl;
	viter = intArr.begin();
	for (; viter != intArr.end(); ++viter)
	{
		cout << *viter << ",";
	}
	cout << endl;
	return 0;
}

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