当前位置: 首页 > news >正文

【C++】关于list的使用底层实现

文章目录

  • 一、认识list
  • 二、list的使用
    • 2.1 constructor(构造函数)
    • 2.2 Iterators
      • 2.2.1 迭代器的分类:
    • 2.3 Capacity(容量相关)
    • 2.4 Element access(元素访问)
    • 2.5 Modifiers(链表修改)
    • 2.6 Operations(对链表的一些操作)
  • 三、list的底层实现
    • ✨ list的节点:ListNode
    • ✨ list的成员变量:
    • ⭐ list 的迭代器:
      • 🌸list_iterator:
      • ⭐list的iterator是否要实现拷贝构造、析构函数、赋值重载?
      • 🌸list_const_iterator:
      • 🌸关于const_iterator中间为什么要加_?
      • ⭐迭代器的模板化复用:
    • ✨ list的成员函数:
      • 🌀list(构造):
      • 🌀迭代器相关:
      • 🌀insert:
      • 🌀push_back:
      • 🌀push_front:
      • 🌀erase:
      • 🌀pop_back:
      • 🌀pop_front:
      • 🌀empty:
      • 🌀size:
      • ⭐clear:
      • ⭐~list(析构):
      • ⭐list(拷贝构造-深拷贝):
      • ⭐operator=:
  • 四、list 容器的模拟实现整体代码
    • 🌈list.h:
    • 🌈测试代码:

一、认识list

  • list是一种序列容器,可以在序列中的任何位置进行常数时间插入删除操作,并且支持双向迭代
  • list容器是用双向链表实现的;双向链表的每个元素存储在互不相关的独立节点中。通过每个节点的前驱和后继指针链接,内部保持了元素的顺序。
  • 它和单向链表(forward_list)非常相似:主要区别在于单向链表对象是单链表,因此只能向前迭代,但相对更小且更高效。
  • 与其他基本标准序列容器(array, vector and deque)相比,list在插入、提取和移动容器中任何位置的元素时表现通常更好,尤其是在已经获得迭代器的情况下,因此在频繁使用这些操作的算法(如排序算法)中也表现更佳。
  • list和单向链表相比其他序列容器的主要缺点是它们无法通过位置直接访问元素;例如,要访问列表中的第六个元素,必须从已知位置(如开头或结尾)迭代到该位置,这需要线性时间。它们还会消耗一些额外的内存来保存与每个元素相关的链接信息

list的详细介绍请参考:list

二、list的使用

2.1 constructor(构造函数)

构造函数接口说明
list (size_type n, const value_type& val =value_type())构造的list中包含n个值为val的元素
list()构造空的list
list (const list& x)拷贝构造函数
list (InputIterator first, InputIterator last)用[first, last)区间中的元素构造

注意:value_type表示第一个模板参数(T),size_type表示无符号整型。

2.2 Iterators

大家可暂时将迭代器理解成一个指针,该指针指向list中的某个节点。

函数声明接口说明
begin +end返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器
rbegin+ rend返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的reverse_iterator,即begin位置

注意:

  1. beginend正向迭代器,对迭代器执行++操作,迭代器向后移动
  2. rbegin(end)rend(begin)反向迭代器,对迭代器执行++操作,迭代器向前移动

📖示例:利用迭代器遍历链表

void test_list1()
{//迭代器list<int> l1;l1.push_back(1);l1.push_back(2);l1.push_back(3);l1.push_back(4);//打印list<int>::iterator it = l1.begin();while (it != l1.end()){cout << *it << " ";it++;}cout << endl;//支持迭代器同样支持范围forfor (auto ch : l1){cout << ch << " ";}cout << endl;
}

list的迭代器不支持+-,因为底层不是原生指针了,像stringvector的迭代器可以用原生指针实现,是因为其底层物理空间是连续的。

2.2.1 迭代器的分类:

  • 按功能分:
迭代器说明
iterator正向迭代器
reverse_iterator反向迭代器
const_iteratorconst正向迭代器
coonst_reverse_iteratorconst反向迭代器
  • 按性质分:
迭代器举例支持功能
单向迭代器forwad_list/unordered_map只支持++
双向迭代器list/map/set支持++/–
随机迭代器vector/string支持++/–/+/-
input/output

说明:
   这些迭代器都是包含的关系,单向是特殊的双向,双向是特殊的随机。
   性质由容器的底层结构决定,底层结构又决定可以使用哪些算法(algorithm),比如 sort只能使用随机迭代器, reverse(逆置)支持双向迭代器随机迭代器也能用,find(查找)使用的是input迭代器

void test_list2()
{//迭代器list<int> l1; l1.push_back(1);l1.push_back(2);l1.push_back(3);l1.push_back(4);//sort(l1.begin(),l1.end());    会报错string s1("asenncewoc");sort(s1.begin(), s1.end());cout << s1 << endl;
}

2.3 Capacity(容量相关)

函数声明接口说明
empty检测list是否为空,是返回true,否则返回false
size返回list中有效节点的个数

list没有扩容。

2.4 Element access(元素访问)

函数声明接口说明
front返回list的第一个节点中值的引用
back返回list的最后一个节点中值的引用

📖示例:

void test_list3()
{list<int> l1;l1.push_back(1);l1.push_back(2);l1.push_back(3);l1.push_back(4);cout << l1.front() << endl;  //1cout << l1.back() << endl;   //4
}

2.5 Modifiers(链表修改)

函数声明接口说明
push_front在list首元素前插入值为val的元素
pop_front删除list中第一个元素
push_back在list尾部插入值为val的元素
emplace_back构造并在末尾插入元素
pop_back删除list中最后一个元素
insert在list position 位置前插入值为val的元素
erase删除list position位置的元素
swap交换两个list中的元素
clear清空list中的有效元素

说明:
   insert 插入元素并不会导致迭代器失效,因为相较于 vector 中的 insert,list 中的 insert 并不会去扩容挪动数据,而 vector 中的 insert 可能会进行扩容挪动数据,最终导致迭代器失效。erase会导致迭代器失效,失效的只有指向被删除节点的迭代器,其他迭代器不会受到影响。

📖erase:

void test_list4()
{//迭代器list<int> l1;l1.push_back(1);l1.push_back(2);l1.push_back(3);l1.push_back(4);//删除下标为3位置处的元素//l1.erase(it+3); 这样list不支持了list<int>::iterator it = l1.begin();int k=3;while(k){k--;it++;}l1.erase(it);for (auto ch:l1){cout << ch << " ";}//运行结果:1 2 3cout << endl;
}

📖emplace_back和push_back的区别:

void test_list5()
{//emplace_back相比于push_back更高效一点//emplace_back是模板的可变参数list<A> l2;A aa1(2,2);l2.push_back(aa1);l2.push_back(A(2,2));  //传入匿名对象l2.push_back(3,3);  //不支持两个参数,这是push_back与emplace_back的区别所在l2.emplace_back(aa1);l2.emplace_back(A(3,3));l2.emplace_back(4,4);  //支持
}

📖insert:

void test_list7()
{//iterator insert (iterator position, const value_type& val);//如果想在下标为3前插入数据//不能像这样了,l1.insert(it+3,4);auto It = l1.begin();int k = 3;while (k){It++;k--;}l1.insert(It, 5);for (auto ch : l1){cout << ch << " ";}cout << endl;
}

2.6 Operations(对链表的一些操作)

函数声明接口说明
reverse对链表进行逆置
sort对链表中的元素进行排序
merge对两个有序的链表进行合并,得到一个有序的链表
unique对链表中的元素去重,前提链表必须有序
remove删除具有特定值的节点
splice将 A 链表中的节点转移到 B 链表,A链表中的节点会删除。

