【C++】list容器及其模拟实现

一.什么是list

  list是可以在常数范围任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。list的底层机构是双向列表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向前一个元素和后一个元素。

  list的最大缺点就是不支持任意位置的随机访问,即不支持下标访问,要访问list中的元素就需要从前开始迭代,产生了线性时间的开销;另外list还需要额外的空间,以保存每个节点的的相关联信息。

二.list的使用 

2.1 list的构造

构造函数接口说明
list(size_t type n,const value_type& val=value_type())构造list中包含n个值为val的元素
list()(⭐)构造空的list
list(const list& x)(⭐)拷贝构造函数
list(Inputlterator first,Inputlterator last)用(first,last]区间中的元素构造list

标注:(⭐)表示重点掌握

格式一:构造一个空的list

list<int> lit1;//构造int类型空容器

格式二:构造list中包含n个值为val的元素

list<int> lit1(10,2);//构造一个十个元素全为2的list

格式三:拷贝构造函数

list<int> lit2(lit1);//复制lit1中到lit2中

格式四:用(first,last]区间中的元素构造list

string s("hello world");
list<char> lit1(s.begin(),s.end());
//构造string对象某段迭代器区间的内容

格式五:构造数组某段区间的内容

int arr[]={1,2,3,4,5,6};
size_t size=sizeof(arr)/sizeof(arr[0]);
list<int> lit1(arr,arr+size);
//构造数组某段区间的内容

格式六:使用{}构造 C++11支持

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

代码展示:

#include<list>
#include<iostream>
using namespace std;
void test01
{
   list<int> lit1;
   list<int> lit2(lit1);
   list<int> lit3(lit2.begin(),lit2.end());
   list<int> lit4(6,100);

   //以数组为迭代器区间构造lit5
   int arr[]={1,2,3,4,5,6};
   list<int> lit5(arr,arr+sizeof(arr)/sizeof(arr[0]));

   //列表初始化C++11
   list<int> lit6{1,2,3,4,5,6};
   //用迭代器方法打印lit5中的元素

   list<int> iterator it=lit5.begin();
   while(it!=lit5.end())
   {
      cout<<*it<<" ";
      it++;
   }
   //用范围for方式遍历

   for(auto& e:lit5)
   {
      cout<<e<<" ";
   }
} 

2.2 list的遍历 

接口名称操作
迭代器begin(),end()OR rbegin(),rend()
范围forC++11支持更简单的for的新遍历方式(底层还是借用迭代器实现)

注意: 遍历链表只能使用迭代器和范围for

  • begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动
  • rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动
接口名称操作
begin()返回链表第一个元素
end()返回最后一个元素下一个元素
rbegin()返回最后一个元素
rend()返回第一个元素前一个元素

2.2.1 正向迭代器

  • begin():返回第一个元素
  • end();返回最后一个元素下一个元素
  • int arr[]={1,2,3,4,5,6};
       list<int> lit5(arr,arr+sizeof(arr)/sizeof(arr[0]));
       list<int> iterator it=lit5.begin();
       while(it!=lit5.end())
       {
          cout<<*it<<" ";
          it++;
       }
    //打印1 2 3 4 5 6

2.2.2反向迭代器 

  • rbegin:返回最后一个元素
  • rend:返回第一个元素前一个元素

int arr[]={1,2,3,4,5,6};
   list<int> lit5(arr,arr+sizeof(arr)/sizeof(arr[0]));
   list<int> iterator it=lit5.rbegin();
   while(it!=lit5.rend())
   {
      cout<<*it<<" ";
      it++;
   }
//打印6 5 4 3 2 1

2.2.3范围for 

支持迭代器一定支持范围for

 //用范围for方式遍历

   for(auto& e:lit5)
   {
      cout<<e<<" ";
   }
} 

2.3 list容器常见访问方式 

2.3.1 list capacity

函数声明接口说明
empty检测list是否为空,是则返回true,否则返回false
size返回list中有效节点个数
resize用于调整列表的大小
① empty
#include <iostream>
#include <list>
using namespace std;
 
int main()
{
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	if(lt.empty())
    {
       cout<<"空"<<endl;
    }
    else 
    {
       cout<<"不是空"<<endl;
    }
	return 0;
}
//输出不是空
② size

#include <iostream>
#include <list>
using namespace std;
 
int main()
{
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	cout << lt.size() << endl; //3
	return 0;
}
  ③ resize

使用resize()方法可以重新指定list的大小,并根据需要插入或删除list中的元素来达到指定的大小

