返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C++list的模拟实现
  • 619
分享到

C++list的模拟实现

C++ listC++ list模拟实现 2023-05-16 14:05:50 619人浏览 独家记忆
摘要

目录一、节点的结构,list的迭代器的结构,以及list的结构1、节点的结构2、迭代器的结构3、list的结构二、迭代器的实现1、*运算符重载2、++ 与 --运算符3、->运

一、节点的结构,list的迭代器的结构,以及list的结构

1、节点的结构

对于链表的节点我们都很熟悉了,节点中包含两个域,一个指针域一个数据域,为了让list能够通用,我们选择使用模板。
节点的结构如下:

template<class T>
//struct也能定义类,默认类的访问限定符是 public
struct list_node
{
	//这个指针指向前一个节点
	list_node<T>* _prev;
	//这个指针指向后一个节点
	list_node<T>* _next;
	//这个是数据域中的元素
	T _data;
	
	//对节点使用匿名对象进行初始化
	list_node(const T& data = T())
		:_prev(nullptr)
		,_next(nullptr)
		,_data(data)
	{}
};

2、迭代器的结构

现在我们已经有了节点了,我们还要有迭代器,如果没有迭代器我们就不能很好的访问每一个节点。
对于迭代器我们要让它指向我们想要的节点,这才能便于我们的访问,于是很明显我们迭代器的成员变量就要是一个节点的指针!同时为了让list能够通用,我们选择使用模板来定义迭代器。
迭代器的结构如下:

//这里后面的两个参数,在实际应用时通常是T& , T* 或者是 const T& , const T*
//根据加与不加const 可以分别实例化出:普通正向迭代器与正向const迭代器
template<class T, class Ref, class Ptr>
struct __list_iterator
{
	//将节点的类型进行typedef方便使用
	typedef list_node<T> node;
	
	//将类自己进行typedef方便使用
	typedef __list_iterator<T,Ref,Ptr> self;

	//成员变量 是一个指向节点的指针
	node* _pnode;

	//构造函数 用一个节点的地址对迭代器进行初始化,
	 __list_iterator(node* pnode)
		:_pnode(pnode)
	{}
};

3、list的结构

由于list是带头双向循环链表,我们只需要一个指向头节点的指针便能够管理所有的节点了。

template<class T>
class list
{
public:
	//将节点的类型进行typedef方便使用
	typedef list_node<T> node;
	//将迭代器进行typedef方便使用
	typedef __list_iterator<T, T&, T*> iterator;
	//将const迭代器进行typedef方便使用
	typedef __list_iterator<T, const T&, const T*> const_iterator;
	//默认构造函数
	list()
	{
		empty_init();
	}
	//初始化函数
	void empty_init()
	{
		//申请一个头节点,将节点的地址给_head
		_head = new node;
		//让哨兵位节点的 前指针指向自己
		_head->_prev = _head;
		//让哨兵位节点的 后指针指向自己
		_head->_next = _head;
	}
private:
	//指向哨兵位节点的指针
	node* _head;
};

到此为止我们一共建立了三个类,下面我们模拟实现链表的各种接口时,我们还要继续丰富迭代器类的接口与list类的接口

二、迭代器的实现

由于链表的许多操作都要用到迭代器,但是迭代器的一些其他接口我们还没有实现,在这里我们来实现迭代器的所有接口。

1、*运算符重载

对于原生指向节点的指针来说*运算符能让我们拿到节点,但还无法拿到节点数据域中的数据,但是对于迭代器来说*运算符就要拿到容器中存储的数据,所以我们还要对迭代器的*运算符进行重载。

// *运算符重载
Ref operator*()
{
	//迭代器中的那个指针不能是nullptr
	assert(_pnode);
	//返回节点中的数据域中的数据
	return _pnode->_data;
}

2、++ 与 --运算符

++运算符分为两种:一种是前置++一种是后置++,这两个函数构成函数重载,后置++的参数部分会多一个int类型。(--运算符同理)

对于原生指向节点的指针来说:++指针是让指针移动到下一个紧挨着的同类型的指针位置,但是对于迭代器来说:++是让迭代器指向下一个节点的位置,这两者并不匹配,所以我们要对++运算符进行函数重载。

