C++ : List接口介紹及實現

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],實際上是不存在的,不能訪問,經常作爲循環結束判斷結束條件使用
  • beginend爲正向迭代器,對迭代器執行++操作,迭代器向後移動(指向下一個迭代器)
  • rbegin(end)rend(begin)爲反向迭代器,對迭代器執行++操作,迭代器向前移動
  • cbegincend爲const的正向迭代器,與begin和end不同的是:該迭代器指向節點中的元素值不能修改
  • crbegincrend爲const的反向得帶器,與rbegin和rend不同的是:該迭代器指向節點中的元素值不能修改

接口應用

  • push_back()push_front():使用list的成員函數push_back和push_front插入一個元素到list中。其中push_back()從list的末端插入,而 push_front()實現的從list的頭部插入
  • pop_backpop_front():通過刪除最後一個元素,通過pop_front()刪除第一個元素;序列必須不爲空,如果當list爲空的時候調用pop_back()和pop_front()會使程序崩掉
  • insert():在指定位置插入一個或多個元素(三個重載):
l1.insert(l1.begin(),100); 在l1的開始位置插入100。
l1.insert(l1.begin(),2,200); 在l1的開始位置插入2100。
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;
}

運行結果:
在這裏插入圖片描述

附圖:insert

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章