STL源碼剖析 序列式容器|Vector

容器的概觀和分類

  • array 數組 、list 鏈表、tree樹 、stack堆棧、queue隊列、hash table散列表、set集合、map映射表
  • 根據數據在容器中的排列順序,將上述數據結構分為序列式和關聯式兩種類型

  • SGI STL使用內縮方式來表達基層和衍生層之間的關系
  • 衍生不是派生,本質上是一種內含的關系,例如heap內含vector,priority-queue內含heap;stack 和 queue都內含deque;set map multiset multimap 內含一個 RB-tree,hash_x都內含hashtable
  • 序列式容器,內部元素都可序但是未必有序。C++本身提供了一個序列式容器array,STL提供了vector、list、deque、priority_queue等
  • stack 和 queue都是在deque的基礎上改頭換面形成的,技術上將其歸類為一種 配接器

Vector

  • vector類似于array,二者唯一的差別在于 空間的運用的靈活性。
  • array是靜態空間,一旦配置就不會改變大小,如果存儲空間不夠需要申請空間進行元素的拷貝,這些操作均需要用戶自己來執行,最后還需要釋放先前擁有的區間。? 具體操作:(配置新的空間 | 數據移動 | 釋放先前的內存空間 )
  • vector是動態空間,隨著元素的加入,內部的實現機制會自行進行內存空間的擴充從而容納新的元素
  • vector的實現技術:關鍵在于其對于大小的控制和重新配置的時候對于數據的移動的效率。第二次申請的內存空間是先前的兩倍
  • SGI vector將vector實現于更加底層的<stl_vector.h> ,具體使用的時候直接引入<vector>頭文件即可
