原文網址
參考網址
請你來說一下map和set有什么區別,分別又是怎么實現的?
- map和set都是C++的關聯容器,其底層實現都是紅黑樹(RB-Tree)。由于 map 和set所開放的各種操作接口,RB-tree 也都提供了,所以幾乎所有的 map 和set的操作行為,都只是轉調 RB-tree 的操作行為。
map和set區別在于
- (1)map中的元素是key-value(關鍵字—值)對:關鍵字起到索引的作用,值則表示與索引相關聯的數據;Set與之相對就是關鍵字的簡單集合,set中每個元素只包含一個關鍵字。
- (2)set的迭代器是const的,不允許修改元素的值;map允許修改value,但不允許修改key。其原因是因為map和set是根據關鍵字排序來保證其有序性的,如果允許修改key的話,那么首先需要刪除該鍵,然后調節平衡,再插入修改后的鍵值,調節平衡,如此一來,嚴重破壞了map和set的結構,導致iterator失效,不知道應該指向改變前的位置,還是指向改變后的位置。所以STL中將set的迭代器設置成const,不允許修改迭代器的值;而map的迭代器則不允許修改key值,允許修改value值。
- (3)map支持下標操作,set不支持下標操作。map可以用key做下標,map的下標運算符[ ]將關鍵碼作為下標去執行查找,如果關鍵碼不存在,則插入一個具有該關鍵碼和mapped_type類型默認值的元素至map中,因此下標運算符[ ]在map應用中需要慎用,const_map不能用,只希望確定某一個關鍵值是否存在而不希望插入元素時也不應該使用,mapped_type類型沒有默認值也不應該使用。如果find能解決需要,盡可能用find。
請你來介紹一下STL的allocaotr??
- STL的分配器用于封裝STL容器在內存管理上的底層細節。在C++中,其內存配置和釋放如下:
- new運算分兩個階段:(1)調用::operator new配置內存;(2)調用對象構造函數構造對象內容
- delete運算分兩個階段:(1)調用對象析構函數;(2)調用::operator delete釋放內存
- 為了精密分工,STL allocator將兩個階段操作區分開來:內存配置有alloc::allocate()負責,內存釋放由alloc::deallocate()負責;對象構造由::construct()負責,對象析構由::destroy()負責。
- 同時為了提升內存管理的效率,減少申請小內存造成的內存碎片問題,SGI STL采用了兩級配置器,當分配的空間大小超過128B時,會使用第一級空間配置器;當分配的空間大小小于128B時,將使用第二級空間配置器。第一級空間配置器直接使用malloc()、realloc()、free()函數進行內存空間的分配和釋放,而第二級空間配置器采用了內存池技術,通過空閑鏈表來管理內存。
參考鏈接
- 標準庫 STL :Allocator能做什么?
- C++中std::allocator的使用
- 第10篇:C++ 堆內存管理器-allocator