resize()方法有两种重载形式:

  •  void resize(size_type count, const T& value = T()):

        这个重载会将列表的大小调整为count。如果count大于当前列表的大小,则在列表末尾插入足够数量的值为value的元素。如果count小于当前列表的大小,则删除超出count的元素。 

  • void resize(size_type count): 

       这个重载会将列表的大小调整为count。如果count大于当前列表的大小,则在列表末尾插入默认构造的元素。如果count小于当前列表的大小,则删除超出count的元素。

代码演示


#include <iostream>
#include <list>
using namespace std;
int main()
{
    list<int> mylist = { 1, 2 ,3};
    // 把mylist的大小设为5
    mylist.resize(5);
    cout << "第一次resize后list中元素为:";
    for (auto a : mylist) {
        cout << a << " ";
    }
    cout << endl;
    // 把mylist的大小设为1
    mylist.resize(1);
    cout << "第二次resize后list中元素为:";
    for (auto a : mylist) {
        cout << a << " ";
    }
    cout << endl;

    // 把list大小设为3,不足部分填充9
    mylist.resize(3, 9);
    cout << "第三次resize后list中元素为:";
    for (auto a : mylist) {
        cout << a << " ";
    }
    cout << endl;
}

2.3.2 list modifiers

函数声明接口说明
push_front在list首元素前插入值为val的元素
push_back

在list尾部插入值为val的元素

pop_front删除list中第一个元素
pop_back删除list中最后一个元素
insert在list position位置插入值为val的元素
erase删除list position位置的元素
swap交换 两个list中的元素
clear清空list中的有效元素
① push_back()——添加元素(list尾部) 

代码演示:

#include <iostream>
#include <list>
using namespace std;
 
int main()
{
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	for(auto& e:lt)
    {
      cout<<e<<" ";
    }
	return 0;
}
//1 2 3 
② push_front()——插入list元素(头部) 

代码示例:

#include <iostream>
#include <list>
using namespace std;
 
int main()
{
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
    lt.push_front(99);
	for(auto& e:lt)
    {
      cout<<e<<" ";
    }
	return 0;
}
//99 1 2 3 
③ pop_back()——移除list元素(尾部) 

代码示例

#include <iostream>
#include <list>
using namespace std;
 
int main()
{
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
    lt.push_front(99);
    lt.pop_back();
	for(auto& e:lt)
    {
      cout<<e<<" ";
    }
	return 0;
}
//99 1 2  
   pop_front()——删除list元素(头部)

代码示例

#include <iostream>
#include <list>
using namespace std;
 
int main()
{
	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
    lt.push_front(99);
    lt.pop_back();
    lt.pop_front();
	for(auto& e:lt)
    {
      cout<<e<<" ";
    }
	return 0;
}
// 1 2  
 ⑤ insert()——添加元素(任意位置)

insert共有三种形式:

  • - insert(iterator, value):

          向iterator迭代器指向元素的前边添加一个元素value

  • - insert(iterator, num, value);

          向iterator迭代器指向元素的前边添加num个元素value

  • - insert(iterator, iterator1, iterator2);

         向iterator迭代器指向元素的前边添加[iterator1,iterator2)之间的元素。

 代码示例一

#include <iostream>
#include <list>
using namespace std;
int main()
{
    list<int> mylist = { 1, 2 ,3};
    list<int>::iterator it = mylist.begin();
    it++;
    mylist.insert(it, 99);
    for (auto a : mylist) {
        cout << a << " ";
    }
    cout << endl;
}
//1 99 2 3

 代码示例二

#include <iostream>
#include <list>
using namespace std;
int main()
{
    list<int> mylist = { 1, 2 ,3};
    list<int>::iterator it = mylist.begin();
    it++;
    mylist.insert(it,3, 99);
    for (auto a : mylist) {
        cout << a << " ";
    }
    cout << endl;
}
//1 99 99 99 2 3

 代码示例三

#include <iostream>
#include <list>
using namespace std;
int main()
{
    list<int> mylist = { 1, 2 ,3};
    list<int>::iterator it = mylist.begin();
    it++;
    list<int> mylist1 = { 5, 6 ,7 };
    list<int>::iterator it1 = mylist1.begin();
    list<int>::iterator it2 = mylist1.end();
    mylist.insert(it,it1, it2);
    for (auto a : mylist) {
        cout << a << " ";
    }
    cout << endl;
   
}
//1 5 6 7 2 3
 ⑥ erase()——删除元素(任意位置) 

 erase 共有 两 种形式:
list.erase(iterator)

 删除迭代器iterator指向的元素
list.erase(iterator1,iterator2)

 删除迭代器iterator1指向的元素到iterator2指向元素之间的元素,[iterator1,iterator2)

代码演示一

#include <iostream>
#include <list>
using std::list;
using namespace std;
int main()
{
    list<int> mylist = { 1, 2 ,3};
    list<int>::iterator it = mylist.begin();
    it++;
    list<int> mylist1 = { 5, 6 ,7 };
    list<int>::iterator it1 = mylist1.begin();
    list<int>::iterator it2 = mylist1.end();
    mylist.erase(it);
    for (auto a : mylist) {
        cout << a << " ";
    }
    cout << endl;
   
}
//1 3

代码演示二:

#include <iostream>
#include <list>
using std::list;
using namespace std;
int main()
{
    list<int> mylist = { 1, 2 ,3,5,6,7};
    list<int>::iterator it1 = mylist.begin();
    it1++;
    list<int>::iterator it2 = mylist.end();
    it2--;
    mylist.erase(it1,it2);
    for (auto a : mylist) {
        cout << a << " ";
    }
    cout << endl;
   
}
//1 7

 ⑦ swap()——交换元素

交换两个list的元素,使用swap()方法,list1.swap(list2),两个list存储的元素类型必须相同,元素个数可以不同

代码演示:

#include <iostream>
#include <list>
using namespace std;
int main()
{
    list<int> mylist1{ 1, 11, 111, 1111 };
    list<int> mylist2{ 2, 22 };
    cout << "初始mylist1为:";
    for (auto num : mylist1) {
        cout << num << " ";
    }
    cout << endl;
    cout << "初始mylist1为:";
    for (auto num : mylist2) {
        cout << num << " ";
    }
    // 交换mylist1和mylist2的元素
    mylist1.swap(mylist2);
    cout << endl;
    cout << "swap交换元素后mylist1:";
    for (auto num : mylist1) {
        cout << num << " ";
    }
    cout << endl;
    cout << "swap交换元素后mylist2:";
    for (auto num : mylist2) {
        cout << num << " ";
    }
}
//初始mylist1为:1 11 111 1111
//初始mylist1为:2 22
//swap交换元素后mylist1:2 22
//swap交换元素后mylist2:1 11 111 1111
 ⑧clear——清除元素

 clear 函数用于清空容器,清空后容器的size为0, 但是头结点(哨兵位)不会被清除

2.3.3——常见操作函数 

函数声明接口说明
splice将元素从列表转移到其它列表
remove删除具有特定值的元素
remove_if删除满足条件的元素
unique删除重复值
sort容器中的元素排序
merge合并排序列表

reverse

反转元素的顺序

①splice——将元素从列表转移到其它列表

splice共有四种形式,分别为: 

  • splice(iterator_pos, otherList) :

       将otherList中的所有元素移动到iterator_pos指向元素之前

  • splice(iterator_pos, otherList, iter1):

      从 otherList转移 iter1 指向的元素到当前list。元素被插入到 iterator_pos指向的元素之前。

  • splice(iterator_pos, otherList, iter_start, iter_end) :

       从 otherList转移范围 [iter_start, iter_end) 中的元素到 当前列表。元素被插入到 iterator_pos指向的元素之前。

注意:

1. splice 操作不会进行元素的复制或移动,只是修改指针连接,因此效率较高。
2. 在 splice 操作后,被移动的元素将不再属于原始列表。
3. splice 操作后,原始列表和插入列表的大小会相应改变。
4. 插入操作的时间复杂度为 O(1) 

代码示例一: 

#include <iostream>
#include <list>
using std::list;
using namespace std;
int main()
{
    list<int> lit1 = { 1,2,3,4,5 };
    list<int> lit2 = { 99,98 };
    list<int>::iterator it = lit1.begin();
    lit1.splice(it, lit2);//将lit2插入到lit1第一个元素之前
    for (auto& e : lit1)
    {
        cout << e << " ";
    }
    return 0;
}
//lit1:99 98 1 2 3 4 5
//lit2:空

代码示例二:

#include <iostream>
#include <list>
using std::list;
using namespace std;
int main()
{
    list<int> lit1 = { 1,2,3,4,5 };
    list<int> lit2 = { 99,98 };
    list<int>::iterator it = lit1.begin();
    list<int>::iterator it1 = lit2.begin();
    lit1.splice(it, lit2,it1);//将lit2第it1的元素插入到lit1第一个元素之前
    for (auto& e : lit1)
    {
        cout << e << " ";
    }
    cout << endl;
    for (auto& e : lit2)
    {
        cout << e << " ";
    }
    return 0;
}
//lit1:99 1 2 3 4 5
//lit2:98