#include <iostream>
#include <new>        //for placement new
#include <cstddef>    //for ptrdiff_t size_t
#include <cstdlib>    //for exit
#include <climits>    //for UINT_MAXnamespace Chy{template <class T>inline T* _allocate(ptrdiff_t size,T*){std::set_new_handler(0);T* tmp = (T*)(::operator new((std::size_t)(size * sizeof (T))));if (tmp == 0){std::cerr << "out of memory" << std::endl;exit(1);}return tmp;}template<class T>inline void _deallocate(T* buffer){::operator delete (buffer);}template<class T1,class T2>inline void _construct(T1 *p,const T2& value){new(p) T1 (value);  //沒看懂}template <class T>inline void _destroy(T* ptr){ptr->~T();}template <class T>class allocator{public:typedef T           value_type;typedef T*          pointer;typedef const T*    const_pointer;typedef T&          reference;typedef const T&    const_reference;typedef std::size_t size_type;typedef ptrdiff_t   difference_type;template<class U>struct rebind{typedef allocator<U>other;};pointer allocate(size_type n,const void * hint = 0){return _allocate((difference_type)n,(pointer)0);}void deallocate(pointer p,size_type n){_deallocate(p);}void construct(pointer p,const T& value){_construct(p,value);}void destroy(pointer p){_destroy(p);}pointer address(reference x){return (pointer)&x;}const_pointer const_address(const_reference x){return (const_pointer)&x;}size_type max_size()const{return size_type(UINT_MAX/sizeof (T));}};
}template<class T,class Alloc>
class simple_alloc{
public:static T* allocate(std::size_t n){return 0==n?0:(T*)Alloc::allocate(n * sizeof(T));}static T* allocate(void){return (T*)Alloc::allocate(sizeof (T));}static void deallocate(T* p,size_t n){if (n!=0){Alloc::deallocate(p,n * sizeof(T));}}static void deallocate(T* p){Alloc::deallocate(p,sizeof(T));}
};//如果是copy construction 等同于assignment而且destructor 是 trivial以下就會有效
//如果是POD型別 執行的流程就會跳轉到以下函數,這個是通過function template的參數推導機制得到的
template<class ForwardIterator,class Size,class T>
inline ForwardIterator __uninitizlized_fill_n_aux(ForwardIterator first,Size n,const T&x){return fill_n(first,n,x); //交給高階函數執行
}struct __true_type{};
struct __false_type{};template<class T>
struct __type_traits {typedef __true_type this_dummy_member_must_be_first;typedef __false_type has_trivial_default_constructor;typedef __false_type has_trivial_copy_constructor;typedef __false_type has_trivial_assignment_constructor;typedef __false_type has_trivial_destructor;typedef __false_type is_POD_type;
};//函數的邏輯是
//首先萃取出 迭代器first的value type,然后判斷這個型別是否是POD類型
template<class ForwardIterator,class Size,class T,class T1>
inline ForwardIterator __uninitizlized_fill_n(ForwardIterator first,Size n,const T&x,T1*){//以下使用的是__type_traits<T1>::is_POD_type is _PODtypedef typename __type_traits<T1>::is_POD_type is_POD;return __uninitizlized_fill_n_aux(first,n,x,is_POD());
}template<class ForwardIterator,class Size,class T>
ForwardIterator uninitialized_fill_n(ForwardIterator first,Size n,const T&x){return __uninitizlized_fill_n(first,n,x,value_type(first));//使用value_type()判斷first的value type
}//alloc 是SGI STL默認的空間配置器
template <class T,class Alloc>
class vector{
public://vector的嵌套型別定義typedef T            value_type;typedef value_type * pointer;typedef value_type * iterator;typedef value_type & reference;typedef std::size_t  size_type;typedef ptrdiff_t    difference_type;
protected://simple_alloc 是SGI STL的空間配置器typedef simple_alloc<value_type,Alloc>data_allocator;iterator start; //表示目前使用空間的頭部iterator finish; //表示目前使用空間的尾部iterator end_of_storage; //表示目前可用空間的尾部void insert_aux(iterator position,const T&x);void deallocate(){if (start){data_allocator ::deallocate(start,end_of_storage - start);}}void fill_initialize(size_type n,const T& value){start = allocate_and_fill(n,value);finish = start+n;end_of_storage = finish;}public:iterator begin(){return start;}iterator end(){return finish;}size_type size() const {return size_type(end() - begin());}size_type capacity() const{return size_type (end_of_storage - begin());}bool empty(){return begin() == end();}reference operator[] (size_type n){return (*(begin() + n));}vector():start(0),finish(0),end_of_storage(0){};vector(size_type n,const T& value){fill_initialize(n,value);}vector(int n,const T& value){fill_initialize(n,value);}vector(long n,const T& value){fill_initialize(n,value);}explicit vector(size_type n){fill_initialize(n,T());}~vector(){Chy::allocator<T>::destroy(start,finish);  //全局函數deallocate();  //vector的成員函數}reference front(){return *begin();}  //第一個元素reference back(){return *(end()-1);} //最后一個元素void push_back(const T& x){if (finish != end_of_storage){Chy::allocator<T>::construct(finish,x);++finish;} else{insert_aux(end(),x);// vector的成員函數}}void pop_back(){  //將最尾端的元素取出--finish;Chy::allocator<T>::destroy(finish);}iterator erase(iterator position){ //清除某個位置上的元素if (position+1 != end()){std::copy(position+1,finish,position); //后續元素向前移動 進行元素的覆蓋}--finish;Chy::allocator<T>::destroy(finish);return position;}void resize(size_type new_size,const T& x){if (new_size < size()){erase(begin()+new_size,end());} else{std::inserter(end(),new_size - size(),x);}}void resize(size_type new_size){resize(new_size,T());}void clear(){erase(begin(),end());}
protected://配置空間并填滿內容iterator allocate_and_fill(size_type n,const T&x){iterator result = data_allocator ::allocate(n);uninitialized_fill_n(result,n,x); //全局函數return result;}
};

vector迭代器

  • vector維護的是一個連續的線性空間,所以不論其元素的的型別是怎樣,普通的指針都可以作為vector的迭代器從而滿足所有的必要的條件
  • 因為vector迭代器所需要的操作,比如 operator*??operator->?operator++?operator--?operator+?operator-?operator+=?operator-= 是普通指針天生就具備的能力
  • 普通指針支持隨機存取,正因為普通指針具備這樣的能力,所以vector提供的是random Access iterators
  • vector的迭代器是普通的指針
  • 例子
std::vector<int>::iterator i_vite; //i_vite的型別本質上是int*
std::vector<Shape>::iterator s_vite;  //s_vite的型別本質上是Shape*