说明
   链表逆置可以使用 list 自身的接口,也可以使用算法库中的 reverse,二者没有什么区别。链表排序只能使用 list 自身的 sort 接口(底层是利用归并排序),不能使用算法库的 sort,因为算法库中的 sort 底层是通过快排来实现的,而快排中会涉及到三数取中需要迭代器 - 迭代器,而list迭代器不支持。在Debug版本下,算法库中的sort不及list中的sort,但在release版本下,算法库中的sort速度远快于list中的sort。
   所以当我们要对链表排序时,可以先将list中的数据拷贝到vector中,在vector中排序,排完序后再拷贝回list。

📖merge:

void test_list8()
{//merge合并listlist<double> first, second;first.push_back(3.1);first.push_back(2.2);first.push_back(2.9);second.push_back(3.7);second.push_back(7.1);second.push_back(1.4);first.sort();//默认排升序second.sort();first.merge(second);// (secon现在为空了)cout << "first contains:";for (list<double>::iterator it = first.begin(); it != first.end(); ++it)cout << ' ' << *it;cout << '\n';
}

📖 unique:

void test_list9()
{//unique删除重复元素:要求数据必须有序//底层去重可以用双指针解决list<int> l1;l1.push_back(1);l1.push_back(2);l1.push_back(3);l1.push_back(4);l1.push_back(4);l1.push_back(5);l1.push_back(9);l1.push_back(7);l1.sort();l1.unique();for (list<int>::iterator it = l1.begin(); it != l1.end(); ++it)cout << ' ' << *it;cout << '\n';}

📖splice:

void test_list10()
{//splice意思是剪接:Transfer elements from list to listlist<int> mylist1, mylist2;list<int>::iterator it;//插入数据for (int i = 1; i <= 4; ++i)mylist1.push_back(i);      // mylist1: 1 2 3 4for (int i = 1; i <= 3; ++i)mylist2.push_back(i * 10);   // mylist2: 10 20 30it = mylist1.begin();++it;                        //将mylist2的所有节点转移到mylist1中it指向的节点后。mylist1.splice(it, mylist2); // mylist1: 1 10 20 30 2 3 4// mylist2 (empty)// "it" still points to 2 (the 5th element)int k = 0;cin >> k;list<int>::iterator lt = find(mylist1.begin(),mylist1.end(),k);//void splice (iterator position, list& x, iterator i);//仅将x指向i的元素传输到容器中。mylist1.splice(mylist1.begin(),mylist1,lt);for (list<int>::iterator it = mylist1.begin(); it != mylist1.end(); ++it)cout << ' ' << *it;cout << '\n';int c = 0;cin >> c;list<int>::iterator at = find(mylist1.begin(), mylist1.end(), c);//void splice (iterator position, list& x, iterator first, iterator last);//将范围[first,last]从x传输到容器中。mylist1.splice(mylist1.begin(), mylist1, at,mylist1.end());for (list<int>::iterator It = mylist1.begin(); It != mylist1.end(); ++It)cout << ' ' << *It;cout << '\n';
}

三、list的底层实现

✨ list的节点:ListNode

list底层就是一个双向链表,它是通过一个一个的节点连接而成的。每个节点是由一个结构体封装的,节点包括指针域,有前驱指针prev(指向前一个节点)和后驱指针next(指向后一个节点),以及数据域_data,存储数据。

template<class T>
struct ListNode
{//构造ListNode(const T& data=T()):_data(data),_next(nullptr),_prev(nullptr){}T _data;ListNode<T>* _next;ListNode<T>* _prev;
};

    编译器默认生成的构造函数达不到我们的要求,需要自己实现。在这里并没有使用class定义节点类,因为节点是要经常访问的,而struct默认访问权限是public,class默认访问权限是private,所以使用struct定义更直观。
    将这个类加上template<class T>后,就能够实现节点存储不同类型的数据,这也是C++模板的好处。

✨ list的成员变量:

设计思路:ListNode只是一个单独的节点,要想对一个链表操作,需要定义一个list类,对链表的各种操作,都在这个类里面实现。

template<class T>
class list
{
public:typedef ListNode<T> Node;
private:Node* _head;size_t _size;
}

list的成员变量有一个指针_head,指向list的头节点,还有size,表示list存在几个有效的节点。
因为list中有指针_head,指向list的头节点,需要手动写构造函数,否则编译器默认生成的构造会将_head初始化为随机值,导致程序运行时出现未定义行为。

⭐ list 的迭代器:

设计思路:list底层的物理空间不是连续的,用原生指针实现list迭代器就不行了,可以将迭代器封装成一个类,在类中重载++,- -,解引用*等。迭代器类中的成员变量只有结点类类型的指针 _node,因为迭代器的本质就是指针。

🌸list_iterator:

template<class T>
struct list_iterator
{//结点类的类型取的别名是 Nodetypedef ListNode<T> Node;//为迭代器类取的别名是Self。typedef list_iterator<T> Self;//构造函数list_iterator(Node* node):_node(node){ }//解引用希望得到的是Node中的dataT& operator*(){return _node->_data;}//迭代器指向下一个节点//返回的是迭代器类型Self operator++(){_node=_node->_next;return *this;//解引用得到指向下一个节点的迭代器}Self operator--(){_node=_node->_prev;return *this;}//后置++,返回的是++之前的迭代器Self operator++(int){Self tmp(*this);_node=_node->_next;return tmp;}Self operator--(int){Self tmp(*this);_node=_node->_prev;return tmp;}T* operator->(){return &(_node->data);}bool operator!=(const Self& s) const{//比较迭代器指向的节点的地址是否一样return _node!=s._node;		}bool operator==(const Self& s) const{return _node==s._node;		}Node* _node;//指向节点的指针
};

注意:这里的类名不能直接命名为iterator,因为每种容器的迭代器底层实现都有所不同,即可能会为每一种容器都单独实现一个迭代器类,如果都直接使用 iterator,会导致命名冲突,命名为list_iterator表示实现的是list的迭代器。

⭐list的iterator是否要实现拷贝构造、析构函数、赋值重载?

    其中重载operator++(int)时,即后置++,会调用到iterator的拷贝构造,但是iterator并不用实现拷贝构造,编译器默认生成的浅拷贝即可。函数结束后后调用编译器默认生成的析构函数为浅析构仅销毁 _node 指针本身,而不会释放_node指向的 Node 节点,这就不会有浅拷贝常见的问题:同一块空间被释放两次。tmp 是函数内的局部对象,存储在栈内存中,栈内存由编译器自动管理。
    list_iterator不用自己实现拷贝构造和析构,这恰好符合迭代器的设计要求,迭代器的作用是指向节点、提供访问接口,而非直接对list中的节点操作,节点的创建和释放是由 list 容器负责。
在这里插入图片描述

🌸list_const_iterator:

template<class T>
struct list_const_iterator
{typedef ListNode<T> Node;typedef list_const_iterator<T> Self;//解引用希望得到的是Node中的dataconst T& operator*(){return _node->_data;}//迭代器指向下一个节点//返回的是迭代器类型Self operator++(){_node=_node->_next;return *this;//解引用得到指向下一个节点的迭代器}Self operator--(){_node=_node->_prev;return *this;}//后置++,返回的是++之前的迭代器Self operator++(int){Self tmp(*this);_node=_node->_next;return tmp;}Self operator--(int){Self tmp(*this);_node=_node->_prev;return tmp;}const T* operator->(){return &(_node->data);}bool operator!=(const Self& s) const{//比较迭代器指向的节点的地址是否一样return _node!=s._node;		}bool operator==(const Self& s) const{return _node==s._node;		}Node* _node;//指向节点的指针
};

🌸关于const_iterator中间为什么要加_?

  • 第一个原因是:
        C++规定:const_iterator 表示 “常量迭代器”(只能读取元素,不能修改元素),与普通的 iterator(可读可写)形成对比。
  • 第二个原因是:避免与const iterator混淆。
    const_iterator:迭代器本身可以移动(++/-- --),但通过它访问的元素是 const 的(不能修改)。
    const iterator:迭代器本身是 const 的(不能移动),但通过它访问的元素可以修改(如果元素本身非 const)。

⭐迭代器的模板化复用:

   上面实现的list_iteratorlist_const_iterator在很多地方存在冗余,只有operator和operator->的返回值类型不同,当是list_iterator类时,它们的返回值类型是T&T*,当是list_const_iterator类时,它们的返回值类型是const T&const T*。我们可以将operator和operator->的返回值类型设置成迭代器类的模板参数,当需要的返回值类型是T&和T时,就传入模板参数T&和T
   所以我们定义一个通用的迭代器模板类,通过模板参数 Ref(引用类型)Ptr(指针类型),统一 operator* 和 operator-> 的实现,同时适配普通迭代器和 const 迭代器。

template<class T,class Ref,class Ptr>
struct _list_iterator
{typedef ListNode<T> Node;typedef _list_iterator<T,Ref,Ptr> Self;typedef Ref reference;typedef Ptr pointer;//构造函数_list_iterator(Node* node):_node(node){}reference operator*(){return _node->_data;}//重载++Self& operator++(){_node = _node->_next;return *this;}Self operator++(int){Self tmp(*this);_node = _node->_next;return tmp;}Self& operator--(){this->_node = _node->_prev;return *this;}//后置--Self operator--(int){Self tmp(*this);this->_node = _node->_prev;return tmp;}//重载!=bool operator!=(const Self& s) const{return _node != s._node;}//重载->pointer operator->() {//如果链表中的节点中的数据为自定义类型,需要对链表中的节点的数据解引用才能访问return &(_node->_data);}Node* _node;
};

✨ list的成员函数:

🌀list(构造):

完成对链表对象的初始化,即得到一个空的新链表。

//默认构造
list()
{_head=new Node;//为头节点申请合法的空间,并构造一个节点_head->next=_head;_head->prev=_head;
}

new Node先调用operator new申请空间 ,再在申请的空间上调用Node的构造。
在这里插入图片描述

🌀迭代器相关:

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;*///写法二://return it(_head->_next);//写法三:return _head->_next;//会进行隐式类型转换:单参数类型的构造函数支持隐式类型转换
}
iterator end()//最后一个数据的下一个位置,即哨兵位的头节点
{return _head;
}//只读
const_iterator begin() const
{return _head->_next;
}
const_iterator end() const
{return _head;
}

🌀insert:

iterator insert(iterator pos,const T& x)
{Node* newnode = new Node(x);//在it迭代器之前插入Node* pcur = pos._node;Node* prev = pcur->_prev;//prev newnode pcurprev->_next = newnode;newnode->_prev = prev;newnode->_next = pcur;pcur->_prev = newnode;_size++;//C++标准规定:返回刚插入的元素的迭代器return newnode;
}

🌀push_back:

复用insert。

void push_back(const T& x)
{insert(end(),x);
}

🌀push_front:

复用insert。

void push_front(const T& x)
{insert(begin(), x);
}

🌀erase:

iterator erase(iterator pos)
{assert(pos!=end());Node* pcur = pos._node;Node* next = pcur->_next;Node* prev = pcur->_prev;//pcur pos nextprev->_next = next;next->_prev = prev;delete pcur;_size--;//返回下一个位置的迭代器,因为删除后那个位置的迭代器失效了。return next;
}

🌀pop_back:

复用erase。

void pop_back()
{erase(--(this->end()));
}

🌀pop_front:

复用erase。

void pop_front()
{erase(this->begin());
}

🌀empty:

//判空
bool empty() const
{return _size == 0;
}

🌀size:


size_t size() const
{return _size;
}

⭐clear:

通过遍历节点的方式,逐个释放节点,只保留头节点。
法一:依赖实现的erase,实现链表的清空。

