【C++修炼之路】11. list类
创始人
2024-04-20 07:29:46
0

在这里插入图片描述
每一个不曾起舞的日子都是对生命的辜负

list

  • 本节目标
  • 1. list的介绍及使用
    • 1.1 list的介绍
    • 1.2 list的使用
    • 1.3 模拟list节点的结构
    • 1.4 list类的封装
  • 补充:list的自带排序函数
    • 1. sort
    • 2. unique
  • 2. list的迭代器
    • 2.1 list的迭代器失效问题
    • 2.2 迭代器的分类
    • 2.3 迭代器的模拟实现
      • 2.3.1 普通迭代器
      • 2.3.2 const迭代器(难)
      • 2.3.3 模拟迭代器完整版
  • 3. list模拟实现完整代码
    • 3.1 list.h
    • 3.2 Test.cpp
  • 4. 模拟实现的注意事项
    • 4.1 深拷贝的问题
    • 4.2 类名和类型的问题
  • 5. vector与list的优缺点
    • 1. vector优点与缺点
    • 2. list优点与缺点

本节目标

本节将会讲述list的使用,以及list的底层实现,对于底层实现,list的底层就是我们之前学过数据结构中的链表,因此这就与vector的结构相差很大,迭代器的部分也与vector有很大差别,迭代器的相关内容极为重要,也是本节的难点;此外也会有vector与list两者之间进行对比,下面开始正文。

注:本文的模拟实现会贯穿全文而不是集中在某一小节

1. list的介绍及使用

1.1 list的介绍

对于list类来说,其中的大多数函数功能都与string、vector相同,大部分实用的成员函数也是非常相似,因此我们在这里也是简单明了的查看文档:list文档 。通过文档我们可以更加直观的了解成员函数的功能。

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

image-20221207205517254

通过这个结构我们已经知道,list的迭代器实现起来将会困难一些,原因是其节点的地址是不连续的,其次也会存在对节点的类型

1.2 list的使用

对于使用这里,与vector调用方式是一样的,无论是push_back,还是find等,查上面的文档就好了。而对于list的重要的部分,是模拟实现中的问题。

1.3 模拟list节点的结构

对于一个双向带头循环链表的节点的结构,为了存入prev、next指针以及数据,我们需要一个类来封装这几个变量,这个类在C语言中也可以称为结构体,但实际上又有所区别,在这个节点类中,虽然没有成员函数,但是却有着公有和私有的区别,因此为了便于实现,我们采用struct公有的类封装;此外类也需要实现构造函数。由于我们不知道存入什么变量,因此会用到模板。

template
struct list_node
{list_node* _next;//这里不写也没有错误list_node* _prev;T _data;list_node(const T& x)//构造函数:_next(nullptr),_prev(nullptr),_data(x)
}

1.4 list类的封装

对于list类来讲,私有的成员变量是指向头结点的指针。

template
class list
{typedef list_node node; //将节点重命名一下
public://成员函数://……
private:node* _head;//指向头结点的指针变量
}

框架搭起来之后,之后的模拟实现将会逐渐扩充里面的内容。

补充:list的自带排序函数

1. sort

之前的vector类,可以用到算法库的排序sort,但当查看list的文档,发现其自带一个排序函数:image-20221208213708529

由于list是链表结构,而算法库中的排序的底层是快速排序,不能实现链表的排序,因此设计了一个list自带的排序,通过前面的学习,list排序有纯粹的暴力插入排序,也有更好的归并排序,而这个list的sort的底层就是归并排序。

然而,对于实际来说,通过将list的值赋值给vector之后调用算法库sort的时间还是要比只接归并排序快的,因此在这里排序还是推荐拷贝到vector后调用算法库的排序。

那就验证一下看看哪个更快:(代码放这里,自己也可以动手试一下)