代码示例三:

#include <iostream>
#include <list>
using std::list;
using namespace std;
int main()
{
    list<int> lit1 = { 1,2,3,4,5 };
    list<int> lit2 = { 99,98 };
    list<int>::iterator it = lit1.begin();
    list<int>::iterator it1 = lit2.begin();
    lit1.splice(it, lit2,it1,lit2.end());//将lit2中的元素插入到lit1第一个元素之前
    for (auto& e : lit1)
    {
        cout << e << " ";
    }
    cout << endl;
    for (auto& e : lit2)
    {
        cout << e << " ";
    }
    return 0;
}
//lit1:99 98 1 2 3 4 5
//lit2:空

 ② unique——删除重复值

注意:只能删除相邻的重复元素

代码示例:

#include <iostream>
#include <list>
using std::list;
using namespace std;
int main()
{
    list<int> lit1 = { 1,1,2,3,4,5,5,5 };
    lit1.unique();
    for (auto& e : lit1)
    {
        cout << e << " ";
    }
    return 0;
}
//lit1:1 2 3 4 5
 ③merge——合并有序链表

注意:只能是有序的才是正确的否则会报错

#include <iostream>
#include <list>
using std::list;
using namespace std;
int main()
{
    list<int> lit1 = { 1,2,3,4,5 };
    list<int> lit2 = { 6,7,8,9 };
    lit1.merge(lit2);
    for (auto& e : lit1)
    {
        cout << e << " ";
    }
    return 0;
}
//lit1:1 2 3 4 5 6 7 8 9

三.迭代器失效 

迭代器失效即迭代器指向的节点无效,即该节点被删除了,因为list的底层结构为带头节点的双向循环链表,因此在list中进行插入时是不会导致list迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器其他迭代器不会受到影响

代码演示: 

1.错误代码

#include <iostream>
#include <list>
using std::list;
using namespace std;
int main()
{
    list<int> lit1 = { 1,2,3,4,5 };
    list<int> lit2 = { 6,7,8,9 };
    list<int>::iterator it = lit1.begin();
    while(it!=lit1.end())
    {
        lit1.erase(it);
        //erase()执行之后,it所指向的节点已经被删除,因此it无效,在下一次使用it时,必须先给其复制
        ++it;
    }
    return 0;
}
//执行会报错

2.正确代码

#include <iostream>
#include <list>
using std::list;
using namespace std;
int main()
{
    list<int> lit1 = { 1,2,3,4,5 };
    list<int> lit2 = { 6,7,8,9 };
    list<int>::iterator it = lit1.begin();
    while(it!=lit1.end())
    {
        lit1.erase(it++);
    }
    return 0;
}

四.list和vector的比较

对比vectorlist
底层结构动态顺序表,连续空间带头结点的双向循环链表
随机访问支持随机访问,首地址+下标不能随机访问,可通过find查找,访问随即元素时间复杂度O(N)
插入和删除任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1)
空间利用率底层为连续空间,不容易造成内存碎片,空间利用率较高,缓存利用率高。可以一次将一个数据附近的空间都加载到缓存,不用频繁地从内存读取数据底层节点动态开辟,容易造成内存碎片,空间利用率低,缓存利用率低
迭代器原生态指针对指针进行了封装
迭代器失效容量相关的操作都有可能导致迭代器失效,如插入引起的扩容,删除元素等插入元素不会导致迭代器失效,删除节点会导致,且只影响当前迭代器,其他迭代器不受影响
使用场景不关心插入和删除效率,支持随机访问大量插入和删除操作,不关心随机访问的场景

五.模拟实现list 

①对节点进行封装

template<class T>
struct list_Node
{
	//明确一个节点包含的内容:自身数据,指向下一个节点,上一个节点
	list_Node<T>* prev;
	list_Node<T>* next;
	T data;
	list_Node(const T& x=T())
	{
		prev = nullptr;
		next = nullptr;
		data = x;
	}
};

注意:list_Node(const T& x=T()),由于不确定list的类型所以使用匿名对象

②迭代器封装

正向迭代器:

template <class T>
struct list_iterator
{
	typedef list_Node<T> Node;
	Node* _node;
	list_iterator(Node* node):_node(node){}
	T& operator*()
	{
		return _node->data;
	}
	//再次声明,能不能用引用返回关键就是那块空间还在不在,在就可以用引用返回
	T* operator->()
	{
		//->重载实质就是返回指针指向的那块空间,然后编译器会对那块空间进行解引用
		return  &(_node->data);
	}
	//前置++
	//返回的是原本空间的值,所以可以用引用
	list_iterator<T>& operator++()
	{
		_node = _node->next;
		return *this;
	}
	list_iterator<T>& operator--()
	{
		_node = _node->prev;
		return *this;
	}
	//后置++
	list_iterator<T> operator++(int)
	{
		list_iterator<T> tmp(*this);
		_node = _node->next;
		return *this;
	}
	list_iterator<T> operator--(int)
	{
		list_iterator<T> tmp(*this);
		_node = _node->prev;
		return *this;
	}
	bool operator==(const list_iterator<T>& n)
	{
		return _node == n._node;
	}
	//判断两个迭代器相不相等主要就是看结点指向以及节点内容以不一样
	bool operator!=(const list_iterator<T>& n)
	{
		return _node != n._node;
	}
};

反向迭代器:

template<class T>
struct Reverse_iterator
{
	typedef list_Node<T> Node;
	Node* _node;
	Reverse_iterator(Node* node):_node(node){}
	//解引用
	T& operator*()
	{
		return _node->data;
	}
	T* operator->()
	{
		return &(_node->data);
	}
	Reverse_iterator<T> operator++()
	{
		_node = _node->prev;
		return *this;
	}
	Reverse_iterator<T> operator--()
	{
		_node = _node->next;
		return *this;
	}
	Reverse_iterator<T>& operator++(int)
	{
		Reverse_iterator<T>* tmp(*this);
		_node = _node->prev;
		return *this;
	}
	Reverse_iterator<T>& operator--(int)
	{
		Reverse_iterator<T>* tmp(*this);
		_node = _node->next;
		return *this;
	}
	bool operator==(const Reverse_iterator<T>& n)
	{
		return _node == n._node;
	}
	bool operator!=(const Reverse_iterator<T>& n)
	{
		return _node != n._node;
	}
};

反向迭代器是向前迭代

 ③对list的封装

	template <class T>
	class list
	{	
	public:
		typedef list_Node<T> Node;
		typedef list_iterator<T> iterator;
		typedef Reverse_iterator<T> reverse_iterator;
		void init()
		{
			head = new Node;
			head->prev = head;
			head->next = head;
			size = 0;
		}
		list()
		{
			init();
		}
		list(const list<T>& itl)
		{
			init();
			for (auto& e : itl)
			{
				push_back(e);
			}
		}
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it=erase(it);
			}
		}
		~list()
		{
			clear();
			delete head;
			head = nullptr;
			size = 0;
		}
		void swap(list<T> it)
		{
			std::swap(head, it.head);
			std::swap(size, it.size);
		}
		iterator operator=(list<T>& it)
		{
			swap(it);
			return *this;
		}
		iterator begin()
		{
			return iterator(head->next);
		}
		iterator begin()const
		{
			return iterator(head->next);
		}
		iterator end()
		{
			return iterator(head);
		}
		iterator end()const
		{
			return iterator(head);
		}
		//
		//
		reverse_iterator rbegin()
		{
			return reverse_iterator(head->prev);
		}
		reverse_iterator rbegin()const
		{
			return reverse_iterator(head->prev);
		}
		reverse_iterator rend()
		{
			return reverse_iterator(head);
		}
		reverse_iterator rend()const
		{
			return reverse_iterator(head);
		}
		void push_back(const T& x)
		{
			Node* newnode = new Node(x);
			newnode->next = head;
			newnode->prev = head->prev;
			head->prev->next = newnode;
			head->prev = newnode;
			size++;


		}
		void pop_back()
		{
			erase(end()--);
		}
		void insert(iterator pos, const T& x)
		{
			Node* newnode = new Node(x);
			Node* next = pos._node;
			Node* prev = pos._node->prev;
			newnode->next = next;
			newnode->prev = prev;
			pos._node->prev = newnode;
			prev->next = newnode;
			size++;
		}
		iterator erase(iterator pos)
		{
			assert(pos != end());
			Node* cur = pos._node;
			Node* next = cur->next;
			Node* prev = cur->prev;
			prev->next = next;
			next->prev = prev;
			size--;
			delete cur;
			return iterator(next);
//避免迭代器失效
		}
	private:
		Node* head;
		size_t size;
	};
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值