首頁 > 軟體

C++之list容器介紹及使用方式

2023-02-06 06:00:20

一、list底層結構

list底層是帶頭節點的雙向迴圈連結串列

  • 雙向:可以從前往後,也可以從後往前遍歷
  • 迴圈:找尾節點的時間複雜度為O( 1 )
  • 帶頭節點:程式碼實現簡單,不用考慮連結串列為空等特殊情況,可令end()迭代器指向頭節點的位置

二、構造方法

建構函式

list<int> l1;
list<int> l2(5, 3);
//迭代器
vector<int> v{ 1,2,3,4,5 };
list<int> l3(v.begin(), v.end());
//C++11
list<int> l4{ 1,2,3,4,5 };

拷貝建構函式

利用l1拷貝構造l2

list<int> l1{ 1,2,3,4,5 };
list<int> l2(l1);

三、元素存取和迭代器

back&front

list<int> l1{ 1,2,3,4,5 };
cout << l1.front() << endl;
cout << l1.back() << endl;

三種遍歷方式

list<int> l1{ 1,2,3,4,5 };

採用下面三種方式對下面這個list<int>型別的物件進行遍歷列印:

1.迭代器

list<int>::iterator it = l1.begin();
for (it; it != l1.end(); it++)
{
	cout << *it << " ";
}
cout << endl;

列印結果:

2.範圍for

注意這裡e是int型別,不用再進行解除參照

//範圍for
for (auto e : l1)
{
	cout << e << " ";
}
cout << endl;

列印結果:

3.反向迭代器

list<int>::reverse_iterator rit = l1.rbegin();
for (rit; rit != l1.rend(); rit++)
{
	cout << *rit << " ";
}
cout << endl;

列印結果:

四、元素修改

尾插、頭插、尾刪、頭刪

insert、erase

list支援任意位置的插入,注意list物件的迭代器不支援加減數位,因為其底層空間不連續,如圖:

如果要往一個位置進行插入,可以通過find函數返回位置進行,find是一個通用的函數模板,返回值是傳入引數的迭代器型別

list<int> l1{ 1,2,3,4,5 };
l1.insert(find(l1.begin(), l1.end(), 3), 10);//任意位置插入
l1.erase(find(l1.begin(), l1.end(), 10), l1.end());//任意位置的刪除

swap

list內建的交換函數

list<int> l1{ 1,2,3,4,5 };
list<int> l2{ 5,6,7,8,9 };
l1.swap(l2);

resize

resize改變有效元素的個數,多的元素用第resize二個引數填充,如果沒有給第二個引數,則預設用T()。

list<int> l1{ 0,1,2 };
l1.resize(5, 3);

五、特殊操作

remove

刪除值為value的元素

list<int> l1{ 3,0,1,3,2,3 };
l1.remove(3);

remove_if

remove_if的引數是一個判斷條件,可以是函數指標或者函數物件

//判斷5的倍數
bool MultipleFive(int n)
{
	return 0 == n % 5;
}

void Test10()
{
	//此處傳遞函數指標
	list<int> l1{ 10,0,1,3,5,7,20 };
	l1.remove_if(MultipleFive);
}

unique、sort

unique,去重,刪除所有重複元素,使用unique之前要先呼叫sort進行排序,這裡的sort是list內建的sort,不是標準庫中的sort

void Test()
{
	list<int> l1{ 1,3,3,5,4,0,2,5,4 };
	l1.sort();//預設升序
	l1.unique();//刪除重複元素
}

結果:

對於sort的使用,還可以自定義函數,並將函數指標作為引數傳遞給sort函數進行排序:

reverse

對連結串列進行逆置

void Test()
{ 
	list<int> l1{ 1,3,5,7,9 };
	l1.reverse();
}

結果:

六、list迭代器失效問題

list底層結構為帶頭結點的雙向迴圈連結串列,因此在list中進行插入時是不會導致list的迭代器失效的,只有在刪除時才會失效,並且失效的只是指向被刪除節點的迭代器,其他迭代器不會受到影響。 

erase導致的迭代器失效

如圖所示,it迭代器所指向的位置被刪除後,迭代器失效:

改正方法:

while (it != l1.end())
{
	//it=l1.erase(it);
	l1.erase(it++);
}

這裡 l1.erase(it++)語句也能達到效果,因為後置++會將自增後的結果儲存在臨時變數中,而前置則不可以。 

resize導致的迭代器失效

resize減少有效元素個數也會導致迭代器失效:

list<int> l1{ 1,3,5,7,9 };
auto it = l1.end();
l1.resize(3);

上面這個程式中,reseze減少有效元素個數後,it指向的位置元素已經被刪除,迭代器失效,如果再使用該迭代器,則會出錯。

七、vector與list對比

vector(動態順序表)

list(帶頭結點的雙向迴圈連結串列)

對比vectorlist
底層結構動態順序表,連續空間帶頭結點的雙向迴圈連結串列
存取支援隨機存取,首地址+下標不能隨機存取,可通過find查詢,存取隨即元素時間複雜度O(N)
插入刪除任意位置插入和刪除效率低,需要搬移元素,時間複雜度為O(N),插入時有可能需要增容,增容:開闢新空間,拷貝元素,釋放舊空間,導致效率更低任意位置插入和刪除效率高,不需要搬移元素,時間複雜度為O(1)
空間利用率底層為連續空間,不容易造成記憶體碎片,空間利用率較高,快取利用率高。可以一次將一個資料附近的空間都載入到快取,不用頻繁地從記憶體讀取資料底層節點動態開闢,容易造成記憶體碎片,空間利用率低,快取利用率低
迭代器原生態指標對指標進行了封裝
迭代器失效容量相關的操作都有可能導致迭代器失效,如插入引起的擴容,刪除元素等插入元素不會導致迭代器失效,刪除節點會導致,且隻影響當前迭代器,其他迭代器不受影響
使用場景不關心插入和刪除效率,支援隨機存取大量插入和刪除操作,不關心隨機存取的場景

總結

以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。 


IT145.com E-mail:sddin#qq.com