STL之序列式容器(Vector/Deque/List)

序列式容器

序列式容器包括:靜態數組 array 、動態數組 vector 、雙端隊列 deque 、單鏈表 forward_ list 、雙鏈表 list 。這五個容器中,我們需要講解三個 vector deque list 的使 用,包括:初始化、遍歷、尾部插入與刪除、頭部插入與刪除、任意位置進行插入與 刪除、元素的清空、獲取元素的個數與容量的大小、元素的交換、獲取頭部與尾部元素等。

頭文件

#include <vector>
template<class T,class Allocator = std::allocator<T>
> class vector;#include <deque>
template<class T,class Allocator = std::allocator<T>
> class deque;#include <list>
template<class T,class Allocator = std::allocator<T>
> class list;

初始化容器對象

對于序列式容器而言,初始化的方式一般會有五種。

初始為空

vector<int> number;
deque<int> number;
list<int> number;

初始為多個相同的值

vector<int> number(10, 1);
deque<int> number(10, 1);
list<int> number(10, 1);

使用迭代器范圍

int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
vector<int> number(arr, arr + 10);//左閉右開區間
//vector可以直接替換為deque與list

使用大括號

vector<int> number = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
//vector可以直接替換為deque與list

拷貝構造或者移動構造

vector<int> number1 = {1, 2, 4, 6};
vector<int> number2(number1);
vector<int> number3(std::move(number1));
//vector可以直接替換為deque與list

遍歷容器中的元素

就是訪問容器中的每個元素,一般可以采用下標或者迭代器的方式進行遍歷。

//1、使用下標進行遍歷(要求容器必須是支持下標訪問的,list不支持下標,所以就不適用)
for(size_t idx = 0; idx != number.size(); ++idx)
{cout << number[idx] << " ";
}//2、使用未初始化的迭代器進行遍歷
vector<int>::iterator it;
for(it = numbers.begin(); it != numbers.end(); ++it)
{cout << *it << " ";
}//3、使用初始化迭代器進行遍歷
vector<int>::iterator it = number.begin();
for(; it != number.end(); ++it)
{cout << *it << " ";
}//4、使用for加上auto進行遍歷
for(auto &elem : number)
{cout << elem << " ";
}

在容器的尾部插入與刪除

可以向容器的尾部插入一個元素或者將容器的最后一個元素刪除。

//尾部插入一個元素(注意:是拷貝一份副本到尾部)
void push_back( const T& value);
void push_back( T&& value);
//尾部刪除
void pop_back();

在容器的頭部插入與刪除

可以向容器的頭部插入一個元素或者將容器的第一個元素刪除。

//頭部插入
void push_front( const T& value);
void push_front( T&& value);
//頭部刪除
void pop_front();
對于 deque list 而言,是支持這兩個操作的, 但是對于vector沒有提供這兩個操作。 vector 不支持在頭部進行插入元素與刪除元素。 這是從效率方面進行的考慮。

vector的原理

vector 頭部是固定的,不能進行插入與刪除,只提供了在尾部進行插入與刪除的操作,所以如果真的要在頭部插入或者刪除,那么其他的元素會發生移動,這樣操作就比較復雜。
探討 vector 底層實現 ,三個指針:
_ M _ start :指向第一個元素的位置;
_ M _ finish :指向最后一個元素的下一個位置;
_ M _ end _ of _ storage :指向當前分配空間的最后一個位置的下一個位置;
那么這三個指針是怎么來的呢,我們可以從其源碼中獲取答案(這里可以閱讀 vector的源碼)


void test()
{
vector<int> number = {1, 2, 3, 4};
&number;//error,只是獲取對象棧上的地址,也就是_M_start的地址
&number[0];//ok
&*number.begin();//ok
int *pdata = number.data();//ok
cout << "pdata = " << pdata << endl;//使用printf,思考一下printf與cout打印地址的區別
}

deque的原理