?請你來說一說STL迭代器刪除元素
- 這個主要考察的是迭代器失效的問題。
- 1.對于序列容器vector,deque來說,使用erase(itertor)后,后邊的每個元素的迭代器都會失效,但是后邊每個元素都會往前移動一個位置,但是erase會返回下一個有效的迭代器;
- 2.對于關聯容器map set來說,使用了erase(iterator)后,當前元素的迭代器失效,但是其結構是紅黑樹,刪除當前元素的,不會影響到下一個元素的迭代器,所以在調用erase之前,記錄下一個元素的迭代器即可。
- 3.對于list來說,它使用了不連續分配的內存,并且它的erase方法也會返回下一個有效的iterator,因此上面兩種正確的方法都可以使用。
請你說一說STL中MAP數據存放形式
- 紅黑樹。unordered map底層結構是哈希表
請你講講STL有什么基本組成
- 容器、算法、迭代器、函數對象、適配器、內存分配器這 6 部分構成,其中后面 4 部分是為前 2 部分服務的
STL的組成 | 含義 |
---|
容器 | 一些封裝數據結構的模板類,例如 vector 向量容器、list 列表容器等。 |
算法 | STL 提供了非常多(大約 100 個)的數據結構算法,它們都被設計成一個個的模板函數,這些算法在 std 命名空間中定義,其中大部分算法都包含在頭文件 <algorithm> 中,少部分位于頭文件 <numeric> 中。 |
迭代器 | 在?C++?STL 中,對容器中數據的讀和寫,是通過迭代器完成的,扮演著容器和算法之間的膠合劑。 |
函數對象 | 如果一個類將 () 運算符重載為成員函數,這個類就稱為函數對象類,這個類的對象就是函數對象(又稱仿函數)。 |
適配器 | 可以使一個類的接口(模板的參數)適配成用戶指定的形式,從而讓原本不能在一起工作的兩個類工作在一起。值得一提的是,容器、迭代器和函數都有適配器。 |
內存分配器 | 為容器類模板提供自定義的內存申請和釋放功能,由于往往只有高級用戶才有改變內存分配策略的需求,因此內存分配器對于一般用戶來說,并不常用。 |
頭文件
- <iterator> <functional> <vector> <deque> <list> <queue> <stack> <set> <map> <algorithm> <numeric> <memory> <utility>?
注意事項
- 分配器給容器分配存儲空間
- 算法通過迭代器獲取容器中的內容
- 仿函數可以協助算法完成各種操作
- 配接器用來套接適配仿函數
參考鏈接
請你說說STL中map與unordered_map
Map映射
- map 的所有元素都是 pair,同時擁有實值(value)和鍵值(key)。pair 的第一元素被視為鍵值,第二元素被視為實值。所有元素都會根據元素的鍵值自動被排序。不允許鍵值重復。
- 底層實現:紅黑樹
- 適用場景:有序鍵值對不重復映射
Multimap
- 多重映射。multimap 的所有元素都是 pair,同時擁有實值(value)和鍵值(key)。pair 的第一元素被視為鍵值,第二元素被視為實值。所有元素都會根據元素的鍵值自動被排序。允許鍵值重復。
- 底層實現:紅黑樹
- 適用場景:有序鍵值對可重復映射
請你說一說vector和list的區別,應用,越詳細越好
Vector
- 連續存儲的容器,動態數組,在堆上分配空間,本質是鏈表
- 底層實現:數組
- 兩倍容量增長:
- vector 增加(插入)新元素時,如果未超過當時的容量,則還有剩余空間,那么直接添加到最后(插入指定位置),然后調整迭代器。
- 如果沒有剩余空間了,則會重新配置原有元素個數的兩倍空間,然后將原空間元素通過復制的方式初始化新空間,再向新空間增加元素,最后析構并釋放原空間,之前的迭代器會失效。
性能:
- 訪問:O(1)
- 插入:在最后插入(空間夠):很快
- 在最后插入(空間不夠):需要內存申請和釋放,以及對之前數據進行拷貝。
- 在中間插入(空間夠):內存拷貝
- 在中間插入(空間不夠):需要內存申請和釋放,以及對之前數據進行拷貝。
- 刪除:在最后刪除:很快
- 在中間刪除:內存拷貝
- 適用場景:經常隨機訪問,且不經常對非尾節點進行插入刪除。
List
- 動態鏈表,在堆上分配空間,每插入一個元數都會分配空間,每刪除一個元素都會釋放空間。
- 底層:雙向鏈表
性能
- 訪問:隨機訪問性能很差,只能快速訪問頭尾節點。
- 插入:很快,一般是常數開銷
- 刪除:很快,一般是常數開銷
- 適用場景:經常插入刪除大量數據
區別
- 1)vector底層實現是數組;list是雙向 鏈表。
- 2)vector支持隨機訪問,list不支持。
- 3)vector是順序內存,list不是。
- 4)vector在中間節點進行插入刪除會導致內存拷貝,list不會。
- 5)vector一次性分配好內存,不夠時才進行2倍擴容;list每次插入新節點都會進行內存申請。
- 6)vector隨機訪問性能好,插入刪除性能差;list隨機訪問性能差,插入刪除性能好。
應用
- vector擁有一段連續的內存空間,因此支持隨機訪問,如果需要高效的隨即訪問,而不在乎插入和刪除的效率,使用vector。
- list擁有一段不連續的內存空間,如果需要高效的插入和刪除,而不關心隨機訪問,則應使用list。
請你來說一下STL中迭代器的作用,有指針為何還要迭代器
1、迭代器
- Iterator(迭代器)模式又稱Cursor(游標)模式,用于提供一種方法順序訪問一個聚合對象中各個元素, 而又不需暴露該對象的內部表示。或者這樣說可能更容易理解:Iterator模式是運用于聚合對象的一種模式,通過運用該模式,使得我們可以在不知道對象內部表示的情況下,按照一定順序(由iterator提供的方法)訪問聚合對象中的各個元素。
- 由于Iterator模式的以上特性:與聚合對象耦合,在一定程度上限制了它的廣泛運用,一般僅用于底層聚合支持類,如STL的list、vector、stack等容器類及ostream_iterator等擴展iterator。
2、迭代器和指針的區別
- 迭代器不是指針,是類模板,表現的像指針。他只是模擬了指針的一些功能,通過重載了指針的一些操作符,->、*、++、--等。迭代器封裝了指針,是一個“可遍歷STL( Standard Template Library)容器內全部或部分元素”的對象,?本質是封裝了原生指針,是指針概念的一種提升(lift),提供了比指針更高級的行為,相當于一種智能指針,他可以根據不同類型的數據結構來實現不同的++,--等操作。
- 迭代器返回的是對象引用而不是對象的值,所以cout只能輸出迭代器使用*取值后的值而不能直接輸出其自身。
3、迭代器產生原因
- Iterator類的訪問方式就是把不同集合類的訪問邏輯抽象出來,使得不用暴露集合內部的結構而達到循環遍歷集合的效果。
請你說一說epoll原理
- 調用順序:
- int epoll_create(int size);
- int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
- int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
- 首先創建一個epoll對象,然后使用epoll_ctl對這個對象進行操作,把需要監控的描述添加進去,這些描述如將會以epoll_event結構體的形式組成一顆紅黑樹,接著阻塞在epoll_wait,進入大循環,當某個fd上有事件發生時,內核將會把其對應的結構體放入到一個鏈表中,返回有事件發生的鏈表。
參考鏈接
?n個整數的無序數組,找到每個元素后面比它大的第一個數,要求時間復雜度為O(N)
#include <iostream>
#include <vector>
#include <stack>
using namespace std;//n個整數的無序數組,找到每個元素后面比它大的第一個數,要求時間復雜度為O(N)
vector<int> find(vector<int> &num)
{int len = num.size();//空數組,返回空if(len == 0)return {};stack<int> notFind;//棧:num中還未找到符合條件的元素索引vector<int> res(len, -1);//返回結果:初始化-1,表示未找到int i = 0;while(i < len)//遍歷數組{//如果棧空或者當前num元素不大于棧頂,將當前元素壓棧,索引后移if(notFind.empty() || num[notFind.top()] >= num[i])notFind.push(i++);else//有待處理元素,且num當前元素大于棧頂索引元素,符合條件,更新結果數組中該索引的值,棧頂出棧。{res[notFind.top()] = num[i];notFind.pop();}}return res;
}int main()
{vector<int> num = {1, 3, 2, 4, 99, 101, 5, 8};// vector<int> num = {1, 1, 1, 1, 1, 1, 1};// vector<int> num = {};vector<int> res = find(num);for(auto i : res)cout << i << " ";return 0;
}
請你回答一下STL里resize和reserve的區別
- resize():改變當前容器內含有元素的數量(size()),eg: vector<int>v; v.resize(len);v的size變為len,如果原來v的size小于len,那么容器新增(len-size)個元素,元素的值為默認為0.當v.push_back(3);之后,則是3是放在了v的末尾,即下標為len,此時容器是size為len+1;
- reserve():改變當前容器的最大容量(capacity),它不會生成元素,只是確定這個容器允許放入多少對象,如果reserve(len)的值大于當前的capacity(),那么會重新分配一塊能存len個對象的空間,然后把之前v.size()個對象通過copy construtor復制過來,銷毀之前的內存;
- vector 的reserve增加了vector的capacity,但是它的size沒有改變!而resize改變了vector的capacity同時也增加了它的size!
- 原因如下: ?reserve是容器預留空間,但在空間內不真正創建元素對象,所以在沒有添加新的對象之前,不能引用容器內的元素。加入新的元素時,要調用push_back()/insert()函數。
- ?resize是改變容器的大小,且在創建對象,因此,調用這個函數之后,就可以引用容器內的對象了,因此當加入新的元素時,用operator[]操作符,或者用迭代器來引用元素對象。此時再調用push_back()函數,是加在這個新的空間后面的。
- resize增加大的容量超過reserve,會將capacity和size一樣
- resize減少容量 不會影響到capacity
- 通過 shrink_to_fit 將capacity保持和size一致的大小
參考鏈接
測試代碼
#include <iostream>
#include <vector>
using namespace std;
int main() {vector<int> a;a.reserve(100);a.resize(50);cout<<a.size()<<" "<<a.capacity()<<endl;//50 100a.resize(150);cout<<a.size()<<" "<<a.capacity()<<endl;//150 150a.reserve(50);cout<<a.size()<<" "<<a.capacity()<<endl;//150 150a.reserve(200);cout<<a.size()<<" "<<a.capacity()<<endl;//150 200a.resize(50);cout<<a.size()<<" "<<a.capacity()<<endl;//50 200a.shrink_to_fit();cout<<a.size()<<" "<<a.capacity()<<endl;//50 50
}
請你說一說stl里面set和map怎么實現的
- 集合,所有元素都會根據元素的值自動被排序,且不允許重復。
- 底層實現:紅黑樹
- set 底層是通過紅黑樹(RB-tree)來實現的,由于紅黑樹是一種平衡二叉搜索樹,自動排序的效果很不錯,所以標準的 STL 的 set 即以 RB-Tree 為底層機制。又由于 set 所開放的各種操作接口,RB-tree 也都提供了,所以幾乎所有的 set 操作行為,都只有轉調用 RB-tree 的操作行為而已。
- 適用場景:有序不重復集合
- map映射。map 的所有元素都是 pair,同時擁有實值(value)和鍵值(key)。pair 的第一元素被視為鍵值,第二元素被視為實值。所有元素都會根據元素的鍵值自動被排序。不允許鍵值重復。
- 底層:紅黑樹
- 適用場景:有序鍵值對不重復映射