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 ::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::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;
}

C++ : List接口介绍及实现_第1张图片

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 
#include 

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 
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;
}

运行结果:
C++ : List接口介绍及实现_第2张图片

附图:insert

C++ : List接口介绍及实现_第3张图片

你可能感兴趣的:(C++)