探索 deque 底層實現
deque 是由多個片段組成的,片段內部是連續的,但是片段之間不連續的、分散的,多個片段被一個稱為中控器的結構控制,所以說deque 是在物理上是不連續的,但是邏輯上是連續的。
我們依舊可以從其源碼中獲取答案(這里可以閱讀 deque 的源碼)
從繼承圖中可以看到,中控器其實是一個二級指針,由 _M_map 表示,還有一個表示中控器數組大小的 _M_map_size deque 的迭代器也不是一個簡單類型的指針,其迭代器是一個類類型,deque 有兩個迭代器指針,一個指向第一個小片段,一個指向最后一個小片段。其結構圖如下:

list的原理

list是雙向鏈表,其實現如下:

在容器的任意位置插入

三種序列式容器在任意位置進行插入的操作是insert函數,函數接口如下

//1、在容器的某個位置前面插入一個元素
iterator insert( iterator pos, const T& value );
iterator insert( const_iterator pos, const T& value );
number.insert(it, 22);//2、在容器的某個位置前面插入count個相同元素
void insert(iterator pos, size_type count, const T& value);
iterator insert(const_iterator pos, size_type count, const T& value);
number.insert(it1, 4, 44);//3、在容器的某個位置前面插入迭代器范圍的元素
template<class InputIt>
void insert(iterator pos, InputIt first, InputIt last);
template<class InputIt>
iterator insert(const_iterator pos, InputIt first, InputIt last);
vector<int> vec{51, 52, 53, 54, 55, 56, 57, 58, 59};
number.insert(it, vec.begin(), vec.end());//4、在容器的某個位置前面插入大括號范圍的元素
iterator insert(const_iterator pos, std::initializer_list<T> ilist);
number.insert(it, std::initialiser_list<int>{1, 2, 3});
三種序列式容器的插入示例如下:
//此處list可以換成vector或者deque
list<int> number = {1, 4, 6, 8, 9};
++it;
auto it = number.begin();//1、在容器的某個位置前面插入一個元素
number.insert(it, 22);//2、在容器的某個位置前面插入count個相同元素
number.insert(it, 3, 100);//3、在容器的某個位置前面插入迭代器范圍的元素
vector<int> vec{51, 52, 53, 54, 55, 56, 57, 58, 59};
number.insert(it, vec.begin(), vec.end());//4、在容器的某個位置前面插入大括號范圍的元素
number.insert(it, {1, 2, 3});
insert 在任意位置進行插入, list 使用起來很好,沒有任何問題,但是 deque vector 使用起來可能會出現問題,因為 vector 是物理上連續的 ,所以在中間插入元素會導致插入元素后面的所有元素向后移動,deque 也有類似情況, 可能因為插入而引起底層容量 不夠而擴容,從而使得迭代器失效 ( 申請了新的空間,但是迭代器還指向老的空間 ) ,即使沒有擴容,插入之后的迭代器也失效了( 不再指向之前的元素了 )

vector的迭代器失效