//前置++运算符
self& operator++()
{
	_pnode = _pnode->_next;
	return (*this);
}

//后置++运算符
self operator++(int)
{
	//先保存++之前的结果
	self tmp(*this);
	_pnode = _pnode->_next;
	//返回++之前的值
	return tmp;
}

//前置--运算符
self& operator--()
{
	_pnode = _pnode->_prev;
	return (*this);
}

//后置--运算符
self operator--(int)
{
	self tmp(*this);
	_pnode = _pnode->_prev;
	return tmp;
}

3、->运算符重载

虽然在前面我们已经实现了迭代器*的运算符重载,已经可以访问数据域中的数据了。但是当我们的list里面存储的是自定义类型的数据,而我们想要访问自定义类型中的成员变量时迭代器*的运算符就不能够帮到我们了。
例如:

struct Date
{
	int _year;
	int _month;
	int _day;
}
//it是迭代器,指向了存储了Date类型的节点
//假设:在没有->操作符时,我们想要修改_year的值,
(*it)._year = 2023;
//如果有了-> 操作符,我们就能这样操作,更加符合我们的使用习惯
it->_year = 2023;

于是我们来实现:->运算符的重载,我们先来看代码:

// ->运算符重载
Ptr operator->()
{
	return &(_pnode->_data);
}

看到这里你可能会觉得很奇怪,觉得这段代码是错误的,下面我们就来详细讲解这里的问题和注意事项。

_pnode是迭代器的成员变量,是一个节点的指针,它使用的->是c++的内置类型的操作符,这段代码(_pnode->date) 是拿到的是节点中存储的数据,这段代码&(_pnode->date) 是拿到的是节点中存储的数据的地址,返回之后我们好像并没有得到自定义类型中的数据,好像还差一次->操作,比如这样:

it->->_year = 2023; 
//it-> 等价于 (&(_pnode->date)) 

//(&(_pnode->date))->year = 2023;

实际上按上面的运算符重载函数写法确实是少了一次->,但是C++为了代码的简洁性在这里进行了特殊处理,我们写->的运算符重载时只需要返回list里面自定义类型的地址就行了,在外面实际应用时,编译器在编译时会为我们自动加上一次->。

4、 !=运算符重载 与 ==运算符重载

我们在使用迭代器进行遍历数据的时候,经常要使用关系运算符 != ==来判断条件是否达到,在这里我们对关系运算符 != ==进行函数重载。
判断两个迭代器是否相等的办法就是两个迭代器是不是指向同一个位置!

// !=运算符重载
bool operator!=(const self& s)
{
	return _pnode != s._pnode;
}

// ==运算符重载
bool operator==(const self& s)
{
	return _pnode == s._pnode;
}

三、list的实现

在实现完迭代器之后,我们就要实现list的其他接口了。

1、迭代器接口

虽然在list的类外我们已经实现了迭代器的各种接口,但是list类内我们还没有提供使用迭代器的接口的函数,这个函数就是我们常用的begin()与end()函数!下面我们来一起实现一下。

//正向迭代器
iterator begin()
{
	//_head指向的是哨兵位的头节点,_head的下一个才是第一个节点!
	//这里使用的是一个指针构造的匿名对象做返回值,编译器会对此进行优化,能够增加效率
	return iterator(_head->_next);
}

iterator end()
{
	//由于是双向循环链表,所以最后一个节点的下一个位置就是哨兵位节点
	return iterator(_head);
}
//const迭代器的思路与普通迭代器类似
const_iterator begin() const
{
	return const_iterator(_head->_next);
}
const_iterator end() const 
{
	return const_iterator(_head);
}

2、插入函数

list链表的插入很简单,我们需要先申请一个新节点存储我们想要插入的数据,然后将新节点的_prev指针指向前一个节点,同时新节点的_next指针指向当前节点。同时再对当前节点与前一个节点中相应的指针进行更新,就完成了指针的链接。

void insert(iterator pos, const T& x)
{
	//先申请一个节点,存储我们要插入的数据
	node* new_node = new node(x);
	node* prev = pos._pnode->_prev;
	//链接过程
	prev->_next = new_node;
	new_node->_prev = prev;
	new_node->_next = pos._pnode;
	pos._pnode->_prev = new_node;
}

插入函数写完以后,我们的头插尾插函数也就相当于写完了

头插函数

void push_front(const T& x)
{
	//在begin()位置进行插入就是头插!
	insert(begin(), x);
}

尾插函数

//尾插函数
void push_back(const T& x)
{
	//在end()位置进行插入,就是尾插
	insert(end(), x);
}

3、删除函数

链表的删除没有顺序表那么复杂,但是我们应该注意:应该先将前后节点的连接关系给建立好,然后再删除节点!

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;
	//返回指向原节点的下一个节点的迭代器,外部接收后可以防止迭代器失效!
	return iterator(next);
}

同理删除函数写完以后,我们的头删尾删函数也就相当于写完了!

头删函数

void pop_front()
{
	erase(begin());
}

尾删函数

void pop_back()
{
	//由于end()是最后一个节点的下一个位置,所以这里要对end()进行一次自减运算
	erase(--end());
}

4、清除函数

清除函数的作用就是删除除了哨兵位节点以外所有节点,现在我们有了迭代器我们访问每个节点都变得非常容易,删除相应的节点也变的非常容易,我们只需要遍历一遍链表逐一进行删除就行了。

void clear()
{
	list<T>::iterator it = begin();
	while (it != end())
	{
		//erase函数删除相应节点以后会返回下一个节点的迭代器
		it = erase(it);
	}
}

5、交换函数

对于链表的交换我们只需要交换list的成员变量中指向哨兵位节点的指针(即_head指针)就可以完成整个链表的交换了!

//swap函数
void swap(list<T>& lt)
{
	std::swap(_head, lt._head);
}

6、迭代器区间的构造函数

此函数的作用就是用一个迭代器的区间来构造一个链表,要实现这个函数我们只需要用迭代器进行遍历,然后将遍历到的数据一个一个尾插就能构成一个新的链表了,同时为了能够支持更多的迭代器能够去构造链表,我们可以将该函数变成一个函数模板。

