C++的list类(一):list类的常见操作和模拟实现

目录

前言

List类的迭代器

List类的模拟实现

list.h文件

test.cpp文件


前言

  • vector的insert和erase都会导致迭代器失效
  • list的insert不会导致迭代器失效,erase会导致迭代器失效
  • insert导致失效的原因是开辟了新空间后,迭代器扔指向原空间
  • erase导致失效的原因是销毁的空间不是连续的空间,迭代器找不到下一块小空间的位置

List类的迭代器问题

类模板:C++模板初阶

内部类:C++的类和对象(七):友元、内部类

问题1:原生指针不能充当迭代器(原生指针是天然的迭代器的前提是空间连续)**

**原因:**原生指针指向的是连续空间的情况下才可以充当迭代器

  • 数组:是一片连续的存储空间,数组的原生指针数组名,++即是下一个元素的地址
  • 链表:不是一片连续的存储空间,链表的原生指针Node*,++不是下一个结点的地址

**问题2:**对结点的原生指针的解引用得不到当前所在结点的数据

List类的模拟实现

难点:Node、iterator、list三个类的间接嵌套使用

  • Node、iterator、list都是一个类
  • Node类负责表示的单个结点的结构,并提供相关的方法来操作单个结点
  • list类负责管理所有结点间的关系及提供对外接口来让用户操作整个链表
  • iterator类负责实现封装原生指针和实现迭代器需要的方法

结点类模板

cpp 复制代码
template <class T>
struct ListNode
{
	ListNode<T>* _next;//结点的后继指针
	ListNode<T>* _prev;//结点的前驱指针
	T _data;//结点中存放的数据
	
	ListNode(const T& x = T())//构造Node类类型的对象(一个结点对象)
		:_next(nullptr)//未传入指定数据,x就会等于该匿名对象
		,_prev(nullptr)//传入指定数据,x会等于那个指定的数据,T()不起作用
		,_data(x)
	{}
};

涉及知识点

1、在定义一个类时,如果类中的数据可以公用就选struct,需要保护一部分就用class,结点中的数据和前驱后继指针应该都能被访问到,所以可以直接选用struct

2、T()是一个``T类型匿名对象,在构造结点时未传入有效数据,x就会给予T()进行初始化:

  1. 若T是内置类型(如 int、float、指针等),将x将被初始化为0、0.0或nullptr等默认值

  2. T是自定义类类型,则将调用该类的默认构造函数来创建一个匿名对象

普通迭代器类模板

构造迭代器对象

cpp 复制代码
template<class T>
struct ListIterator
{
	typedef ListNode<T> Node;//此时迭代器类可以使用结点类
	typedef ListIterator<T> iterator;//将迭代器类重名名为iterator

	Node* _node;
	ListIterator(Node* node)//用_node封装原生指针,_node会被传入的原生指针初始化
        :_node(node)        //_node = _head->_next
	{}                      //_node就相当于原生指针
}

*运算符重载

cpp 复制代码
//*it
T& operator*()//传引用返回避免了拷贝,且该数据可读可修改
{
	return _node->_data;//返回_node指向的结点对象中存放的数据_data
}
  • _node->_data会被编译器转变为*(_node)._data

->运算符重载

cpp 复制代码
//a->b
T* operator->()//返回值为T*,T*表示T类型的指针
{
	return &_node->_data;//获取T类类型对象的地址,将它交给一个指向T类类型的对象的"匿名"指针,该指针的类型是T*,之后利用该指针去访问该对象中的成员变量的值
}

问题:为什么要返回_data的地址而不是返回_data?

答:用->访问对象中的成员,左操作数是指向该对象的指针而不是该对象本身,右操作数是要访问对象的成员(A* ptr = &aa1,ptr->_a1,ptr存放的是该对象的地址,此后就可以用ptr访问_a1了)*,返回的地址不用一个有名的指针承接,直接接->,此时返回的内容就类似T*类型的匿名指针* ( (指向对象的匿名指针)->对象的成员 )
问题:返回值类型可不可以是T&?

答:不可以 ,T* + 返回的地址 = 一个T*类型的指向返回地址的匿名指针,返回的地址被存放在了一个匿名指针中,T& + 返回的地址 = 未定义行为(函数返回一个对象的地址,则该函数的返回值类型必须是 该对象的类型*)**

前置和后置++、--、==、!=重载

cpp 复制代码
//前置++
terator& operator++()//iterator&的意思是,在使用迭代器时,++操作是在该迭代器自身进行的
{
	_node = _node->_next;
	return *this;
}

//后置++(返回++前的值)
iterator operator++(int)
{
	iterator tmp(*this);//拷贝构造一个新的

	_node = _node->_next;

	return tmp;
}

//前置--
iterator& operator--()
{
	_node = _node->_prev;
	return *this;
}

//后置--(返回--前的值)
iterator operator--(int)
{
	iterator tmp(*this);

	_node = _node->_prev;

	return tmp;
}

//不等于
bool operator!=(const iterator& it)//(const iterator& this,const iterator& it)
{
	return _node != it._node;//this->_node != it.node
}

//等于
bool operator==(const iterator& it)
{
		return _node == it._node;
}

解决代码冗余的迭代器模板

涉及知识点

1、普通迭代器是迭代器本身可以修改,迭代器指向的内容也可以修改

2、const迭代器是****迭代器本身可以修改,迭代器指向的内容不可以修改

3、同一命名空间下,多个类之间可以通过typedef的方式使用其他类的内容

4、

链表类模板

涉及知识点

完整代码

list.h文件

test.cpp文件

~over~

相关推荐
StayInLove几秒前
G1垃圾回收器日志详解
java·开发语言
TeYiToKu2 分钟前
笔记整理—linux驱动开发部分(9)framebuffer驱动框架
linux·c语言·arm开发·驱动开发·笔记·嵌入式硬件·arm
dsywws5 分钟前
Linux学习笔记之时间日期和查找和解压缩指令
linux·笔记·学习
无尽的大道8 分钟前
Java字符串深度解析:String的实现、常量池与性能优化
java·开发语言·性能优化
爱吃生蚝的于勒12 分钟前
深入学习指针(5)!!!!!!!!!!!!!!!
c语言·开发语言·数据结构·学习·计算机网络·算法
yeyuningzi13 分钟前
Debian 12环境里部署nginx步骤记录
linux·运维·服务器
羊小猪~~15 分钟前
数据结构C语言描述2(图文结合)--有头单链表,无头单链表(两种方法),链表反转、有序链表构建、排序等操作,考研可看
c语言·数据结构·c++·考研·算法·链表·visual studio
binishuaio21 分钟前
Java 第11天 (git版本控制器基础用法)
java·开发语言·git
zz.YE23 分钟前
【Java SE】StringBuffer
java·开发语言
就是有点傻27 分钟前
WPF中的依赖属性
开发语言·wpf