vector 為例,如果使用 insert 插入元素,而每次插入元素的個數不確定,可能剩余空間不足以存放插入元素的個數,那么insert 在插入的時候 底層就可能導致擴容,從而導 致迭代器還指向老的空間,繼續使用該迭代器會出現迭代器失效的問題
void test()
{
vector<int> number = {1, 2, 3, 4, 5, 6, 7, 8, 9};
display(number);
cout << "number.size() = " << numbers.size() << endl;//9
cout << "number.capacity() = " << numbers.capacity() << endl;//9cout << endl << "在容器尾部進行插入: " << endl;
number.push_back(10);
number.push_back(11);
display(number);
cout << "number.size() = " << number.size() << endl;//11
cout << "number.capacity() = " << number.capacity() << endl;//18cout << endl << "在容器vector中間進行插入: " << endl;
auto it = number.begin();
++it;
++it;
number.insert(it, 22);
display(number);
cout << "*it = " << *it << endl;
cout << "number.size() = " << number.size() << endl;//12
cout << "number.capacity() = " << number.capacity() << endl;//18numbers.insert(it, 7, 100);//因為插入個數不確定,有可能底層已經發生了擴容
display(numbers);
cout << "*it = " << *it << endl;
cout << "numbers.size() = " << numbers.size() << endl;//19
cout << "numbers.capacity() = " << numbers.capacity() <<endl;//24//正確辦法是重置迭代器的位置
vector<int> vec{51, 52, 53, 56, 57, 59};
numbers.insert(it, vec.begin(), vec.end());//繼續使用該迭代器就會出現問題(內存錯誤)
display(numbers);
cout << "*it = " << *it << endl;
cout << "numbers.size() = " << numbers.size() << endl;
cout << "numbers.capacity() = " << numbers.capacity() << endl;//解決方案:每次在插入元素的時候,可以將迭代器的位置進行重置更新,避免因為底層擴容,迭代器還指向老
//的空間而出現問題
vector<int> vec{51, 52, 53, 56, 57, 59};
it = number.begin();//重新置位
++it;
++it;
numbers.insert(it, vec.begin(), vec.end());//繼續使用該迭代器就會出現問題(內存錯誤)
display(numbers);
cout << "*it = " << *it << endl;
cout << "numbers.size() = " << numbers.size() << endl;
cout << "numbers.capacity() = " << numbers.capacity() << endl;
}
因為 vector push _ back 操作每次只會插入一個元素,所以可以按照統一的形式 2 * capacity() ,但是 insert 的時候,插入的元素個數是不定的,所以就不能一概而論。這里可以分別討論一下,我們設置capacity () = n , size () = m , insert 插入的元素個數為 t個:
如果 t < n - m ,新插入元素的個數比剩余空間小,這個時候就無需擴容,所以直接插入;
如果 n - m < t < m ,就按照 m 2 倍去進行擴容,新的空間就是 2 * m ;如果n - m < t < n t > m , 就按照 t + m 去進行擴容;
如果 t > n 時,依舊按照 t + m 去進行擴容 ;
這就是vector 進行 insert 擴容的原理(這個原理可以了解一下,主要是為了告訴大家不
是兩倍擴容)。

在容器的任意位置刪除元素

三種序列式容器的刪除操作是erase函數,函數接口如下

