<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
列表是一種順序容器,它允許在序列中的任何位置執行常數時間插入和刪除操作,並允許在兩個方向上進行迭代。
它的底層是一個帶頭雙向迴圈連結串列。附一篇博主用C語言寫的帶頭雙向迴圈連結串列:【資料結構】帶頭雙向迴圈連結串列的實現
list不能用演演算法庫的sort進行排序。演演算法庫中的sort的底層是一個快排,需滿足三數取中,需要傳入隨機存取迭代器,所以list並不適用。
當然list中提供了一個自己的sort,它的底層是一個歸併排序。但是這個sort比vector使用演演算法庫的sort還慢,甚至比list的資料先push_back到vector到再用演演算法庫的sort還要慢。
insert,迭代器不失效
earse失效
1、單向迭代器:只能++,不能--。例如單連結串列,雜湊表;
2、雙向迭代器:既能++也能--。例如雙向連結串列;
3、隨機存取迭代器:能++--,也能+和-。例如vector和string。
迭代器是內嵌型別(內部類或定義在類裡)
普通迭代器
迭代器的實現需要支援解除參照(為了取資料),支援++--。
博主模擬實現string和vector時,直接將原生指標typedef成迭代器,是因為陣列的結構正好滿足迭代器的行為(注:string和vector可以用原生指標實現,但是vs中採用自定義型別封裝的方式實現),但是list中的節點地址是不連續的,不能使用原生指標,需要使用類進行封裝+運運算元過載實現。
//用類封裝迭代器 template <class T> struct __list_iterator { typedef list_node<T> node; //用節點的指標進行構造 __list_iterator(node* p) :_pnode(p) {} //迭代器運運算元的過載 T& operator*() { return _pnode->_data; } __list_iterator<T>& operator++()//返回值不要寫成node* operator++(),因為迭代器++肯定返回迭代器啊,你返回節點指標型別不對 { //return _pnode->_next; _pnode=_pnode->_next; return *this;//返回的是迭代器 } bool operator!=(const __list_iterator<T>& it) { return _pnode != it._pnode; } public: node* _pnode;//封裝一個節點的指標 };
const迭代器
const迭代器的錯誤寫法:
typedef __list_iterator<T> iterator; const list<T>::iterator it=lt.begin();
因為typedef後,const修飾的是迭代器it,只能呼叫operator*(),調不了operator++()。(過載operator++()為const operator++()也不行,因為const版本++還是改變不了)
正確寫法:想實現const迭代器,不能在同一個類裡面動腦筋,需要再寫一個const版本迭代器的類。
//用類封裝const迭代器 template <class T> struct __list_const_iterator { typedef list_node<T> node; //用節點的指標進行構造 __list_const_iterator(node* p) :_pnode(p) {} //迭代器運運算元的過載 const T& operator*()const { return _pnode->_data; } __list_const_iterator<T>& operator++()//返回值不要寫成node*,因為迭代器++肯定返回迭代器啊,你返回節點指標型別不對 { //return _pnode->_next;//返回型別錯誤的 _pnode = _pnode->_next; return *this;//返回的是迭代器 } __list_const_iterator<T>& operator--() { _pnode = _pnode->_prev; return *this; } bool operator!=(const __list_const_iterator<T>& it)const { return _pnode != it._pnode; } public: node* _pnode;//封裝一個節點的指標 }; typedef __list_const_iterator<T> const_iterator;
當然,這樣寫__list_iterator和__list_const_iterator這兩個類會出現程式碼重複。STL庫中是通過類別範本多給一個引數來實現,這樣,同一份類別範本就可以生成兩種不同的型別的迭代器(以下為仿STL庫的模擬實現):
//用類封裝普通/const迭代器 template <class T,class Ref> struct __list_iterator { typedef list_node<T> node; typedef __list_iterator<T,Ref> Self; //用節點的指標進行構造 __list_iterator(node* p) :_pnode(p) {} //迭代器運運算元的過載 Ref operator*() { return _pnode->_data; } Self& operator++()//返回值不要寫成node*,因為迭代器++肯定返回迭代器啊,你返回節點指標型別不對 { //return _pnode->_next;//返回型別錯誤的 _pnode=_pnode->_next; return *this;//返回的是迭代器 } Self& operator--() { _pnode = _pnode->_prev; return *this; } bool operator!=(const Self& it) { return _pnode != it._pnode; } public: node* _pnode;//封裝一個節點的指標 }; typedef __list_iterator<T, T&> iterator; typedef __list_iterator<T, const T&> const_iterator;
1、封裝底層實現,不暴露底層實現的細節;
2、多種容器提供統一的存取方式,降低使用成本;
C語言沒有運運算元過載和參照等語法,是實現不了迭代器的。
迭代器的用法就是模擬指標的行為,如果現在有一個指向結構的指標,那麼就需要用到->來解除參照。
//*的過載:返回節點的資料 Ref operator*() { return _pnode->_data; } //->的過載:返回資料的指標 T* operator->() { return &_pnode->_data; }
但是operator->使用T*做返回值型別,這樣無論是普通迭代器和const迭代器都能修改,所以operator->的返回值型別應該改為泛型:
template <class T, class Ref,class Ptr> Ptr operator->() { return &_pnode->_data; } typedef __list_iterator<T, T&, T*> iterator; typedef __list_iterator<T, const T&, const T*> const_iterator;
1、呼叫拷貝構造時,連結串列內節點資料為什麼已經是深拷貝了?
2、類名和型別的區別
普通類:類名等於型別
類別範本:類名不等價於型別,例如list類別範本類名是list,型別list<int>等。
所以我們平常寫函數形參和返回值時,總會帶上形參和返回值的型別:
// 賦值運運算元過載寫法2(賦值運運算元過載都可以這麼幹) list<T>& operator=(list<T> lt) { swap(lt); return *this; }
但是C++在類別範本裡面可以用類名代替型別:
// 賦值運運算元過載寫法2(賦值運運算元過載都可以這麼幹) list& operator=(list lt) { swap(lt); return *this; }
建議寫程式碼的時候嚴格區分型別和類名,讓自己和別人能夠看的很明白。(瞭解下C++有這種坑語法即可)
vector和list像左右手一樣,是互補關係,缺一不可。vector的優點正是list的缺點,list的優點也是vector的缺點,實際選用容器時,按照需求擇優選用。
vector的優點(結構厲害):
1、支援下標的隨機存取;
2、尾插尾刪效率高(當然擴容的那一次尾插尾刪會較慢);
3、CPU快取記憶體命中高(資料從快取載入至CPU中,會載入連續的一段資料,vector因為結構連續,快取記憶體命中高)。
vector的缺點:
1、非尾插尾刪效率低;
2、擴容有消耗,並存在一定的空間浪費。
vector迭代器失效問題:
insert/erase均失效。(如果string的insert和erase形參是迭代器,那麼也會失效,但是大部分介面是下標傳參,不考慮失效問題,只有幾個介面是迭代器傳參,需要注意迭代器失效問題)
list的優點:
1、按需申請釋放,無需擴容;
2、任意位置插入刪除時間O(1);(這裡說的是插入刪除,不要加上查詢的時間)
list的缺點:
1、不支援下標的隨機存取;
2、CPU快取記憶體命中率低;
3、每一個節點除了儲存資料外,還需要額外儲存兩個指標。
list迭代器失效問題:
insert不失效,erase失效。
#pragma once #include <iostream> #include <algorithm> #include <assert.h> #include <vector> using std::cout; using std::endl; namespace jly { //連結串列節點的類 template <class T> struct list_node { list_node(const T& x = T())//給一個預設值,變成預設建構函式 :_next(nullptr) , _prev(nullptr) , _data(x) {} list_node* _next; list_node* _prev; T _data; }; //用類封裝普通/const迭代器 template <class T, class Ref,class Ptr> struct __list_iterator { typedef list_node<T> node; typedef __list_iterator<T, Ref,Ptr> Self; //用節點的指標進行構造 __list_iterator(node* p) :_pnode(p) {} //迭代器運運算元的過載 Ref operator*() { return _pnode->_data; } Ptr operator->() { return &_pnode->_data; } Self& operator++()//返回值不要寫成node*,因為迭代器++肯定返回迭代器啊,你返回節點指標型別不對 { //return _pnode->_next;//返回型別錯誤的 _pnode = _pnode->_next; return *this;//返回的是迭代器 } Self operator++(int)//後置++ { _pnode = _pnode->_next; return Self(_pnode->_next); } Self& operator--() { _pnode = _pnode->_prev; return *this; } Self operator--(int)//後置-- { _pnode = _pnode->_prev; return Self(_pnode->_prev); } bool operator!=(const Self& it)const { return _pnode != it._pnode; } bool operator==(const Self& it)const { return _pnode == it._pnode; } public: node* _pnode;//封裝一個節點的指標 }; //用類封裝const迭代器 //template <class T> //struct __list_const_iterator //{ // typedef list_node<T> node; // //用節點的指標進行構造 // __list_const_iterator(node* p) // :_pnode(p) // {} // //迭代器運運算元的過載 // const T& operator*()const // { // return _pnode->_data; // } // __list_const_iterator<T>& operator++()//返回值不要寫成node*,因為迭代器++肯定返回迭代器啊,你返回節點指標型別不對 // { // //return _pnode->_next;//返回型別錯誤的 // _pnode = _pnode->_next; // return *this;//返回的是迭代器 // } // __list_const_iterator<T>& operator--() // { // _pnode = _pnode->_prev; // return *this; // } // bool operator!=(const __list_const_iterator<T>& it)const // { // return _pnode != it._pnode; // } //public: // node* _pnode;//封裝一個節點的指標 //}; //連結串列類(控制哨兵位) template <class T> class list { public: typedef list_node<T> node; typedef __list_iterator<T, T&,T*> iterator; typedef __list_iterator<T, const T&,const T*> const_iterator; //typedef __list_const_iterator<T> const_iterator; //建構函式 void empty_initialize()//用於初始化哨兵位 { _head = new node; _head->_next = _head; _head->_prev = _head; _size = 0; } list() { empty_initialize(); } template <class InputIterator> list(InputIterator first, InputIterator last)//迭代器區間構造 { empty_initialize(); while (first != last) { push_back(*first); ++first; } } //解構函式 ~list() { clear(); delete _head; _head = nullptr; } //拷貝構造 list(const list<T>& lt) { 先初始化*this //empty_initialize(); //for (const auto& e : lt)//取lt的資料給e //{ // push_back(e); //} //工具人寫法 list<T> tmp(lt.begin(),lt.end()); empty_initialize(); swap(tmp); } void swap(list<T>& lt) { std::swap(_head, lt._head);//交換頭指標 std::swap(_size,lt._size); } 賦值運運算元過載寫法1 //list<T>& operator=(const list<T>& lt) //{ // //防止自己給自己賦值 // if (this != <) // { // clear(); // for (const auto& e : lt) // { // push_back(e); // } // } // return *this; //} // 賦值運運算元過載寫法2(賦值運運算元過載都可以這麼幹) list<T>& operator=(list<T> lt) { swap(lt);//直接交換 return *this; } //插入刪除 void push_back(const T& x) { /*node* newNode = new node(x); node* tail = _head->_prev; newNode->_prev = tail; newNode->_next = _head; tail->_next = newNode; _head->_prev = newNode;*/ insert(end(), x); } void pop_back() { erase(--end()); } void push_front(const T& x)//頭插 { insert(begin(), x); } void pop_front() { erase(begin()); } iterator insert(iterator pos, const T& x) { node* newNode = new node(x); node* prev = pos._pnode->_prev; node* cur = pos._pnode; newNode->_prev = prev; newNode->_next = cur; prev->_next = newNode; cur->_prev = newNode; //return iterator(newNode); pos._pnode = newNode; ++_size; return pos; } iterator erase(iterator pos) { assert(!empty()); node* prev = pos._pnode->_prev; node* next = pos._pnode->_next; prev->_next = next; next->_prev = prev; delete pos._pnode; --_size; //return iterator(next); pos._pnode = next; return pos; } //連結串列小介面 bool empty()const { return _head->_next == _head; } void clear() { /*assert(!empty); node* cur = _head->_next; while (cur != _head) { node* next = cur->_next; delete cur; cur = next; }*/ iterator it = begin(); while (it != end()) { it = erase(it);//erase返回刪除元素的下一個 } } size_t size()const { return _size; } //普通begin(),end()迭代器 iterator begin() { //return iterator(_head->_next); return __list_iterator<T, T&,T*>(_head->_next); } iterator end() { return __list_iterator<T, T&,T*>(_head); } //const begin(),end()迭代器 const_iterator begin()const { //return const_iterator(_head->_next); return __list_iterator<T, const T&,const T*>(_head->_next); } const_iterator end()const { return __list_iterator<T, const T&,const T*>(_head); } private: node* _head;//哨兵位 size_t _size;//用於統計節點個數,空間換時間 //不加這個私有變數,統計節點個數時間O(N),有這個私有變數,時間O(1),但是每個節點的體積變大 }; //測試函數 struct Pos { int _row; int _col; Pos(int row = 0, int col = 0) :_row(row) , _col(col) {} }; void test() { list<Pos> i; i.push_back(Pos(1, 2)); i.push_back(Pos(2, 5)); i.push_back(Pos(4, 3)); list<Pos>::iterator it = i.begin(); while (it != i.end()) { cout << (&( * it))->_row;//*it取資料,再取地址、解除參照得到_row,多此一舉 cout << it->_row;//同第三種寫法,編譯器為了可讀性,省略了一個-> cout << it.operator->()->_row;//it.operator->()是顯示呼叫,->_row是解除參照得到_row it++; } } void test1() { list<std::vector<int>> i; std::vector<int> v1(1, 2); std::vector<int> v2(2, 4); std::vector<int> v3(3, 5); i.push_back(v1); i.push_back(v2); i.push_back(v3); list<std::vector<int>> m(i); i = m; cout << m.size(); } }
到此這篇關於利用C++模擬實現STL容器:list的文章就介紹到這了,更多相關C++ STL容器list內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45