1.list库函数测试及简要说明
1.1 sort
list提供sort(底层是归并)是为了便捷性,其实库里提供的函数都是有说法的,比如vector没有直接提供头插和头删,list的迭代器没有直接+运算符重载,因为成本高,不期望你在使用容器时使用这些行为,如果偶尔vector要头删、头插,这是ok的,通过insert可以间接完成,或者list要偶尔排序,小数据量排序,也是可以的,有时候大佬们的智慧结晶需要小火慢炖,慢慢体会
我们看到下面的测试结果,当数据量较大时,还是有明显的差距的,vector就比较适合排序,不适合删除和插入;但是list就不适合直接排序,适合插入删除,所以如果我们有要经常性地大量数据排序的需求,那就先放到合适的容器中这也是容器多种多样,各有各的使用场景,要按需选取
cpp
void mytest_list3() {
srand(time(0));
int N = 10;
list<int> lt1, lt2;
vector<int> v;
v.reserve(N);
for (int i = 0; i < N; i++) {
auto e = rand();
lt1.push_back(e);
lt2.push_back(e);
}
int begin1 = clock();
lt1.sort();
int end1 = clock();
int begin2 = clock();
for (auto e : lt2)
v.push_back(e);
sort(v.begin(), v.end());
int i = 0;
for (auto& e : lt2)
e = v[i++];
int end2 = clock();
printf("list sort:%d\n", end1 - begin1);
printf("vector sort:%d\n", end2 - begin2);
}
int main() {
mytest_list3();
return 0;
}
输出结果(VS 2022 release)
因为debug模式特点是可以调试,会压大量调试信息,特别是递归的情况下,效率会大幅度下降,list的sort是由循环写的,而不是递归,因为递归要找中间结点,不方便;release模式下递归和循环才是"平等"赛跑,linux下g++编译器默认是64位,release版本,debug模式要加-g

1.2 reverse
list本身提供了reverse,算法库里也提供了reverse,效果一样;STL两大组件,算法和容器,算法将容器共有的一些算法抽取出来,比如sort,reverse,find等,算法统一使用容器的迭代器进行调用,不关心数据的底层数据结构,各容器比如list的迭代器要向vector,string的原生指针一样进行++、解引用,这些由容器各自实现;而从算法角度来看,所有容器的使用方法都是采用迭代器,但底层实现已经大有不同
一般算法库里有的情况下,容器不会再单独提供,但如果算法库里的算法无法满足容器本身的需求,容器内部会单独实现,比如string可以使用算法库里的find,但也提供了find,因为需求种类较为多样,查找字符、字符串等(string产生时间早于stl,严格来说不属于stl,但是string是容器,和slt容器很类似)

1.3 splice

cpp
void mytest_list4() {
list<int> lt1,lt2;
for (int i = 0; i < 10; i++)
lt1.push_back(i);
for (int i = -10; i <0; i++)
lt2.push_back(i);
print(lt1);
print(lt2);
list<int>::iterator it = lt2.begin();
for (int i = 0; i < 5; i++) {
it++;
}
lt1.splice(lt1.begin(), lt2,lt2.begin(),it);
print(lt1);
print(lt2);
lt1.splice(lt1.begin(), lt2, lt2.begin());
print(lt1);
print(lt2);
lt1.splice(lt1.begin(), lt2);
print(lt1);
print(lt2);
lt1.splice(lt1.begin(), lt1,++lt1.begin(), lt1.end());
print(lt1);
}
int main() {
mytest_list4();
return 0;
}
输出
cpp
0 1 2 3 4 5 6 7 8 9
-10 -9 -8 -7 -6 -5 -4 -3 -2 -1
//lt2的前五个转移到lt1的第一个数据之前
-10 -9 -8 -7 -6 0 1 2 3 4 5 6 7 8 9
-5 -4 -3 -2 -1
//lt2的第一个数据转移到lt1的第一个数据之前
-5 -10 -9 -8 -7 -6 0 1 2 3 4 5 6 7 8 9
-4 -3 -2 -1
//lt2所有数据转移到lt1的第一个数据之前
-4 -3 -2 -1 -5 -10 -9 -8 -7 -6 0 1 2 3 4 5 6 7 8 9
//lt1第一个位置之后的数据全部移到最前面
-3 -2 -1 -5 -10 -9 -8 -7 -6 0 1 2 3 4 5 6 7 8 9 -4
1.4 remove = find + erase
cpp
void mytest_list5() {
int a[] = { 0,1,2,3,4,5,6,7,8,9 };
list<int> lt1(a,a+10);
print(lt1);
list<int>::iterator it = find(lt1.begin(), lt1.end(),3);
if (it != lt1.end()) {
lt1.erase(it);
}
print(lt1);
lt1.remove(4);
print(lt1);
lt1.remove(100);//如果是lt1中没有的值,不做处理
print(lt1);
}
输出
cpp
0 1 2 3 4 5 6 7 8 9
0 1 2 4 5 6 7 8 9
0 1 2 5 6 7 8 9
0 1 2 5 6 7 8 9
1.5 insert

list的insert不涉及迭代器失效的问题,因为vector进行insert时迭代器失效比如auto it=v.begin()+3,it是第三个位置的指针,要么是异地扩容,it成为野指针;要么是插入之后,pos及之后的指针指向的数据和插入之前指向的数据不一样了;要么vector进行erase删除第三个位置数据,it的值不变,指向第三个位置的含义也不变,关键就是vector中第三个之后的数据都往前挪了,第三个位置之后的原本的迭代器的指向的数据和erase之前不同了,这才是问题;
那么list不涉及元素挪动和异地扩容的问题,it获取到第三个位置的数据,插入之后,it指向的数据与插入之前指向的数据一致,但是it不再是指向第三个位置了,而是第四个
cpp
void mytest_list6() {
list<int> lt1;
lt1.push_back(1);
lt1.push_back(2);
lt1.push_back(3);
lt1.push_back(4);
lt1.push_front(10);
lt1.push_front(20);
print(lt1);
list<int>::iterator it = lt1.begin();
for (int i = 0; i < 5; i++) {
it++;
}
lt1.insert(it, 100);
*it *= 10;//作用的是插入之前的第五个数据,值为4
print(lt1);
}
输出
cpp
20 10 1 2 3 4
20 10 1 2 3 100 40
1.6 erase

cpp
void mytest_list7() {
list<int> lt1;
for (int i = 0; i < 10; i++)
lt1.push_back(i);
print(lt1);
list<int>::iterator it = lt1.begin();
while (it != lt1.end()) {//这个地方是!=,而不是<,因为链表的逻辑关系和物理位置大学没有必然联系
if (*it % 2 == 0)
it = lt1.erase(it);
else
it++;
}
print(lt1);
}
int main() {
mytest_list7();
return 0;
}
输出
cpp
0 1 2 3 4 5 6 7 8 9
1 3 5 7 9
2 底层实现
2.1 迭代器
算法库里的sort是不支持list的,因为底层是快排,只有randam access迭代器才能支持,而list是bidiretional迭代器

下图只表示不同种类迭代器的功能范畴,B包含A是指B有的功能,A都能支持


在实现list迭代器的时候注意链表的数据存储并不是连续的,这就要求我们把指针封装成一个类,进行++、*、->等一系列运算符的重载,其实任何迭代器都是指针实现的,只不过有的时候指针就足够了,比如string和vector,但有的时候指针不足以满足需求,所以还要进行封装,让迭代器能够像原生指针那样++、 *、->等等,但是由于list底层并不是连续存储,+的代价较大,所以没有直接提供,和迭代器的分类有关,容器的迭代器是什么类型取决于容器本身的存储性质,比如vector和string本身连续存储就支持随机存取,是randan access,+/-的代价都不大
cpp
#pragma once
namespace diy {
template<class T>//定义节点
struct list_node {
list_node<T>* _prev;
list_node<T>* _next;
T _val;
list_node(const T& val = T()):
_prev(nullptr),
_next(nullptr),
_val(val)
{}
};
//因为有const对象和普通对象的两种迭代器,所以定义了两个类,实现有些冗余,但是模版存在的意义就是根据参数定义不同的类
template<class T>
struct __list_iterator {
typedef list_node<T> Node;
Node* _node;
__list_iterator(Node* node):
_node(node)
{}
__list_iterator(const __list_iterator<T>& it){
_node = it._node;
}
T& operator*() {
return _node->_val;
}
__list_iterator<T>& operator++() {
_node = _node->_next;
return *this;
}
__list_iterator<T> operator++(int) {//后置
__list_iterator<T> tmp(*this);
_node = _node->_next;
return tmp;
}
bool operator==(const __list_iterator<T>& it) {
return _node == it._node;
}
bool operator!=(const __list_iterator<T>& it) {
return _node != it._node;
}
};
template<class T>
struct __list_const_iterator {
typedef list_node<T> Node;
Node* _node;
__list_const_iterator(Node* node) :
_node(node)
{
}
__list_const_iterator(const __list_const_iterator<T>& it) {
_node = it._node;
}
const T& operator*() {
return _node->_val;
}
__list_const_iterator<T>& operator++() {
_node = _node->_next;
return *this;
}
__list_const_iterator<T> operator++(int) {//后置
__list_const_iterator<T> tmp(*this);
_node = _node->_next;
return tmp;
}
bool operator==(const __list_const_iterator<T>& it) {
return _node == it._node;
}
bool operator!=(const __list_const_iterator<T>& it) {
return _node != it._node;
}
};
template<class T>
class list {
public:
list() {
_head = new Node;
_head->_prev = _head;
_head->_next = _head;
}
typedef __list_iterator<T> iterator;
typedef __list_const_iterator<T> const_iterator;
/*typedef const __list_iterator<T> const_iterator; 这样实现是有问题的,因为const修饰的是指针,而不是指针指向的内容,但list迭代器一定是需要++或--的,因为需要进行遍历等操作
const T* ptr1; 要的是这种效果
T* const ptr2; 不是这种效果*/
iterator begin() {
return _head->_next;//iterator(_head->_next)
}
iterator end() {
return _head;//iterator(_head)
}
const_iterator begin() const{
return _head->_next;//iterator(_head->_next)
}
const_iterator end() const{
return _head;//iterator(_head)
}
引出类型别名参数
cpp
template<class T, class Ref,class Ptr>//T是基础类型参数,Ref是类型别名参数,可以是&和const等,用于实例化不同类,满足需求;Ptr用于->操作符重载时的不同返回值
struct __list_iterator {
typedef list_node<T> Node;
Node* _node;
typedef __list_iterator<T, Ref,Ptr> self;
__list_iterator(Node* node):
_node(node)
{}
Ref operator*() {
return _node->_val;
}
Ptr operator->() {
return &_node->_val;
}
self& operator++() {
_node = _node->_next;
return *this;
}
self operator++(int) {//后置
self tmp(*this);
_node = _node->_next;
return tmp;
}
bool operator==(const self& it) {
return _node == it._node;
}
bool operator!=(const self& it) {
return _node != it._node;
}
};
template<class T>
class list {
public:
typedef __list_iterator<T,T&> iterator;
typedef __list_iterator<T, const T&> const_iterator;
iterator begin() {
return _head->_next;//iterator(_head->_next)
}
iterator end() {
return _head;//iterator(_head)
}
const_iterator begin() const{
return _head->_next;//iterator(_head->_next)
}
const_iterator end() const{
return _head;//iterator(_head)
}
private:
typedef list_node<T> Node;
Node* _head;
size_t _size;
};
}
*/-> 运算符重载
cpp
struct A {
A(int a1 = 0, int a2 = 0) :
_a1(a1),
_a2(a2)
{}
int _a1;
int _a2;
};
void test3() {
diy::list<diy::A> lt;
lt.push_back(diy::A(1, 1));
lt.push_back(diy::A(2, 2));
lt.push_back(diy::A(3, 3));
lt.push_back(diy::A(4, 4));
lt.push_back(diy::A(5, 5));
diy::list<diy::A>::iterator it = lt.begin();
while (it != lt.end()) {
cout << (*it)._a1 << " " << it->_a2<< " ";
it++;
}
cout << endl;
}
int main(){
test3();
return 0;
}

2.2 构造
cpp
void empty_init() {
_head = new Node;
_head->_prev = _head;
_head->_next = _head;
_size = 0;
}
//默认构造
list() {
empty_init();
}
//拷贝构造
list(const list<T>& lt) {
//list(const list<T>& lt)也可以,在类内,可以用类名代替类型名,但有了模版之后,类名是类名,类型名是类根据不同参数实例化出来的类型的名字,二者并不相同
empty_init();
for (auto& e : lt) {
push_back(e);
}
}
//迭代器构造
template<class InputIterator >
list(InputIterator first, InputIterator last) {
empty_init();
auto it = first;
while (it != last) {
push_back(*it);
it++;
}
}
2.3 库函数实现
2.3.1 插入
cpp
iterator insert(iterator pos, const T& val) {
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(val);
prev->_next = newnode;
newnode->_prev = prev;
cur->_prev = newnode;
newnode->_next = cur;
_size++;
return newnode;
}
void push_front(const T& val) {
insert(begin(), val);
}
//尾插
void push_back(const T& val) {
/*Node* tmp = new Node(val);
Node* tail = _head->_prev;
tmp->_next = _head;
_head->_prev = tmp;
tail->_next = tmp;
tmp->_prev = tail;
_size++;*/
insert(end(), val);
}
2.3.2 删除
cpp
iterator erase(iterator pos) {
assert(pos != end());
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
prev->_next = next;
next->_prev = prev;
delete cur;
cur = nullptr;
_size--;
return next;
}
void pop_front() {
erase(begin());
}
void pop_back() {
erase(--end());
}
2.3.2 其它函数
cpp
#pragma once
#include <assert.h>
#include <algorithm>
namespace diy {
template<class T>
class list {
public:
//复制运算符重载
void swap(list<T>& lt) {
std::swap(_head, lt._head);
std::swap(_size, lt._size);
}
list<T>& operator=(list<T> tmp) {//传值传参
swap(tmp);
return *this;
}
~list() {
clear();
delete _head;
_head = nullptr;
}
void clear() {
iterator it = begin();
while (it != end()) {
it = erase(it);
}
}
size_t size() const{
/*iterator it = begin();
size_t n = 0;
while (it != end()) {
n++;
it++;
}
return n;*/
return _size;
}
private:
typedef list_node<T> Node;
Node* _head;
size_t _size;
};
}
牛刀小试
- 下面有关vector和list的区别,描述错误的是( )
A.vector拥有一段连续的内存空间,因此支持随机存取,如果需要高效的随机存取,应该使用vector
B.list拥有一段不连续的内存空间,如果需要大量的插入和删除,应该使用list
C.vector::iterator支持"+"、"+="、"<"等操作符
D.list::iterator则不支持"+"、"+="、"<"等操作符运算,但是支持了[ ]运算符
D
- 以下代码实现了从表中删除重复项的功能,请选择其中空白行应填入的正确代码( )
A. p=cur+1;aList.erase(p++);
B.p=++cur; p == cur ? cur = p = aList.erase(p ) : p = aList.erase(p );
C.p=cur+1;aList.erase(p );
D.p=++cur;aList.erase(p );
cpp
template<typename T>
void removeDuplicates(list<T> &aList){
T curValue;
list<T>::iterator cur, p;
cur = aList.begin();
while (cur != aList.end()){
curValue = *cur;
//空白行 1
while (p != aList.end()){
if (*p == curValue){
//空白行 2
}
else{
p++;
}
}
}
}
B
- 以下程序输出结果为( )
A.4 3 2 1 0 5 6 7 8 9
B.0 1 2 3 4 9 8 7 6 5
C.5 6 7 8 9 0 1 2 3 4
D.5 6 7 8 9 4 3 2 1 0
cpp
int main(){
int ar[] = { 0,1, 2, 3, 4, 5, 6, 7, 8, 9 };
int n = sizeof(ar) / sizeof(int);
list<int> mylist(ar, ar+n);
list<int>::iterator pos = find(mylist.begin(), mylist.end(), 5);
reverse(mylist.begin(), pos);
reverse(pos, mylist.end());
list<int>::const_reverse_iterator crit = mylist.crbegin();
while(crit != mylist.crend()){
cout<<*crit<<" ";
++crit;
}
cout<<endl;
}
C
- 下面程序的输出结果正确的是( )
A.1 2 3 4 5 6 7 8 9
B. 1 2 3 4 6 7 8 9
C.程序运行崩溃
D.1 2 3 4 0 5 6 7 8 9
cpp
int main(){
int array[] = { 1, 2, 3, 4, 0, 5, 6, 7, 8, 9 };
int n = sizeof(array) / sizeof(int);
list<int> mylist(array, array+n);
auto it = mylist.begin();
while (it != mylist.end()){
if(* it != 0)
cout<<* it<<" ";
else
it = mylist.erase(it);
++it;
}
return 0;
}
B