//刪除指定迭代器位置的元素
iterator erase(iterator position);
//刪除一個迭代器范圍的元素
iterator erase(iterator first, iterator last);
對于 vector 而言,會導致刪除迭代器之后的所有元素前移,從而導致刪除元素之后的所有迭代器失效(迭代器的位置沒有改變,但是因為元素的移動,導致迭代器指向的不是刪除之前的元素,所以失效);deque vector 復雜,要看 pos 前后的元素個數來決定,deque erase 函數可以看 STL 源碼,需要看刪除位置與 size () 的一半的大小,然后看是挪動前一半還是后一半,盡量減少挪動的次數;list 會刪除指向的元素,從而導致指向刪除元素的迭代器失效。
這里以 vector erase 為例,看看其刪除元素的操作與刪除后的效果。
//題意:刪除vector中所有值為4的元素。
vector<int> vec = {1, 3, 5, 4, 4, 4, 4, 7, 8,4, 9};
for (vector<int>::iterator it = vec.begin(); it != vec.end(); ++it)
{if(4 == *it){vec.erase(it);}
}//發現刪除后有些4沒有刪除掉,可以推測出是什么原因嗎?是那些4沒有刪除呢?
//正確解法:
for (auto it = vec.begin(); it != vec.end();)
{ if (4 == *it){vec.erase(it);//此處可以使用it接收erase的結果,更通用一些,即:it = vec.erase(it);}else{++it;}
}

其他操作

//1、清除容器中的所有元素(三個序列式容器都有)
void clear();//2、獲取元素個數(三個序列式容器都有)
size_type size() const;//3、獲取容量大小(只有vector有)
size_type capacity() const;//4、回收多余的空間,使得元素的個數與容量大小對應,不存在沒有使用的空間(vector與deque有這個函數)
void shrink_to_fit();//5、交換兩個相同容器中的元素(三個序列式容器都有)
void swap( vector& other);
vector<int> number1 = {1, 2, 3};
vector<int> number2 = {10, 20, 30};
number1.swap(number2);//之后number1中的內容與number2中的內容做了交換//6、更改容器中元素個數(三個序列式容器都有)
//以vector為例,執行resize時候,如果count < size(),就將多余的元素刪除;如果count > size(),就在//之前的元素后面執行insert添加元素(沒有指定就添加默認值),元素的個數在改變的同時,容量也在發生改變 //(上一次的兩倍或者本次元素個數)
void resize( size_type count, T value = T() );
void resize( size_type count);
void resize( size_type count, const value_type& value);//7、獲取第一個元素(三個序列式容器都有)
reference front();
const_reference front() const;//8、獲取最后一個元素(三個序列式容器都有)
reference back();
const_reference back() const;//9、C++11增加的可變參數模板的幾個函數
//在容器的尾部就地構造一個元素
template< class... Args >
void emplace_back( Args&&... args);
vector<Point> vec;
vec.emplace_back(1, 2);//就地將(1, 2)構建為一個對象存放在vector的尾部,減少拷貝或者移動

list的特殊操作

排序函數sort

void sort();//默認以升序進行排序,其實也就是,使用operator<進行排序
template< class Compare >
void sort(Compare comp);//其實也就是傳入一個具有比較的類型,即函數對象
template <typename T1, typename T2>
struct Compare
{bool operator()(const T1 &a, const T2 &b) const{return a < b;}
};

移除重復元素u n i q u e

void unique();
size_type unique();
注意使用 unique 的時候,要保證元素 list 是已經排好順序的,否則使用 unique 是沒有用的。

逆置鏈表中的元素r e v e r s e

void reverse();
void reverse() noexcept;
將鏈表中的元素逆置。

合并鏈表的函數m e r g e

//合并兩個鏈表(other既可以是左值也可以是右值)
void merge( list& other );
void merge( list&& other );
template <class Compare>
void merge( list& other, Compare comp );
template <class Compare>
void merge( list&& other, Compare comp );
合并的鏈表必須是有序的,如果沒有順序,合并沒有效果。兩個鏈表合并之后,并且另一個鏈表就為空了。

從一個鏈表轉移元素到另一個鏈表s p l i c e

//移動other鏈表到另一個鏈表的某個指定位置前面
void splice(const_iterator pos, list& other);
void splice(const_iterator pos, list&& other);//移動other鏈表中的某個元素到另一個鏈表的某個指定位置前面
void splice(const_iterator pos, list& other, const_iterator it);
void splice(const_iterator pos, list&& other, const_iterator it);//移動other鏈表的一對迭代器范圍元素到另一個鏈表的某個指定位置前面
void splice(const_iterator pos,list& other, const_iterator first, const_iterator last);
void splice(const_iterator pos,list&& other,const_iterator first, const_iterator last);

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

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

相關文章

swift菜鳥教程6-10(運算符,條件,循環,字符串,字符)

一個樸實無華的目錄 今日學習內容&#xff1a;1.Swift 運算符算術運算符比較運算符邏輯運算符位運算符賦值運算區間運算符其他運算符 2.Swift 條件語句3.Swift 循環4.Swift 字符串字符串屬性 isEmpty字符串常量let 變量var字符串中插入值字符串連接字符串長度 String.count使用…

泛微ECOLOGY9 記 數據展現集成 自定義開窗測試中對SQL 的IN語法轉換存在BUG

背景 搭建流程時&#xff0c;需將明細表1中的合同字段 供明細表2中的合同開窗查詢使用。 最終實現如下圖&#xff1a; 選擇 發票號時&#xff0c;自動帶出明細表1中的采購合同號清單&#xff0c;然后在明細表2中開窗采購合同號時&#xff0c;只跳出明細表1中有的采購合同號&am…

(自用)藍橋杯準備(需要寫的基礎)

要寫的文件 led_app lcd_app key_app adc_app usart_app scheduler LHF_SYS一、外設引腳配置 1. 按鍵引腳 按鍵引腳配置如下&#xff1a; B1&#xff1a;PB0B2&#xff1a;PB1B3&#xff1a;PB2B4&#xff1a;PA0 2. LCD引腳 LCD引腳配置如下&#xff1a; GPIO_Pin_9 /* …

PM2 完全指南:Node.js 應用后臺啟動、關閉與重啟詳解

文章目錄 **PM2 完全指南&#xff1a;Node.js 應用后臺啟動、關閉與重啟詳解****1. 什么是 PM2&#xff1f;****2. 安裝 PM2****全局安裝****驗證安裝** **3. 使用 PM2 啟動 Node.js 應用****基本啟動****指定應用名稱****集群模式&#xff08;多進程負載均衡&#xff09;****監…

Linux環境變量詳解

引言 在Linux系統中&#xff0c;環境變量是一種非常重要的概念&#xff0c;它影響著系統的運行方式和應用程序的行為。無論你是Linux新手還是經驗豐富的管理員&#xff0c;深入理解環境變量都能幫助你更高效地使用和管理Linux系統。本文將從基礎概念到高級應用&#xff0c;全面…

408 計算機網絡 知識點記憶(8)

前言 本文基于王道考研課程與湖科大計算機網絡課程教學內容&#xff0c;系統梳理核心知識記憶點和框架&#xff0c;既為個人復習沉淀思考&#xff0c;亦希望能與同行者互助共進。&#xff08;PS&#xff1a;后續將持續迭代優化細節&#xff09; 往期內容 408 計算機網絡 知識…

@linux系統SSL證書轉換(Openssl轉換PFX)

在Linux中&#xff0c;你可以使用OpenSSL工具將PFX/P12格式的證書轉換為單獨的CRT&#xff08;證書&#xff09;、KEY&#xff08;私鑰&#xff09;文件以及提取證書鏈 1. 提取私鑰文件(.key) openssl pkcs12 -in your_certificate.pfx -nocerts -out private.key -nodes系統會…

DAOS系統架構-組件

如上圖所示&#xff0c;一個完整的DAOS系統是由管理節點組件、客戶端節點組件、服務端節點組件以及網絡通信組件四個部分組成。管理節點組件通過管理網絡通道&#xff08;藍色&#xff09;對DAOS服務管理和監控。客戶端節點組件通過數據網絡通道&#xff08;紅色&#xff09;與…

制作一款打飛機游戲教程2:背景滾動

滾動原型開發 接下來&#xff0c;我們開始聚焦滾動原型的開發。我們需要確定游戲關卡的長度以及背景滾動的速度。 地圖與精靈空間限制 在開發過程中&#xff0c;我們遇到了地圖與精靈空間限制的問題。PICO 8的地圖編輯器下半部分與精靈表共享空間&#xff0c;這意味著我們只…

計算機組成原理——CPU與存儲器連接例題

計算機組成原理——CPU與存儲器連接例題 設CPU共有16根地址線和8根數據線&#xff0c;并用(MREQ) ?作為訪存控制信號&#xff08;低電平有效&#xff09;&#xff0c;(WR) ?作為讀/寫命令信號&#xff08;高電平讀&#xff0c;低電平寫&#xff09;。現有下列存儲芯片&#…

GNSS靜態數據處理

1 安裝數據處理軟件&#xff1a;儀器之星&#xff08;InStar &#xff09;和 Trimble Business Center 做完控制點靜態后&#xff0c;我們需要下載GNSS數據&#xff0c;對靜態數據進行處理。在處理之前需要將相關軟件在自己電腦上安裝好&#xff1a; 儀器之星&#xff08;InS…

Process Explorer 性能調優實戰:精準定位資源泄漏與高負載進程

一、下載與安裝 ?下載地址? Process Explorer安裝包下載&#xff1a;https://pan.quark.cn/s/950c36ba5364下載后解壓壓縮包&#xff0c;運行 procexp.exe&#xff08;32 位系統&#xff09;或 procexp64.exe&#xff08;64 位系統&#xff09;?。 ?界面概覽? 主界面以樹…

SVMSPro分布式綜合安防管理平臺-->以S3存儲革新,開啟智能安防新紀元

SVMSPro分布式綜合安防管理平臺–>以S3存儲革新&#xff0c;開啟智能安防新紀元 在數字化轉型浪潮下&#xff0c;企業安防管理正面臨海量數據存儲、跨區域協同以及數據安全的嚴峻挑戰。如何實現高效、彈性、低成本的存儲擴容&#xff1f;如何確保關鍵錄像數據萬無一失&…

Python 裝飾器(Decorator)

文章目錄 代碼解析1. 裝飾器定義 timer(func)2. 應用裝飾器 timer **執行流程****關鍵點****實際應用場景****改進版本&#xff08;帶 functools.wraps&#xff09;** 這是一個 Python 裝飾器&#xff08;Decorator&#xff09; 的示例&#xff0c;用于測量函數的執行時間。下…

git commit時自動生成Change-ID

創建全局鉤子目錄&#xff1a; 創建一個全局的Git hooks目錄&#xff1a; mkdir -p ~/.githooks 下載并設置commit-msg鉤子腳本&#xff1a; 下載Gerrit的commit-msg鉤子腳本&#xff0c;并放置在全局鉤子目錄中(如下載不了&#xff0c;可從本頁面附件中下載&#xff0c;“…

最新Ktransformers v0.24(Docker)并發部署DeepSeek-V3-0324模型

一、介紹 KTransformers v0.2.4 發布說明 我們非常高興地宣布&#xff0c;期待已久的 KTransformers v0.2.4 現已正式發布&#xff01;在這個版本中&#xff0c;我們對整 體架構進行了重大重構&#xff0c;更新了超過 1 萬行代碼&#xff0c;為社區帶來了備受期待的多并發支…

飛牛私有云5大硬核功能實測!

&#x1f4f8; 1. 智能相冊&#xff1a;AI搜圖原圖自由 - 自動備份&#xff1a;手機照片/視頻實時同步&#xff0c;支持RAW格式、實況照片無損備份&#xff0c;釋放128G手機秒變256G。 - AI黑科技&#xff1a; - 人臉識別&#xff1a;自動歸類人物相冊&#xff0c;輸入「媽媽…

webrtc pacer模塊(一) 平滑處理的實現

Pacer起到平滑碼率的作用&#xff0c;使發送到網絡上的碼率穩定。如下的這張創建Pacer的流程圖&#xff0c;其中PacerSender就是Pacer&#xff0c;其中PacerSender就是Pacer。這篇文章介紹它的核心子類PacingController及Periodic模式下平滑處理的基本流程。平滑處理流程中還有…

【android bluetooth 協議分析 01】【HCI 層介紹 1】【hci_packets.pdl 介紹】

在 AOSP 的藍牙協議棧 (Gabeldorsche) 中&#xff0c;hci_packets.pdl 是一個 協議描述語言文件&#xff0c;用于定義 HCI (Host Controller Interface) 層的數據包結構和通信協議。以下是詳細解析&#xff1a; 1. 文件作用 system/gd/hci/hci_packets.pdl 協議自動化生成&…

操作系統 4.2-鍵盤

鍵盤中斷初始化和處理 提取的代碼如下&#xff1a; // con_init 函數&#xff0c;初始化控制臺&#xff08;包括鍵盤&#xff09;的中斷 void con_init(void) {set_trap_gate(0x21, &keyboard_interrupt); } ? // 鍵盤中斷處理函數 .globl _keyboard_interrupt _keyboard…