一.list的介绍
list容器的底层是双向循环带头链表,在CPP中,我们对双向循环带头链表进行了一定程度的封装。
如果你不了解双向链表,那么可以浏览此片博文:双向链表
二.list的定义方式以及赋值
2.1list的构造方式
在这里我们要学习六种list的构造方式。
构造1:构造一个某类型容器
list<int> list1;
构造2:构造一个含有一个值的某类型容器
list<int> list2(3);//构造只含有一个数值3的结点的list容器
构造3:构造一个含有n个值的某类型容器
list<int> list3(3, 5);//构造含有三个数值都是5的结点的list容器
构造4:复制某个容器构造该容器的复制品
list<int> list4(list3);//拷贝构造
构造5:使用某个迭代器区间进行构造
list<int> list5(list3.begin(), list3.end());//迭代器区间初始化
构造6:使用列表构造一个某类型容器
list<int> list6{ 1,2,3,4,5,6 };//使用列表构造
2.2operator=重载函数
同样的,我们也可以使用重载后的operator=操作符来进行给一个空的list容器赋值。
方式1:将别的同类型容器赋值给该容器
list<int> list7 = list5;
方式2:使用列表
list<int> list8 = { 1,2,3,4,5,6 };
三.list的插入和删除
list库中提供给我们大家的需要我们学习的函数是头插和尾插函数以及头删和尾删函数。
下面我们逐个进行学习。
3.1push_front和pop_front
我们可以通过这两个函数进行头插和头删。
函数原型如下:
void push_front(const value_type& val); void pop_front();
我们可以使用这两个函数对list的头部进行删除和插入操作。
如下:
void test2() { list<int> list1; list1.push_front(3); list1.push_front(4); list1.push_front(5); list1.push_front(6); print(list1);//为了方便写博客,这个是自己写的函数。 list1.pop_front(); print(list1); }
结果:
6 5 4 3
5 4 3
3.2push_back和pop_back
我们可以通过这两个函数进行尾插和尾删。
函数原型如下:
void push_back(const value_type& val); void pop_back();
我们可以使用这两个函数对list的尾部进行删除和插入操作。
void test2() { list<int> list2; list2.push_back(1); list2.push_back(2); list2.push_back(3); list2.push_back(4); print(list2); list2.pop_back(); print(list2); }
结果:
1 2 3 4
1 2 3
3.3insert
insert函数用于在指定位置插入数据。
insert函数的函数原型如下:
iterator insert(iterator position, const value_type& val);//在pos位置插入一个val void insert(iterator position, size_type n, const value_type& val);//在position位置插入n个val void insert(iterator position, InputIterator first, InputIterator last);//在position位置插入[first,last)区间的内容。
insert:
- 函数1:在position位置插入一个val
- 函数2:在position位置插入n个val
- 函数3:在position位置插入迭代器区间[first,last)中的内容。
void test3() { list<int> l1(3, 1); l1.insert(l1.begin(), 2); print(l1); l1.insert(l1.begin(), 3,3); print(l1); list<int> l2{ 3,2,1 }; l1.insert(l1.begin(), l2.begin(),l2.end()); print(l1); }
结果:
2 1 1 1
3 3 3 2 1 1 1
3 2 1 3 3 3 2 1 1 1
3.4erase
erase函数用于在指定位置删除数据。
erase函数的函数原型如下:
iterator erase(iterator position); iterator erase(iterator first, iterator last);
erase:
- 函数1:删除position位置的数据。
- 函数2:删除迭代区间[first,last)中的数据。
void test3() { list<int> l1(3, 1); l1.insert(l1.begin(), 2); l1.insert(l1.begin(), 3,3); list<int> l2{ 3,2,1 }; l1.insert(l1.begin(), l2.begin(),l2.end()); l1.erase(l1.begin()); print(l1); l1.erase(++l1.begin(), --l1.end());//注意是前置++和--,而不是后置的++-- print(l1); }
结果:
2 1 3 3 3 2 1 1 1
2 1
四.list的迭代器使用
list和别的容器不同的地方在于:list的迭代器只支持++和--,不支持+和-。
因此,下面的操作是不合法的:
list<int>::iterator it = find(3, l1.begin(), l1.end()); it + 1;
4.1begin和end
begin()和end()是list的正向迭代器函数
- begin()函数返回list容器的第一个位置
- end()函数返回list容器的最后一个位置的下一个位置
图解如下:
4.2rbegin和rend
rbegin()和rend()是list的反向迭代器函数
- rbegin()函数返回vector容器的最后一个位置
- rend()函数返回vector容器的第一个位置的前一个位置
图解如下:
同样的,我们可以使用以上的两种迭代器对list内的元素进行遍历。
如下:
void test4() { list<int> l1{ 1,2,3,4 }; //正向迭代器 list<int>::iterator it1 = l1.begin(); while (it1 != l1.end()) { cout << *it1 << ' '; it1++; } cout << endl; //反向迭代器 list<int>::reverse_iterator it2 = l1.rbegin(); while (it2 != l1.rend()) { cout << *it2 << ' '; it2++; } cout << endl; }
结果:
1 2 3 4
4 3 2 1
五.list的元素获取
5.1front和back
front用于获取头部元素,back用于获取尾部元素。
这两个函数比较简单,我们使用一下即可。
void test5() { list<int> l1{ 1,2,3,4 }; cout << l1.front() << endl; cout << l1.back() << endl; }
结果:
1
4
六.list的空间控制
6.1size
我们在初始化一个list容器之后,可以通过这个函数查看该容器的有效数据个数。
函数原型如下:
void size();
在我们用两个1初始化一个list容器之后,可以使用这个函数查看它的有效数据个数。
6.2resize
使用这个函数可以重设函数的有效数据。
函数原型如下:
void resize(size_type n, value_type val = value_type());
resize:
- 如果有效数据个数大于当前size,若规定了特定值则用特定值填充,否则用数据类型的默认值填充
- 如果有效数据个数小于当前size,则只保留前size个数据。
void test6() { list<int> l1(2, 1); cout << l1.size() << endl; l1.resize(5); print(l1);//1 1 0 0 0 l1.resize(10, 1); print(l1);//1 1 0 0 0 1 1 1 1 1 l1.resize(3); print(l1);//1 1 0 }
6.3empty
empty函数判断list容器是否为空的函数。
empty:
- 如果vector容器为空,则返回真
- 如果vector容器不为空,则返回假。
list<int> l1(2, 1); list<int> l2; cout << l1.empty() << endl;//0-->假 cout << l2.empty() << endl;//1-->真
6.4clear
clear函数用于清除list容器中的数据并置size为0.
clear:
- 清除掉list容器内所有的元素,并将size置为0
list<int> l1(2, 1); cout << l1.empty() << endl;//0-->假 l1.clear(); cout << l1.empty() << endl;//1-->真
6.5assign
assign函数用于将新内容分配给容器,替换其当前内容。
assign函数原型如下:
void assign(InputIterator first, InputIterator last); void assign(size_type n, const value_type & val);
assign:
- 函数2:将所给迭代器区间当中的内容分配给容器。
- 函数1:将n个值为val的数据分配给容器。
void test13() { list<int> l1(3, 1); list<int> l2(3,2); print(l1);//1 1 1 l1.assign(5, 2); print(l1);///2 2 2 2 2 l1.assign(l2.begin(), l2.end()); print(l1);//2 2 2 }
结果:
1 1 1
2 2 2 2 2
2 2 2
七.list的操作相关函数
7.1sort
sort函数是用于升序排列list内的元素的。
函数原型如下:
void sort();
sort:
- 将list内的元素默认排为升序。
void test7() { list<int> l1{ 10,9,8,7,6,5,4,3,2,1 }; print(l1);//10 9 8 7 6 5 4 3 2 1 l1.sort(); print(l1);//1 2 3 4 5 6 7 8 9 10 }
结果:
10 9 8 7 6 5 4 3 2 1
1 2 3 4 5 6 7 8 9 10
7.2splice
splice的功能类似于vector的insert函数,它可以用于在指定位置将另一个list容器插入。
但是,这个和insert函数不同的是,splice函数本质是一种转移,而不是复制。
splice的函数原型如下:
void splice(iterator position, list& x); void splice(iterator position, list& x, iterator i); void splice(iterator position, list& x, iterator first, iterator last);
splice:
- 函数1:在position位置转移x容器的所有元素
- 函数2:在position位置转移xi位置的元素
- 函数3: 在position位置转移x的[first,last)区间内的元素。
void test8() { list<int> l1{ 5,4,3,2,1 }; list<int> l2{ 6,7,8,9,10 }; list<int> l3{ 0,11,12,13 }; list<int> l4{ 0,11,12,13 }; l1.splice(l1.end(), l2); print(l1);//5 4 3 2 1 6 7 8 9 10 l1.splice(l1.end(), l3,++l3.begin()); print(l1);//5 4 3 2 1 6 7 8 9 10 11 print(l3);//0 12 13 l3.splice(l3.begin(), l4,++l4.begin(),--l4.end()); print(l3);//11 12 0 11 12 print(l4);//0 13 }
结果:
5 4 3 2 1 6 7 8 9 10
5 4 3 2 1 6 7 8 9 10 11
0 12 13
11 12 0 12 13
0 13
这里需要大家注意的是,splice函数的本质是转移!
现在我通过画图的形式来解析这段代码:
四个容器:
第一次转移:
第二次转移:
第三次转移:
7.3remove
remove函数是用来删除list容器内的值的。
函数原型如下:
void remove (const value_type& val);
remove:
- remove函数可以删除掉容器内所有值是val的结点
void test9() { list<int> l1{ 5,4,4,3,2,1 }; print(l1);//5 4 4 3 2 1 l1.remove(4); print(l1);//5 3 2 1 }
结果:
5 4 4 3 2 1
5 3 2 1
7.4unique
这个函数是用来去除相同的元素的。
它会拿当前的元素和下一个元素进行比较,如果和下一个元素相同的话,则删掉下一个元素。
因为它的这个特性,我们常用于有序列表的去重。
函数原型如下:
void unique();
我们可以通过下面这段代码体会到这个函数的性质:相邻比较,相等删除。
void test10() { list<int> l1{ 1,1,3,3,4,1 }; l1.unique(); print(l1);//1 3 4 1 }
结果:
1 3 4 1
7.5merge
merge函数用于合并两个有序list容器。
merge函数的原型如下:
void merge (list& x);
merge:
- merge函数用于合并两个有序的list容器,合并后的list容器依旧有序。
- 作为参数被传递的容器被合并后会为空
void test11() { list<int> l1{1,3,5,7,9}; list<int> l2{2,4,6,8,10}; l1.merge(l2); print(l1);//1 2 3 4 5 6 7 8 9 10 print(l2);// }
结果:
1 2 3 4 5 6 7 8 9 10
7.6reverse
reverse用于翻转链表。
函数原型如下:
void reverse();
void test12() { list<int> l1{ 1,3,5,7,9 }; l1.reverse(); print(l1); }
结果:
9 7 5 3 1
7.7swap
swap函数用于交换两个容器的内容。
void test14() { list<int> l1(3, 1); list<int> l2(3, 2); l1.swap(l2); print(l1);//2 2 2 print(l2);//1 1 1 }
结果:
2 2 2
1 1 1
八.list的迭代器失效问题
在之前的文章中提过这个问题,此处大家可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,而双向循环链表的扩容和顺序表不同,是不会出现异地扩容的现象的。因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。
下面的这段代码就是非常典型的一段迭代器失效的代码:
void test15() { 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时,必须先给其赋值 l.erase(it); ++it; } }
和vector一样,我们在使用前给迭代器赋值即可。
void test15() { int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; list<int> l(array, array + sizeof(array) / sizeof(array[0])); list<int>::iterator it = l.begin(); while (it != l.end()) { it=l.erase(it); ++it; //这行代码和上面两行代码等效 //l.erase(it++) } }
九.后记
有关list的模拟实现可参考博主的下篇博文。
如果你想更深入的了解list的相关内容,可参考cpp官网:cpp官网
码字不易,给个点赞收藏叭~~~