博覽網:STL與泛型編程第三周筆記

1.容器deque

C++ STL容器deque和vector很類似,也是采用動態數組來管理元素。

使用deque之前需包含頭文件:

#include

它是定義在命名空間std內的一個class template:

template

class _Ax = allocator<_Ty> >

class deque;

第一個template參數用來表示元素型別,第二個可有可無,指定內存模型。一般使用默認的內存模型。

與vector不同的是deque的動態數組首尾都開放,因此能夠在首尾進行快速地插入和刪除操作。

deque是一種分段連續的容器,特點是雙向開口,可以認為它是一段連續的內存空間,不僅可以向前方增加內存空間,也可以向后方增加內存空間。

在實際內存中實現雙向擴充是比較復雜的事情,那么deque中是如何實現的呢?deque通過一個控制器來串聯一系列的緩沖器(buffer),從而達到邏輯上的連續效果。

deque的內存管理示意圖,如下圖所示:

deque是通過一個vector在維護自身的控制器,在控制器中存儲的是指向buffer的指針,因此我們需要用一個指向指針的指針來指向這個vector的地址。

deque能在邏輯上實現內存連續,最關鍵的是iterator在起作用。迭代器運行到邊界的時候,都需要檢測是否到邊界,然后通過回到控制buffer的那個vector來管理邊界的buffer了。在iterator中,cur、first、last和node分別指向了用戶使用時的當前的數據,first指向了buffer的第一塊空間,last指向了buffer之后那個不在buffer中的空間,而node指向了控制buffer的指針序列中的實際位置

deque的源代碼如下所示(參考課程PPT):

deque iterator的源代碼如下所示:

deuqe的插入問題:

元素插入的時候因為是按順序排列,如果插入元素不在兩頭在中間,會改變其他元素的位置,如果插入點距離前段比較近,那么移動前段比較合適,效率較高;

如果插入點距離后端比較近,那么將插入點之后的元素向后移動比較快。

deque insert函數的源代碼如下:

iterator?insert(iterator?postion,?const?value_type&?x){

if(postion.cur?==?start.cur)??//如果安插點是deque的最前端

{

push_front(x);??//直接使用push_front

return?start;

}

else?if(postion.cur?==?finish.cur)??//如果安插點是deque的最末位

{

push_back(x);??//直接交給push_back

iterator?tmp?=?finish;

--tmp;

return?tmp;

}

else

{

return?insert_aux(postion,?x);

}

}

template?

typename?deque::iterator_deque::?itert_aux(iterator?pos,?const?value_type&?x){

difference_type?index?=?pos?-?start;????//安插點之前的元素個數

value_type?x_copy?=?x;

if(index?<?size()?/?2){??//如果安插點之前的元素較少

push_front(front());??//在最前端加入第一個元素同值的元素

.......

copy(front2,?pos1,?front1);??//元素搬移

}

else?{????//安插點之后的元素較少

push_back(back());//在尾端加入最末元素同值的元素

......

copy_backward(pos,?back2,?back1);//元素搬移

}

*pos?=?x_copy;//在安插點上設定新值

return?pos;

}

deque如何模擬連續空間,全是的確iterators的功勞

具體代碼如下:

reference?operator[](size_type?n)

{

return?start[difference_type(n)];

}

reference?front()

{

return?*start;

}

reference?back()

{

iterator?tmp?=?finish;

--tmp;

return?*tmp;

}

size_type?size()?const

{

return?finish?-?start;

}

bool?empty()?const

{

return?finish?==?start;

}

reference?operator*?()?const

{

return?*cur;

}

pointer?operator->()?const

{

return?&(operator*());

}

//兩個iterator之間的距離相當于

//(1)兩個iterator之間的buffer的總長度+

//(2)itr至buffer末尾的長度+

//(3)x至buffer開頭的長度

difference_type

operator-?(const?self&?x)?const

{

return?difference_type(buffer_size())?*?(node?-?x.node?-?1)?+?(cur?-?first)?+?(x.last?-?x.cur);

//buffer?size?*?首尾buffer之間的buffer之間的數量?+?末尾(當前)buffer的元素量?+?起始buffer的元素量

}

self&?operator++()

{

++cur;???????????????????//切換至下一個元素

if(cur?==?last){?????????//如果抵達緩沖區的末尾

set_node(node?+?1);??//就跳至下一個節點(緩沖區)的起點

cur?=?first;

}

return?*this;

}

