List
- list是可以在常數範圍內在任意位置進行插入和刪除的序列式容器,並且該容器可以前後雙向迭代
- list的底層是雙向鏈表結構,雙向鏈表中每個元素存儲在互不相關的獨立節點中,在節點中通過指針指向其前一個元素和後一個元素
- list與forward_list非常相似:最主要的不同在於forward_list是單鏈表,只能朝前迭代,已讓其更簡單高效
- 與其他的序列式容器相比(array,vector,deque),list通常在任意位置進行插入、移除元素的執行效率更好
- 與其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的隨機訪問,比如:要訪問list的第6個元素,必須從已知的位置(比如頭部或者尾部)迭代到該位置,在這段位置上迭代需要線性的時間開銷;list還需要一些額外的空間,以保存每個節點的相關聯信息(對於存儲類型較小元素的大list來說這可能是一個重要的因素)
List接口
構造函數 | 接口說明 |
---|---|
list() | 構造空的list |
list (size_type n, const value_type& val = value_type()) | 構造的list中包含n個值爲val的元素 |
list (const list& x) | 拷貝構造函數 |
list (InputIterator first, InputIterator last) | 用[first, last)區間中的元素構造list |
構造
void test_list1()
{
list<int> l1; // 構造空的l1
list<int> l2(3, 100); // l2中放3個值爲100的元素
list<int> l3(l2.begin(), l2.end()); // 用l2的[begin(), end() )左閉右開的區間構造l3
list<int> copy(l3); // 用l3拷貝構造初始化copy
//應用迭代器,打印copy中的元素
//list<int> ::iterator copyit = copy.begin();
auto copyit = copy.begin();
while (copyit != copy.end())
{
cout << *copyit << " ";
++copyit;
}
cout << endl;
print_list(copy);
//基於範圍的for
for (const auto& e : l3) //建議加上const及&;如果希望可更改的話,不加const
{
cout << e << " ";
}
cout << endl;
l3.push_back(111);
for (const auto& e : l3)
{
cout << e << " ";
}
cout << endl;
//反向迭代器
//list<int>::reverse_iterator rit = l3.rbegin();
auto rit = l3.rbegin();
while (rit != l3.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
}
int main()
{
test_list1()
system("pause");
return 0;
}
List_Iterator
把迭代器理解成一個指針,該指針指向list中的某個節點
函數聲明 | 接口說明 |
---|---|
begin() | 返回第一個元素的迭代器 |
end() | 返回最後一個元素下一個位置的迭代器 |
rbegin() | 返回第一個元素的reverse_iterator,即end位置 |
rend() | 返回最後一個元素下一個位置的reverse_iterator,即begin位置 |
cbegin() (C++11) | 返回第一個元素的cosnt_iterator |
cend() (C++11) | 返回最後一個元素下一個位置的const_iterator |
crbegin() (C++11) | 即crend()位置 |
crend() (C++11) | 即crbegin()位置 |
- begin()和end():通過調用list容器的成員函數begin()得到一個指向容器起始位置的iterator,可以調用list容器的 end() 函數來得到list末端下一位置,相當於:int a[n]中的第n+1個位置a[n],實際上是不存在的,不能訪問,經常作爲循環結束判斷結束條件使用
- begin與end爲正向迭代器,對迭代器執行++操作,迭代器向後移動(指向下一個迭代器)
- rbegin(end)與rend(begin)爲反向迭代器,對迭代器執行++操作,迭代器向前移動
- cbegin與cend爲const的正向迭代器,與begin和end不同的是:該迭代器指向節點中的元素值不能修改
- crbegin與crend爲const的反向得帶器,與rbegin和rend不同的是:該迭代器指向節點中的元素值不能修改
接口應用
- push_back() 和push_front():使用list的成員函數push_back和push_front插入一個元素到list中。其中push_back()從list的末端插入,而 push_front()實現的從list的頭部插入
- pop_back和pop_front():通過刪除最後一個元素,通過pop_front()刪除第一個元素;序列必須不爲空,如果當list爲空的時候調用pop_back()和pop_front()會使程序崩掉
- insert():在指定位置插入一個或多個元素(三個重載):
l1.insert(l1.begin(),100); 在l1的開始位置插入100。
l1.insert(l1.begin(),2,200); 在l1的開始位置插入2個100。
l1.insert(l1.begin(),l2.begin(),l2.end());在l1的開始位置插入l2的從開始到結束的所有位置的元素
- erase():刪除一個元素或一個區域的元素(兩個重載)
l1.erase(l1.begin()); 將l1的第一個元素刪除
l1.erase(l1.begin(),l1.end()); 將l1的從begin()到end()之間的元素刪除
- empty():利用empty() 判斷list是否爲空
- clear(): 清空list中的所有元素
- reverse():通過reverse()完成list的逆置
- swap():交換兩個鏈表(兩個重載),一個是l1.swap(l2); 另外一個是swap(l1,l2),都可能完成連個鏈表的交換
- resize(): 如果調用resize(n)將list的長度改爲只容納n個元素,超出的元素將被刪除,如果需要擴展那麼調用默認構造函數T()將元素加到list末端。如果調用resize(n,val),則擴展元素要調用構造函數T(val)函數進行元素構造,其餘部分相同
- remove():存在就刪除;不存在就不刪除,且不會報錯
- unique():去重(針對排序的鏈表);時間複雜度:O(N)
- emplace() :等價於insert,C++11,構造+插入;emplace_back(等價push_back)
push_back尾插:先構造好元素,然後將元素拷貝到節點中,插入時先調構造函數,再調拷貝構造函數
emplace_back尾插:先構造節點,然後調用構造函數在節點中直接構造對象
emplace_back比push_back更高效,少了一次拷貝構造函數的調用
部分接口代碼實現
#include <iostream>
#include <list>
using namespace std;
//頭(尾)插,頭(尾)刪*******************************************************
void test_list2()
{
list<int> l;
l.push_back(1);
l.push_front(2);
l.push_front(3);
for (const auto& e : l)
cout << e << " ";
cout << endl;
l.pop_back();
l.pop_front();
l.assign(5, 6); //分配賦值,覆蓋之前的代碼
for (const auto& e : l)
cout << e << " ";
cout << endl;
}
//插入刪除****************************************************************************
//erase(存在就刪除;不存在就會報錯)
void test_list3()
{
list<int> l;
l.push_back(1);
l.push_back(2);
l.push_back(3);
l.push_back(4);
l.insert(l.begin(), 0); //插入
auto pos = find(l.begin(), l.end(), 3); //find可以對不同的迭代器都進行查找->泛型(模板)迭代器區間的範圍是左閉右開:[ )
l.insert(pos, 11); //像以上代碼,如果要尋找的數值不存在(如:找300),則l.end()會一直往後找,直至找到最後一位,就會插到最後,類似於尾插,爲避免這種現象,要進行如下判斷
if (pos != l.end())
{
l.insert(pos, 30); //list插入不會導致迭代器失效
*pos = 33; //pos剛纔存的是3,解引用之後,把33賦值給他
}
l.erase(pos); //list刪除會導致迭代器失效
//*pos = 33; //迭代器已經失效(已經刪掉了,野指針了),會報錯
for (const auto& e : l)
cout << e << " ";
cout << endl;
//刪除所有偶數
auto eit = l.begin();
while (eit != l.end())
{
if (*eit % 2 == 0)
{
eit = l.erase(eit); //erase自動返回下一個位置的迭代器
}
else
{
++eit;
}
}
for (const auto& e : l)
cout << e << " ";
cout << endl;
}
//交換*******************************************************************************
#include <time.h>
void test_list4()
{
list<int> l1(1000000, 3);
list<int> l2(1000000, 4);
//效率近似
size_t begin1 = clock();
swap(l1, l2); //深拷貝
size_t end1 = clock();
size_t begin2 = clock();
l1.swap(l2); //成員變量的交換(推薦使用)
size_t end2 = clock();
cout << end1 - begin1 << endl;
cout << end2 - begin2 << endl;
}
//改變容量***************************************************************************
void test_list5()
{
list<int> l;
l.resize(10); //默認缺省值是0
l.resize(20, 1); //充當插入(改變容量)
l.resize(5); //充當刪除
l.clear(); //不清理頭節點,可以繼續插入(析構就不一樣了,析構也刪除頭節點)
l.push_back(100);
for (const auto& e : l)
cout << e << " ";
cout << endl;
}
//remove(存在就刪除;不存在就不刪除,不會報錯)**************************************
//remove_if(滿足特定條件就刪除,是一個迭代器)
void test_list6()
{
list<int> l;
l.push_back(1);
l.push_back(2);
l.push_back(3);
l.push_back(4);
l.remove(2); //1 3 4
l.remove(5); //1 2 3 4
for (const auto& e : l)
cout << e << " ";
cout << endl;
}
//unique(去重),針對排序的鏈表*******************************************************
void test_list7()
{
list<int> l;
l.push_back(1);
l.push_back(2);
l.push_back(1);
l.push_back(2);
l.push_back(5);
l.push_back(3);
l.push_back(2);
l.push_back(4);
l.sort(); //先排序(目的是提高效率;不排序直接刪的話代價太大了) 時間複雜度:N*log(N)
l.unique(); //再去重 時間複雜度:O(N)
for (const auto& e : l)
cout << e << " ";
cout << endl;
}
//***********************************************************************************
//emplace(等價於insert,C++11,構造+插入);emplace_back(等價於push_back)
void test_list8()
{
list<int> l;
l.push_back(1);
l.push_back(2);
l.push_back(3);
l.emplace_back(4);
for (const auto& e : l)
cout << e << endl;
}
// push_back尾插:先構造好元素,然後將元素拷貝到節點中,插入時先調構造函數,再調拷貝構造函數
// emplace_back尾插:先構造節點,然後調用構造函數在節點中直接構造對象
// emplace_back比push_back更高效,少了一次拷貝構造函數的調用
// emplace_back / emplace_front / emplace
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{
cout << "Date(int, int, int):" << this << endl;
}
Date(const Date& d)
: _year(d._year)
, _month(d._month)
, _day(d._day)
{
cout << "Date(const Date&):" << this << endl;
}
private:
int _year;
int _month;
int _day;
};
void test_list9()
{
list<Date> l;
//Date d1(2019, 5, 15); //構造一次
//l.push_back(d1); //拷貝構造
//l.emplace_back(d1); //拷貝構造
//左值引用可以引用左值;右值引用也可以引用左值
//Date d1(2019, 5, 15);
//構造匿名對象正常構造、拷貝構造(沒有差別)
l.push_back(Date(2019, 5, 15)); //正常構造、拷貝構造
l.emplace_back(Date(2019, 5, 15)); //正常構造、拷貝構造
l.emplace_back(2019, 5, 15); //只調一次構造(優勢),少拷貝構造一次
}
int main()
{
test_list2();
test_list3();
//test_list4();
test_list5();
test_list6();
test_list7();
test_list8();
test_list9();
system("pause");
return 0;
}
運行結果: