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

08--深入解析C++ list:高效操作与实现原理

1. list介绍

1.1. list概述

template < class T, class Alloc = allocator<T> > class list;Lists are sequence containers that allow constant time insert 
and erase operations anywhere within the sequence, and iteration in both directions.

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

我们用代码来体会一下list的缺点:

void test_op1()
{srand(time(0));const int N = 1000000;//一百万数据//两个链表list<int> lt1;list<int> lt2;//一个顺序表vector<int> v;//生成随机数据,尾插到链表1和顺序表v中去for (int i = 0; i < N; ++i){auto e = rand()+i;//加上这个i主要是为了减少重复数字概率lt1.push_back(e);v.push_back(e);}//vector排序int begin1 = clock();sort(v.begin(), v.end());int end1 = clock();//list排序int begin2 = clock();lt1.sort();int end2 = clock();//打印比较两者用时printf("vector sort:%d\n", end1 - begin1);printf("list sort:%d\n", end2 - begin2);
}
void test_op2()
{srand(time(0));const int N = 1000000;list<int> lt1;list<int> lt2;for (int i = 0; i < N; ++i){auto e = rand();lt1.push_back(e);lt2.push_back(e);}// 拷贝vectorint begin1 = clock();vector<int> v(lt2.begin(), lt2.end());// 排序sort(v.begin(), v.end());// 拷贝回lt2lt2.assign(v.begin(), v.end());int end1 = clock();//lt1排序int begin2 = clock();lt1.sort();int end2 = clock();//打印printf("list copy vector sort copy list sort:%d\n", end1 - begin1);printf("list sort:%d\n", end2 - begin2);
}

1.2. 相关接口的介绍

1.2.1. insert

作用:在指定位置之前插入结点

iterator insert (iterator position, const value_type& val);

注释: An iterator that points to the first of the newly inserted elements.

问题:我们vector中insert会引发迭代器失效问题,我们list中的insert会有迭代器失效问题吗?为什么?

答:不会。这是由底层结构决定的。

同理,erase也会引起vector失效,但是对于list就不会引起迭代器失效问题。

1.2.2. splice转移

作用:把指定位置转移到指定位置。

前提:转移的数据元素必须一致!

1.2.3. remove

作用:移除链表中是val的值,没有则不做处理

注意,会全部删除,而不仅仅是删除一个

1.2.4. unique去重

作用:排序后,去重复结点

前提:是排序之后才行。

1.2.5. merge合并

作用:合并两个有序链表

前提:两个链表必须排序, 如果不是有序的, 则会断言报错, 并且sort和merge使用的比较函数必须要一致!

1.2.6. sort排序

排序效率一般:这里需要重点强调一下list中的sort排序和vector中的sort排序效率差距还是挺大的,建议数据量比较大的话有条件就用vector进行排序,即使是从list把数据拷贝到vector再拷贝回list.

2. list -- 简单模拟实现

2.1. 实现要点分析

ListNode的成员变量为什么是prev和next两个指针呢?

因为我们要写的是双向链表,对于每个结点而言都需要去存储前一个结点的地址和后一个结点的地址,结点本身还要存储上自己的数据val这样才可以。

list::迭代器的设计(为什么不用原生指针设计迭代器?)

但是这里有个问题:迭代器怎么写???用原生指针typedef一下吗?
当然不行。
在vector中,用原生指针充当迭代器是完全可以的,但是对于list,原生指针++或者--操作之后,会指向内存的下一块区域,问题就是list在内存中实际的存储是不确定的。


重要的原因在于,这个原生指针++、--操作之后的行为不是我们想要的,如果我们可以修改他的++、--行为岂不是很好吗?
于是,我们将原生指针封装为一个类,重载他的运算符,就解决了这个问题。本质上,封装原生指针就是为了扩大我们对指针的控制权限。

总结一下: 原生指针迭代器运算不符合我们预期, 因此我们封装为一个类, 掌控迭代器的行为.

为了便于大家理解,我在这里提出下面几个问题帮助大家进一步理解上面代码:

  • 为什么list的迭代器需要对原生指针进行封装?答:为了重载他的操作符, 掌控迭代器的行为.
  • 迭代器需要写构造函数和拷贝构造函数吗?为什么?构造函数需要写,拷贝构造不用写,因为编译器自动浅拷贝, list迭代器浅拷贝就足够用了。
  • 迭代器需要写析构函数吗?不用,因为迭代器不用考虑结点的释放,释放结点属于list的工作
  • 迭代器什么操作符需要进行重载?根据需要和实际意义,比如在当前场景下迭代器重载大于和小于就没有什么实际意义,可以选择不重载。而需要重点重载的是++, --, 解引用, ... 这些运算符.
  • 上面我们写的iterator类准确来说是类型还是迭代器?是迭代器类型,迭代器是在list中实现的。

请思考一下为什么这个begin()和end()的返回类型是iterator,而不能是iterator&,为什么是迭代器值返回,而不是引用返回呢,毕竟引用返回效率更高啊?

因为迭代器如果返回引用,就会造成很大的问题,毕竟外界可以修改begin和end,这也就会造成begin/end的指向错误。
我用下面例子来进行说明:

operator->的理解

对于自定义类型

struct A
{int _a;int _b;//构造函数A(const int& a = 0, const int& b = 0){_a = a;_b = b;}
};

我想弄个list<A>请问此时应该怎么进行数据遍历呢?
可能你会写出下面的代码:

szg::list<A> la;
A a(1, 1);
la.push_back(a); // 有名对象尾插
la.push_back(A(2,2)); // 匿名对象尾插
la.push_back({3,3}); // CPP11新语法,多参数隐式类型转换//访问
szg::ListIterator<A> itA = la.begin();
while (itA != la.end())
{std::cout << (*itA)._a << " ";std::cout << (*itA)._b << " ";itA++;
}
std::cout << std::endl;

总感觉很奇怪,但是这是正确的。

倘若这不是struct A,而是class A呢???有人可能会说提供Get_A和Get_B函数,一般CPP中不习惯写Get函数,这种JAVA是常用这样的方法的。

CPP会重载一个operator->去解决这个问题。
在ListIterator类中,我们可以写下下面代码:

T* operator->()//返回对应val值的地址
{return &_iterator->_val;
}

之后,我们可以这样写Test:

struct A
{int _a;int _b;//构造函数A(const int a = 0, const int b = 0){_a = a;_b = b;}
};
szg::list<A> la;
A a(1, 1);
la.push_back(a); // 有名对象尾插
la.push_back(A(2,2)); // 匿名对象尾插
la.push_back({3,3}); // CPP11新语法,多参数隐式类型转换//访问
szg::list<A>::iterator it = la.begin();
while (it != la.end())
{/*std::cout << (*itA)._a << " ";std::cout << (*itA)._b << " ";*///std::cout << (*it)._a << " ";std::cout << it->_a << " ";std::cout << it->_b << " ";it++;
}
std::cout << std::endl;

这里需要注意的是,在我们调用operator的时候,编译器对其做了优化,按照逻辑我们需要写为it.operator->()->_a,这里我们可以少写一个->,更加符合我们的使用习惯。

const迭代器问题

不知道大家发现了没有,一直以来都没用过const迭代器去遍历,实际上,在上面所写的代码中就完全没有const的影子,如果用const迭代器就会报语法错误,因为压根没有实现。

倘若你认为这样写:


那我可以告诉你这样的const迭代器跟非const是一样的行为,一样可以修改迭代器指向的内容。

辨析:
const迭代器和非const迭代器的区别???
const迭代器不可修改迭代器指向的内容,非const迭代器可以修改迭代器指向元素的内容。

倘若你灵机一动,说改成这样:


那么你这样就是让迭代器本身不可更改,而不是迭代器指向的内容不可更改

实际上,想要正确写出const迭代器有两种方法:一是再写一个const_iterator迭代器类,再一个就是把const作为一个参数传入ListIterator中,让其根据模板自动生成一份const迭代器类。

下面来依次介绍两种方法:
对于两种方法,都是重新写一个类而已,只不过前者是自己写,后置式让编译器根据模板进行推导罢了。

template<class T>
struct ConstListIterator
{typedef ListNode<T> node;const node* _iterator;//itrator构造ConstListIterator(const node* node):_iterator(node){}//解引用重载const T& operator* ()const{return _iterator->_val;}const T* operator-> ()const//返回对应val值的地址{return &_iterator->_val;}//前置++重载ConstListIterator<T>& operator++(){_iterator = _iterator->_next;return *this;}//后置++重载//ConstListIterator<T> operator++(int)//{//	ListIterator<T> temp(*this); // 构建一个临时对象//	_iterator = _iterator->_next; // 让当前this指向下一个结点//	return temp; // 但是返回临时变量//} //error:temp类型错误。ConstListIterator<T> operator++(int){ConstListIterator<T> tmp(*this);_iterator = _iterator->_next;return tmp;}//!=重载bool operator!=(const ConstListIterator<T>& l){//return this->_iterator != l._iterator;return _iterator != l._iterator;}//==重载bool operator==(const ListIterator<T>& l){return this->_iterator == l._iterator;}
};

上面就是一个写好的const迭代器类,请注意:在list中使用的时候也要用typedef更改一下名字,这样struct ConstListIterator就成为了List类中的内部类,自由使用了。

简单测试:

//test
szg::list<int> li;
li.push_back(1);
li.push_back(2);
li.push_back(3);
li.push_back(4);
li.push_back(5);const szg::list<int> li2 = li;szg::list<int>::const_iterator it = li2.begin();
while (it != li2.end())
{std::cout << *it << " ";it++;
}
std::cout << std::endl;


当然,我们也可以用第二种方式让编译器为我们写const迭代器:

template<class T, class Ref, class Pon>
struct ListIterator
{
public:typedef ListNode<T> node;node* _iterator;
public://itrator构造ListIterator(node* node):_iterator(node){}//解引用重载Ref operator* (){return _iterator->_val;}Pon operator-> ()//返回对应val值的地址{return &_iterator->_val;}//前置++重载ListIterator<T, Ref, Pon>& operator++(){_iterator = _iterator->_next;return *this;}//后置++重载ListIterator<T, Ref, Pon> operator++(int){ListIterator<T, Ref, Pon> temp(*this); // 构建一个临时对象_iterator = _iterator->_next; // 让当前this指向下一个结点return temp; // 但是返回临时变量}//!=重载bool operator!=(const ListIterator<T, Ref, Pon>& l){return this->_iterator != l._iterator;}//==重载bool operator==(const ListIterator<T, Ref, Pon>& l){return this->_iterator == l._iterator;}
};

CPP11快捷构造函数 -- 初始化列表构造

list(std::initializer_list<T> il)
{empty_initialization();//申请一个哨兵位for (auto& node : il)//然后一直尾插数据{push_back(node);}
}

2.2. 代码示例

接口实现清单

typedef ListNode<T> Node;
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T*> const_iterator;Node* _head;
size_t _size;

  1. 迭代器
    • iterator begin()
    • iterator end()
    • const_iterator begin() const
    • const_iterator end() const
  1. 构造
    • void empty_init()
    • list()
    • list(const list<T>& lt)
    • list<T>& operator=(list<T> lt)
    • ~list()
  1. 增删查改
    • void push_back(const T& x)
    • void push_front(const T& x)
    • void insert(iterator pos, const T& val)
    • void pop_back()
    • void pop_front()
    • iterator erase(iterator pos)
    • void clear()
  1. 其他
    • size_t size() const
    • bool empty()
#pragma once
#include<assert.h>namespace bit
{template<class T>struct ListNode{ListNode<T>* _next;ListNode<T>* _prev;T _data;ListNode(const T& x = T()):_next(nullptr),_prev(nullptr),_data(x){}};// typedef ListIterator<T, T&, T*> iterator;// typedef ListIterator<T, const T&, const T*> const_iterator;template<class T, class Ref, class Ptr>// T: list里面装的数据类型// Ref: 迭代器解引用返回值类型 T& or const T&// Ptr: 迭代器箭头运算符返回值类型 T* or const T*struct ListIterator{typedef ListNode<T> Node;typedef ListIterator<T, Ref, Ptr> Self;Node* _node;ListIterator(Node* node):_node(node){}// *it//T& operator*()Ref operator*(){return _node->_data;}// it->//T* operator->()Ptr operator->(){return &_node->_data;}// ++itSelf& 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& it){return _node != it._node;}bool operator==(const Self& it){return _node == it._node;}};//template<class T>//struct ListConstIterator//{//	typedef ListNode<T> Node;//	typedef ListConstIterator<T> Self;//	Node* _node;//	ListConstIterator(Node* node)//		:_node(node)//	{}//	// *it//	const T& operator*()//	{//		return _node->_data;//	}//	// it->//	const T* operator->()//	{//		return &_node->_data;//	}//	// ++it//	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& it)//	{//		return _node != it._node;//	}//	bool operator==(const Self& it)//	{//		return _node == it._node;//	}//};template<class T>class list{typedef ListNode<T> Node;public://typedef ListIterator<T> iterator;//typedef ListConstIterator<T> const_iterator;typedef ListIterator<T, T&, T*> iterator;typedef ListIterator<T, const T&, const T*> const_iterator;//iterator begin()//{//	//return iterator(_head->_next);//	iterator it(_head->_next);//	return it;//}iterator begin(){return _head->_next;}iterator end(){return _head;}// const����������Ҫ�ǵ����������޸ģ����ǵ�����ָ������ݣ�// ������ָ������ݲ����޸ģ�const iterator����������Ҫconst������// T* const p1// const T* p2const_iterator begin() const{return _head->_next;}const_iterator end() const{return _head;}void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}list(){empty_init();}// lt2(lt1)list(const list<T>& lt){empty_init();for (auto& e : lt){push_back(e);}}// ��Ҫ������һ�����Ҫ�Լ�д���// ����Ҫ������һ��Ͳ���Ҫ�Լ�д�����Ĭ��dz�����Ϳ���void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}// lt1 = lt3list<T>& operator=(list<T> lt){swap(lt);return *this;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}}~list(){clear();delete _head;_head = nullptr;}/*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;}*/void push_back(const T& x){insert(end(), x);}void push_front(const T& x){insert(begin(), x);}void pop_back(){erase(--end());}void pop_front(){erase(begin());}void insert(iterator pos, const T& val){Node* cur = pos._node;Node* newnode = new Node(val);Node* prev = cur->_prev;// prev newnode cur;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;_size++;}iterator erase(iterator pos){Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;_size--;return iterator(next);}size_t size() const{return _size;}bool empty(){return _size == 0;}private:Node* _head;size_t _size;};
#pragma once
#include <algorithm>
#include <iostream>
#include <cassert>
// #include <list>
using namespace std;namespace zzg
{template<class T>class ListNode{public:T _data;ListNode<T>* _prev;ListNode<T>* _next;public:ListNode(const T& val = T()):_data(val), _prev(nullptr), _next(nullptr){}~ListNode(){// _data = 0; // 模板参数, 不一定可以赋值为0_prev = _next = nullptr;}};template<class T, class Ref, class Ptr>class ListIterator{typedef ListNode<T> Node;typedef ListIterator<T, Ref, Ptr> Self;public:Node* _ptr;public:ListIterator(Node* ptr = nullptr):_ptr(ptr){}~ListIterator(){_ptr = nullptr;}Self& operator++ (){_ptr = _ptr->_next;return *this;}Self operator++ (int){Self t(*this);_ptr = _ptr->_next;return t;}Self& operator-- (){_ptr = _ptr->_prev;return *this;}Self operator-- (int){Self t(*this);_ptr = _ptr->_prev;return t;}Ref operator* (){return _ptr->_data;}Ptr operator-> (){return &(_ptr->_data);}bool operator != (const Self& it){return _ptr != it._ptr;}bool operator== (const Self& it){return _ptr == it._ptr;}};template <class T>class list{private:typedef ListNode<T> Node;Node* _head;size_t _size;void empty_init(){_head = new Node();_head->_next = _head;_head->_prev = _head;}public: typedef ListIterator<T, T&, T*> iterator;typedef ListIterator<T, const T&, const T*> const_iterator;iterator begin(){return _head->_next;}iterator end(){return _head;}const_iterator begin() const{return _head->_next;}const_iterator end() const{return _head;}public:// 构造 与 析构list() // 默认构造:_head(nullptr), _size(0){empty_init();}list(const list<T>& lt) // 拷贝构造:_head(nullptr), _size(0){empty_init(); // 初始化节点for(Node * cur = lt._head->_next; cur != lt._head; cur = cur->_next){push_back(cur->_data); // 逐个添加元素}}list<T>& operator=(list<T> lt) // 赋值构造{swap(lt);return *this;}~list(){clear();delete _head;_head = nullptr;_size = 0;}// 其他void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}size_t size() const{return _size;}bool empty() const{return _size == 0;}// 增删改查void push_back(const T& x){// Node* ptail = _head->_prev; // 指向最后一个节点// Node* newNode = new Node(x); // 构造新节点//newNode->_prev = ptail; // 新节点的前驱指向尾节点//newNode->_next = _head; // 新节点的后继指向头节点//ptail->_next = newNode; // 尾节点的后继指向新节点//_head->_prev = newNode; // 头节点的前驱指向新节点//_size++; // 更新大小insert(end(), x);}void push_front(const T& x){insert(begin(), x);}void insert(iterator pos, const T& val){Node* cur = pos._ptr;Node* prev = cur->_prev;Node* newNode = new Node(val);newNode->_next = cur;newNode->_prev = prev;cur->_prev = newNode;prev->_next = newNode;_size++;}void pop_back(){erase(--end());}void pop_front(){erase(begin());}iterator erase(iterator pos){assert(begin() != end());Node* cur = pos._ptr;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete cur;_size--;return iterator(next);}void clear(){iterator it = begin();while (it != end()){// cout << it._ptr->_data << endl;/*erase(it);it++;*/ // bug: 如果这样写, 就等于是先把一个东西删除, 再用删除的哪个东西再去取下一个节点的指针it = erase(it);}}// debug函数void print_list(){cout << "-----print_list-----" << endl;for (Node* cur = _head->_next; cur != _head; cur = cur->_next){cout << cur->_data << " ";}cout << endl << "size: " << _size << endl;cout << "-----print_end------" << endl;cout << endl;}};// 构造函数测试void test1(){list<int> l1; // 默认构造l1.push_back(1);l1.push_back(2);l1.print_list();list<int> l2(l1); // 拷贝构造l2.print_list();list<int> l3; // 默认构造l3 = l1; // 赋值构造l3.push_back(7);l3.push_back(9);l3.print_list();}// 增删 测试void test2(){list<int> l1;l1.insert(l1.begin(), 1); // 头插 l1.insert(l1.begin(), 2); // 头插l1.insert(l1.begin(), 3); // 头插l1.insert(l1.end(), 4); // 尾插l1.insert(l1.end(), 5); // 尾插l1.print_list();l1.push_front(0); // 头插l1.push_back(10); // 尾插l1.print_list();// 0 3 2 1 4 5 10l1.pop_back(); // 0 3 2 1 4 5l1.print_list();l1.pop_front(); // 3 2 1 4 5l1.print_list();l1.erase(l1.begin()); // 2 1 4 5l1.print_list();l1.clear();l1.print_list();}void test(){// test1();test2();}
};
http://www.dtcms.com/a/328277.html

相关文章:

  • 从爬虫新手到DrissionPage实践者的技术旅程
  • 【IP查询】使用IP66(ip66.net)验证IP地址定位的准确率
  • 小智智能交互算法通过国家备案,视觉大模型引领AI应用新浪潮
  • 机器学习之TF-IDF文本关键词提取
  • 终端安全检测与防御技术
  • 数据结构:中缀到后缀的转换(Infix to Postfix Conversion)
  • 【速通版!语义通信基础与前沿学习计划】
  • C++中类之间的关系详解
  • AR巡检:三大核心技术保障数据准确性
  • Langchain入门:构建一个PDF摄取和问答系统
  • 51 单片机分层架构的模块依赖关系图
  • 解决ROS编译顺序不对,需要内部依赖,因此要多次编译的问题
  • Python初学者笔记第二十二期 -- (JSON数据解析)
  • MySQL 数据库表操作与查询实战案例
  • 双十一美妆数据分析:洞察消费趋势与行业秘密
  • 机械臂的智能升维:当传统机械臂遇见Deepoc具身智能大模型从自动化工具到具身智能体的范式革命
  • Element用法---Loading 加载
  • C++的异常的使用和规范
  • 【盘古100Pro+开发板实验例程】FPGA学习 | 均值滤波 | 图像实验指导手册
  • 【代码随想录day 18】 力扣 501.二叉搜索树中的众数
  • 免费播客翻译与转录:用中文收听全球播客
  • Langchain入门:文本摘要
  • C++学习之数据结构:AVL树
  • java八股文-MySql面试题-参考回答
  • GPFS api
  • 在 C语言 中构建安全泛型容器:使用 maybe 实现安全除法
  • 【PCB设计经验】去耦电容如何布局?
  • 力扣top100(day01-04)
  • 企业级的即时通讯平台怎么保护敏感行业通讯安全?
  • 电竞 体育数据 API 应用场景全解析