void test_op()
{srand(time(0));const int N = 1000000;vector v;v.reserve(N);list lt1;list lt2;for (int i = 0; i < N; ++i){auto e = rand();//v.push_back(e);lt1.push_back(e);lt2.push_back(e);}// 拷贝到vector排序,排完以后再拷贝回来int begin1 = clock();for (auto e : lt1){v.push_back(e);}sort(v.begin(), v.end());size_t i = 0;for (auto& e : lt1){e = v[i++];}int end1 = clock();int begin2 = clock();// sort(lt.begin(), lt.end());lt2.sort();int end2 = clock();printf("vector sort:%d\n", end1 - begin1);printf("list sort:%d\n", end2 - begin2);
}

debug下:image-20221208214916818

但我们发现没有明显区别,别急,看看release下的:

release下:image-20221208215013880

很明显,vector比list的快很多。这里补充一下,release是优化后的代码运行的时间,当我们编译完代码打包好,代码也会以release的版本供给客户所使用。

2. unique

sort实现了之后,我们也来了解一下另一个特有的成员函数:unique:去重函数,由于这has必须建立在有序的基础上才能使用,因此我们也可以想象得到底层是如何实现的,毕竟刚学C语言的时候经常会自己写出那种的代码,那么接下来就看一下怎么使用吧:

1. 本来无序:image-20221208220915879

2. 有序之后:image-20221208221016585

可见,调用unique的前提是数据必须有序。

2. list的迭代器

2.1 list的迭代器失效问题

在上一节的vector中,我们讲述了迭代器失效的问题,vector的insert和erase都会导致迭代器失效,insert是 。因为挪动空间导致,erase是因为删掉之后不存在这个元素导致。而对于list来讲,list的insert是不会失效的,因为list的insert并没有移动空间,而是直接插入节点,而erase由于删除的原因也会造成list中的迭代器失效。

因此这里只有erase会造成list迭代器失效。而解决这一问题就可以根据返回值来改变失效后的迭代器。

image-20221207213452433

image-20221207213510372

这样,删除之后也不会造成失效了。

2.2 迭代器的分类

1、单向迭代器:只能++,不能–。例如单链表,哈希表;

2、双向迭代器:既能++也能–。例如双向链表;

3、随机访问迭代器:能+±-,也能+和-。例如vector和string。

迭代器是内嵌类型(内部类或定义在类里)

2.3 迭代器的模拟实现

对于list结构,已经提到过是双向带头循环链表,而对于迭代器的begin和end又是左闭右开区间,因此模拟实现时begin在_head->next的位置,end在_head的位置,因为最后一个节点的下一个就是_head。

对于list的迭代器,与vector有很大的不同,因为每一个节点都是通过指针的方式链接的,因此迭代器的++和–并不是单一的地址++与–,而是以解引用的方式进行,也就是说需要多一个运算符重载,那么既然又多了一个需求,对于迭代器我们也就有必要也封装成类供给list类使用。

迭代器的类:(这个可以先不看)

template
struct __list_iterator
{typedef list_node node;typedef __list_iterator Self;node* _pnode;__list_iterator(node* p)//构造:_pnode(p){}
}

这里我们采用了三个模板,为的就是处理下面的const。

2.3.1 普通迭代器

实现普通迭代器,我们用不到上面的三个模板参数,只需要一个T就够了,因此在这里为了更好的理解,我们不看上面迭代器的类,在这里自己造一个。

template
struct __list_iterator
{typedef list_node node;node* _pnode;//改变的就是这个唯一成员变量__list_iterator(node* p)//拷贝构造:_pnode(p){}T& operator*(){return _pnode->_data;//解引用的运算符重载}__list_iterator& operator++()//前置{_pnode = _pnode->_next;return *this;}__list_iterator& operator++(int)//后置{__list_iterator it(*this);//拷贝构造_pnode = _pnode->_next;return it;}__list_iterator& operator--()//前置,后置和上面的一样{_pnode = _pnode->_prev;return *this;}bool operator!=(const __list_iterator& it){return _pnode != it._pnode;}
}

因此我们可以看到普通的迭代器除了封装之外没有什么难点。

2.3.2 const迭代器(难)

那么对于const迭代器来说,vector是在解引用的时候直接加上const就可以了,但list明显不能像vector那样直截了当,这也是这一节最难以实现的部分。

image-20221209141519800

其与vector对比,对于const的list迭代器来说,因为本身是以类的方式进行,而const实际上就代表迭代器指向的内容不可改变,也就是说只需要改变普通迭代器的解引用运算符重载就可以了,因此我们实现const有两种思路可行,一是再写一个类,只将普通迭代器运算符重载的函数换成const类型,也就是这样:多加了一个const类型的迭代器的类。

template
struct __list_const_iterator
{typedef list_node node;node* _pnode;//改变的就是这个唯一成员变量__list_const_iterator(node* p)//拷贝构造:_pnode(p){}const T& operator*()//只对这个进行修改{return _pnode->_data;//解引用的运算符重载}__list_const_iterator& operator++()//前置{_pnode = _pnode->_next;return *this;}__list_const_iterator& operator++(int)//后置{__list_iterator it(*this);//拷贝构造_pnode = _pnode->_next;return it;}__list_const_iterator& operator--()//前置,后置和上面的一样{_pnode = _pnode->_prev;return *this;}bool operator!=(const __list_const_iterator& it){return _pnode != it._pnode;}
}

但我们发现这种方式会产生很多的代码冗余,因为除了解引用运算符重载,别的都没有变化,因此大佬在设计这里的时候用到了多个模板参数,通过传入的类型不同,就将这个迭代器的类转化成const的和非const的:

//typedef __list_iterator iterator;
//typedef __list_iterator const_iterator; 
template//模仿大佬在STL中的写法,能避免副本造成的代码冗余
struct __list_iterator//封装成类的迭代器
{typedef list_node node;typedef __list_iterator Self;node* _pnode;__list_iterator(node* p):_pnode(p){}// iterator it// *it// ++itRef operator*()//const迭代器看这个{return _pnode->_data;}那么能不能重载一个T&? 像下面: const iterator*it++it 不能调++了,因为const不能调用非const,那这个时候可不可以将++的运算符继续重载? 不能,++是写的函数,不可能把他变成const, 因此像下面这样重载,可以解引用,但是不能++, 所以换思路,可以将迭代器这整个类再写一个const版本的出来,就是会产生代码冗余//const T& operator*() const//{//	return _pnode->_data;//}Self& operator++()//前置{_pnode = _pnode->_next;return *this;}Self& operator--()//前置{_pnode = _pnode->_prev;return *this;}bool operator!=(const Self& it){return _pnode != it._pnode;}
};

即这样的一个迭代器类通过在list类中传入对应的类型就可以实现const和非const。

总结一下实现const的迭代器的两种方法:

  1. 重新写一个类,不过里面只有一个函数是不一样的,会造成代码冗余
  2. 利用模板参数!将一个类通过传入的类型不同能够自动演化出不同的类。

2.3.3 模拟迭代器完整版

如果对于list,这个T是一个类,并且有两个成员变量,翻入list中是如何迭代的呢?

struct Pos
{int _row;int _col;Pos(int row=0, int col=0):_row(row),_col(col){}
};

我们可以在通过迭代器进行这样的访问:

list lt(1,2);
list::iterator it = lt.begin();
while(it != lt.end())
{cout <<(*it)._row<<":"<<(*it)._col<

但事实上,我们在C/C++中有另一种方式:->即直接it->_row;下面的Ptr就是。

因此我们也需要考虑如何将这样的运算符也重载进去,只需要在类中再加一个模板参数,到现在三个模板参数,已经齐了。这也是我们最终的迭代器的类:

//typedef __list_iterator iterator;
//typedef __list_iterator const_iterator; 
template//模仿大佬在STL中的写法,能避免副本造成的代码冗余
struct __list_iterator//封装成类的迭代器
{typedef list_node node;typedef __list_iterator Self;node* _pnode;__list_iterator(node* p):_pnode(p){}// iterator it// *it// ++itRef operator*()//const迭代器看这个{return _pnode->_data;}Ptr operator->()//const迭代器看这个{return &_pnode->_data;}Self& operator++()//前置{_pnode = _pnode->_next;return *this;}Self& operator++(int)//后置  //自己加的,这里写成T对于自己加的Pos会报错{//Self it = *this;Self it(*this);_pnode = _pnode->_next;return it;}Self& operator--()//前置{_pnode = _pnode->_prev;return *this;}bool operator!=(const Self& it) const{return _pnode != it._pnode;}bool operator==(const Self& it) const{return _pnode == it._pnode;}
};

但意的是,->返回的值仍然是一个指针,那我们调用时确是一个函数:那返回时不应该是it->->_row吗?image-20221209152519696

这是因为编译器做了一定程度的优化,将两个->变成了一个,因此,我们直接写成两个->也是对的。image-20221209152756539

这样就完成了我们整个迭代器类的封装。

3. list模拟实现完整代码

下面就看看整体的代码,个人感觉只有这样整体观察才能理解更多·。因为一些比如push_back、push_front、pop_back、pop_front我们在实现string、vector的时候已经描述过,都是以复用的形式出现的,整体看才会明白。

3.1 list.h

#pragma oncenamespace cfy
{templatestruct list_node{list_node* _next;//不加也没错,但是写上好一些list_node* _prev;T _data;list_node(const T& x)//构造:_next(nullptr), _prev(nullptr), _data(x){}};//typedef __list_iterator iterator;//typedef __list_iterator const_iterator; template//模仿大佬在STL中的写法,能避免副本造成的代码冗余struct __list_iterator//封装成类的迭代器{typedef list_node node;typedef __list_iterator Self;node* _pnode;__list_iterator(node* p):_pnode(p){}// iterator it// *it// ++itRef operator*()//const迭代器看这个{return _pnode->_data;}Ptr operator->()//const迭代器看这个{return &_pnode->_data;}那么能不能重载一个T&? 像下面: const iterator*it++it 不能调++了,因为const不能调用非const,那这个时候可不可以将++的运算符继续重载? 不能,++是写的函数,不可能把他变成const, 因此像下面这样重载,可以解引用,但是不能++, 所以换思路,可以将迭代器这整个类再写一个const版本的出来,就是会产生代码冗余//const T& operator*() const//{//	return _pnode->_data;//}Self& operator++()//前置{_pnode = _pnode->_next;return *this;}Self& operator++(int)//后置  //自己加的,这里写成T对于自己加的Pos会报错{//Self it = *this;Self it(*this);_pnode = _pnode->_next;return it;}Self& operator--()//前置{_pnode = _pnode->_prev;return *this;}bool operator!=(const Self& it) const{return _pnode != it._pnode;}bool operator==(const Self& it) const{return _pnode == it._pnode;}};templateclass list{typedef list_node node;public:typedef __list_iterator iterator;typedef __list_iterator const_iterator; //虽然是一个类模板,但是T不同就不是一个类const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}iterator begin(){return iterator(_head->_next);}iterator end(){//iterator it(_head);//return it;//直接利用匿名对象更为便捷return iterator(_head);}void empty_initialize(){_head = new node(T());_head->_next = _head;_head->_prev = _head;_size = 0;//实现计算动态大小}list() // 不是list的原因:构造函数和类名相同,和类型list不同{empty_initialize();}~list(){clear();delete _head;_head = nullptr;}lt2(lt1)  传统写法//list(const list& lt)//实现const迭代器之后就没有错误了//{//	empty_initialize();//	for (const auto& e : lt)//	{//		push_back(e);//	}//}//list& operator=(const list& lt)//实现const迭代器之后就没有错误了//{//	if (this != <)//	{//		clear();//		for (const auto& e : lt)//		{//			push_back(e);//		}//	}//	return *this;//}void swap(list& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}template list(InputIterator first, InputIterator last)//迭代器区间构造,和vector的对应{empty_initialize();while (first != last){push_back(*first);++first; //first++;}}// 拷贝构造的现代写法list(const list& lt)//list(const list& lt) 官方库是这样写的,也可以,语法设计问题,但是不建议自己这样写{empty_initialize();//初始化头结点,防止交换后tmp野指针不能正常的调用析构 list tmp(lt.begin(), lt.end());swap(tmp);}//lt3 = ltlist& operator=(list lt)//不能加引用,lt是调用拷贝构造生成的//list& operator=(list lt) 官方库是这样写的,也可以{swap(lt);return *this;}size_t size() const//增加一个计数的成员,以空间换时间{return _size;}bool empty(){return _size == 0;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}_size = 0;}void push_back(const T& x){/*node* newnode = new node(x);node* tail = _head->_prev;tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;*/insert(end(), x);}void push_front(const T& x){insert(begin(), x);}void pop_front(){erase(begin());}void pop_back(){erase(--end());}iterator insert(iterator pos, const T& x){node* newnode = new node(x);node* cur = pos._pnode;node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;cur->_prev = newnode;newnode->_next = cur;++_size;return iterator(pos);}iterator erase(iterator pos){assert(pos != end());node* prev = pos._pnode->_prev;node* next = pos._pnode->_next;prev->_next = next;next->_prev = prev;delete pos._pnode;--_size;return iterator(next);}private:node* _head;size_t _size;};void test_list1(){list lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.insert(++lt.begin(), 10);//iterator 1、内嵌类型 2、像指针一样list::iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";++it;}cout << endl;for (auto e : lt){cout << e << " ";}cout << endl;}void test_list2(){list lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_front(10);lt.push_front(20);lt.push_front(30);lt.pop_back();lt.pop_back();lt.pop_back();lt.pop_front();lt.pop_front();for (auto e : lt){cout << e << " ";}cout << endl;}void test_list3(){list lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);for (auto e : lt){cout << e << " ";}cout << endl;list lt1(lt);//默认是浅拷贝,指向同一块lt.pop_back();lt.pop_back();for (auto e : lt1){cout << e << " ";}cout << endl;list lt3 = lt1;for (auto e : lt3){cout << e << " ";}}//const T* p1//T* const p2;//const迭代器类似p1的行为,保护指向的对象不被修改,迭代器本身可以修改//void print_list(const list& lt)//{//	//const list::iterator cit = lt.begin();//	//不符合const迭代器的行为,因为他保护迭代器本省不能修改,那么我们也就不能用++迭代器//}//void print_list(const list& lt)//{//	list::const_iterator it = lt.begin(); //重载了,找的另一个类的迭代器//	while (it != lt.end())//	{//		(*it) += 2;//不能写//		cout << *it << " ";//		++it;//	}//	cout << endl;//}void test_list4(){list lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);list::iterator it = lt.begin();while (it != lt.end()){(*it) += 2;cout << *it << " ";++it;//it++;}cout << endl;//print_list(lt);list lt1(lt);for (auto e : lt1){cout << e << " ";}cout << endl;list lt2 = lt;for (auto e : lt2){cout << e << " ";}cout << endl;cout << lt.size() << endl;}struct Pos{int _row;int _col;Pos(int row=0, int col=0):_row(row),_col(col){}};void print_list(const list& lt){list::const_iterator it = lt.begin(); //重载了,找的另一个类的迭代器while (it != lt.end()){//it->_row++;  增加第三个模板参数cout << it->_row << ":" << it->_col << endl;++it;}cout << endl;}void test_list5(){list lt;Pos p1(1, 1);lt.push_back(p1);lt.push_back(p1);lt.push_back(p1);lt.push_back(Pos(2, 3));lt.push_back(Pos(2, 3));list::iterator it = lt.begin();while (it != lt.end()){//cout << (*it)._row << ":" << (*it)._col << endl;cout << it->_row << ":" << it->_col << endl;//编译器为了可读性,做了特殊处理,省略了一个->,//那么省略之前应该是it->->_row//cout << it.operator->()->_col << ":" << it.operator->()->_row << endl;++it;}cout << endl;print_list(lt);}
}//实现const迭代器的两种方法:
// 1. 重新写一个类,里面只有一个函数是不一样的,目的为了const
// 2. 利用模板!// 类名和类型的问题:
// 普通类  类名 等价于 类型
// 类模板  类名 不等价于 类型
// 如: list模板 类名list 类型list
// 类模板里面可以用类名代表类型,但是不建议那么用//vector和list对比:
//vector优点:
//1、下标随机访问
//2、尾插尾删效率高(但不明显)
//3、CPU高速缓存命中率高:因为物理空间连续
//
//vector缺点:
//1、前面部分插入删除效率低
//2、扩容有消耗,还存在一定空间浪费,扩容开多了浪费,开少了频繁扩容
//
//list优点:
//1、按需申请释放,无需扩容
//2、任意位置插入删除O(1),前提是已经知道这个位置了,查找是O(n)
//
//list缺点:
//1、不支持下标的随机访问
//2、CPU高速缓存命中率低
//
//
//总结:vector和list不是敌对关系,而是互补配合//总结迭代器失效问题:
//vector -> insert/erase
//list   -> erase
//
//string有没有迭代器失效?->insert/erase失效,和vector类似
//但一般不关注string的迭代器失效,因为string insert/erase常用接口函数都是下标支持,迭代器支持用的很少

3.2 Test.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
#include
#include
#include
using namespace std;
#include"list.h"void test_list1()
{list lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);list::iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";it++;}cout << endl;lt.push_front(10);lt.push_front(20);lt.push_front(30);lt.push_front(40);for (auto e : lt){cout << e << " ";}cout << endl;lt.pop_back();lt.pop_back();lt.pop_front();lt.pop_front();for (auto e : lt){cout << e << " ";}cout << endl;
}void test_list2()
{list lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);auto pos = find(lt.begin(), lt.end(), 3);if (pos != lt.end()){//insert之后pos是否失效呢?不失效,因为链表不存在挪动空间和扩容的问题lt.insert(pos, 30);}cout << *pos << endl;//(*pos)++;for (auto e : lt){cout << e << " ";}cout << endl;lt.erase(pos);//erase之后,pos就失效了,因为空间都被干掉了//cout << *pos << endl;for (auto e : lt){cout << e << " ";}cout << endl;}//Operations:
void test_list3()
{list lt;lt.push_back(1);lt.push_back(2);lt.push_back(5);lt.push_back(3);lt.push_back(4);lt.push_back(4);lt.push_back(4);lt.push_back(5);lt.push_back(6);lt.push_back(7);lt.push_back(6);for (auto e : lt){cout << e << " ";}cout << endl;lt.remove(3);lt.remove(30);for (auto e : lt){cout << e << " ";}cout << endl;lt.sort();//迭代器·功能分类//1、单向 ++ //2、双向 ++ -- list//3、随机 + -for (auto e : lt){cout << e << " ";}cout << endl;//去重:建立在有序的基础上lt.unique();for (auto e : lt){cout << e << " ";}cout << endl;}void test_op()
{srand(time(0));const int N = 10000000;vector v;v.reserve(N);list lt1;list lt2;for (int i = 0; i < N; ++i){auto e = rand();//v.push_back(e);lt1.push_back(e);lt2.push_back(e);}// 拷贝到vector排序,排完以后再拷贝回来int begin1 = clock();for (auto e : lt1){v.push_back(e);}sort(v.begin(), v.end());size_t i = 0;for (auto& e : lt1){e = v[i++];}int end1 = clock();int begin2 = clock();// sort(lt.begin(), lt.end());lt2.sort();int end2 = clock();printf("vector sort:%d\n", end1 - begin1);printf("list sort:%d\n", end2 - begin2);
}int main()
{//test_op();cfy::test_list5();return 0;
}

下面也是上述代码实现的链接。

C++/list模拟实现最终版 · 蓝桉/CPP - 码云 - 开源中国 (gitee.com)

4. 模拟实现的注意事项

4.1 深拷贝的问题

对于vector,我们知道有深浅拷贝的问题,即浅拷贝会造成析构两次导致出现错误,但我们此次的list实现方式,则不存在这种问题:

image-20221209161511165

4.2 类名和类型的问题

普通类: 类名等价于类型

类模板: 类名不等于类型

举例:list模板类名为list,但是实际上的类型是list,这两个在具体函数返回值还是有区别的,但是在类模板里面可以直接用类名来代替类型,不过不建议这么用,因为对其他人不好理解。举个例子看看。

例1:image-20221209162329837

就是我们节点类这里,指针直接不写也是可以的。

例2: 这个就看代码吧

// 赋值运算符重载
list& operator=(list lt)
{swap(lt);return *this;
}

也就是说这个在类中也可以变成这样,这也是正确的:

// 赋值运算符重载写法2(赋值运算符重载都可以这么干)
list& operator=(list lt)
{swap(lt);return *this;
}

5. vector与list的优缺点

1. vector优点与缺点

优点:

  • 支持下标随机访问。
  • 尾删尾插效率高(但不明显)。
  • CPU高速缓存命中率高:因为物理空间连续。

缺点:

  • 前面部分插入删除效率低。
  • 扩容有消耗,还存在一定的空间浪费,扩容开多了浪费,开少了浪费空间。

2. list优点与缺点

优点:

  • 按需申请释放,无需扩容。
  • 任意位置插入删除是O(1),前提是已经知道这个位置了,查找是O(n)。

缺点:

  • 不支持下标的随机访问
  • CPU高速缓存命中率低

因此vector与list不是相互替代的关系,而是互补配合。

相关内容

热门资讯

微信安卓系统转苹果系统,轻松实... 你有没有想过,从微信安卓系统转到苹果系统,这中间的转换过程,就像是一场说走就走的旅行,充满了未知和惊...
如何刷安卓8.0系统,安卓8.... 你有没有想过,让你的安卓手机升级到最新的8.0系统,让它焕发出全新的活力呢?别急,今天我就来给你详细...
安卓系统里查看路由,安卓系统下... 你是不是也和我一样,对家里的无线网络充满了好奇?想知道安卓手机里怎么查看路由器信息?那就跟着我一起探...
手机出现安卓系统信号,手机信号... 你有没有发现,最近你的安卓手机信号好像变得特别不稳定呢?是不是觉得有时候信号满格,却还是接不到电话,...
创维安卓系统怎么安装,享受智能... 你家的创维电视是不是最近有点儿不给力,想要给它来个升级,让它焕发新生呢?那就得给它装个安卓系统啦!别...
中兴刷原生安卓系统,原生安卓系... 亲爱的读者们,你是否厌倦了那些千篇一律的安卓系统,想要给你的手机来点新鲜感?今天,就让我带你一起探索...
云系统与安卓系统软件,构建智能... 你有没有想过,你的手机里那些神奇的软件,其实都是靠云系统和安卓系统软件的默契配合才变得如此强大呢?想...
如何禁止安卓系统联网,全方位操... 你有没有想过,你的安卓手机其实是个小宇宙,里面藏着无数的秘密和信息?但是,你知道吗?有时候,这些信息...
a安卓系统不兼容,揭秘a设备的... 最近是不是发现你的安卓手机有些不对劲?比如,某个APP突然罢工了,再比如,你下载了一个新游戏,结果发...
安卓系统刷固件教程,解锁设备潜... 你有没有想过,你的安卓手机其实就像一个隐藏着无限可能的宝藏呢?没错,就是那个你每天不离手的宝贝。今天...
电脑系统安卓界面,功能与美学的... 你有没有发现,现在手机和电脑的界面越来越像了呢?没错,就是那个我们每天都要打交道的好伙伴——安卓界面...
吃鸡王座安卓系统,登顶吃鸡巅峰 你有没有想过,在手机游戏中,谁才是真正的“吃鸡王座”呢?今天,就让我带你一探究竟,看看安卓系统上的那...
安卓点名系统下载,安卓点名系统... 你有没有想过,在繁忙的学习生活中,有没有一种神奇的工具,能让你轻松管理课堂纪律,还能让点名变得如此有...
手机安装通用安卓系统,引领智能... 你有没有想过,为什么你的手机可以安装那么多好玩的应用?秘密就在于它搭载了通用安卓系统!想象一个系统就...
安卓系统仿真器,功能与操作指南 你有没有想过,在电脑上也能玩安卓游戏?没错,这就是安卓系统仿真器的神奇之处!想象你坐在电脑前,手握鼠...
安卓系统可以刷街机,畅享虚拟游... 你知道吗?现在用安卓系统刷街机,简直就像变魔术一样神奇!没错,就是那种让你仿佛穿越回童年,手握游戏杆...
安卓系统画画软件画笔,绘制无限... 你有没有发现,手机里的画画软件越来越丰富啦?尤其是安卓系统上的那些,简直让人眼花缭乱。今天,就让我带...
安卓系统垃圾和缓存,提升使用体... 手机里的安卓系统是不是越来越慢了?是不是觉得打开一个应用都要等半天?别急,今天就来跟你聊聊安卓系统里...
安卓系统图片转入苹果,轻松实现... 你是不是也有过这样的烦恼?手机里存了好多珍贵的照片,突然想换手机,却发现安卓系统的照片怎么也弄不到苹...
华为matebooke装安卓系... 你有没有想过,你的华为MateBook也能装上安卓系统呢?没错,就是那个我们平时手机上用的安卓系统!...