Vector數據結構

  • 數據結構很簡單:線性連續空間
  • 使用start和finish指向配置的連續空間中的已經使用的范圍區間
  • 使用迭代器end_of_storage指向的是整個一塊連續的空間(包含備用空間)的尾端
    iterator start; //表示目前使用空間的頭部iterator finish; //表示目前使用空間的尾部iterator end_of_storage; //表示目前可用空間的尾部
  • vector分配的內存空間實際上是大于客戶端申請的內存空間,主要是為了防止以后內存的擴充,這個便是容量的概念。
  • 即容量的概念 永遠大于或者等于其大小,一旦容量等于大小,即是滿載。
  • 使用三個迭代器,完成 vector的首尾標定、使用空間的大小、容量、空容器的判斷、注標[]的運算子、最前端的元素 和最末尾的元素等等

Vector的構造和內存管理

  • //vector缺省使用alloc作為空間配置器,并據此另外定義了data_allocator,為的是更方便的以元素大小作為配置單位
  • ? ? typedef simple_alloc<value_type,Alloc>data_allocator; 所以data_allocator::allocate(n)? ; 表示配置n個元素的空間
  • vector提供了很多的構造器

  • uninitialized_fill_n 根據第一個參數的特性(使用type_traits 進行類型推導),決定使用fill_n() 還是通過反復調用 construct() 來完成任務
  • 使用push_back()將元素插入到vector的尾端的時候,函數檢查是否存在備用的空間,如果有直接在備用空間進行元素的構造,并且調整迭代器finish使得vector變大
  • 如果沒有備用空間就需要進行元素的擴充(配置 、移動數據、釋放空間)
    void push_back(const T& x){if (finish != end_of_storage){              //還存在備用空間Chy::allocator<T>::construct(finish,x); //直接進行元素的構造++finish;                               //調整水位高度} else{                                     //無備用空間insert_aux(end(),x);// vector的成員函數}}
//insert_aux 偽代碼
template<class T,class Alloc>
void vector<T,Alloc>::insert_aux(iterator position, const T &x) {if (finish != end_of_storage){  //還有備用空間//在備用空間起始處構造一個元素,并使用vector最后一個元素數值為其初始化Chy::allocator<T>::construct(finish,*(finish-1));//調整水位++finish;T x_copy = x;copy_backward(position,finish-2,finish-1);*position = x_copy;} else{ //無備用空間const size_type old_size = size();const size_type len = old_size != 0 ? 2*old_size:1;//配置原則://如果原大小為0 則配置1 (單位是 元素的大小)//如果原大小不為0 配置內存大小是先前的兩倍//前半段用于放置先前的數據,后半段用于存儲新的數據iterator new_start = data_allocator::allocate(len); //實際配置內存iterator new_finish = new_start;try{//進行先前元素的拷貝,將原vector的內容拷貝到新的vectornew_finish = unintialized_copy(start,position,new_start);//為新的元素設定初始數值 xChy::allocator<T>::construct(new_finish,x);//調整水位++new_finish;//將原vector的備用空間中的內容也忠實拷貝過來new_finish = unintialized_copy(position,finish,new_finish);}catch (){//commit or rollbackChy::allocator<T>::destroy(new_start,new_finish);data_allocator::allocate(new_start,len);throw ;}//析構并且釋放原有的vectorChy::allocator<T>::destroy(begin(),end());deallocate();//調整迭代器 指向新的vectorstart = new_start;finish = new_finish;end_of_storage = new_start + len;}
}
  • ?動態增加大小 不是在現有的內存空間后面接入新的空間 (? 無法保證原有空間之后是否還尚有可以配置的空間?)
  • 需要在背的地方申請空間
  • 因此一旦造成空間的重新配置,指向原有vector的所有迭代器就會失效了
    void pop_back(){  //將最尾端的元素取出--finish;     //將尾端標記往前移動一格,表示放棄尾端元素Chy::allocator<T>::destroy(finish);}
    void pop_back(){  //將最尾端的元素取出--finish;     //將尾端標記往前移動一格,表示放棄尾端元素Chy::allocator<T>::destroy(finish);}//清除[first ,last)內部所有元素iterator erase(iterator first,iterator last){iterator i = copy(last,finish,first);Chy::allocator<T>::destroy(i,finish);finish = finish - (last - first);return first;}//清除某個特定位置上的元素iterator erase(iterator position){ //清除某個位置上的元素if (position+1 != end()){std::copy(position+1,finish,position); //后續元素向前移動 進行元素的覆蓋}--finish;Chy::allocator<T>::destroy(finish);return position;}void clear(){erase(begin(),end());}

  • 這一塊還不是很清晰 需要進一步完善 insert(position,n,x)

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/446281.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/446281.shtml
英文地址,請注明出處:http://en.pswp.cn/news/446281.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

ansible 修改文件變量_Ansible Playbook中的變量與引用

Ansible是一個系列文章&#xff0c;我會盡量以通俗易懂、詼諧幽默的總結方式給大家呈現這些枯燥的知識點&#xff0c;讓學習變的有趣一些。Ansible自動化運維前言前面有說到使用playbook來搞一些復雜的功能&#xff0c;我們使用YAML來寫playbook&#xff0c;就像我們用其它語言…

java 判斷日期為第幾天

題目1 編寫程序&#xff1a;從鍵盤上輸入2019年的“month”和“day”&#xff0c;要求通過程序 輸出輸入的日期為2019年的第幾天。 代碼1 從12月往下加日期數 package l1_switch_case; import java.util.Scanner; public class SwitchDemo4 {public static void main(Strin…

STL源碼剖析 list概述

目錄 list的節點(node) list迭代器 list 的構造和內存管理 list 的元素操作 list相較于vector連續的線性空間就顯得很復雜&#xff0c;他的存儲空間是不連續的&#xff0c;好處是每次插入和刪除一個元素的時候&#xff0c;只需要配置或者釋放一個元素的空間 插入和刪除十分的…

vsftp不允許切換到其它目錄_IntelliJ IDEA如何對project的目錄進行篩選顯示?

如果你的項目很龐大&#xff0c;同一個功能用到的各種文件散落在多個文件夾&#xff0c;開發時切換不便&#xff0c;可以利用scope功能&#xff0c;只顯示該功能用到的文件&#xff0c;讓project列表十分清爽&#xff0c;提高開發效率。本文使用的IDEA版本為2020.1。1、打開sco…

java 年份對應的中國生肖

題目 編寫一個程序&#xff0c;為一個給定的年份找出其對應的中國生肖。 中國的生肖基于12年一個周期&#xff0c; 每年用一個動物代表&#xff1a; rat、ox、tiger、rabbit、dragon、snake、horse、sheep、monkey、 rooster、dog、pig。 提示&#xff1a;2019年&#xff1a;豬…

密碼學專題 對稱加密算法

一般來說&#xff0c;使用OpenSSL對稱加密算法有兩種方式&#xff0c;一種是使用API函數的方式&#xff0c;一種是使用OpenSSL提供的對稱加密算法指令方式。本書將介紹對稱加密算法的指令方式OpenSSL的對稱加密算法指令主要用來對數據進行加密和解密處理&#xff0c;輸入輸出的…

網絡防火墻單向和雙向_單向晶閘管與雙向晶閘管之間的不同之處

晶閘管是回一個可以控導點開關&#xff0c;能以弱電去控制強電的各種電路。晶閘管常用于整流&#xff0c;調壓&#xff0c;交直流變化&#xff0c;開關&#xff0c;調光等控制電路中。具有提交小&#xff0c;重量輕&#xff0c;耐壓高&#xff0c;容量大&#xff0c;效率高&…

java 遍歷100以內的偶數,偶數的和,偶數的個數

題目 遍歷100以內的偶數&#xff0c;偶數的和&#xff0c;偶數的個數 代碼 package l2_for; /*遍歷100以內的偶數&#xff0c;偶數的和&#xff0c;偶數的個數*/ public class ForDemo1 {public static void main(String[] args) {//方法1&#xff1a;int sum1 0,count10;f…

python版本切換_怎么切換python版本

展開全部 &#xff08;1&#xff09;分別安2113裝 python-2.7.12.amd64.msi python-3.5.2-amd64.exe &#xff08;python官網下載的&#xff09; 順序無所謂&#xff08;為5261了看著4102方便&#xff0c;我把安裝路徑修改統一了1653&#xff09; &#xff08;2&#xff09;配置…

java 打印

題目 編寫程序從1循環到150&#xff0c;并在每行打印一個值&#xff0c;另外在每個3的倍數行 上打印出“foo”,在每個5的倍數行上打印“biz”,在每個7的倍數行上打印 輸出“baz”。 代碼 package l2_for;/** 編寫程序從1循環到150&#xff0c;并在每行打印一個值&#xff0c…

react.lazy 路由懶加載_Vue面試題: 如何實現路由懶加載?

非懶加載import List from /components/list.vue const router new VueRouter({routes: [{ path: /list, component: List }] })方案一(常用)const List () > import(/components/list.vue) const router new VueRouter({routes: [{ path: /list, component: List }] })方…

STL源碼剖析 deque雙端隊列 概述

vector是單向開口的連續線性空間&#xff0c;deque是一種雙向開口的連續線性空間。deque可以在頭尾兩端分別進行元素的插入和刪除操作vector和deque的差異 1&#xff0c;deque允許常數時間內對于頭端元素進行插入和刪除操作2&#xff0c;deque沒有所謂容量(capacity)的概念&…

java 最大公約數和最小公倍數

題目 題目&#xff1a;輸入兩個正整數m和n&#xff0c;求其最大公約數和最小公倍數。 比如&#xff1a;12和20的最大公約數是4&#xff0c;最小公倍數是60。 說明&#xff1a;break關鍵字的使用 代碼一 package l2_for; //題目&#xff1a;輸入兩個正整數m和n&#xff0c;求…

python的自帶數據集_Python的Sklearn庫中的數據集

一、Sklearn介紹 scikit-learn是Python語言開發的機器學習庫&#xff0c;一般簡稱為sklearn&#xff0c;目前算是通用機器學習算法庫中實現得比較完善的庫了。其完善之處不僅在于實現的算法多&#xff0c;還包括大量詳盡的文檔和示例。其文檔寫得通俗易懂&#xff0c;完全可以當…

STL源碼剖析 stack 棧 概述->(使用deque雙端隊列 / list鏈表)作為stack的底層容器

Stack是一種先進后出的數據結構&#xff0c;他只有一個出口stack允許 新增元素、移除元素、取得最頂端的元素&#xff0c;但是無法獲得stack的內部數據&#xff0c;因此satck沒有遍歷行為Stack定義的完整列表 (雙端隊列作為Stack的底層容器) 將deque作為Stack的底部結構&#…

java 三位數的水仙花數

代碼 package l2_for;public class ForDemo6 {public static void main(String[] args) {for (int i 100; i <999 ; i) {int i1i/1%10;int i2i/10%10;int i3i/100%10;if (i(int)(Math.pow(i1,3)Math.pow(i2,3)Math.pow(i3,3))){System.out.print(i"\t");}}} }

python怎么實現圖像去噪_基于深度卷積神經網絡和跳躍連接的圖像去噪和超分辨...

Image Restoration Using Very Deep Convolutional Encoder-Decoder Networks with Symmetric Skip Connections作者&#xff1a;Xiao-Jiao Mao、Chunhua Shen等本文提出了一個深度的全卷積編碼-解碼框架來解決去噪和超分辨之類的圖像修復問題。網絡由多層的卷積和反卷積組成&a…

STL源碼剖析 queue隊列概述

queue是一種先進先出的數據結構&#xff0c;他有兩個出口允許新增元素&#xff08;從最底端 加入元素&#xff09;、移除元素&#xff08;從最頂端刪除元素&#xff09;&#xff0c;除了對于頂端和底端元素進行操作之外&#xff0c;沒有辦法可以獲取queue的其他元素即queue沒有…

java輸入正數和負數并計算個數

題目 從鍵盤讀入個數不確定的整數&#xff0c;并判斷讀入的正數和負數的個數&#xff0c;輸入 為0時結束程序。 知識點 最簡單“無限” 循環格式&#xff1a;while(true) , for(;;),無限循環存在的原因是并不 知道循環多少次&#xff0c;需要根據循環體內部某些條件&#xf…

python為什么運行不了_python為什么會環境變量設置不成功

學習python編程&#xff0c;首先要配置好環境變量。本文主要講解python的環境變量配置&#xff0c;在不同版本下如何安裝 Windows 打開Python官方下載網站 x86:表示是32位電腦 x86-64:表示是64位電腦 目前Python版本分為2.x版本和3.x版本。推薦大家使用3.x版本。 設置環境變量&…