void clear()
{auto it = this->begin();while(it!=this->end()){it=erase(it);}
}

法二:不依赖实现的erase。

//清空链表
void clear()
{if (_head->_next == _head) {return;}Node* pcur = _head->_next;while (pcur != _head){Node* next = pcur->_next;//保存下一个节点delete pcur;pcur = next;}_head->_next = _head;_head->_prev = _head;_size = 0;
}

⭐~list(析构):

通过复用clear,将链表清空,然会释放头指针,最后将头指针置空。

~list()
{clear();delete _head;_head = nullptr;
}

⭐list(拷贝构造-深拷贝):

   拷贝构造是用一个已有对象去构造出另一个对象,首先将待构造对象进行初始化,然后通过复用push_back,将数据尾插到新链表,达到深拷贝的目的。

void empty_list()
{_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;
}
//拷贝构造(深拷贝)现代写法
list(list<T>& x)
{//创建一个新的空链表,只有头节点empty_list();for (iterator it = x.begin(); it != x.end(); it++){push_back(*it);}
}

⭐operator=:

		void swap(list<T>& t1, list<T>& t2){std::swap(t1._head, t2._head);std::swap(t1._size, t2._size);}//重载赋值list<T>& operator=(list<T> t){swap(*this, t);return *this;}

四、list 容器的模拟实现整体代码

🌈list.h:

#pragma once
#include<iostream>
#include<assert.h>
using std::cout;
using std::cin;
using std::endl;
using std::ostream;
using std::istream;
namespace hwy_list
{template<class T>struct ListNode{//构造函数ListNode(const T& val = T())// 带默认参数的构造函数//若 T 是内置类型(如 int、double):T() 会执行 “零初始化”,因此 _data 被初始化为 0(int)、0.0(double)等。//若 T 是指针类型(如 int*):T() 会初始化为 nullptr,因此 _data(指针)被初始化为空指针。//若 T 是自定义类型(如 string、AA):T() 会调用 T 的默认构造函数,_data 的值由 T 的默认构造函数决定。:_data(val),_prev(nullptr),_next(nullptr){}T _data;ListNode* _prev;ListNode* _next;};template<class T, class Ref, class Ptr>struct list_iterator{typedef ListNode<T> Node;typedef list_iterator<T, Ref, Ptr> Self;typedef Ref reference;typedef Ptr ptrainer;//构造函数,指向一个节点list_iterator(Node* node):_node(node){}//重载*reference operator*(){return _node->_data;}Self& operator++(){//获取下一个节点的地址Self tmp(*this);//会调用拷贝构造,是浅拷贝_node = _node->_next;return *this;}Self& operator--(){_node = _node->_prev;return *this;}//后置--Self operator--(int){//返回修改以前的迭代器Self tmp(*this);_node = _node->_prev;return tmp;}//后置++Self operator++(int){//返回之前的迭代器Self tmp(*this);_node = _node->_next;return tmp;//返回拷贝构造的临时对象(浅拷贝)}bool operator!= (const Self& s){return _node != s._node;}//返回迭代器指向的节点中的_data的地址ptrainer operator->(){return &(_node->_data);}Node* _node;//_node是一个指针,指向一个节点};//创建链表管理类template<class T>class list{public:typedef ListNode<T> Node;//1.先调用operator new申请空间  2.在在申请的空间上执行构造//构造函数typedef list_iterator<T, T&, T*> iterator;typedef list_iterator<T, const T&, const T*> const_iterator;void empty_list(){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}list(){empty_list();}//尾插,即最后一个节点后插入void push_back(const T& x){insert(end(), x);}//在pos位置前面插入节点iterator insert(iterator pos, const T& x){Node* newnode = new Node(x);Node* pcur = pos._node;Node* prev = pcur->_prev;//prev newnode nextprev->_next = newnode;newnode->_prev = prev;newnode->_next = pcur;pcur->_prev = newnode;_size++;return newnode;}//删除pos位的节点,该节点就失效了iterator erase(iterator pos){assert(pos != end());Node* pcur = pos._node;Node* next = pcur->_next;Node* prev = pcur->_prev;//prev newnode nextprev->_next = next;next->_prev = prev;delete pcur;_size--;return next;//隐式类型转换}//头删void pop_back(){erase(begin());}//头插void push_front(const T& x){insert(begin(),x);}void pop_front(){erase(--(end()));}//普通迭代器iterator begin(){iterator it(_head->_next);//传入一个节点的地址return it;//return _head->_next;//会进行隐式类型转换}iterator end()//最后一个数据的下一个位置,即哨兵位的头节点{return _head;}//const迭代器const_iterator begin() const{const_iterator it(_head->_next);//传入一个节点的地址return it;//return _head->_next;//会进行隐式类型转换}const_iterator end() const//最后一个数据的下一个位置,即哨兵位的头节点{return _head;}size_t size() const{return _size;}bool empty(){return _size == 0;}//拷贝构造(深拷贝)list(list<T>& x){//创建一个新的空链表,只有头节点empty_list();for (iterator it = x.begin(); it != x.end(); it++){push_back(*it);}}void swap(list<T>& t1, list<T>& t2){std::swap(t1._head, t2._head);std::swap(t1._size, t2._size);}//重载赋值list<T>& operator=(list<T> t){swap(*this, t);return *this;}//清空链表void clear(){if (_head->_next == _head) {return;}Node* pcur = _head->_next;while (pcur != _head){Node* next = pcur->_next;//保存下一个节点delete pcur;pcur = next;}_head->_next = _head;_head->_prev = _head;_size = 0;}//析构~list(){//delete只能作用于指针clear();delete _head;_head = nullptr;}private:Node* _head;size_t _size;};template<class Container>void print_container(const Container& v){//传入的是const迭代器,需要调用const迭代器。//所以需要实现const迭代器typename Container::const_iterator lt = v.begin();//auto lt = v.begin();while (lt != v.end()){//const 迭代器不能修改//*lt += 20;cout << *lt << endl; //这里的->返回的是指针类型呀,cout并没有指针类型的<<重载lt++;}cout << endl;/*for (auto& ch:v){cout << ch << " ";}cout << endl;*/}
}

🌈测试代码:

test.cpp:

#include"list.h"
namespace hwy_list
{void test01(){list<int> l1;l1.push_back(1);l1.push_back(2);l1.push_back(3);l1.push_back(4);auto it = l1.begin();int k;cin >> k;while (k){k--;++it;}l1.insert(it, 9);list<int>::iterator lt = l1.begin();while (lt != l1.end()){cout << " " << *lt;lt++;}cout << endl;}void test02(){list<int> l1;l1.push_back(1);l1.push_back(2);l1.push_back(3);l1.push_back(4);auto it = l1.begin();int k;cin >> k;while (k){k--;++it;}l1.erase(it);for (auto ch : l1){cout << " " << ch;}cout << endl;}void test03(){list<int> l1;l1.push_back(1);l1.push_back(2);l1.push_back(3);l1.push_back(4);l1.push_back(99);l1.push_front(66);for (auto ch : l1){cout << " " << ch;}cout << endl;l1.pop_back();l1.pop_front();for (auto ch : l1){cout << " " << ch;}cout << endl;cout << l1.size() << endl;}void test04(){list<int> l1;l1.push_back(1);l1.push_back(2);l1.push_back(3);l1.push_back(4);l1.push_back(99);l1.push_front(66);print_container(l1);int c = l1.size();cout << c << endl;cout << l1.empty() << endl;}struct AA{AA(int a1 = 0, int a2 = 0):_a1(a1),_a2(a2){}int _a1;int _a2;};template<class T>ostream& operator<<(ostream& os, const T& obj){os << obj._a1 << obj._a2;  // 自定义输出格式return os;}void test05(){list<AA> l1;l1.push_back(AA());l1.push_back(AA());l1.push_back(AA());l1.push_back(AA());auto it = l1.begin();while (it != l1.end()){//cout << (*it)._a1 << ':' <<(*it)._a2<<endl ;//cout << it.operator->()->_a1 << ':' << it.operator->()->_a2 << endl; 原本应该有两个->//简写cout << *it << endl;	//这里就是对的		it++;}print_container(l1);//有问题}//迭代器失效问题void test06(){list<int> l1;l1.push_back(1);l1.push_back(2);l1.push_back(3);l1.push_back(4);l1.push_back(4);l1.push_back(99);l1.push_front(66);//插入不会造成迭代器失效//删除节点会,删除后就是野指针了l1.clear();print_container(l1);}void test07(){list<int> l2;l2.push_back(1);l2.push_back(2);l2.push_back(3);l2.push_back(4);l2.push_back(5);list<int> l1(l2);print_container(l1); //这个里边对象没有_a1和_a2print_container(l2);}void test08(){list<int> l2, l1;l2.push_back(1);l2.push_back(2);l2.push_back(3);l2.push_back(4);l2.push_back(5);l1 = l2;print_container(l2);print_container(l1);}
}
int main()
{hwy_list::test01();hwy_list::test02();hwy_list::test03();hwy_list::test04();hwy_list::test05();hwy_list::test06();hwy_list::test07();hwy_list::test08();return 0;
}

🎁结语:
    今天的分享就到这里,感谢各位大佬的关注,还请大家多多支持哦!
在这里插入图片描述

http://www.dtcms.com/a/568945.html

相关文章:

  • Ubuntu安装单节点MicroK8s
  • Ubuntu22.04单节点部署k8s(无需外网)
  • 网站策划和运营微信最火的公众号排行
  • 北京景网站建设北京网站被处罚
  • 【深度学习新浪潮】llama.cpp完全适配Qwen3-0.6B/8B模型!从转换到部署保姆级教程
  • 24.NAT Server
  • Spring中@Controller与@RestController核心解析
  • 中企动力做的网站价格区间做网站v赚钱
  • uni-app中的地图引入(map)
  • Milvus:Json字段详解(十)
  • 八千字 《宠知汇》HarmonyOS应用案例热点技术解析
  • Box64 模拟器 让Steam 在 RISC-V 运行
  • 基于Django的智慧园区管理系统开发全解析
  • 2025上海国际汽车灯光及智能座舱展览将带来哪些新技术与新体验?
  • uniapp + Vue2 + Vuex + 持久化存储
  • 企业网站备案需要多久中文wordpress 主题
  • 香港 SFC 新规解读:虚拟资产交易平台迈向共享流动性与产品多元化时代
  • LegionSpace黑客松指南(一):项目开发流程指引
  • 网络注册公司怎么注册seo关键词推广价格
  • 阿拉伯语与中文对照词汇表PDF识别错误自动修正系统
  • 城市更新第一步:PDF转CAD,将历史图纸一键转化为设计复用底图
  • 矛盾论局事物本质内在逻辑洞察矛盾化解冲突拥抱矛盾智慧破局实战应用电子书籍PDF
  • 四层神经网络(含反向传播 Backpropagation)的完整数值计算+流程图示例
  • 第二部分(上):套接字
  • 深度学习Adam优化器核心概念全解析:参数,梯度,一阶动量,二阶动量
  • 网站模板哪里下载网站设计合同附件
  • 学习Linux——网络——网卡
  • 《原神》运行卡顿解决方案:游戏运行库合集一键安装指南
  • Java + Spring Boot + Redis技术栈,在实际使用缓存时遇到 缓存击穿、缓存穿透、缓存雪崩
  • Elasticsearch安装使用