//迭代器区间构造,传入的迭代器应该至少是一个二元迭代器,能支持向前和向后遍历,这时链表的最低要求。
template<class Biditerator>
list(Biditerator first, Biditerator last)
{
	//调用初始化函数
	empty_init();
	//遍历迭代器同时将数据形成一个新节点插入链表中
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

7、拷贝构造

有了迭代器区间构造和交换函数我们就可以写现代写法的拷贝构造了!
现代写法的拷贝构造就是用迭代器区间构造一个完整的链表,然后交换给拷贝对象。

//拷贝构造
list(const list<T>& lt)
{
	//初始化
	empty_init();
	//用迭代器区间构造创建一个新的list对象
	list<T> tmp(lt.begin(), lt.end());
	//将this指针指向的对象与这个新的tmp对象进行交换,拷贝就变相完成了
	swap(tmp);
}

8、赋值重载

有了拷贝构造和交换函数,我们还是可以采用现代版本的赋值重载,原理与上面的拷贝构造同理。

//赋值运算符重载
//注意这里的传参方式是传值传参
list<T>& operator=(list<T> lt)
{
	//将this指针指向的对象与这个lt对象进行交换,赋值就变相完成了
	swap(lt);
	return (*this);
}

9、析构函数

最后就是析构函数了,由于我们已经实现过了clear函数,所以我们可以先调用clear函数删除所有有效节点,然后再删除哨兵位的节点就行了!

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

到此这篇关于C++list的模拟实现的文章就介绍到这了,更多相关C++ ist实现内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: C++list的模拟实现

本文链接: https://lsjlt.com/news/210109.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • C++list的模拟实现
    目录一、节点的结构,list的迭代器的结构,以及list的结构1、节点的结构2、迭代器的结构3、list的结构二、迭代器的实现1、*运算符重载2、++ 与 --运算符3、->运...
    99+
    2023-05-16
    C++ list C++ list模拟实现
  • C++模拟实现list功能
    目录list介绍构造函数无参构造函数有参构造函数模板区间构造函数拷贝构造函数赋值运算符重载析构函数迭代器迭代器构造函数迭代器关系运算符重载迭代器++ --运算符重载迭代器 * 运算符...
    99+
    2024-04-02
  • 详解C++ STL模拟实现list
    目录list 概述接口总览list 的节点默认成员函数默认构造函数析构函数拷贝构造函数复制赋值函数list 的迭代器构造函数operator==operator!=operator*...
    99+
    2023-01-11
    C++ STL实现list C++ STL list
  • C++中list的使用与模拟实现
    目录一、list的介绍以及使用1.1 list的介绍1.2 list的使用1.2.1 list的构造1.2.2 list iterator的使用1.2.3 list capacity...
    99+
    2024-04-02
  • C++深入探究list的模拟实现
    目录迭代器正向迭代器类反向迭代器类push_back尾插函数push_front头插函数insert插入函数erase删除函数pop_front函数pop_back函数构造函数析构函...
    99+
    2024-04-02
  • C++模拟实现List迭代器详解
    目录概念迭代器使用迭代器模拟实现迭代器的大体结构构造函数解引用重载重载自增实现自减实现运算符重载迭代器失效模拟List概念 迭代器是一种抽象的设计概念,其定义为:提供一种方法,使他能...
    99+
    2024-04-02
  • C++之list容器模拟怎么实现
    这篇“C++之list容器模拟怎么实现”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“C++之list容器模拟怎么实现”文章吧...
    99+
    2023-07-05
  • C++之list容器模拟实现方式
    目录总述一、节点类二、迭代器类成员变量构造函数*重载->重载“++”“==“和”!=”三、反向迭代器类成...
    99+
    2023-02-05
    C++ list容器 list容器模拟实现 模拟实现list
  • 利用C++模拟实现STL容器:list
    目录一、list的介绍二、list的排序三、迭代器1、list的迭代器失效问题2、迭代器的功能分类3、list迭代器的模拟实现4、迭代器价值5、迭代器operator->的重载...
    99+
    2022-12-08
    C++实现STL容器list C++ STL容器list C++ STL容器
  • C++初阶之list的模拟实现过程详解
    list的介绍 list的优点: list头部、中间插入不再需要挪动数据,O(1)效率高 list插入数据是新增节点,不需要增容 list的缺点: ...
    99+
    2024-04-02
  • C++ STL vector的模拟实现
    1. vector的介绍和使用 vector是表示可变大小数组的序列容器。 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对v...
    99+
    2024-04-02
  • C++模拟实现vector的方法
    今天小编给大家分享一下C++模拟实现vector的方法的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。1. 模拟实现vecto...
    99+
    2023-07-02
  • c++ vector模拟实现的全过程
    一、vector是什么? vector是表示可变大小数组的序列容器,它也采用连续存储空间来存储元素,因此可以采用下标对vector的元素进行访问,它的大小是动态改变的,vector...
    99+
    2024-04-02
  • C++ vector类的模拟实现方法
    vector和string虽然底层都是通过顺序表来实现的,但是他们利用顺序表的方式不同,string是指定好了类型,通过使用顺序表来存储并对数据进行操作,而vector是利用了C++...
    99+
    2024-04-02
  • C++中vector的模拟实现实例详解
    目录vector接口总览 默认成员函数 构造函数 拷贝构造 赋值重载 析构函数 迭代器相关函数 begin和end 容量相关函数 size和capacity reserve resi...
    99+
    2024-04-02
  • C++中如何模拟实现vector
    这篇文章给大家分享的是有关C++中如何模拟实现vector的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。vector接口总览namespace nzb{//模拟实现vectortemplate<c...
    99+
    2023-06-25
  • c++模拟实现string类详情
    目录一、string类简介二、模拟实现成员变量成员函数迭代器重载运算符[ ]三、几种常见函数reserve()resize()push_back()append()重载+=inser...
    99+
    2024-04-02
  • 详解C++STL模拟实现forward_list
    目录forward_list 概述接口总览forward_list 的节点默认成员函数默认构造函数析构函数forward_list 的迭代器构造函数operator==operato...
    99+
    2023-01-13
    C++ STL实现forward_list C++ STL forward_list
  • c++中vector的使用和模拟实现
    一、接口介绍 1、插入数据 void push_back(const T& x) 在当前vector尾部插入x,如果容量不够扩大二倍。 iterator insert(it...
    99+
    2024-04-02
  • C++中priority_queue的使用与模拟实现
    目录priority_queue的使用priority_queue简介priority_queue的使用priority_queue的模拟实现priority_queue的使用 pr...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作