C++-->stl: list的使用

发布于:2025-08-10 ⋅ 阅读:(18) ⋅ 点赞:(0)

前言list的认识

list是可以在固定时间(O(1))内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向(当然有一个哨兵位 就是说头节点)
其前一个元素和后一个元素。
3. listforward_list非常相似:最主要的不同在于forward_list是单链表,只能向前迭代,已让其更简单高(list是doubly list的接口 forward_list是单链表的接口)
效。
4. 与其他的序列式容器相比(arrayvectordeque)list通常在任意位置进行插入、移除元素的执行效率 更好。
5. 与其他序列式容器相比,listforward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list 的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间  (需要创建一个结点去遍历,比随机访问效率低)
   开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的list来说这不合适 可能是一个重要的因素

1 list的构造
常用的构造函数

default (1)

默认构造)

explicit list (const allocator_type& alloc = allocator_type());

      fill (2)

 n个val值填充

explicit list (size_type n, const value_type& val = value_type(),
                const allocator_type& alloc = allocator_type());

range (3)

   迭代器构造

template <class InputIterator>
  list (InputIterator first, InputIterator last,
         const allocator_type& alloc = allocator_type());

copy (4)

拷贝构造

list (const list& x);
参考代码 : 
   
void print_list(list<int>& list1)
{
	list<int>::iterator it = list1.begin();
	while (it != list1.end())
	{
		cout << *it;
		it++;

	}
	cout << endl;
	  
}
void construct_test()
{
	list<int>list_1;// 空构造
	list<int>list_2(20,0);// n  val
	list<int>::iterator it = list_2.begin();  // 迭代器构造 
	list<int> th(list_2.begin(),list_2.end());
	// 不支持的写法 list<int> th(list_2.begin(),list_2.begin()+4);
	// 因为list的迭代器不支持随机访问哦 其实本质上是因为只给迭代器封装了++的操作 
	    
	int arr[] = {1,2,3,4,5,6};
	list<int>four(arr,arr+4);// 这也是迭代器构造 数组肯定是可以通过下标 随机访问啦 
	// 拷贝构造
	//list<int> five = four;
	list<int> five(four);
	print_list(five);

	
}

2. list的迭代器 

    list的图解 

迭代器的理解:

  
  这个迭代器的内部封装可以大致这么理解:
    肯定是对node的操作,封装node的移动,有++,--以及!=   判断俩个迭代器是否相等就这样就很常见,封装的目的很简单,就是为了统一容器的操作哦!。
    后期博客我会更新其中的模拟实现这是很有意思的:
   内部是没有封装+某个数的 
   所以之前例子中指出 像这样的 list<int>::iterator(it,it+3);  是不被允许的哦!
总之因为list的底层容器是双向链表,每个节点地址之间是不连续的,所以我们为了统一容器操作就封装迭代器了。
迭代器使用:代码实例:
void iterator_test() {

	int arr[] = { 1,2,3,4,5,6 };
	list<int>four(arr, arr + 5);
	  
	 // 用迭代器遍历four这个链表
	list<int>::iterator it = four.begin();// 正向迭代器
	list<int>::reverse_iterator rit = four.rbegin();// 反向迭代器
	while (it != four.end())
	{
		cout << *it;
		it++;

	}
	cout << endl;
	while (rit != four.rend())
	{
		cout << *rit;
		rit++;

	}
	cout << endl;

}
输出:12345   
           54321

doubly list的图解

   这里只标出了 node之间指针的关系,这里值得注意的就是如何实现双向循环的,就是创建一个头节点,头节点作用就是用来很方便的实现增删查改(减少判空这个策略数据结构中可以多了解理解) 总之就是让头节点的pre指向尾节点,尾节点的next指向头节点。 总之头节点是不存放有效数据的! 所以下面我说说迭代器遍历的问题!

 list迭代器遍历的理解: 

      显然 list<T>iterator:: begin 指向的是头节点的下一个位置,node结构体中总是存放的是头节点,所以需要遍历的时候需要总是从头节点开始一一遍历,而不是随机访问。 
    判断到尾部的方法有很多一个是根据size,以及cur节点的遍历到了头节点了。

3. list element access(元素获取)

     list的获取常用的就是一个是front  和 back  双向链表根据头节点实现起来很方便的。

     

3.1 reference front()  和reference back的使用
        这个list的front的成员函数,简单来说就是返回一个头节点的元素引用。 
这里的referrence其实是一个typedef,这个在库里面实现是很常见的  可以这样理解:
typedef reference T;
  back同理如上。
   
// list::front
#include <iostream>
#include <list>

int main ()
{
  std::list<int> mylist;

  mylist.push_back(77);
  mylist.push_back(22);

  // now front equals 77, and back 22

  mylist.front() -= mylist.back();

  std::cout << "mylist.front() is now " << mylist.front() << '\n';

  return 0;
}

输出: 55

4. list modified 元素修改

    list常用的修改方面的有:  头插,头删,尾删,尾插,还有指定位置删除,和指定位置插入,还有指定尾插删除, 以及swap (经常被用于去写构造函数的还有拷贝构造)

4.1 push_front 和pop_front

实现:
// list::push_front
#include <iostream>
#include <list>

int main ()
{
  std::list<int> mylist (2,100);         // two ints with a value of 100
  mylist.push_front (200);
  mylist.push_front (300);

  std::cout << "mylist contains:";
  for (std::list<int>::iterator it=mylist.begin(); it!=mylist.end(); ++it)
    std::cout << ' ' << *it;

  std::cout << '\n';
  return 0;
}

实现:
// list::pop_front
#include <iostream>
#include <list>

int main ()
{
  std::list<int> mylist;
  mylist.push_back (100);
  mylist.push_back (200);
  mylist.push_back (300);

  std::cout << "Popping out the elements in mylist:";
  while (!mylist.empty())
  {
    std::cout << ' ' << mylist.front();
    mylist.pop_front();
  }

  std::cout << "\nFinal size of mylist is " << mylist.size() << '\n';

  return 0;
}

4.2 push_back和pop_front 

    尾插和尾删 返回值都是void注意哦
  
// list::push_back
#include <iostream>
#include <list>

int main ()
{
  std::list<int> mylist;
  int myint;

  std::cout << "Please enter some integers (enter 0 to end):\n";

  do {
    std::cin >> myint;
    mylist.push_back (myint);
  } while (myint);

  std::cout << "mylist stores " << mylist.size() << " numbers.\n";

  return 0;
}

 

// list::pop_back
#include <iostream>
#include <list>

int main ()
{
  std::list<int> mylist;
  int sum (0);
  mylist.push_back (100);
  mylist.push_back (200);
  mylist.push_back (300);

  while (!mylist.empty())
  {
    sum+=mylist.back();
    mylist.pop_back();
  }

  std::cout << "The elements of mylist summed " << sum << '\n';

  return 0;
}

4.3 insert

  在指定位置(用迭代器哦) 插入一个元素,
     插入n个val 在指定位置
  在指定位置插入一个范围迭代器区间的元素

void insert_test() {

	int arr[] = { 1,2,3,4,5 };//    1  2  3  4  5
	int arr1[] = { 11,12,13,14,15 };//    11  12  13  14  15
	vector<int>v1(arr,arr+5);
	list<int> mylist(arr1,arr1+5);     //     !

	list<int>::iterator it = mylist.begin();
	it++;
	mylist.insert(it,10);// 迭代器可能存在失效的问题 所以这里一定要给it重赋值   

	print_list(mylist);

	// n val
	it = mylist.begin();
	it++;
	mylist.insert(it, 10,1);
	 
	print_list(mylist);
	   //  范围插入
	it = mylist.begin();
	it++;
	mylist.insert(it, v1.begin()+2, v1.end());
	print_list(mylist);




}
输出:11 10 12 13 14 15
11 1 1 1 1 1 1 1 1 1 1 10 12 13 14 15
11 3 4 5 1 1 1 1 1 1 1 1 1 1 10 12 13 14 15

4.4 erase 删除

 传递的参数是list的迭代器,指定位置删除和指定的迭代器范围区间的元素删除。

std:: iterator& advance(iterator,len) 这是库里的移动迭代器的函数

// erasing from list
#include <iostream>
#include <list>

int main ()
{
  std::list<int> mylist;
  std::list<int>::iterator it1,it2;

  // set some values:
  for (int i=1; i<10; ++i) mylist.push_back(i*10);

                              // 10 20 30 40 50 60 70 80 90
  it1 = it2 = mylist.begin(); // ^^
  advance (it2,6);            // ^                 ^
  ++it1;                      //    ^              ^

  it1 = mylist.erase (it1);   // 10 30 40 50 60 70 80 90
                              //    ^           ^

  it2 = mylist.erase (it2);   // 10 30 40 50 60 80 90
                              //    ^           ^

  ++it1;                      //       ^        ^
  --it2;                      //       ^     ^

  mylist.erase (it1,it2);     // 10 30 60 80 90
                              //        ^

  std::cout << "mylist contains:";
  for (it1=mylist.begin(); it1!=mylist.end(); ++it1)
    std::cout << ' ' << *it1;
  std::cout << '\n';

  return 0;
}

5. 迭代器失效问题

   我们之前说过迭代器的实现可以理解为一个封装了node的移动和判断的类。  这里的数据存放是node,所以本质上还是用node*指向了一个节点,所以在list中只要该节点不删除,这个节点依然存在。 
  在list中插入不会导致迭代器失效,因为这个节点并没有被销毁(vector中的迭代器失效是因为他们的内存是连续的,当扩容的时候可能导致旧内存被销数据被移动到新内存)但是list的底层是双向循环链表,内存之间都是用指针(地址)连接起来的,不用在乎是否连续,你不主动销毁内存是不会被销毁的。
   所以只有当删除的时候才会被销毁。

谈谈erase和insert的返回值

   erase:
iterator erase (iterator position);
iterator erase (iterator first, iterator last); 

insert:

single element (1)
iterator insert (iterator position, const value_type& val);
fill (2)
    void insert (iterator position, size_type n, const value_type& val);
range (3)
template <class InputIterator>
    void insert (iterator position, InputIterator first, InputIterator last);
insert:大体上分为两,一种是插入一个元素,一个是插入多个元素。
返回值:简单理解就是返回插入元素后的下一个位置的元素
erase则是返回删除元素的下一个位置
处理迭代器失效: 就是重新赋值给迭代器:
错误实例 没有重新赋值 
修正: 
   代码: 
void TestListIterator1()
{
	int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	list<int> l(array, array + sizeof(array) / sizeof(array[0]));
	auto it = l.begin();
	while (it != l.end())
	{
		// erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给
		//其赋值
			it =l.erase(it);
		++it;
	}
}


网站公告

今日签到

点亮在社区的每一天
去签到