self?operator++(int)

{

self?tmp?=?*this;

++*this;

return?tmp;

}

self&?operator--()

{

if(cur?==?first){?????????//如果目前在緩沖區開頭,

set_node(node?-?1);???//就跳至前一節點(緩沖區)的最末端。

cur?=?last;

}

--cur;???????????????????//往前移動一個元素(最末元素)

return?*this;

}

self?operator--(int)

{

self?tmp?=?*this;

--*this;

return?tmp;

}

void?set_node(map_pointer?new_node)

{

node?=?new_node;

first?=?*new_node;

last?=?first?+?difference_type(buffer_size));

}

self&?operator+=(difference_type?n?){

difference_type?offset?=?n?+?(cur?-?first);

if(offset?>=?0?&&?offset?<?difference_type(buffer_size())

//目標位置在同一級緩存區

cur?+=?n;

else{

//目標位置不在同一級緩存區內

difference_type?node_offset?=?offset?>?0??offset?/?difference_type(buffer_size()):?-difference_type((-offset?-?1)?/?buffer_size;

//切換至正確的的緩存區

set_node(node?+?node_offset);

cur?=?first?+?(offset?-?node_offset?*?difference_type(buffser_size());

}

return?*this;

}

operator+(difference_type?n)?const

{

self?tmp?=?*this;

return?tmp?+=?n;

}

self&?operator-=(difference_type?n)

{

return?*this?+=?-?n;

}

self?operator-(difference_type?n)

{

self?tmp?=?*this;

return?tmp?-=?n;

}

reference?operator[]?(difference_type?n)const

{

return?*(*this?+?n);

}

GNU 4.9版本中實現的dequeUML圖,如下圖所示:

衍生:

deque和vector的不同之處:

1、兩端都能夠快速插入和刪除元素。vector只能在尾端進行。

2、deque的元素存取和迭代器操作會稍微慢一些。因為deque的內部結構會多一個間接過程。

3、迭代器是特殊的智能指針,而不是一般指針。它需要在不同的區塊之間跳轉。

4、deque可以包含更多的元素,其max_size可能更大。因為不止使用一塊內存。

5、不支持對容量和內存分配時機的控制。

注意:在除了首尾兩端的其他地方插入和刪除元素,都將會導致指向deque元素的任何pointers、references、iterators失效。不過,deque的內存重分配優于vector。因為其內部結構顯示不需要復制所有元素。

6、deque的內存區塊不再被使用時,會被釋放。deque的內存大小是可縮減的。不過,是不是這么做以及怎么做由實作版本定義。

deque和vector相似的特性:

1、在中間部分插入和刪除元素相對較慢,因為所有元素都要被移動。

2、迭代器屬于隨即存取迭代器。

最好采用deque的情形:

1、需要在兩端插入和刪除元素。

2、無需引用容器內的元素。

3、要求容器釋放不再使用的元素。

2.容器 queue

2.1.queue模版類的定義在頭文件中

queue與stack模版非常類似,queue模版也需要定義兩個模版參數,一個是元素類型,一個是容器類型,元素類型是必要的,容器類型是可選的,默認為dqueue類型。

定義queue對象的示例代碼如下:

queueq1;

queueq2;

queue的基本操作有:

1).入隊:如q.push(x):將x元素接到隊列的末端;

2).出隊:如q.pop() 彈出隊列的第一個元素,并不會返回元素的值;

3),訪問隊首元素:如q.front()

4),訪問隊尾元素,如q.back();

5),訪問隊中的元素個數,如q.size();

2.2.優先隊列

在頭文件中,還定義了一個非常有用的模版類priority_queue(優先隊列),優先隊列與隊列的差別在于優先隊列不是按照入隊的順序出隊,而是按照隊列中元素的優先權順序出隊(默認為大者優先,也可以通過指定算子來指定自己的優先順序)。

priority_queue模版類有三個模版參數,元素類型,容器類型,比較算子。其中后兩個都可以省略,默認容器為vector,默認算子為less,即小的往前排,大的往后排(出隊時序列尾的元素出隊)。

定義priority_queue對象的示例代碼如下:

priority_queueq1;

priority_queue >q2;

priority_queue,greater >q3;//定義小的先出隊

priority_queue的基本操作均與queue相同

