STL小結 仿寫TinySTL專案有感

2022-10-07 21:45:11 字數 3247 閱讀 6265

六大元件:容器(類别範本)、演算法(函式模板)、迭代器(類别範本,設計模式)、配置器(類别範本)、配接器(容器介面卡、函式配接器)、仿函式(類或類别範本)。整個專案採用大多的是泛型程式設計的思想(模板機制)。使得多種資料型別在同一個演算法或結構上都可以操作,在編譯器就確定聚類資料型別。

(一)配置器

一般new都是要先申請空間,再在空間構造物件。delete是要先析構物件,再釋放空間。

專案為了更好的分工,把物件的構造和析構(construct和destroy)、空間的申請和釋放(malloc和自由連結串列)分成兩大塊。

一級配置器(class __malloc_alloc_template):呼叫malloc和realloc,如果不成功,改呼叫oom_malloc和oom_realloc(丟擲異常或exit(1))。

二級配置器(class __default_alloc_template):16個自由連結串列

記憶體配置過程:小於128bytes就找16個自由連結串列裡面合適的那個連結串列(上調到8的倍數)還有沒有,有就直接返回。如果沒有,就向記憶體池申請所需塊的大小掛到自由連結串列(refill/chunk_alloc(),可能獲得20個新節點,也可能小於20個)。如果還是沒有,使用malloc從堆中申請,申請的大小是需求的兩倍(一倍放在自由連結串列,一倍放在記憶體池)。如果malloc還是失敗,那再從剩下的自由連結串列找找。實在不行就bad_alloc異常。

(二)迭代器。

精髓在於trait思想。trait可以被稱為萃取機,通過它來獲取迭代器的一些資訊,在stl裡面發揮巨大作用。

template

struct

iterator_traits

//下面針對原生指標。通過類别範本的特化來支援原生指標。(模板偏特化)

template

struct iterator_traits<_tp>

可以通過iterator_traits::itereator_category獲取迭代器的資訊,如果要直接使用typename後面的那個簡單一點的iterator::iterator_type來獲取迭代器的資訊,前提是迭代器必須是一個類。但是實際上並不是所有的迭代器都是一個類,原生指標也是一種迭代器,但是原生指標不是類,所以沒法定義內嵌型別typeof。

迭代器類別有五種。input_iterator是隻讀的,支援==/!=/++/*/->等操作。output_iterator是隻寫的,支援++/*等操作。

forward_iterator是單向一步的,可讀寫。bidirection_iterator是雙向一步的。randomaccess_iterator是隨機多步的。

(三)容器和介面卡

stl容器有:vector、list雙向連結串列、deque、map(unordered/multi)、set(unordered/multi)

介面卡是將一個類的介面轉換成客戶希望的另一個介面。比如把香港插頭轉成內地插頭。

stl容器介面卡有:stack(可以使用deque做底層),queue(可以使用deque做底層),heap(平時雖然都是靜態陣列,但可以使用vector動態的來實現),priority_queue(使用vector[容器]和heap[push和pop演算法]做底層),slist是單向連結串列。

序列式容器是通過元素在容器的位置,順序儲存和訪問元素。關聯式容器時通過鍵key來儲存和讀取元素。

分別從每個容器的迭代器、建構函式、屬性獲取來展開介紹。

(1)關於vector。

線性儲存結構。需要三個迭代器分別指向資料的頭(start)、資料的尾(finish)、陣列的尾(end_of_storage)。

屬性end()返回的是finish。

push_back函式,插入新元素的時候,會先檢查是否有備用空間,如果有就直接在備用空間構造元素,並調整迭代器finish。如果沒有備用空間,就要呼叫insert_aux函式擴充空間(重新配置-移動資料-釋放原空間)。pop_back函式就是直接移動finish迭代器。

erase函式可以刪除[first, last)範圍中的所有元素,也可以刪除指定迭代器位置的元素。刪除範圍內的元素,第一步要將finish迭代器後面的元素拷貝,然後返回拷貝完成的尾部迭代器,最後再刪除。刪除指定位置的就是將指定位置後面所有元素向前移動,最後析構最後一個元素。

insert函式分成三種情況。如果備用空間足夠而且插入點後面的現有元素多於新增元素;如果備用空間足夠而且插入點後面的現有元素少於新增元素;如果備用空間不夠。

由於元素空間重新配置會導致之前的迭代器失效,比如插入元素或者刪除元素。

優點:動態擴容,便於隨機訪問,節省空間。

缺點:插入刪除的時間複雜度高,只能在末端pop和push,重分配麻煩。

(2)關於list。

雙向連結串列,鏈式儲存結構。有pre和next兩個指標。可以頭插和尾插。插入刪除就是雙向連結串列的。

優點:插入刪除方便,兩端可push和pop。

缺點:不支援隨機訪問,相比vector佔用空間多。

(3)關於deque。

線性儲存+鏈式儲存(分段連續空間)。邏輯上也是線性儲存。和vector的區別在於,deque可以在常數時間內插入刪除(頭尾都可以),但是deque沒有容量概念,因為它是動態以分段連續空間組合而成,隨時可以增加一塊新的空間並拼接起來。

中控器:採用雜湊(指標,緩衝區)作為deque的儲存空間主體。其中緩衝區就是另外一段比較大的連續線性空間。每個緩衝區都設計了三個迭代器(cur表示當前所指的位置,first表示當前陣列中頭的位置,last表示當前陣列中尾的位置)

優點:隨機訪問方便,插入刪除方便,可在兩端push和pop。

缺點:因為涉及比較複雜,採用分段連續空間,所以佔用記憶體多。

(4)關於map和set。 只有前面帶unordered的才是雜湊表實現的,否則一定是紅黑樹實現的(因為要排序)

紅黑樹特性:每個節點都是紅色或者黑色。根節點一定是黑色。紅節點的左右一定是黑色。每個葉子節點一定是黑色。(三個一定黑)。從某節點到其所有葉子節點都包含相同數量的黑色節點。

應用於linux系統的網路epoll呼叫、虛擬記憶體管理、stl的set和map。

鍵是不能改變的,因為紅黑樹的排序的,如果改變鍵的話整棵樹都亂了。

如果字首帶multi就是允許鍵重複,那就採用insert_equal,而不是insert_unique。

(三)演算法

resize是直接改變容器內元素的數量,賦初始值。

reverse只是改變容器的最大容量,不會放值進去。如果reverse的容量大於之前的,那麼會重新分配一塊能存len個物件的空間,然後把之前的物件複製過來,再銷燬之前的記憶體。