一、前言
? ? ? ? C++非常重視效率,對效率有損失的代碼常常是能省則省。使用list要包含的頭文件是<list>,要包含頭文件就是#iinclude <list>,List肯定是一種鏈表,我們不妨回憶一下那種鏈表插入刪除效率最快也就是最簡單,還能減少判斷次數,鏈表的結尾是否要判斷nullptr。毫無疑問,list所使用的結構是帶頭雙向環形鏈表。帶頭意味著插入形式單一,無論鏈表中是否存在數據,都只需要將新結點的上一個指針和下一個指針做處理,省去判斷是否存在結點,而且寫起來也非常簡單。雙向意味著只要知道鏈結點的位置就可以訪問它的下一個位置或是上一個位置,插入刪除都十分方便,如果是單向還要一個一個的遍歷查找。環形鏈表就更加簡單了,學過雙向鏈表、單(向)鏈表的同學都知道,不管是單鏈表還是雙向鏈表尾插都十分不便,如果有固定的尾插地址維護起來更是難上加難,但是雙向(可以往后也可以往前)環形鏈表根本就沒有這樣的顧慮,因為可以從開頭往后走到達尾插位置。形狀大概就像土家族的吊腳樓。
二、list的詳細介紹
? ? ? ? 在STL容器中,我們只要會其中的一個容器的接口,我們就可以觸類旁通。不用抱太多疑慮。
? ? ? ? 和大多數STL容器一樣。包含的頭文件還是它的本名<list>
? ? ? ? 包含就要做#include <list>,不想加每次聲明list對象和迭代器都加std::在對象之前的話#include <list>? ? ? ? using namespace std;也是可以的。
? ? ? ? 2.1? ? ? ? size()函數和empty()函數
? ? ? ? 注意該容器不是和Vector和String一樣的數組存儲。而是通過一個特質的結構體對象進行鏈接。(小編盡量快點出一個關于指針和結構體指針的專題。)連接的結點地址不一定要像數組一樣連續,,所以list沒有像Vector和String有容量capacity這個概念,地址分布可以盡量松散一些,可以不用申請那么大塊空間,更好申請。size()函數就是反映list中的數據有多少。empty()反映list是否為空。
// 形如這樣的結點進行鏈接。
// 這里是隨便寫的,list的結點肯定不長這樣。比這要復雜。
struct List_Node
{// 要存儲的內容。// 有模板的話,什么類型都可以。int _val;// 上一個結點的地址。struct List_Node* _prev;// 下一個結點的地址。struct List_Node* _next;
};#include <iostream>
#include <list>int main()
{std::list<int> lt;for (int i = 0; i <= 13; ++i){lt.push_back(i);lt.push_back(i);std::cout << "當i為 " << i << " 時list中的數據總量: " << lt.size() << std::endl;}return 0;
}
????????2.2? ? ? ? insert()函數和erase()函數
? ? ? ? 通過Vector和String的學習,我們知道insert()函數和erase()函數都是在指定位置插入刪除,同時用到的都是與指針有著相似作用的迭代器。下面話不多說,直接開始上示例。
#include <iostream>
#include <list>int main()
{std::list<int> lt;for (int i = 0; i <= 13; ++i){// 頭插lt.insert(lt.begin(), i);// 尾插lt.insert(lt.end(), i);// std::cout << "當i為 " << i << " 時list中的數據總量: " << lt.size() << std::endl;}//lt.unique();//lt.remove(10);// 范圍for,C++創造者認為每次遍歷都要寫一個int太過于麻煩。// 所以直接支持了范圍for。// 范圍for會根據類中的begin()和end()函數(前面提到過的C++定好的函數名不要去改,不知道大家有沒有印象。)// 提供的迭代器進行訪問。for (auto& e : lt){std::cout << e << std::endl;}return 0;
}
? ? ? ? 2.3? ? ? ? front()函數和back()函數
? ? ? ? 前面小編提到鏈表頭插和尾插,那我們怎么快速拿到鏈表首尾的數據,查看是否頭插尾插成功了呢?front()和back()就可以很好的檢測這一點。
#include <iostream>
#include <list>int main()
{std::list<int> lt;for (int i = 0; i <= 13; ++i){lt.insert(lt.begin(), i);lt.insert(lt.end(), i);std::cout << "第" << i << "行" << "front: " << lt.front() << " back: " << lt.back() << std::endl;}return 0;
}
? ? ? ? 2.4? ? ? ? begin()函數和end()函數
? ? ? ? 關于begin()函數和end()函數有一些要補充的內容,Vector的迭代器是隨機迭代器,與指針非常相似可以加減任意符合數組范圍的值來訪問該位置的值,而list的迭代器是雙向迭代器只能通過++、--來改變訪問的位置,畢竟list中的結點并不一定像Vector存儲一樣是連續的。
#include <iostream>
#include <list>int main()
{std::list<int> lt;for (int i = 0; i <= 13; ++i){lt.insert(lt.begin(), i);}std::list<int>::iterator it = lt.begin();while (it != lt.end()){std::cout << *it << std::endl;// 報錯//it + 3;// 雙向迭代器。++it;}return 0;
}
? ? ? ? 2.5? ? ? ? push_front函數和push_back()函數
? ? ? ? 通過函數名稱可以得知這是頭插和尾插函數。話不多說直接上案例。
#include <iostream>
#include <list>int main()
{std::list<int> lt;for (int i = 0; i <= 13; ++i){lt.push_front(i);lt.push_back(i);std::cout << "第" << i << "行" << "front: " << lt.front() << " back: " << lt.back() << std::endl;}return 0;
}
? ? ? ? 2.6? ? ? ? pop_front函數和pop_back()函數
????????通過函數名稱可以得知這是頭刪和尾刪函數。和上面對比很好理解。那為什么Vector沒有頭插頭刪,list卻有?因為效率!還是效率!Vector的結構就像數組一樣,一個指針變量指向一塊地址開頭,進行訪問使用。指針指向一塊地址的開頭該怎么頭刪?只能將要刪除的位置之后的數據往前移動一個單位,正好將要刪除位置的數據覆蓋掉。這非常影響效率,所以沒有實現該函數。不過大家可以試試deque容器,Vector和List的特性兼具,既可以頭刪又可以尾刪。至于為什么等以后再說吧。
????????2.7? ? ? ? unique()函數、remove()函數、remove_if()函數
? ? ? ? unique()函數是去重函數,remove()函數是刪除數據為參數的函數,remove_if()函數就有一點超綱了,是根據條件刪除,那怎么傳條件呢?通過仿函數(就是像函數調用的類調用形式。)
#include <iostream>
#include <list>// 仿函數
class Like_Function
{
public:bool operator () (int x){// 刪除偶數if (x % 2 == 0){// 函數返回值為true對應的參數會被刪除。return true;}return false;}
};int main()
{std::list<int> lt;for (int i = 0; i <= 13; ++i){// 這里我故意插入兩個相同值。// 頭插lt.insert(lt.begin(), i);// 尾插lt.insert(lt.end(), i);}// 去重。lt.unique();// 去掉特殊值。lt.remove(10);// 可以傳一個匿名對象(寫起來方便,就相當于C語言的臨時值)lt.remove_if(Like_Function(/*構造函數要的參數*/));for (auto& e : lt){std::cout << e << std::endl;}return 0;
}