c++-list

文章目录

  • 前言
  • 一、list介绍及使用
    • 1、list介绍
    • 2、list使用
      • 2.1 list构造函数的使用
      • 2.2 list iterator的使用
      • 2.3 list capacity的使用
      • 2.4 list modifiers的使用
      • 2.5 list使用算法库中的find模板生成find方法
      • 2.6 list中的sort方法
  • 二、list模拟实现
    • 1、查看list源码的大致实现思路
    • 2、模拟实现list


前言


一、list介绍及使用

1、list介绍

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

2、list使用

2.1 list构造函数的使用

在这里插入图片描述

void Test01()
{//使用explicit list (const allocator_type& alloc = allocator_type());构造空的lt1list<int> lt1;for (auto e : lt1){cout << e << " ";}cout << endl;//使用explicit list (size_type n, const value_type& val = value_type(),const allocator_type& alloc = allocator_type());//构造lt2,并且在lt2中放10个5.list<int> lt2(10, 5);for (auto e : lt2){cout << e << " ";}cout << endl;//template <class InputIterator>//list(InputIterator first, InputIterator last,const allocator_type & alloc = allocator_type());//使用上面的模板生成的迭代器区间构造函数,来构造lt3。list<int> lt3(lt2.begin(), lt2.end()); for (auto e : lt3){cout << e << " ";}cout << endl;//使用上面的模板生成的迭代器区间构造函数,以数组为迭代器区间构造lt4int array[] = { 34,32,9,23 };list<int> lt4(array, array + sizeof(array) / sizeof(int));for (auto e : lt4){cout << e << " ";}cout << endl;//使用list (const list& x);拷贝构造函数来构造lt4list<int> lt5(lt3);for (auto e : lt5){cout << e << " ";}cout << endl;//使用迭代器来遍历lt4中的元素list<int>::iterator it = lt4.begin();while (it != lt4.end()){cout << *it << " ";++it;}cout << endl;}

在这里插入图片描述

2.2 list iterator的使用

在这里插入图片描述

void Test02()
{int array[] = { 1,2,3,4,5,6,7,8,9 };list<int> lt1(array, array + sizeof(array) / sizeof(array[0]));//使用正向迭代器正向遍历list中的元素list<int>::iterator it = lt1.begin();while (it != lt1.end()){//可以通过迭代器改变元素的值(*it) = (*it) * 2;cout << *it << " ";++it;}cout << endl;//使用反向迭代器逆向遍历list中的元素list<int>::reverse_iterator rit = lt1.rbegin();while (rit != lt1.rend()){//可以通过迭代器改变元素的值(*rit) = (*rit) * 2;cout << *rit << " ";//对rit++,迭代器向前移动++rit;}cout << endl;//使用const修饰的正向迭代器正向遍历list中的元素const list<int> lt2(array, array + sizeof(array) / sizeof(array[0]));list<int>::const_iterator cit = lt2.cbegin();while (cit != lt2.cend()){//不可以通过迭代器改变元素的值//(*cit) = (*cit) * 2;cout << *cit << " ";++cit;}cout << endl;//使用const修饰的反向迭代器逆向遍历list中的元素list<int>::const_reverse_iterator crit = lt2.crbegin();while (crit != lt2.crend()){//不可以通过迭代器改变元素的值//(*crit) = (*crit) * 2;cout << *crit << " ";//对rit++,迭代器向前移动++crit;}cout << endl;
}

在这里插入图片描述

2.3 list capacity的使用

在这里插入图片描述

void Test03()
{int array[] = { 1,2,3,4,5,6,7,8,9 };list<int> lt1(array, array + sizeof(array) / sizeof(array[0]));list<int> lt2;cout << lt1.empty() << endl;cout << lt2.empty() << endl;cout << lt1.size() << endl;cout << lt2.size() << endl;cout << lt1.max_size() << endl;cout << lt2.max_size() << endl;}

在这里插入图片描述

2.4 list modifiers的使用

在这里插入图片描述
list插入和删除

void Test04()
{int array[] = { 1,2,3,4,5,6,7,8,9 };list<int> lt1(array, array + sizeof(array) / sizeof(array[0]));lt1.push_front(0);lt1.push_back(10);for (auto e : lt1){cout << e << " ";}cout << endl;lt1.pop_front();lt1.pop_back();for (auto e : lt1){cout << e << " ";}cout << endl;
}

在这里插入图片描述
insert /erase

void Test05()
{int array[] = { 1,2,3,4,5,6,7,8,9 };list<int> lt1(array, array + sizeof(array) / sizeof(array[0]));// 获取链表中的第二个结点list<int>::iterator pos = ++lt1.begin();cout << *pos << endl;//在pos前插入值为4的元素lt1.insert(pos, 4);for (auto e : lt1){cout << e << " ";}cout << endl;//在pos前插入5个值为6的元素lt1.insert(pos, 5, 6);for (auto e : lt1){cout << e << " ";}cout << endl;//在pos前插入[v.begin(), v.end())区间的元素vector<int> v1(3,5);lt1.insert(pos, v1.begin(), v1.end());for (auto e : lt1){cout << e << " ";}cout << endl;//可以看到上面的插入中,都是在元素2之前插入元素,说明在list中,insert之后迭代器没有失效。// 删除pos位置上的元素lt1.erase(pos);for (auto e : lt1){cout << e << " ";}cout << endl;//删除list中的[begin, end)区间中的元素,及删除list中的所有元素lt1.erase(lt1.begin(), lt1.end());for (auto e : lt1){cout << e << " ";}cout << endl;
}

在这里插入图片描述

resize/swap/clear

void Test06()
{int array[] = { 1,2,3,4,5,6,7,8,9 };list<int> lt1(array, array + sizeof(array) / sizeof(array[0]));list<int> lt2;//将lt2中包含5个3,lt2.resize(5, 3);//如果resize()中的第一个参数小于lt2的size时,则会将lt2的元素减少为n个,即将后面的元素都删除for (auto e : lt2){cout << e << " ";}cout << endl;lt2.resize(2, 3);for (auto e : lt2){cout << e << " ";}cout << endl;//交换lt1和lt2中的元素lt1.swap(lt2);for (auto e : lt1){cout << e << " ";}cout << endl;for (auto e : lt2){cout << e << " ";}cout << endl;//将lt2中的元素清空lt2.clear();cout << lt2.size() << endl;
}

在这里插入图片描述

2.5 list使用算法库中的find模板生成find方法

我们可以看到list库中并没有提供find方法,所以当list想要使用find方法查找元素时,就需要使用算法库中的find模板生成find方法。
在这里插入图片描述

void Test07()
{int array[] = { 1,2,3,4,5,6,7,8,9 };list<int> lt1(array, array + sizeof(array) / sizeof(array[0]));//find返回的是指向元素的迭代器。如果没有查找到元素,find返回lt1.end()迭代器。list<int>::iterator pos = find(lt1.begin(), lt1.end(), 3);if (pos != lt1.end()){//在元素3前面插入30.lt1.insert(pos, 30);}for (auto e : lt1){cout << e << " ";}cout << endl;}

在这里插入图片描述

2.6 list中的sort方法

我们看到算法库中也提供了sort函数模板,但是为什么list库中还要单独实现一个sort函数呢?
这是因为算法库中的sort函数模板中需要传入一个RandomAccessIterator迭代器,即随机迭代器。而我们看到list的迭代器为bidirectional iterator,即双向迭代器,而sort方法需要传入随机迭代器,所以list使用不了。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们看到算法库中的reverse模板中需要一个BidirectionalIterator,即双向迭代器,所以list可以使用reverse函数模板。
在这里插入图片描述
迭代器功能分类:
(1). 单向 ++
(2). 双向 ++ –
(3). 随机 ++ – + -
如果函数模板中要求为随机迭代器,就只能传随机迭代器;如果要求为双向迭代器,则可以传双向迭代器和随机迭代器,因为随机迭代器是特殊的双向迭代器。如果要求为单向迭代器,则可以传单向迭代器、双向迭代器和随机迭代器。

虽然list中提供了sort函数,但是list自己提供的sort函数性能不行,一般都不会使用。
下面我们让list使用自己提供的sort和vector使用算法库提供的sort(快排)进行性能测试。可以看到在Release版本下vector使用算法库提供的sort函数的效率更高一些。

void Test08()
{srand(time(0));const int N = 100000;vector<int> v;v.reserve(N);list<int> lt1;for (int i = 0; i < N; ++i){auto e = rand();v.push_back(e);lt1.push_back(e);}int begin1 = clock();sort(v.begin(), v.end());int end1 = clock();int begin2 = clock();lt1.sort();int end2 = clock();cout << "vector sort: " << end1 - begin1 << endl;cout << "list sort: " << end2 - begin2 << endl;
}

在这里插入图片描述

所以当我们想要将list中的元素进行排序时,可以先将list中的元素都拷贝到vector中,然后vector使用算法库中的sort排序,排完序后再将元素拷贝到list中,这样来提升list排序的性能。可以看到在Release版本下,使用上面的方法来对list中的元素进行排序可以大大提升list排序的性能。

void Test09()
{srand(time(0));const int N = 1000000;vector<int> v;v.reserve(N);list<int> lt1;list<int> lt2;for (int i = 0; i < N; ++i){auto e = rand();lt1.push_back(e);lt2.push_back(e);}//将lt1拷贝到vector中排序int begin1 = clock();for (auto e : lt1){v.push_back(e);}sort(v.begin(), v.end());//排完序后将元素再拷贝到lt1中size_t i = 0;for (auto& e : lt1){e = v[i++];}int end1 = clock();//使用list中提供的sort进行排序int begin2 = clock();lt2.sort();int end2 = clock();cout << "vector sort: " << end1 - begin1 << endl;cout << "list sort: " << end2 - begin2 << endl;
}

在这里插入图片描述

二、list模拟实现

1、查看list源码的大致实现思路

我们知道list的底层实现是一个带头结点的双向循环链表。
在这里插入图片描述
可以看到list的源码中先定义了一个__list_node结构体来表示链表的结点。然后又定义了一个__list_iterator结构体来实现list的迭代器。最后又定义了一个list类来表示list。
在这里插入图片描述
在这里插入图片描述

2、模拟实现list

我们模拟实现list也按照list库中的办法来实现。
所以我们先定义一个struct list_node结构体来表示链表的结点。因为list_node里面的内容都是公开可访问的,所以我们使用struct来定义list_node,而不是使用class来定义list_node。并且因为该链表的结点中的_next和_prev指针域和_data数据域以后可能存任意类型的数据,所以我们使用模板来定义list_node这个结构体。
在这里插入图片描述
接下来我们再来定义list类,因为list类中的一些成员遍历或成员函数是私有的,所以我们使用class来定义list。因为list中以后可能存任意类型的数据,所以我们实现list类时也需要用到模板,直接实现list模板类即可。
我们在list类中定义了一个_head成员变量,这个就是list的头结点,并且我们将头结点的_next指针域和_prev指针域都指向自己,表示该list为空的状态。
在这里插入图片描述

接下来我们实现list类的成员函数push_back,但是在实现这个函数之前我们需要先将list_node结构体的构造函数写好,这样才可以在list中添加新结点时直接将新结点的数据初始化。
在这里插入图片描述
然后再实现push_back函数,因为list是双向循环链表,所以在该链表的尾部插入新结点,其实就是在头结点之前插入新结点。
在这里插入图片描述
我们测试发现push_back方法成功插入新结点。
在这里插入图片描述
在这里插入图片描述
接下来我们再实现list类的迭代器。我们在前面看list源码知道了list的迭代器实现是又创建了一个__list_iterator结构体来实现,所以我们也再创建一个struct __list_iterator结构体用来实现list的迭代器。因为迭代器在使用时需要++向后移动,还需要*来得到数据,所以我们需要实现struct __list_iterator的一些操作符的重载函数。这样当迭代器进行++时,迭代器就会指向下一个结点,当解引用迭代器时,就将迭代器指向的结点的_data返回。判断迭代器是否相等就是判断两个迭代器指向的结点是否为同一个。
在这里插入图片描述

然后我们在list类中将__list_iterator< T >重命名为iterator,然后在begin函数中返回_head->_next结点的指针,因为list为双向循环链表,所以begin就是头结点的下一个结点,而end为有效数据的下一个结点,所以end就指向_head头结点。这时我们就可以使用list的迭代器来遍历list中的元素了,也可以使用范围for来遍历list的元素。
在这里插入图片描述
在这里插入图片描述
在测试代码中我们发现it = lt1.begin()调用了__list_iterator的拷贝构造函数,并且该拷贝构造函数为浅拷贝,但是为什么没有出错呢?是因为在__list_iterator结构体中没有实现析构函数,因为该类只负责提供iterator,而释放的事需要list类中实现。
在这里插入图片描述
当我们实现了struct __list_iterator结构体中的前置++运算符重载函数后,我们继续将后置++运算符重载函数和前置–和后置–等的运算符重载函数也一并实现了。
在这里插入图片描述
上面的迭代器是没有被const修饰的迭代器,当我们在下面的情况中使用迭代器时就会出现错误。这是因为发生了权限放大,list类的成员函数begin中的this没有被const修饰,而lt被const修饰了,所以发生了权限放大。
在这里插入图片描述
所以我们在list类中又写了begin和end函数的const修饰的版本,但是我们知道const修饰的迭代器是不可以改变list中元素的值的,而我们实现的const修饰的迭代器却可以改变list中元素的值,这是因为我们使用const修饰begin和end函数,其实const修饰的是this指针,即相当于iterator begin(const list* this),所以此时this指针指向的list的_head不能改变,但是在begin中并没有改变_head的值,而是将_head和_head->_next当作参数传给了__list_iterator结构体的构造函数然后创建了__list_iterator类型的对象。然后我们看到在__list_iterator中的操作符的重载函数返回的是一个T类型的引用,所以才可以使用it修改_data的值。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

那么如果我们像下面一样使用typedef const iterator const_iterator;来重命名一个const_iterator呢?
这样其实更错误,因为这样定义了const_iterator之后,迭代器就不能++了,因为iterator被const修饰了,即T* const it,it为指针,it的内容不可以修改了,那么it迭代器就不可以向后移动了,而const T* it中,it的值可以修改,但是*it的内容不可以修改。

在这里插入图片描述
所以我们再根据__list_iterator结构体实现一个__list_const_iterator结构体,然后在这个结构体的*操作符重载函数中返回一个const T&的引用当作返回值,这样当使用const_iterator迭代器时就不可以通过迭代器修改list的元素的值了。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们发现上面的__list_iterator和__list_const_iterator结构体中就只有operator*()函数的返回值不同,所以我们可以将这两个结构体合并为一个。我们通过模板参数来实现。我们给__list_iterator结构体的模板中新加一个Ref参数,当迭代器iterator不被const修饰时,就传入T&,当迭代器iterator被const修饰时,就传入const T&。
通过上面的操作我们知道了:
(1). 迭代器要么就是原生指针。
(2). 迭代器要么就是自定义类型对原生指针的封装,模拟指针的行为。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

下面我们自己实现一个AA结构体,然后使用list来存储AA结构体类型的数据,然后我们使用迭代器遍历list中的AA对象,并且使用cout<<来打印,但是因为我们没有在AA结构体中实现<<操作符的重载函数,所以我们没有办法直接打印AA的内容。我们可以通过解引用迭代器,然后使用.来访问AA的成员变量。
在这里插入图片描述
在这里插入图片描述
既然我们知道迭代器返回的其实就是一个指针,那么我们为什么不直接使用->符号来访问AA的成员变量呢,而是要先解引用迭代器,然后再使用.来访问AA的成员变量。这是因为我们只实现了*操作符的重载函数,而没有实现->操作符的重载函数。所以下面我们来实现->的重载函数。
在这里插入图片描述
在这里插入图片描述
在使用->符访问AA的成员变量时,其实应该是两个->,因为第一个->是调用->符的重载函数,第二个->是通过地址访问_a1,但是为了增强可读性,省略了一个。
在这里插入图片描述
我们发现const_iterator迭代器使用->操作符的重载函数可以修改list中元素的值,这是肯定不正确的,所以接下来我们实现const版本的->操作符重载函数,禁止const_iterator迭代器使用->操作符的重载函数修改list中元素的值,所以又要加一个模板参数。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
此时我们就可以看到const_iterator迭代器使用->操作符的重载函数不能修改list中元素的值了。
在这里插入图片描述
下面我们来实现insert和erase函数。
insert为在当前位置的前面插入元素,所以我们可以这样实现。当实现了insert后,头插尾插就可以复用insert函数来实现了。并且我们看到库里面的insert函数返回了一个迭代器,该迭代器指向刚被插入的元素。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
下面我们来实现erase函数。然后我们再复用erase函数来实现头删和尾删。我们看到在库里面的erase函数也返回了一个迭代器,并且该迭代器指向被删除的结点的下一个结点。
我们需要注意的是在erase之后迭代器必定失效,因为pos指向的结点已经被释放了。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
我们在上面相当于写了三个类,这三个类都是使用模板来实现的。而类名加模板参数才是真正的类型,即list< int >才是真正的类型,其中list为类名,int为模板参数。
我们模拟实现的list迭代器创建了一个__list_iterator结构体来实现,我们可以查看VS下的vector的迭代器类型,因为VS下vector的迭代器不是靠原生指针实现的,我们可以看到VS下的vector迭代器的类型更加复杂。
在这里插入图片描述
在这里插入图片描述
下面我们来实现list类的析构函数,析构函数就是将list创建的list结点全部都释放。在实现析构函数之前我们可以先实现clear函数,clear函数也是释放list创建的全部结点,只不过clear函数不会释放头结点。
下面是复用erase函数实现的clear函数,将除了头结点的其它结点全部释放。

在这里插入图片描述
还可以使用下面的方法来实现clear,我们在上面说了erase之后it迭代器会失效,那么为什么在这里it迭代器没有失效呢?
这是因为后置++中,我们使用传值返回,所以erase(it++)其实delete释放的是it的拷贝,即erase中delete的是tmp迭代器。
在这里插入图片描述
在这里插入图片描述
当实现了clear函数后,此时list中就只有头结点了,所以我们实现list类的析构函数是可以复用clear函数,然后再将list的头结点delete就可以了。
在这里插入图片描述
下面我们再来实现迭代器区间构造函数,因为迭代器区间构造函数就不会调用list的默认构造函数将_head头结点进行初始化了,所以我们在迭代器区间构造函数中还要将_head头结点进行初始化,这里我们封装一个empty_init函数用来初始化_head头结点,我们在list的默认构造函数和迭代器区间构造函数中都调用这个empty_init函数。
在这里插入图片描述
下面我们来实现list类的拷贝构造函数,因为有时候会调用拷贝构造函数来构造一个新list对象,所以拷贝构造函数也需要调用empty_init函数来将_head头结点进行初始化。
在这里插入图片描述
如果在拷贝构造函数中没有初始化头结点,则就会出现异常。这是因为lt2对象就是调用拷贝构造函数来创建的,而拷贝构造函数中并没有进行_head头结点的初始化,即根本没有申请头结点的空间,所以头结点的的next和prev都不可访问,因为根本没有申请空间。
在这里插入图片描述
在这里插入图片描述
上面我们使用正常的方法来实现拷贝构造函数,下面我们使用一种比较简便的方法来实现拷贝构造函数。
调用迭代器区间构造函数创建一个临时对象tmp,此时tmp对象就是lt对象的副本,然后我们调用swap函数交换this和tmp的_head头结点,然后this就变为了lt对象的副本了,而此时tmp指向的_head头结点就是this原来的数据,而因为tmp为临时对象,所以出了拷贝构造函数tmp对象就会调用析构函数而销毁了。然后我们测试发现程序又出现了异常,这还是因为拷贝构造函数没有初始化_head头结点。
在这里插入图片描述
在这里插入图片描述
当我们将头结点初始化之后,发现程序就可以正常运行了。
在这里插入图片描述
下面我们再来使用同样的方法实现赋值运算符重载函数,需要注意的是赋值运算符重载函数的形参不能为引用,如果为引用的话就会出现问题。下面我们先写正确的赋值运算符重载函数。
因为赋值运算符重载函数重载函数的形参为传值传参,所以在lt2=lt1时调用赋值运算符重载函数时会先调用拷贝构造函数创建临时对象lt,而lt就是拷贝lt1对象创建的,然后调用swap函数将this指向的对象的头结点和lt对象的头结点进行交换,则此时this指向的对象就和lt1对象的内容一致了,而lt临时对象的内容就是lt2对象原来的内容,因为lt对象为临时对象,所以当出了赋值运算符重载函数时,lt对象就会调用析构函数销毁,即lt对象的析构函数就将lt2对象原来的结点都销毁了。

在这里插入图片描述
在这里插入图片描述
下面我们来看为什么赋值运算符重载函数的形参不可以使用引用。我们可以看到此时lt2=lt1调用赋值运算符重载函数后,发现并不是将lt1对象的内容赋值给lt2对象了,而是交换了lt2对象和lt1对象的内容。这是因为当赋值运算符重载函数的形参为引用时,就不会调用拷贝构造函数来创建临时变量lt,所以此时swap(lt)函数其实就是交换了lt2对象和lt1对象的内容。
在这里插入图片描述
在这里插入图片描述
下面为list类模拟实现的代码。

#pragma once
#include<iostream>
#include<assert.h>
#include<stdlib.h>
#include<vector>
using namespace std;
namespace dong
{template<class T>struct list_node{list_node<T>* _next;list_node<T>* _prev;T _data;//因为T可能为任意类型,所以我们创建一个T类型的匿名对象,然后调用该类型的默认构造参数对该匿名对象进行初始化。list_node(const T& x = T()):_next(nullptr), _prev(nullptr), _data(x){}};template<class T, class Ref, class Ptr>struct __list_iterator{typedef list_node<T> node;typedef __list_iterator<T, Ref, Ptr> self;node* _node;__list_iterator(node* n):_node(n){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}self& operator++(){_node = _node->_next;return *this;}self operator++(int){self tmp(*this);_node = _node->_next;return tmp;}self& operator--(){_node = _node->_prev;return *this;}self operator--(int){self tmp(*this);_node = _node->_prev;return tmp;}bool operator==(const self& s){return _node != s._node;}bool operator!=(const self& s){return _node != s._node;}};/*template<class T>struct __list_const_iterator{typedef list_node<T> node;typedef __list_const_iterator<T> self;node* _node;__list_const_iterator(node* n):_node(n){}const T& operator*(){return _node->_data;}self& operator++(){_node = _node->_next;return *this;}self operator++(int){self tmp(*this);_node = _node->_data;return tmp;}self& operator--(){_node = _node->_prev;return *this;}self operator--(int){self tmp(*this);_node = _node->_prev;return tmp;}bool operator==(const self& s){return _node != s._node;}bool operator!=(const self& s){return _node != s._node;}};*/template<class T>class list{typedef list_node<T> node;public:typedef __list_iterator<T, T&, T*> iterator;typedef __list_iterator<T, const T&, const T*> const_iterator;iterator begin(){/*iterator it(_head->_next);return it;*///上面的代码会先调用__list_iterator的构造函数,然后return返回时又调用__list_iterator的拷贝构造函数。//所以我们返回一个匿名对象,编译器会进行优化,将构造+拷贝构造优化为 -> 构造return iterator(_head->_next);}const_iterator begin() const{return const_iterator(_head->_next);}iterator end(){return iterator(_head);}const_iterator end() const{return const_iterator(_head);}void empty_init(){//初始化头结点_head = new list_node<T>;_head->_next = _head;_head->_prev = _head;}list(){empty_init();}template <class Iterator>list(Iterator first, Iterator last){//先初始化头结点empty_init();while(first != last){push_back(*first);++first;}}//原始方法//list(const list<T>& lt)//{//	empty_init();//	for (auto e : lt)//	{//		push_back(e);//	}//}//下面使用现代方法实现拷贝构造函数void swap(list<T>& tmp){std::swap(_head, tmp._head);}list(const list<T>& lt){empty_init();list<T> tmp(lt.begin(), lt.end());swap(tmp);}~list(){clear();delete _head;_head = nullptr;}void push_back(const T& x){先找到尾结点,即头结点的上一个结点//node* tail = _head->_prev;创建新结点,并且调用list_node的构造函数对该结点进行初始化//node* new_node = new node(x);在头结点前插入新结点//tail->_next = new_node;//new_node->_prev = tail;//_head->_prev = new_node;//new_node->_next = _head;//end()就是有效结点的下一个结点,所以在end()前插入一个结点就是在list最后插入一个结点。insert(end(), x);}void push_front(const T& x){insert(begin(), x);}iterator insert(iterator pos, const T& x){node* cur = pos._node;node* prev = cur->_prev;node* new_node = new node(x);prev->_next = new_node;new_node->_prev = prev;new_node->_next = cur;cur->_prev = new_node;return iterator(new_node);}iterator erase(iterator pos){assert(pos != end());node* prev = pos._node->_prev;node* next = pos._node->_next;prev->_next = next;next->_prev = prev;delete pos._node;return iterator(next);}void pop_back(){erase(--end());}void pop_front(){erase(begin());}void clear(){iterator it = begin();while (it != end()){//erase每次会返回指向下一个结点的迭代器,所以每次都会更新it迭代器。//it = erase(it);//还可以使用这种方法来实现clearerase(it++);}}list<T>& operator=(list<T>& lt){swap(lt);return *this;}private://list的头结点list_node<T>* _head;};void Test01(){list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);list<int>::iterator it = lt1.begin();while (it != lt1.end()){cout << *it << " ";++it;}cout << endl;for (auto e : lt1){cout << e << " ";}cout << endl;}void print_list(const list<int>& lt){list<int>::const_iterator it = lt.begin();while (it != lt.end()){//(*it) *= 2;cout << *it << " ";++it;}cout << endl;}struct AA{int _a1;int _a2;AA(int a1 = 0, int a2 = 0):_a1(a1), _a2(a2){}};void Test02(){list<AA> lt;lt.push_back(AA(1, 1));lt.push_back(AA(2, 2));lt.push_back(AA(3, 3));list<AA>::iterator it = lt.begin();while (it != lt.end()){//cout << *it << " ";//cout << (*it)._a1 << ":" << (*it)._a2 << endl;cout << it.operator->()->_a1 << ":" << it.operator->()->_a2 << endl;//本来应该是两个->,但是为了增强可读性,省略了一个->cout << it->_a1 << ":" << it->_a2 << endl;++it;}cout << endl;}void print_list01(const list<AA>& lt){list<AA>::const_iterator it = lt.begin();while (it != lt.end()){//(*it) *= 2;cout << it->_a1 << " " << it->_a2 << endl;++it;}cout << endl;}void Test03(){list<AA> lt;lt.push_back(AA(1, 1));lt.push_back(AA(2, 2));lt.push_back(AA(3, 3));print_list01(lt);}void Test04(){list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);lt1.insert(++(lt1.begin()), 5);lt1.push_back(10);lt1.push_front(0);for (auto e : lt1){cout << e << " ";}cout << endl;lt1.erase(++(lt1.begin()));lt1.pop_back();lt1.pop_front();for (auto e : lt1){cout << e << " ";}cout << endl;}void Test05(){char array[] = "abcdef";list<int> lt1(array, array + sizeof(array) / sizeof(array[0]));for (auto e : lt1){cout << e << " ";}cout << endl;}void Test06(){list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);lt1.push_back(5);for (auto e : lt1){cout << e << " ";}cout << endl;list<int> lt2(lt1);for (auto e : lt2){cout << e << " ";}cout << endl;}void Test07(){list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);lt1.push_back(5);list<int> lt2;lt2.push_back(10);lt2.push_back(20);lt2.push_back(30);cout << "lt2=lt1前,lt1和lt2的值" << endl;cout << "lt1:";for (auto e : lt1){cout << e << " ";}cout << endl;cout << "lt2:";for (auto e : lt2){cout << e << " ";}cout << endl;lt2 = lt1;cout << "lt2=lt1后,lt1和lt2的值" << endl;cout << "lt1:";for (auto e : lt1){cout << e << " ";}cout << endl;cout << "lt2:";for (auto e : lt2){cout << e << " ";}cout << endl;}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/156908.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

身份证实名核验接口,身份证实名认证,身份证二要素实名认证,身份证实名校验,身份证一致性实名认证

一、接口介绍 验证身份证与姓名是否匹配&#xff0c;查询身份证信息。如校验通过,接口返回生日、性别、地址等信息。广泛应用于信贷、安防、银行、保险等行业及各种身份核查场景。 注意&#xff1a;当请求参数符合“【固定同一个参数&#xff0c;其余参数不同】&#xff0c;”…

基于VScode 使用plantUML 插件设计状态机

本文主要记录本人初次在VScode上使用PlantUML设计 本文只讲述操作的实际方法&#xff0c;假设java已安装成功 。 1. 在VScode下安装如下插件 2. 验证环境是否正常 新建一个文件夹并在目录下面新建文件test.plantuml 其内容如下所示: startuml hello world skinparam Style …

基于小波变换的分形信号r指数求解算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ................................................................... %通过功率谱密度曲线…

WebSocket连接异常 Error parsing HTTP request header Connection reset by peer

问题描述 在使用spring的方式集成websocket时&#xff0c;在配置WebSocketConfigurer后 Configuration EnableWebSocket public class WebSocketConfiguration implements WebSocketConfigurer {ResourceServletWebSocketServerHandler servletWebSocketServerHandler;Overri…

linux总结

cat -n filename 查看文件,-n用来给每一行标行号,可以省略 cat /var/log/mysqld.log | grep password 我们可以通过上述指令&#xff0c;查询日志文件内容中包含password的行信息。 more 作用: 以分页的形式显示文件内容 语法: more fileName 操作说明: 回车键 …

Spring Boot 中的 Redis 数据操作配置和使用

Spring Boot 中的 Redis 数据操作配置和使用 Redis&#xff08;Remote Dictionary Server&#xff09;是一种高性能的开源内存数据库&#xff0c;用于缓存、消息队列、会话管理和数据存储。在Spring Boot应用程序中&#xff0c;Redis被广泛用于各种用例&#xff0c;包括缓存、…

【教学类-35-04】学号+姓名+班级(中3班)学号字帖(A4竖版2份 竖版长条)

图片展示: 背景需求: 2022年9-2023年1月我去过小3班带班&#xff0c;但是没有在这个班级投放过学具&#xff0c;本周五是我在本学期第一次带中3班&#xff0c;所以提供了一套学号描字帖。先让我把孩子的名字和脸混个眼熟。 之前试过一页两套名字的纸张切割方法有&#xff1a;…

distcc分布式编译

distcc https://gitee.com/bison-fork/distcc.git 下载工具链 mingw&#xff0c;https://www.mingw-w64.org/downloads/#w64devkitperl&#xff0c;https://strawberryperl.com/releases.html免安装zip版本&#xff0c;autoconf等脚本依赖perlautoconf、automake&#xff0c…

只有正规才有机会,CTF/AWD竞赛标准参考书来了

目录 前言 一、内容简介 二、读者对象 三、目录 前言 随着网络安全问题日益凸显&#xff0c;国家对网络安全人才的需求持续增长&#xff0c;其中&#xff0c;网络安全竞赛在国家以及企业的人才培养和选拔中扮演着至关重要的角色。 在数字化时代&#xff0c;企业为了应对日益…

Flutter:open_file打开本地文件报错问题

相关插件及版本&#xff1a; open_file: ^3.2.1 问题&#xff1a; 项目中一直用的这个插件&#xff0c;突然发现在安卓高版本不能正常使用&#xff0c;报权限问题permissionDenied&#xff0c;断点调试提示相关权限是MANAGE_EXTERNAL_STORAGE&#xff0c;申请权限之后还是不行&…

springboot集成kafka

1、引入依赖 <dependency><groupId>org.springframework.kafka</groupId><artifactId>spring-kafka</artifactId><version>2.8.6</version></dependency> 2、配置 server:port: 9099 spring:kafka:bootstrap-servers: 192.1…

JWT - 令牌认证授权(认证流程、认证原理、Jwt 工具类)

目录 一、JWT 认证 1.1、对 JWT 的认识 1.1.1、JWT 解释 1.1.2、为什么使用的 JWT 认证&#xff0c;而不是 Session 认证&#xff1f; a&#xff09;基于传统的 Session 认证 1.1.3、JWT 认证流程 1.1.4、优势 1.1.5、JWT 的结构 JWT 第一部分&#xff1a;标头 Header …

LeetCode - 318 最大单词长度乘积(Java JS Py C)

目录 题目来源 题目描述 示例 提示 题目解析 算法源码 题目来源 318. 最大单词长度乘积 - 力扣&#xff08;LeetCode&#xff09; 题目描述 给你一个字符串数组 words &#xff0c;找出并返回 length(words[i]) * length(words[j]) 的最大值&#xff0c;并且这两个单词…

RocketMQ核心编程模型以及生产环境最佳实践

文章目录 一、深入理解RocketMQ的消息模型二、消息确认机制消息生产端采用消息确认加多次重试的机制保证消息正常发送到RocketMQ消息消费者端采用状态确认机制保证消费者一定能正常处理对应的消息消费者也可以自行指定起始消费位点 三、广播消息四、顺序消息机制五、延迟消息六…

【Mybatis】动态 SQL

动态 SQL \<if>标签\<trim>标签\<where>标签\<set>标签\<foreach>标签 动态 sql 是 Mybatis 的强⼤特性之⼀&#xff0c;能够完成不同条件下不同的 sql 拼接。 <if>标签 前端用户输入时有些选项是非必填的, 那么此时传到后端的参数是不确…

ipad手写笔哪个好用?苹果平替笔性价比高的

如果你想要入手一款和iPad匹配的电容笔&#xff0c;想必你的第一想法就是苹果的原装电容笔。然而这款电容笔虽然很好用&#xff0c;但价格会相对的昂贵一些。而平替电容笔&#xff0c;却是一种不错的选择&#xff0c;而且价格也很合理。一支普通的平板电容笔&#xff0c;其售价…

设计模式学习(十二)用设计模式干掉 if-else,太优雅了!

目录 一、场景举例二、什么时候需要改造 if-else&#xff1f;三、策略模式 Map字典3.1 策略接口3.2 策略实现类3.3 策略工厂类&#xff08;策略接口的持有者&#xff09;3.4 客户端&#xff08;测试类&#xff09;3.5 执行结果3.6 总结 四、责任链模式4.1 责任链处理接口4.2 责…

使用【Blob、Base64】两种方式显示【文本、图片、视频】 使用 video 组件播放视频

Blob 显示 Blob 对象的类型是由 MIME 类型&#xff08;Multipurpose Internet Mail Extensions&#xff09;来确定的。MIME 类型是一种标准&#xff0c;用于表示文档、图像、音频、视频等多媒体文件的类型。以下是一些常见的 Blob 对象类型&#xff1a; text/plain&#xff1…

2024届通信工程保研经验分享(预推免入营即offer)

2024届通信工程保研经验分享&#xff08;预推免入营即offer&#xff09; BackGround夏令营情况&#xff1a;预推免情况&#xff1a; BackGround 本科院校&#xff1a;末九 专业&#xff1a;通信工程 rank&#xff1a;3/123&#xff08;预推免绩点排名&#xff09;&#xff0…

基于行波理论的输电线路防雷保护

摘要 随着科技的发展&#xff0c;电力已成为最重要的资源之一&#xff0c;如何保证电力的供应对于国民经济发展和人民生活水平的提高都有非常重要的意义。输电线路的防雷保护就是重点之一。架空输电线路分布很广&#xff0c;地处旷野&#xff0c;易遗受雷击&#xff0c;线路的雷…