list的模拟实现
1.1 list基本结构
list的结构是个带头双向循环链表,每个数据是存储在一个单独的节点内,这个节点除了存储数据还有两个指针分别指向前一个和后一个节点
这里定义节点的类用struct,定义list的类用class的原因是一个默认的共识,一个类如果它的所有成员都不期望用访问限定符限制的时候,习惯上就用struct定义,这里的list_node通常作为链表的一个子结构,是存储每个数据的一个最小单元,链表内是要大量访问内部数据的,所以这里不用访问限定符限制。虽然这样写后别人就可以随便访问这些结构了,但是有迭代器之后,在外层的角度是看不到节点的(例如从使用的角度,在接口使用的时候是不用链表的节点的,无论是访问修改还是插入删除,都是直接用迭代器的),即虽然list_node是公有的,但是其是一种隐形的封装,平时看不到也不会/不需要访问
这里 list_node< T >* _prev; 这个写法是C++ 模板语法 + 指针语法的组合
- list_node< T >是语法规定: list_node是定义的模板结构体,C++ 语法要求:使用模板类 / 结构体时,必须通过<模板参数>指定具体的泛型类型(这里T是模板参数),所以list_node< T >是模板结构体的 “实例化类型写法”,属于语法强制要求。
*是语法规定:*是 C++ 中 “指针类型” 的声明符号,list_node< T >*表示 “指向list_node< T >类型对象的指针”,这是指针的标准语法。
补充一下这里模板声明的作用范围
模板声明的作用范围template< class T > 是 “模板参数声明”,它的作用域仅限于紧跟在它后面的那个类(或结构体、函数)。例如:
代码语言:javascript
AI代码解释
// 第一个类模板:list_node template<class T> // 作用范围:下面的 struct list_node struct list_node { ... }; // 第二个类模板:list template<class T> // 作用范围:下面的 class list class list { ... };这里的两个 template< class T > 是独立的,分别服务于 list_node 和 list 两个类,彼此不影响。
新类模板需要重新声明每个类模板都是独立的实体,即使两个类的功能相关(比如链表的节点和链表本身),定义新的类模板时也必须重新写template< class T >。
原因是:模板参数 T 是 “当前类模板的局部参数”,只在当前类的范围内有效。比如 list_node 中的 T 和 list 中的 T 虽然名字相同,但实际上是两个独立的模板参数(只是习惯上用相同的字母表示)。
特殊情况:嵌套类模板如果一个类模板内部嵌套了另一个类,嵌套类可以直接使用外部类的模板参数,无需重复声明:
代码语言:javascript
AI代码解释
template<class T> class outer { // 嵌套类,直接使用外部的 T class inner { T data; // 这里的 T 继承自 outer 的 template<class T> }; };但如果嵌套类需要自己的独立模板参数(比如同时支持 T 和 U),则需要单独声明:
代码语言:javascript
AI代码解释
template<class T> class outer { // 嵌套类有自己的模板参数 U template<class U> // 单独声明,作用范围:下面的 inner class inner { T a; // 来自 outer 的 T U b; // 来自 inner 自己的 U }; };1.2 构造 + 尾插
在这里插入图片描述
尾插逻辑如图
在这里插入图片描述
在这里插入图片描述
这里运行不了,运行不了的原因是
list_node 缺少无参构造函数 在 List.h 中,list_node 结构体的构造函数只有带参数的版本(list_node(const T& x)),但没有无参构造函数。 而在 list 类的构造函数中,执行了 _head = new Node; —— 这里尝试调用 list_node 的无参构造函数,但该构造函数并不存在,因此会触发编译错误。
这里有三种解决办法:方法 1:利用list_node的带参构造显式传入默认值在 list 类的构造函数中,创建头结点时,显式调用 list_node 的带参构造,并传入 T 类型的默认值(通过 T() 触发 T 的默认构造,也就是使用匿名对象,T有可能是任意类型)。这样无需为 list_node 新增无参构造函数。