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~

相关推荐
cdut_suye7 分钟前
Linux工具使用指南:从apt管理、gcc编译到makefile构建与gdb调试
java·linux·运维·服务器·c++·人工智能·python
qq_4336184411 分钟前
shell 编程(三)
linux·运维·服务器
波音彬要多做31 分钟前
41 stack类与queue类
开发语言·数据结构·c++·学习·算法
捕鲸叉32 分钟前
C++软件设计模式之外观(Facade)模式
c++·设计模式·外观模式
Tlzns32 分钟前
Linux网络——UDP的运用
linux·网络·udp
码农土豆38 分钟前
PaddlePaddle飞桨Linux系统Docker版安装
linux·docker·paddlepaddle
Swift社区39 分钟前
Excel 列名称转换问题 Swift 解答
开发语言·excel·swift
Noah_aa42 分钟前
代码随想录算法训练营第五十六天 | 图 | 拓扑排序(BFS)
数据结构
一道微光43 分钟前
Mac的M2芯片运行lightgbm报错,其他python包可用,x86_x64架构运行
开发语言·python·macos
Hacker_xingchen43 分钟前
天融信Linux系统安全问题
linux·运维·系统安全