🔥个人主页:北辰水墨
🔥专栏:C++学习仓
本节内容我们来讲解list的使用和模拟实现。 本节难点:list迭代器的模拟实现。
一、list的介绍:
列表
列表是一种序列容器,允许在序列的任何位置进行时间复杂度为O(1)的插入和删除操作,并可在前后两个方向上进行迭代。
列表容器被实现为双向链表;双向链表可以将它们包含的每个元素存储在不同且不相关的存储位置中。通过将每个元素关联到其前面的元素和后面的元素的链接来在内部保持顺序。
它们与forward_list非常相似:主要区别在于forward_list对象是单向链表,因此forward_list对象只能向前迭代,作为交换,forward_list对象相对较小和更高效。
与其他基本标准序列容器(数组,向量和双端队列)相比,列表在已获得迭代器的情况下通常在容器内的任何位置执行插入、提取和移动元素方面表现更好,因此在那些大量使用这些操作的算法(如排序算法)中也表现更好。
与其他序列容器相比,列表和forward_list的主要缺点是它们缺乏通过位置直接访问元素的能力;例如,要访问列表中的第六个元素,需要从已知位置(如开头或结尾)迭代到该位置,这在这两者之间的距离上需要线性时间。它们还会消耗一些额外的内存来保存与每个元素关联的链接信息(对于大量小尺寸元素的大型列表可能是一个重要因素)。
总结:list是一个带头双向循环列表。
二、接口函数:
2.1 构造函数:
2.1.1 Default constructor (构造一个空的 std::list):
std::list<int> myList1; // 创建一个空的整型链表
2.1.2 Fill constructor (构造一个有特定数量元素且每个元素都有相同初始值的 std::list):
std::list<int> myList2(5, 10); // 创建一个有5个元素的链表,每个元素都初始化为10
2.1.3 Range constructor (从另一个迭代器定义范围的容器中构建 std::list):
std::vector<int> myVector{1, 2, 3, 4, 5};
std::list<int> myList3(myVector.begin(), myVector.end()); // 使用vector的范围来初始化链表
2.1.4 Copy constructor (使用另一个 std::list 来构造一个新的 std::list, 是副本):
std::list<int> myOriginalList{1, 2, 3, 4, 5};
std::list<int> myList4(myOriginalList); // 使用另一个list来初始化这个新的list
Fill constructor构造函数前面加
explicit,表示不能隐式类型转换
2.2 迭代器
用法保持不变:
#include<iostream>
#include<list>
using namespace std;
int main()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
list<int>::iterator it = lt.begin();
while (it != lt.end())
{
cout << *it << " ";
it++;
}
cout << endl;
list<int>::reverse_iterator rit = lt.rbegin();
while (rit != lt.rend())
{
cout << *rit << " ";
rit++;
}
return 0;
}
2.3 容量操作:
- empty检测list是否为空,是返回true,否则返回false
- size返回有效元素个数的值
2.4 元素访问:
- front返回list的第一个节点值的引用
- back返回list的最后一个节点值的引用
2.5内容操作:
这里大多数函数我们在前面都讲解过,包括头插头删尾插尾删之类的。
2.5.1 assign
清除原来的数据,再链接新数据(模拟实现原理,先clear(),再push_back() )
size的大小会跟着改变。
// list::assign
#include <iostream>
#include <list>
int main()
{
std::list<int> first;
std::list<int> second;
first.assign(7, 100); // 7 ints with value 100
second.assign(first.begin(), first.end()); // a copy of first
int myints[] = { 1776,7,4 };
first.assign(myints, myints + 3); // assigning from array
for (auto u : first)
{
std::cout << u << " ";
}
std::cout << std::endl;
std::cout << "Size of first: " << int(first.size()) << '\n';
std::cout << std::endl;
for (auto u : second)
{
std::cout << u << " ";
}
std::cout << std::endl;
std::cout << "Size of second: " << int(second.size()) << '\n';
return 0;
}
2.5.2
push_front 头插
push_back 尾插
pop_front 头删
pop_back 尾删
2.5.3 insert
1. 不会出现迭代器失效:它不像vector那样会有异地扩容,挪动数据,iterator野指针的问题。
2. 不用it接收返回值:那么it还是指向那个节点的地址。
3. 用it接收返回值:就指向新添加的首节点的地址
#include <iostream>
#include <list>
#include <vector>
int main()
{
std::list<int> mylist;
std::list<int>::iterator it;
// set some initial values:
for (int i = 1; i <= 5; ++i) mylist.push_back(i); // 1 2 3 4 5
it = mylist.begin();
++it; // it points now to number 2 ^
it=mylist.insert(it,3, 10); // 1 10 10 10 2 3 4 5
用it接收返回值 ^
mylist.insert(it, 2, 20); // 1 20 20 10 10 10 2 3 4 5
没有用it接收返回值,还是指向那一块空间 ^
std::cout << "mylist contains:";
for (it = mylist.begin(); it != mylist.end(); ++it)
std::cout << ' ' << *it;
std::cout << '\n';
return 0;
}
2.5.4 erase
在earse时会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不受影响
只需要用it接收earse函数返回值。
返回值为删除元素的下一个节点。
// erasing from list
#include <iostream>
#include <list>
int main ()
{
std::list<int> mylist;
std::list<int>::iterator it1,it2;
// set some values:
for (int i=1; i<10; ++i) mylist.push_back(i*10);
// 10 20 30 40 50 60 70 80 90
it1 = it2 = mylist.begin(); // ^^
advance (it2,6); // ^ ^
++it1; // ^ ^
it1 = mylist.erase (it1); // 10 30 40 50 60 70 80 90
// ^ ^
it2 = mylist.erase (it2); // 10 30 40 50 60 80 90
// ^ ^
++it1; // ^ ^
--it2; // ^ ^
mylist.erase (it1,it2); // 10 30 60 80 90
// ^
std::cout << "mylist contains:";
for (it1=mylist.begin(); it1!=mylist.end(); ++it1)
std::cout << ' ' << *it1;
std::cout << '\n';
return 0;
}
2.5.5 clear
清除数据,保留 带头节点,size变成0
2.6 opeations
std::list
提供了一些有用的成员函数,允许执行各种操作,如元素的合并、移除、排序和倒序。下面是这些函数的简要说明和使用示例:
splice: 将元素从一个列表转移到另一个列表,可以转移整个列表、一个单独的元素或一个元素范围。不会执行任何元素的复制或者移动操作,只是在内部节点之间调整指针。
std::list<int> list1 = {1, 2, 3};
std::list<int> list2 = {4, 5, 6};
list1.splice(list1.end(), list2); // 把list2的所有元素移动到list1的末尾
remove: 从列表中移除所有具有特定值的元素。
list lt={1,2,3,4,5,3,3,4};
lt.remove(3);// 移除所有值为3的元素 最后为 lt={1,2,4,5,4};
remove_if: 根据一个判断条件移除元素。
bool ref(const int n) {return n % 2 == 0; } //判断条件,偶数为true
myList.remove_if(ref); //删除返回值为true的元素
#include <iostream>
#include <list>
bool ref(const int n)
{
return n % 2 == 0;
}
int main()
{
std::list<int> myList = { 1, 2, 3, 4, 5 };
myList.remove_if(ref);
for (auto u : myList)
{
std::cout << u << " ";
}
return 0;
}
unique: 移除连续并且重复的元素,只保留唯一的元素。
必须要是有序的,才可以使用unique函数(底层原理:判断前后元素是否相同)
#include <iostream>
#include<list>
using namespace std;
int main()
{
list<int> lt = {1,5,3,4,5,6,2,3,4,1,5,4,5,6};
lt.unique();
for (auto u : lt)
{
cout << u << " ";
}
cout << endl;
lt.sort();
lt.unique();
for (auto u : lt)
{
cout << u << " ";
}
return 0;
}
一开始无序:不做任何处理
merge: 合并两个已排序的列表,并确保结果列表也是排序的。
std::list<int> list1 = {1, 3, 5};
std::list<int> list2 = {2, 4, 6};
list1.merge(list2); // 合并两个列表为1, 2, 3, 4, 5, 6
sort: 对列表中的元素进行排序。它接受一个比较函数作为参数(可选)。
std::list<int> myList = {4, 3, 5, 2, 1};
myList.sort(); // 排序列表为1, 2, 3, 4, 5
reverse: 反转列表中元素的顺序。
std::list<int> myList = {1, 2, 3, 4, 5};
myList.reverse(); // 反转后列表为5, 4, 3, 2, 1
三、模拟实现:
3.1基本框架:
namespace moon
{
template<class T>
struct __list_node
{
T _val;
__list_node<T>* prev;
__list_node<T>* next;
__list_node(const T& x=T())
:_val(x)
,prev(nullptr)
,next(nullptr)
{}
};
template<class T>
class list
{
typedef __list_node<T> Node;
public:
private:
Node* _head;
size_t size;
};
}
这段代码实现了一个简单双向链表的基础结构。让我们分两部分来解释这个代码:
1. namespace moon 命名空间 moon 用于封装代码,避免与其他库中的同名类型或函数冲突。在这个命名空间中,定义了模板类__list_node和模板类list。
2. 模板类__list_node :
数据的存储是用了T模版类型,还定义了两个指针,分别指向前驱和后继。
还有一个构造函数,用于在创建模板类__list_node的对象时进行初始化。
值得注意的是:构造函数的缺省值是T(),调用他自身的构造,如A类,就去调用A本身的构造函数。而内置类型:在C++中也有对应的构造函数。
是用struct来写的,目的是:允许别人访问我的成员
3.模板类list :
- 类型定义
typedef ListNode<T> Node
是为了简化代码,使得在类list
中可以直接使用Node
来指代ListNode<T>
。_head
是一个指向链表头部节点的指针。_size
是一个size_t
类型的值,用来存储链表的长度(即节点个数)。用class来写,目的是:封装,保护
成员变量包括:指向头节点的指针 和 存储链表的长度的size
3.2 list的基本函数:
🔥list()
list()
{
_head = new Node;
_head->prev = _head;
_head->next = _head;
_size = 0;
}
🔥尾插 push_back()
void push_back(const T& x)
{
Node* NewNode = new Node(x);
Node* end = _head->prev;
end->next = NewNode;
NewNode->prev = end;
_head->prev = NewNode;
NewNode->next = _head;
_size++;
}
我们完成了添加元素的基本操作,那么我们如何遍历它呢?
我们需要用迭代器来遍历,这就是我们本篇文章的重点,迭代器的模拟实现。
3.3迭代器的封装和实现:
我们来思考一下,原生的指针能不能帮助我们完成迭代器?
1. 链表是随机存储的,让原生指针++,是不会跳到下一个节点。应该对++进行运算符重载。
2. 对原生指针进行解引用,得不到我们需要的节点中的_val。
所以我们需要对迭代器进行封装。
template<class T>
struct __list_iterator
{
trpedef __list_node<T> Node;
//成员变量
Node* _PtrNode;
__list_iterator(Node* node)
:_PtrNode(node)
{}
};
注意:我们的迭代器是在list类中访问的:
list<int>::iterator it=lt.begin();
我们应该在list类中实现嵌套,对__list_node<T> typedef
template<class T> class list
{
public:
// 这是一个嵌套类型的别名定义。
typedef __list_iterator<T> iterator;
// ...
};
list<int>::iterator it = lt.begin(); 这一行涉及到了所谓的嵌套类型或者内嵌类型。
在C++中,当一个类型(比如 __list_iterator<T>)是在另一个类型的作用域内部定义的(比如 list<T>)时,这个类型被称为嵌套类型。嵌套类型通常用于与外部类型紧密相关联的概念,例如迭代器、节点或其他辅助类。
这里的 iterator 是 list 类的嵌套类型的别名:
template<class T>
class list {
public:
typedef __list_iterator<T> iterator;
};
所以当我们在类外部引用它时,我们需要使用类的名称(在这个例子中是 list<int>),后跟两个冒号来限定作用域,然后是别名 iterator。因此 list<int>::iterator 指的是 __list_iterator<int>,它是链接到特定的 list 实例化类型的迭代器类型
要点是,内嵌类型通常在类定义内部,并且与该类紧密关联。当我们在类外部谈到这些类型时,需要使用类的名称来限定这些类型,就像我们引用 list<int>::iterator 一样。这种设计方式提供了良好的封装和组织结构,在集合和容器类(如 list )中是一种常见做法
list<int>::iterator it1=lt.begin();
vector<int>::iterator it2=v.begin();
string::iterator it3=str.begin();
目前所学的:这三者的底层完全不同,但是上层都用法相同,就可以对他们进行遍历。
在对应的list vector string类中都对iterator typedef了。其实调用的都是不同的迭代器。
迭代器就是一个节点的指针,我们这个类的成员就是_PtrNode(节点指针)
template<class T>
struct __list_iterator
{
trpedef __list_node<T> Node;
//成员变量
Node* _PtrNode;
__list_iterator(Node* node)
:_PtrNode(node)
{}
};
迭代器与list的联系:
我们还需要在list类中给__list_iterator类提供两个接口,lt.begin() 和lt.end()
template<class T>
class list
{
typedef __list_node<T> Node;
typedef __list_iterator<T> iterator;
public:
list()
{
_head = new Node;
_head->prev = _head;
_head->next = _head;
_size = 0;
}
iterator begin()
{
return _head->next;
}
iterator end()
{
return _head;
}
private:
Node* _head;
size_t _size;
};
}
分析代码:
1. 涉及到隐式类型转换:把_head->next和_head节点指针,隐式转换成迭代器。因为迭代器的构造函数是 __list_iterator(Node* node) 单参数的构造函数。2. begin返回第一个数据的迭代器,end返回最后一个数据的下一个位置
回到最初的问题,实现迭代器的++和*等操作:
🔥operator++()和operator--()
这里的++/--,改变指针的位置,挪到下一个/上一个节点。
前置++:
self& operator++()
{
_PtrNode = _PtrNode->next;//节点的指针
return *this; // 迭代器
}
后置++:
self& operator++(int)
{
self tmp(*this);
_PtrNode=_PtrNode->next;
return tmp;
}
这里的浅拷贝是不会出现问题的,因为是内置类型,指针类型。就是为了创建一个新的指向那个位置的指针。
前置--:
self& operator--()
{
_PtrNode = _PtrNode->prev;//节点的指针
return *this; // 迭代器
}
后置--:
self& operator--(int)
{
self tmp(*this);
_PtrNode=_PtrNode->prev;
return tmp;
}
🔥operator*()
直接解引用不能得到我们想要的数据,所以我们需要对解引用操作符重载
T& operator*()
{
return _PtrNode->_val;
}
🔥operator!=()
bool operator!=(self& lt) //it!=lt.end()
{
return _PtrNode != lt._PtrNode;
}
3.4 list函数的完善:
又了迭代器,我们就可以完善list函数了。
🔥insert()
iterator insert(iterator pos,const T& val)
{
Node* newnode = new Node(val);
//pos是__list_iterator类的一个实例化对象,pos._PtrNode是去取对象中的成员
Node* cur = pos._PtrNode;
Node* prev = cur->prev;
// prev newnode cur
prev->next = newnode;
newnode->prev = prev;
cur->prev = newnode;
newnode->next = cur;
_size++;
return newnode;
}
🔥erase()
iterator erase(iterator pos)
{
Node* cur = pos._PtrNode;
Node* prev = cur->prev;
Node* next = cur->next;
prev->next = next;
next->prev = prev;
delete(cur);
_size--;
return next; //erase函数返回的是下一个节点的迭代器
//同样,这里包括隐式类型转换
}
🔥拷贝构造
list(const list<T>& lt)//这个函数需要先完成const迭代器才行,const迭代器后面会有模拟实现
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
_size = 0;
for (auto e : lt) //通过const迭代器实现
{
push_back(e);
}
}
🔥赋值重载
void swap(list<T>& lt)
{
std::swap(_head, lt._head);
std::swap(_size, lt._size);
}
// lt3 = lt1
list<int>& operator=(list<int> lt) //自定义类型传参,调用拷贝构造
{
swap(lt); //交换头节点和_size
return *this;
//出了函数,lt会去调用析构函数,顺便把一开始*this中的数据清除
}
1. 传参时:拷贝构造,得到lt
2. 调用自己写的swap函数,把头节点和_size交换
3. 出了函数,lt 调用析构函数
🔥头尾删插
有了insert和erase函数,我们就可以对push_back/push_front/ pop_back/pop_front进行复用
void push_back(const T& val)
{
insert(end(), val);
}
void push_front(const T& val)
{
insert(begin(), val);
}
void pop_back()
{
erase(--end()); //end()返回的是带头节点
}
void pop_front()
{
erase(begin());
}
🔥与_size相关的函数
//返回_size的大小
size_t size()
{
return _size;
}
//判断是否为空
bool empty()
{
return _size == 0;
}
🔥链表销毁
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it); //_size在erase函数中会减小
}
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
3.5 进一步完善迭代器:
对于下面的这种类
struct A
{
int _a1;
int _a2;
A(int a1 = 0, int a2 = 0)
:_a1(a1)
, _a2(a2)
{}
};
如果list存的是上面的自定义类型呢?
插入有以下几种方法:
void test_list2()
{
list<A> lt;
A aa1(1, 1);
A aa2 = { 1, 1 };
lt.push_back(aa1);
lt.push_back(aa2);
lt.push_back(A(2, 2));
lt.push_back({ 3, 3 });
lt.push_back({ 4, 4 });
}
上面代码使用不同方式来创建和插入 A 类型的对象到自定义的 list 容器中。下面是每种方式的详细说明以及它们所涉及的概念:
有名对象的直接插入:
A aa1(1, 1);
lt.push_back(aa1);
这里,首先创建了一个命名对象 aa1,使用了 A 的构造函数 A(int a1, int a2) 并为其提供了两个参数。然后,你将 aa1 作为参数传递给 lt.push_back() 函数。在这种情况下,aa1 是有名对象(也就是说,它有一个名称),并且 push_back 函数接受到的是 aa1 的一个副本
多参数隐式类型转换:
A aa2 = { 1, 1 };
lt.push_back(aa2);
aa2 通过列表初始化的方式被创建。这里的列表初始化允许直接用花括号 {} 来初始化对象。C++11 引入的列表初始化特性可以用来初始化任何对象,包括具有构造函数的对象。创建了 aa2 有名对象并将其插入到列表中
通过构造函数创建匿名对象并插入:
lt.push_back(A(2, 2));
在这里,没有给新创建的 A 对象一个名字,因此它是一个匿名对象(也称作临时对象)。这个匿名的 A 对象是通过调用它的构造函数来直接初始化的,并立即被传递到 push_back 函数中。
通过隐式类型转换创建匿名对象并插入:
lt.push_back({ 3, 3 });
与第三种方式类似,创隐式类型转换建了一个匿名的 A 对象,但这次是通过。初始化时没有使用相应类型的构造函数,而是依赖编译器生成的代码来创建一个具有给定初始化列表的对象,并将其传递给 push_back 函数。
在所有这些情况中,实际插入到 list 容器中的都是 A
现在我们来进行遍历:
list<A>::iterator it = lt.begin();
while (it != lt.end())
{
cout<<*it<<" ";
++it;
}
A是自定义类型,不支持留插入,我们解引用得到的_data是A的对象
这里数据是公有的,解引用得到的可以通过.访问符进行访问
cout << (*it)._a1 << ":" << (*it)._a2 << endl;
这种访问方式相当于这种形式:
A* ptr = &aa1;
(*ptr)._a1;
这种指针访问行为十分复杂,我们可以重载一个函数使实现这种访问方式:
ptr->_a1;
在迭代器中重载->运算符
T* operator->()
{
return &_node->_data;
}这里返回的是_data的地址
我们就可以这样访问:
cout << it->_a1 << ":" << it->_a2 << endl;
实际上它的访问方式如下:
cout << it.operator->()->_a1 << ":" << it.operator->()->_a2 << endl;
注意:
这里隐藏了一个箭头,一个是重载,一个是原生指针的访问操作
实际上,并不需要在代码中写两次箭头。这是因为在 C++ 中,operator-> 有一个特殊的规则
当重载 operator->,不会直接返回成员的值,而是应该返回一个指针,这个指针指向的对象包含我们想要访问的成员。当使用 ->运算符时,C++ 会自动和透明地调用重载的 operator-> 并继续 “链式” 访问成员,而不需要程序员显示地添加多余的箭头。
这是如何工作的:
1. 如果有一个用户自定义类型的对象(比如迭代器)it,并且我们调用 it->member,编译器会查找这个类型是否有 operator->
2. 如果这个类型有一个 operator-> 的重载,编译器就调用 it.operator->()
3. it.operator->() 应该返回一个指向对象的指针,这个对象有一个我们尝试访问的 member 成员
4. 编译器取得这个指针,并继续访问 member
这个流程只需要一个 -> 即可。编译器在背后处理了所有操作,直到访问到一个实际对象,然后这个对象的成员可以直接通过 -> 运算符访问
因此,我们不需要手动写成 it->->member;只需写 it->member 即可。编译器会自动将其 “转换” 为 it.operator->()->member
在 ListIterator 示例里,it->_a1 意味着:
1. 调用 it.operator->() 拿到 ListNode 中 _data 成员的地址(这是一个 A 类型的对象)。
2. 使用返回的指针来访问 A 对象的 _a1 成员。
整个过程对于编程者来说是透明的,不需要编写多个 ->。这种处理方式使得重载 -> 可以更自然地使用,就像处理普通的指针一样。
const迭代器
我们上面写的迭代器对于const对象是无法编译成功的,const不能调用非const成员函数
对于const类迭代器,我们需要在list类里面重新增加重载:
typedef _const__list_iterator<T> const_iterator;
const_iterator begin() const
{
return _head->_next;
}
const_iterator end() const
{
return _head;
}
这样子我们就有两个版本:iterator 和 const_iterator
普通对象调用iterator版本 const对象调用iterator版本
1. 这里的const版本不是对普通迭代器进行const修饰。(想象当我们使用const修饰指针,指针不能移动,指针指向的内容可以修改,不符合我们的预期)
2. const迭代器是一个重新定义的类型,本身可以修改,指向的内容不可以修改
总结:const_interator 是一个单独的类,我们要实现的是 不能修改数据的(不能改变迭代器指向内容的值),而不是不能移动的迭代器(让迭代器++/--等等操作,这样子就需要能改变迭代器的值)。
方法一:单独再实现一个const_iterator类
template<class T>
struct _const__list_iterator
{
typedef __list_node<T> Node;
typedef _const__list_iterator<T> self;
//成员变量
Node* _PtrNode;
_const__list_iterator(Node* x)
:_PtrNode(x)
{}
self& operator++()
{
_PtrNode = _PtrNode->next;//节点的指针
return *this; // 迭代器
}
self& operator++(int)
{
self tmp(*this);
_PtrNode = _PtrNode->next;
return tmp;
}
self& operator--()
{
_PtrNode = _PtrNode->prev;//节点的指针
return *this; // 迭代器
}
self& operator--(int)
{
self tmp(*this);
_PtrNode = _PtrNode->prev;
return tmp;
}
const T& operator*()
{
return _PtrNode->_val;
}
bool operator!=(const self& it) //it!=lt.end()
{
return _PtrNode != it._PtrNode;
}
const T* operator->()
{
return &_PtrNode->_val;
}
};
合并两中迭代器:
这里仅仅只是两种返回类型不同,这里我们利用模版来对这里内容进行合并
template<class T,class Ref,class Ptr>
struct __list_iterator
{
typedef __list_node<T> Node;
typedef __list_iterator<T,Ref,Ptr> self;
//成员变量
Node* _PtrNode;
__list_iterator(Node* x)
:_PtrNode(x)
{}
Ref operator*()
{
return _PtrNode->_val;
}
Ptr operator->()
{
return &_PtrNode->_val;
}
self& operator++()
{
_PtrNode = _PtrNode->next;//节点的指针
return *this; // 迭代器
}
self& operator++(int)
{
self tmp(*this);
_PtrNode=_PtrNode->next;
return tmp;
}
self& operator--()
{
_PtrNode = _PtrNode->prev;//节点的指针
return *this; // 迭代器
}
self& operator--(int)
{
self tmp(*this);
_PtrNode = _PtrNode->prev;
return tmp;
}
bool operator!=(const self& it) //it!=lt.end()
{
return _PtrNode != it._PtrNode;
}
};
我们只提取不同的部分,其他部分与原来相同
Ref代表引用,Ptr代表指针
让我们来看一下这个合并后的迭代器的模板参数:
-
T:列表节点存储的数据类型
-
Ref:通过迭代器访问数据时的返回类型,可以是T&或者const T&。
-
Ptr:通过迭代器访问数据的指针类型,可以是T*或者const
这样,我们可以创建一个常量迭代器,为Ref
和Ptr
参数指定常量类型,例如:
__list_iterator<T, const T&, const T*> const_iterator;
对于非常量迭代器,就简单地传递非常量类型的引用和指针:
__list_iterator<T, T&, T*> iterator;
在list
类中,我们需要相应地声明两种类型的迭代器:
template<class T>
class list
{
public:
typedef __list_node<T> Node;
typedef __list_iterator<T,T&,T*> iterator;
typedef __list_iterator<T,const T&,const T*> const_iterator;
list()
{
_head = new Node;
_head->prev = _head;
_head->next = _head;
_size = 0;
}
iterator begin()
{
return _head->next;
}
iterator end()
{
return _head;
}
const_iterator begin() const
{
return _head->next;
}
const_iterator end() const
{
return _head;
}
iterator insert(iterator pos,const T& val)
{
Node* newnode = new Node(val);
Node* cur = pos._PtrNode;//pos是__list_iterator类的一个实例化对象,pos._PtrNode是去取对象中的成员
Node* prev = cur->prev;
// prev newnode cur
prev->next = newnode;
newnode->prev = prev;
cur->prev = newnode;
newnode->next = cur;
_size++;
return newnode;
}
iterator erase(iterator pos)
{
Node* cur = pos._PtrNode;
Node* prev = cur->prev;
Node* next = cur->next;
prev->next = next;
next->prev = prev;
delete(cur);
_size--;
return next;
}
void push_back(const T& val)
{
insert(end(), val);
}
void push_front(const T& val)
{
insert(begin(), val);
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
size_t size()
{
return _size;
}
bool empty()
{
return _size == 0;
}
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it); //_size在erase函数中会减小
}
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
private:
Node* _head;
size_t _size;
};
}
list
类中的其他成员函数像begin
、end
需要按照是否接收常量类型来适配这两种迭代器。
list<int>::const_iterator it=lt.begin();
list<int>:: iterator it=lt.begin();
需要在list<int>类里面访问
通过左边的类型,推出右边是用哪一种迭代器。
模拟实现list的源码,仅供参考,纯手打的代码,可能会有bug,还请各位大佬批评指正。
namespace moon
{
template<class T>
struct __list_node
{
T _val;
__list_node<T>* prev;
__list_node<T>* next;
__list_node(const T& x=T())
:_val(x)
,prev(nullptr)
,next(nullptr)
{}
};
template<class T,class Ref,class Ptr>
struct __list_iterator
{
typedef __list_node<T> Node;
typedef __list_iterator<T,Ref,Ptr> self;
//成员变量
Node* _PtrNode;
__list_iterator(Node* x)
:_PtrNode(x)
{}
Ref operator*()
{
return _PtrNode->_val;
}
Ptr operator->()
{
return &_PtrNode->_val;
}
self& operator++()
{
_PtrNode = _PtrNode->next;//节点的指针
return *this; // 迭代器
}
self& operator++(int)
{
self tmp(*this);
_PtrNode=_PtrNode->next;
return tmp;
}
self& operator--()
{
_PtrNode = _PtrNode->prev;//节点的指针
return *this; // 迭代器
}
self& operator--(int)
{
self tmp(*this);
_PtrNode = _PtrNode->prev;
return tmp;
}
bool operator!=(const self& it) //it!=lt.end()
{
return _PtrNode != it._PtrNode;
}
};
template<class T>
class list
{
public:
typedef __list_node<T> Node;
typedef __list_iterator<T,T&,T*> iterator;
typedef __list_iterator<T,const T&,const T*> const_iterator;
list()
{
_head = new Node;
_head->prev = _head;
_head->next = _head;
_size = 0;
}
iterator begin()
{
return _head->next;
}
iterator end()
{
return _head;
}
const_iterator begin() const
{
return _head->next;
}
const_iterator end() const
{
return _head;
}
iterator insert(iterator pos,const T& val)
{
Node* newnode = new Node(val);
Node* cur = pos._PtrNode;//pos是__list_iterator类的一个实例化对象,pos._PtrNode是去取对象中的成员
Node* prev = cur->prev;
// prev newnode cur
prev->next = newnode;
newnode->prev = prev;
cur->prev = newnode;
newnode->next = cur;
_size++;
return newnode;
}
iterator erase(iterator pos)
{
Node* cur = pos._PtrNode;
Node* prev = cur->prev;
Node* next = cur->next;
prev->next = next;
next->prev = prev;
delete(cur);
_size--;
return next;
}
void push_back(const T& val)
{
insert(end(), val);
}
void push_front(const T& val)
{
insert(begin(), val);
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
size_t size()
{
return _size;
}
bool empty()
{
return _size == 0;
}
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it); //_size在erase函数中会减小
}
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
private:
Node* _head;
size_t _size;
};
}
本篇list的使用与模拟实现,纯干货!!!