初學者在使用priority_queue時,最困難的可能就是如何定義比較算子了。如果是基本數據類型,或已定義了比較運算符的類,可以直接用STL的less算子和greater算子——默認為使用less算子,即小的往前排,大的先出隊。如果要定義自己的比較算子,方法有多種,這里介紹其中的一種:重載比較運算符。優先隊列試圖將兩個元素x和y代入比較運算符(對less算子,調用xy),若結果為真,則x排在y前面,y將先于x出隊,反之,則將y排在x前面,x將先出隊。

容器queue是以deque為底層結構實現的,具體代碼如下:

template?>

class?queue

{

............

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()?const?{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();?}

void?pop(){c.pop.front();}

}

3.容器 stack

容器stack也是以deque為底層結構實現的,需要注意的是queue和stack都不允許遍歷,也不提供iterator,具體代碼如下:

template?>

class?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:

bool?empty()?const{return?c.empty();}

size_type?size()?const{return?c.size();}

reference?top()?const?{return?c.back();}

const_reference?top()?const{?return?c.back();}

void?push?(const?value_type&?x){?c.push_back();?}

void?pop(){c.pop.back();}

}

4.容器 rb_tree

Red-Black tree(紅黑樹)是平衡二元搜尋樹(balanced Binary search tree)中常被使用的一種。

平衡二院搜尋樹的特征:排列規律,有利于search和insert,并保持適度平衡,無任何節點過深。

紅黑樹的實現代碼:

5.容器 set,multiset

5.1需要包含頭文件:

#include

set和multiset都是定義在std空間里的類模板:

[cpp]?view plain?copy?print?

template

class?_Pr?=?less<_Kty>,

class?_Alloc?=?allocator<_Kty>?>

class?set

[cpp]?view plain?copy?print?

template

class?_Pr?=?less<_Kty>,

class?_Alloc?=?allocator<_Kty>?>

class?multiset

只要是可復賦值、可拷貝、可以根據某個排序準則進行比較的型別都可以成為它們的元素。第二個參數用來定義排序準則。缺省準則less是一個仿函數,以operator<對元素進行比較。

所謂排序準則,必須定義strict weak ordering,其意義如下:

1)、必須使反對稱的。

對operator<而言,如果x

2)、必須使可傳遞的。

對operator<而言,如果x

3)、必須是非自反的。

對operator<而言,x

因為上面的這些特性,排序準則可以用于相等性檢驗,就是說,如果兩個元素都不小于對方,則它們相等。

5.2、set和multiset的功能

和所有關聯式容器類似,通常使用平衡二叉樹完成。事實上,set和multiset通常以紅黑樹實作而成。

自動排序的優點是使得搜尋元素時具有良好的性能,具有對數時間復雜度。但是造成的一個缺點就是:

不能直接改變元素值。因為這樣會打亂原有的順序。

改變元素值的方法是:先刪除舊元素,再插入新元素。

存取元素只能通過迭代器,從迭代器的角度看,元素值是常數。

容器set的實現代碼:

template?,?class?Alloc?=?alloc>

class?set{

public:

//typedefs:

typedef?Key?key_type;

typedef?Key?value_type;

typedef?Compare?key_compare;

typedef?Compare?value_compare;

private:

typedef?rb_tree?rep_type;

rep_type?t;

public:

typedef?typename?rep_type::const_iterator?iterator;

...

//set的所有操作,都調用底層rb_tree的函數,從這點看來,set實際應該為container?adapter

}

容器multiset的實現代碼如下:

6.容器 map和multimap

map的實現代碼如下:

multimap實現代碼如下:

容器map獨特的operator[]

衍生:

1.map和multimap容器跟set和multiset容器非常相似,包括外部接口和內部結構上。不同之處就是set和multiset容器是以單個對象為管理元素,而map和multimap容器是以pair為管理元素。map和multimap容器中元素同時是自動排序,它們排序的依據是各個元素的key值。

2.map和multimap容器在內部結構上通常也采用平衡二叉樹(balanced binary tree),擁有跟set和multiset一樣的能力和操作。不同之處就是元素形式上,另外map和multimap容器可以作為關聯數組使用。

3.在map或者multimap容器中查找特定值的元素,除了傳統的遍歷元素查找外,還可以使用通用算法來查找,但要自己設計函數對象

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容