当前位置: 首页 > news >正文

【C++】List容器模拟实现(超详细)

目录

一、前言

二、List模拟实现思想

2.1 Node节点类

2.2 Iterator迭代器类

2.3 list类

三、Node节点类的设计

四、list类的设计

五、push_back函数的实现

六、size()函数和empty()函数的实现

七、iterator类的设计

7.1 iterator的成员变量和构造函数

7.2 解引用操作符重载

7.3 前置++操作符重载

7.4 前置--运算符重载

7.5 !=操作符重载

7.6 ==操作符重载

7.7 后置++操作符重载

7.8 后置--操作符重载

7.9 ->操作符重载

八、list类里面的迭代器

8.1 begin()函数的实现

8.2 end()函数的实现

8.3 测试以上写的代码来遍历list

九、insert函数的实现

十、push_back和push_front函数的实现

十一、erase函数的实现

十二、pop_back和pop_front函数的实现

十三、测试代码

十四、const迭代器的实现

14.1 问题引入

14.2 const迭代器的写法

14.3 const迭代器的实现

14.4 按需实例化的概念

十五、iterator类的最终实现

十六、迭代器失效问题

十七、list的析构函数

17.1 clear()函数

17.2 析构函数

十八、拷贝构造函数

18.1 问题引入

18.2 拷贝构造函数的实现

十九、赋值运算符重载

二十、构造函数的实现

20.1 默认构造函数

20.2 构造并初始化n个value

20.3 使用迭代器区间构造初始化

20.4 使用大括号来构造list

二十一、代码

21.1 list.h

21.2 test.cpp


一、前言

STL 中的 list 是一个带头双向循环链表,本文将会带大家一起从0到1 去模拟实现STL库中的 list 容器,以便于让大家更好的巩固之前学习过的 缺省参数、封装、类的6大默认函数,模板等。

如果大家还有不太了解 list  容器的可以先看看两篇文章:

1.双向链表的实现

2.list容器的理解和使用

二、List模拟实现思想

2.1 Node节点类

节点类是表示list中的每一个节点,有指向前一个数据的指针,数据,和指向下一个节点的指针。

2.2 Iterator迭代器类

由于list中的每一个元素都是离散存放的,所以就不能通过原生指针来实现迭代器,我们可以通过创建一个iterator类来表示迭代器,里面存放节点的指针,然后重载一些运算符来达到库里面迭代器到达的效果。

2.3 list类

存放链表的头指针,我们通过这个头指针就能够访问链表了。

三、Node节点类的设计

链表里面的每一个元素都是一个节点,所以我们需要创建一个list_node的类,来表示每一个节点。

定义如下:

#pragma once
namespace zx{template<class T>struct list_node{T _data;            //节点的数据list_node<T>* _prev; //节点的前指针list_node<T>* _next; //节点的后指针list_node(const T& x)//初始化列表进行初始化:_data(x),_next(nullptr),_prev(nullptr){}};
}

        首先,我们在自己的命名空间内模拟实现 list(为了防止与库冲突),上面的代码就是list节点的结构。在这里是用并没有使用 class,因为 struct 默认访问权限是 public,又因为节点是需要经常访问的,所以使用struct更好。

        我们将这个类加上 template<class T> 后,就能够实现节点存储不同类型的数据,这也是C++模板的好处。

四、list类的设计

存放链表的头指针,我们通过这个头指针就能够访问链表了。

定义如下:迭代器下面再加,一步一步来

template<class T>
class list 
{typedef list_node<T> Node;
public:list() {_head = new Node;_head->next = _head;_head->prev = _head;}
private:Node* _head;
};
  • 因为 list 可以存储各种类型的元素,因此 list 类要设置为模板,T就是存储的元素的类型。
  • 因为 结点类的类名太长,所以用 typedef 关键为它们取了别名。

五、push_back函数的实现

我们先来实现一个简单的函数:尾插,我们想要在list链表尾插一个数据,就需要找到最后一个节点,因为我们的list是双向链表,头节点的前一个节点就是尾节点。

代码如下:

void push_back(const T& x) {Node* newnode = new Node(x);  //创建一个新节点Node* tail = _head->_prev;  //找尾tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;
}

六、size()函数和empty()函数的实现

在链表中,我们想要获取链表中的元素个数需要遍历整个链表,非常麻烦,而且有时候还经常使用size。所以我们可以在list里面增加一个成员变量_size来表示链表的节点个数,所以就要修改以上代码如下所示:

template<class T>
class list 
{typedef list_node<T> Node;
public:list() {_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}void push_back(const T& x) {Node* newnode = new Node(x);  //创建一个新节点Node* tail = _head->_prev;  //找尾tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;++_size;}size_t size() const{return _size;}size_t empty() const{return _size==0;}
private:Node* _head;size_t size;
};

七、iterator类的设计

假设我们在我们所写的list里面push_back了几个数据,那我们怎么来遍历这个list呢?在list的理解和使用里面我们讲过遍历list的方法主要有两个一个是迭代器,一个是范围for,但是范围for是以迭代器为基础的,有了迭代器才能实现范围for。

迭代器如何才能实现呢?能用原生指针来实现嘛?用原生指针指向每一节点的地址,虽然能够通过解引用操作符*来访问到数据,但是list中的节点是不连续的,++这个地址能到达下一个节点的地址嘛?使用节点的指针显然不能满足我们需求,

所以我们就需要创建一个iterator类来实现迭代器,然后通过运算符重载来实现*,->,==,--等一些操作。

迭代器类的意义就是,让使用者可以不必关心容器的底层实现,可以用简单统一的方式对容器内的数据进行访问。
既然 list 的结点指针的行为不满足迭代器定义,那么我们可以对这个结点指针进行封装,对结点指针的各种运算符操作进行重载,使得我们可以用和string和vector当中的迭代器一样的方式使用list当中的迭代器。例如,当你使用 list 当中的迭代器进行自增操作时,实际上执行了p = p->next语句,只是你不知道而已。

总结:list迭代器类,实际上就是对结点指针进行了封装,对其各种运算符进行了重载使得结点指针的各种行为看起来和普通指针一样。(例如,对结点指针自增就能指向下一个节点) 

7.1 iterator的成员变量和构造函数

在iterator类里面,成员变量是节点的指针,然后通过运算符重载来模拟实现元素指针的效果。

代码如下:

template<class T>
struct list_iterator {typedef list_node<T> Node;list_iterator(Node* node):_node(node){}Node* _node;
};

7.2 解引用操作符重载

假如我们现在有一个迭代器对象 it,it里面有节点的指针,我们希望通过解引用操作符来得到这个数据,这时我们就可以使用运算符重载来访问到这个数据了。

代码如下:

T& operator*() {return _node->_data;
}

7.3 前置++操作符重载

list中的元素虽然是离散存放的,但是我们的节点有指向下一个节点的指针,于是可以通过重载++运算符来改变迭代器里面的成员变量,让迭代器的_node指向下一个节点。

代码如下:

list_iterator<T>& operator++() {_node = _node->_next;return *this;
}

但是list_iterator<T>&这个类型名字太长了,我们可以typedef一下,如下所示:

template<class T>
struct list_iterator {typedef list_node<T> Node;typedef list_iterator<T> Self;list_iterator(Node* node):_node(node){}Node* _node;T& operator*() {return _node->_data}Self& operator++() {_node = _node->_next;return *this;}
};

7.4 前置--运算符重载

list中的元素虽然是离散存放的,但是我们的节点有指向前一个节点的指针,于是可以通过重载--运算符来改变迭代器里面的成员变量,让迭代器的_node指向前一个节点。

代码如下:

Self& operator--() {_node = _node->_prev;return *this;
}

7.5 !=操作符重载

如果两个迭代器对象的_node不相同,说明他们两个就不相等。

代码如下:

bool operator!=(const Self& s) const
{return _node != s._node;
}

7.6 ==操作符重载

如果两个迭代器对象的_node相同,说明他们两个就相等。

代码如下:

bool operator!=(const Self& s) const
{return _node == s._node;
}

7.7 后置++操作符重载

Self operator++(int) {//使用了拷贝构造,我们没有写拷贝构造,编译器会默认生成一个浅拷贝的拷贝构造,//我们新创建一个iteartor对象也是需要指向这个对象的,使用浅拷贝也能完成//并不是有指针就需要深拷贝,而是看指针指向的资源是不是属于我的,指针指向的资源是属于链表的Self tmp(*this);_node = _node->_next;return tmp;
}

使用了拷贝构造,我们没有写拷贝构造,编译器会默认生成一个浅拷贝的拷贝构造,我们新创建一个iteartor对象也是需要指向这个对象的,使用浅拷贝也能完成。并不是有指针就需要深拷贝,而是看指针指向的资源是不是属于我的,指针指向的资源是属于链表的,

同理,也不用写析构函数来释放节点,这个节点是属于链表的,迭代器对象只是指向这个节点的。

7.8 后置--操作符重载

Self operator--(int) {Self tmp(*this);_node = _node->_prev;return tmp;
}

7.9 ->操作符重载

有时候,实例化的模板参数是自定义类型,我们想要像 指针 一样访问访问自定义类型力的成员变量,这样显得更通俗易懂,所以就要重载 -> 运算符它的返回值是 T* 

想想如下场景: 

当 list容器 当中的每个结点存储的不是内置类型,而是自定义类型,例如数据存储类,那么当我们拿到一个位置的迭代器时,我们可能会使用 ->运算符访问 struct 的成员: 

代码如下:

void test3() {list<AA> listA;listA.push_back(AA());listA.push_back(AA());listA.push_back(AA());listA.push_back(AA());list<AA>::iterator it = listA.begin();while (it != listA.end()) {cout << *it << " ";++it;}
}

代码里面的*it是取出AA类型的对象,但是因为AA里面没有重载<<运算符,所以不能输出,所以我们需要修改代码,如下所示:

void test3() {list<AA> listA;listA.push_back(AA());listA.push_back(AA());listA.push_back(AA());listA.push_back(AA());list<AA>::iterator it = listA.begin();while (it != listA.end()) {cout << (*it)._a1 << ":" << (*it)._a2 << endl;++it;}
}

虽然使用.运算符可以访问自定义类型里面的数据,但是我们的迭代器it是指向这个节点的,能不能使用->来获取到里面的数据呢?如下所示:

void test3() {list<AA> listA;listA.push_back(AA());listA.push_back(AA());listA.push_back(AA());listA.push_back(AA());list<AA>::iterator it = listA.begin();while (it != listA.end()) {//cout << (*it)._a1 << ":" << (*it)._a2 << endl;cout << it->_a1 << ":" << it->_a2 << endl;++it;}
}

上面的代码运行会报错,因为迭代器里面没有->运算符重载,所以我们就需要写一个->运算符重载函数,如下所示:

T* operator->() {return &_node->_data;
}
  • operator->() 存在的意义:使得 迭代器 访问自定义类型中的成员时更加方便
  • 如果没有这个函数,只能通过 (*迭代器).成员 的方式进行成员访问,很不方便

我们来解释这一段代码,首先_node->-data指向的是list中的节点,然后返回的是这个节点的地址,返回值就是T*类型的指针,这个指针就指向这个节点,然后我们外面就可以通过这个T*类型的地址来使用->来访问自定义类型里面的数据了。

但是你仔细看外面的调用函数:cout << it->_a1 << ":" << it->_a2 << endl;

it->返回的是节点的地址,但是这样能访问到_a1嘛?不是应该需要在加一个->嘛?如下所示:

cout << it->->_a1 << ":" << it->->_a2 << endl;

第一个箭头函数返回的是节点的地址,然后节点的地址在使用箭头来访问里面的数据,按理说应该是两个箭头才对呀,为什么我们这样能运行成功呢?

这是因为编译器做了优化,两个箭头不好看,省略了一个箭头,写两个箭头会报错,我们转化为下面这种方式更容易理解:cout << it.operator->()->_a1 << ":" << it.operator->()->_a2 << endl;

这样写就容易看一点,第一个箭头是运算符重载,第二个箭头是原生指针的取数据。它等价于写一个箭头的方式,为了可读性。

cout << it.operator->()->_a1 << ":" << it.operator->()->_a2 << endl;

cout << it->_a1 << ":" << it->_a2 << endl;

两个功能是一样的。

八、list类里面的迭代器

在实现上面迭代器类里面的功能之后,我们就可以简单的使用迭代器了。

在list类里面typedef迭代器的名字,主要我们这个迭代器是需要给外部使用的,所以需要放到public里面,因为Node是不会让外面访问的,所以它的限定符是private。

typedef list_iterator<T> iterator;

8.1 begin()函数的实现

这个函数的作用是返回list中第一个元素的迭代器。有三种写法:

方式一:创建一个iterator对象,然后返回这个对象

iterator begin() {iterator it(_head->_next);return it
}

方式二:使用匿名对象

iterator begin() {return iterator(_head->_next);
}

方式三:使用隐式类型转换

iterator begin() {return _head->_next;
}

8.2 end()函数的实现

这个函数的作用是返回list中最后一个元素的下一个位置迭代器,也就是头节点位置的迭代器。同样的也有三种写法:

 方式一:创建一个iterator对象,然后返回这个对象

iterator end() {iterator it(_head);return it
}

方式二:使用匿名对象

iterator end() {return iterator(_head);
}

方式三:使用隐式类型转换

iterator end() {return _head;
}

8.3 测试以上写的代码来遍历list

代码如下:

void test1() {list<int> mylist1;mylist1.push_back(1);mylist1.push_back(2);mylist1.push_back(3);mylist1.push_back(4);list<int>::iterator it = mylist1.begin();while (it != mylist1.end()) {cout << *it << " ";++it;}cout << endl;
}

我们运行代码之后,发现有一个bug如下所示:

这是因为我们在new头节点的时候没有传节点的值,Node节点类里面的构造函数需要传入一个节点的值来构造一个节点。有两种解决方案

方案一:在new头节点的时候传入匿名对象

list() {_head = new Node(T());_head->_next = _head;_head->_prev = _head;_size = 0;
}

方案二:在Node节点的构造函数里面写默认参数,默认参数是该类型的默认构造(推荐使用)

list_node(const T& x=T())//初始化列表进行初始化:_data(x), _next(nullptr), _prev(nullptr)
{}

运行代码如下所示:

我们这里使用迭代器来遍历链表,我们之前学习string迭代器和vector迭代器来遍历容器,从表层的来看,他们都是类似的,但是从底层来看天差地别,list迭代器是封装了一个list_iterator类来实现的,

九、insert函数的实现

在某一个位置之前插入一个元素和尾插差不多,都是需要找前一个节点,因为list底层是双向链表,所以非常容易找到前一个节点,代码如下:

void insert(iterator pos, const T& x)
{Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);newnode->_next = cur;newnode->_prev = prev;prev->_next = newnode;cur->_prev = newnode;++_size;
}

十、push_back和push_front函数的实现

在有了insert函数之后,push_back和push_front函数就可以调用insert函数来实现尾插和头插,代码如下:

void push_back(const T& x) {//Node* newnode = new Node(x);  //创建一个新节点//Node* tail = _head->_prev;  //找尾//tail->_next = newnode;//newnode->_prev = tail;//newnode->_next = _head;//_head->_prev = newnode;//++_size;insert(end(), x);
}
void push_front(const T& x)
{insert(begin(), x);
}

十一、erase函数的实现

void erase(iterator pos)
{assert(pos != end());Node* prev = pos._node->_prev;Node* next = pos._node->_next;prev->_next = next;next->_prev = prev;delete pos._node;--_size;
}

十二、pop_back和pop_front函数的实现

在有了erase函数之后,pop_back和pop_front函数就可以调用erase函数来实现尾删和头删,代码如下:

void pop_back() {erase(--end());
}
void pop_front() {erase(begin());
}

十三、测试代码

void test2() {list<int> mylist1;//测试尾插mylist1.push_back(1);mylist1.push_back(2);mylist1.push_back(3);mylist1.push_back(4);//测试头插mylist1.push_front(10);mylist1.push_front(20);//测试迭代器和运算符重载list<int>::iterator it = mylist1.begin();while (it != mylist1.end()) {cout << *it << " ";++it;}cout << endl;//测试插入list<int>::iterator it1 = mylist1.begin();mylist1.insert(++it1, 999);//测试删除list<int>::iterator it2 = mylist1.begin();++it2;mylist1.erase(++it2);for (auto ele : mylist1) {cout << ele << " ";}cout << endl;//测试头删mylist1.pop_front();//测试尾删mylist1.pop_back();for (auto ele : mylist1) {cout << ele << " ";}cout << endl;
}

运行如下所示:

十四、const迭代器的实现

14.1 问题引入

现在我们写一个打印容器的代码如下所示,

template<class container>
void print_container(const container& con) 
{for (auto ele : con) {cout << ele << " ";}cout << endl;
}

然后写一个测试代码来测试这个函数是否能打印,如下所示:

void test4() 
{list<int> mylist1;mylist1.push_back(1);mylist1.push_back(2);mylist1.push_back(3);mylist1.push_back(4);print_container(mylist1);
}

我们发现运行之后会报错,这是为什么呢?

我们将这个print_container函数给注释,然后再test4函数里面使用范围for打印一下,如下所示:

void test4() 
{list<int> mylist1;mylist1.push_back(1);mylist1.push_back(2);mylist1.push_back(3);mylist1.push_back(4);//print_container(mylist1);for (auto ele : mylist1) {cout << ele << " ";}cout << endl;
}

发现这样能够正常运行,如下所示:

这是为什么呢?

print_container函数里面是也是使用范围for来遍历容器的,为什么不能正常运行呢?

这是因为:我们将这个容器传入给print_container函数,并且形式参数还加了引用和const,范围for本质就是迭代器遍历,const容器需要调用const迭代器,我们还没有实现const迭代器。所以就运行不了。

14.2 const迭代器的写法

看以上的print_container函数的代码,我们将里面的范围for换成迭代器,如下所示:

template<class container>
void print_container(const container& con) 
{list<int>::iterator it = mylist1.begin();while (it != mylist1.end()) {cout << *it << " ";++it;}
}

为什么const迭代器不能写成 const list<int>::iterator it = mylist1.begin();呢?而是需要写成const_iterator呢?

const iterator:给迭代器加const修饰

const_iterator:这里的const_iterator是单独的一个类型,

为什么const迭代器不是普通迭代器前面加const呢?

首先我们要理解const迭代器是迭代器不能修改还是迭代器指向的值不能修改,要弄清上面的问题,就需要理解这个问题。

const iterator:是迭代器本身不能修改

const_iterator:是迭代器指向的内容不能修改

我们要实现的是迭代器指向的内容不能修改而不是迭代器本身不能修改,所以就需要使用const_iterator,而不是const iterator。

14.3 const迭代器的实现

如何让迭代器指向的类容不能修改呢?

我们在迭代器里面访问节点主要是通过这两个函数来实现的:

T& operator*() {return _node->_data;
}
T* operator->() {return &_node->_data;
}

operator*返回这个数据的引用,operator返回的是这个数据的指针。如果我们在返回值前面加上const那是不是就不能修改了呢?如下所示:

const T& operator*() {return _node->_data;
}
const T* operator->() {return &_node->_data;
}

const T& operator*()返回的是const别名,

const T* operator->() 返回的是const指针,

这样就都不能修改了。

那现在我们如何实现const_iterator呢?

一个简单的做法就是再创建一个list_const_iterator类来表示const_iterator,代码和list_iterator基本上相同,代码如下所示:

template<class T>
struct list_const_iterator {typedef list_node<T> Node;typedef list_const_iterator<T> Self;list_const_iterator(Node* node):_node(node){}Node* _node;const T& operator*() {return _node->_data;}const T* operator->() {return &_node->_data;}Self& operator++() {_node = _node->_next;return *this;}Self& operator--() {_node = _node->_prev;return *this;}Self operator++(int) {//使用了拷贝构造,我们没有写拷贝构造,编译器会默认生成一个浅拷贝的拷贝构造,//我们新创建一个iteartor对象也是需要指向这个对象的,使用浅拷贝也能完成//并不是有指针就需要深拷贝,而是看指针指向的资源是不是属于我的,指针指向的资源是属于链表的Self tmp(*this);_node = _node->_next;return tmp;}Self operator--(int) {Self tmp(*this);_node = _node->_prev;return tmp;}bool operator!=(const Self& s) const{return _node != s._node;}bool operator==(const Self& s) const{return _node == s._node;}
};

然后在list里面typedef一下,如下所示:

        typedef list_iterator<T> iterator;//普通迭代器
        typedef list_const_iterator<T> const_iterator;//const迭代器

然后再加两个const版本的begin和end函数如下所示:

const_iterator begin() const
{return _head->_next;
}
const_iterator end() const
{return _head;
}

当我们的list容器时const容器的时候,就会走这两个函数,返回的迭代器就是const迭代器。

然后修改我们的print_container函数如下所示:

template<class container>
void print_container(const container& con) 
{list<int>::const_iterator it = con.begin();while (it != con.end()) {cout << *it << " ";++it;}
}

测试代码如下所示:

void test4() 
{list<int> mylist1;mylist1.push_back(1);mylist1.push_back(2);mylist1.push_back(3);mylist1.push_back(4);print_container(mylist1);
}

运行如下所示:

迭代器能够实现,范围for肯定也能实现。

14.4 按需实例化的概念

请查看print_container函数的代码:

template<class container>
void print_container(const container& con) 
{list<int>::const_iterator it = con.begin();while (it != con.end()) {*it+=10;cout << *it << " ";++it;}
}

如果我们的迭代器是const迭代器,在while循环里面写了*it+=10;在不执行这个函数的情况下会报错嘛?

运行试一下。

发现能够正常运行。

这就是按需实例化的概念,当我们没有执行函数模板的时候,是不会实例化的,里面有的错误可以是不会被检查出来的,有的比较大的错误也是会被检查出来,如缺少分号。编译器看见模板的时候会简单的扫一眼,只能看出比较明显的错误,有些错误在没有实例化是不会看出来的。如果我们执行这个函数就会报错,如下所示:

十五、iterator类的最终实现

在上面我们使用两个类list_node和list_const_iterator来模拟实现迭代器,虽然能够实现迭代器的功能,但是这两个类里面的大部分功能都是相同的,相似度太高了,能不能想办法解决一下呢?

我们来查看库里面是如何实现的?

库里面是增加了两个模板参数,然后typedef普通迭代器和const迭代器。假如我们的T是int类型,普通迭代器的参数就是int,int&,int*;const迭代器的参数就是int,const int&,const int*。然后在实现*和->运算符重载的时候,使用这个模板参数。这样就不用写两个类了。虽然我们不用写两个类,但是编译器还是帮我们实例化出两个不同的类。

代码如下:

template<class T,class Ref,class Ptr>
struct list_iterator {typedef list_node<T> Node;typedef list_iterator<T,Ref,Ptr> Self;list_iterator(Node* node):_node(node){}Node* _node;Ref operator*() {return _node->_data;}Ptr operator->() {return &_node->_data;}Self& operator++() {_node = _node->_next;return *this;}Self& operator--() {_node = _node->_prev;return *this;}Self operator++(int) {//使用了拷贝构造,我们没有写拷贝构造,编译器会默认生成一个浅拷贝的拷贝构造,//我们新创建一个iteartor对象也是需要指向这个对象的,使用浅拷贝也能完成//并不是有指针就需要深拷贝,而是看指针指向的资源是不是属于我的,指针指向的资源是属于链表的Self tmp(*this);_node = _node->_next;return tmp;}Self operator--(int) {Self tmp(*this);_node = _node->_prev;return tmp;}bool operator!=(const Self& s) const{return _node != s._node;}bool operator==(const Self& s) const{return _node == s._node;}
};

然后修改list里面的typedef,如下所示:

//typedef list_iterator<T> iterator;//普通迭代器
//typedef list_const_iterator<T> const_iterator;//const迭代器
typedef list_iterator<T, T&, T*> iterator;//普通迭代器
typedef list_iterator<T, const T&, const T*> const_iterator;//const迭代器

十六、迭代器失效问题

1.我们在链表某一个节点位置之前插入一个节点,会导致后续的迭代器失效嘛?

显然是不会的,链表在内存中存放是离散存放的,每个节点都不会相互影响,而vector和string在内存中是连续存放的,在一个位置插入数据,会导致后续的元素位置发生变化,会导致迭代器失效。

2.如果我们在链表中删除某一个元素,会导致迭代器失效嘛?

erase会导致这个要删除元素的迭代器失效,因为这个节点delete了,这个迭代器指向的元素就不存在了,所以erase需要返回下一个位置的迭代器,因此我们需要修改erase给他加返回值,如下所示:

iterator erase(iterator pos)
{assert(pos != end());Node* prev = pos._node->_prev;Node* next = pos._node->_next;prev->_next = next;next->_prev = prev;delete pos._node;--_size;return iterator(next);
}

十七、list的析构函数

list里面的每一个节点和头节点都是new出来的,所以我们需要写析构函数将这些节点释放,

首先我们实现一个clear函数来释放每一个元素节点,然后再在析构函数里面释放头节点

17.1 clear()函数

void clear()
{auto it = begin();while (it != end()) {it = erase(it);}
}

17.2 析构函数

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

十八、拷贝构造函数

18.1 问题引入

当我们执行以下代码会发生什么:

void test5()
{list<int> mylist1;mylist1.push_back(10);mylist1.push_back(20);mylist1.push_back(30);mylist1.push_back(40);list<int> mylist2(mylist1);print_container(mylist1);print_container(mylist2);
}

运行之后会报错,如下所示:

这是为什么呢?

18.2 拷贝构造函数的实现

上面的错误是因为我们编译器实现的拷贝构造函数时浅拷贝,mylist1和mylist2指向的是同一块内存空间,里面的每一个节点都是同一块空间,程序结束之后会将同一块空间释放两次,发生错误。所以我们要写一个深拷贝的拷贝构造函数,代码如下:

list(const list<T>& lt)
{for (auto& e : lt) {push_back(e);}
}

代码解释:我们可以遍历lt容器,然后将每一个元素尾插到新的list中。

但是这样写是错的,这是因为新的list是没有头结点的,list<int> mylist2(mylist1);将mylist1拷贝构造给mylist2,然后执行上面的代码,此时mylist2是没有头结点的,没有头结点就一直尾插,所以会发生错误。于是就要修改默认构造函数和拷贝构造函数了。

代码如下:

void empty_init()
{_head = new Node();_head->_next = _head;_head->_prev = _head;_size = 0;
}
list() 
{empty_init();
}
//lt2<lt1>
list(const list<T>& lt)
{empty_init();for (auto& e : lt) {push_back(e);}
}

十九、赋值运算符重载

和拷贝构造函数一样,编译器默认生成的赋值运算符重载也是浅拷贝的,所以我们也需要自己实现一个深拷贝的赋值运算符重载。如下所示:

//lt1=lt3 赋值运算符重载
list<T>& operator=(list<T> lt) 
{swap(lt);return *this;
}
void swap(list<T>& lt)
{std::swap(_head, lt._head);std::swap(_size, lt._size);
}

测试代码如下:

void test6()
{list<int> mylist1;mylist1.push_back(10);mylist1.push_back(20);mylist1.push_back(30);mylist1.push_back(40);list<int> mylist2;mylist2.push_back(1);mylist2.push_back(2);mylist2.push_back(3);print_container(mylist2);mylist2 = mylist1;print_container(mylist2);

二十、构造函数的实现

20.1 默认构造函数

上面已经写过了,这里之间复制过来

void empty_init()
{_head = new Node();_head->_next = _head;_head->_prev = _head;_size = 0;
}
list() 
{empty_init();
}

20.2 构造并初始化n个value

通过用 n 个 val 来对对象进行初始化,需要注意这里的 T( )是一个匿名对象,作为 val 的缺省参数,因为我们并不知道传给val的是一个对象还是一个整形(或其他),给缺省参数的好处在于,对于自定义类型编译器会去调用自定义类型的构造函数来对val进行初始化,如果是内置类型,它也是有自己的构造函数.代码如下:

//用n个val个构造      			   
list(int n, const T& val = T())
{empty_init();for (int i = 0; i < n; i++){push_back(val);}
}

测试代码如下:

void test7() {list<int> mylist1(6,1);print_container(mylist1);
}

运行结果如下:

20.3 使用迭代器区间构造初始化

由于list可以存储各种类型的元素,所以区间构造时自然也会用到各种类型的迭代器,因此区间构造也应该定义为模版,需要给出模版参数列表。具体实现和上一个函数是差不多的。

代码如下:

//迭代器区间构造
template<class iterator>
list(iterator first, iterator last)
{empty_init();while (first != last){push_back(*first);//尾插数据,会根据不同类型的迭代器进行调用++first;}
}

 测试代码如下:

void test8() {string s1("hello world");list<char> mylist1(s1.begin(),s1.end());print_container(mylist1);
}

运行结果如下:

20.4 使用大括号来构造list

在list的理解和使用里面我们讲解了使用大括号来对list进行构造,我们也可以模型实现一些这个构造,如下所示:

list(initializer_list<int> il)
{empty_init();for (auto& e : il) {push_back(e);}
}

测试代码如下:

void test7() {list<int> mylist1({1,2,3,4,5,6});print_container(mylist1);
}

运行结果如下:

二十一、代码

21.1 list.h

#pragma once
#include<iostream>
using namespace std;
#include<assert.h>
namespace zx {template<class T>struct list_node{T _data;            //节点的数据list_node<T>* _prev; //节点的前指针list_node<T>* _next; //节点的后指针list_node(const T& x=T())//初始化列表进行初始化:_data(x), _next(nullptr), _prev(nullptr){}};//template<class T>//struct list_iterator {//	typedef list_node<T> Node;//	typedef list_iterator<T> Self;//	list_iterator(Node* node)//		:_node(node)//	{//	}//	Node* _node;//	T& operator*() {//		return _node->_data;//	}//	T* operator->() {//		return &_node->_data;//	}//	Self& operator++() {//		_node = _node->_next;//		return *this;//	}//	Self& operator--() {//		_node = _node->_prev;//		return *this;//	}//	//	Self operator++(int) {//		//使用了拷贝构造,我们没有写拷贝构造,编译器会默认生成一个浅拷贝的拷贝构造,//		//我们新创建一个iteartor对象也是需要指向这个对象的,使用浅拷贝也能完成//		//并不是有指针就需要深拷贝,而是看指针指向的资源是不是属于我的,指针指向的资源是属于链表的//		Self tmp(*this);//		_node = _node->_next;//		return tmp;//	}//	Self operator--(int) {//		Self tmp(*this);//		_node = _node->_prev;//		return tmp;//	}//	bool operator!=(const Self& s) const//	{//		return _node != s._node;//	}//	bool operator==(const Self& s) const//	{//		return _node == s._node;//	}//};//template<class T>//struct list_const_iterator {//	typedef list_node<T> Node;//	typedef list_const_iterator<T> Self;//	list_const_iterator(Node* node)//		:_node(node)//	{//	}//	Node* _node;//	const T& operator*() {//		return _node->_data;//	}//	const T* operator->() {//		return &_node->_data;//	}//	Self& operator++() {//		_node = _node->_next;//		return *this;//	}//	Self& operator--() {//		_node = _node->_prev;//		return *this;//	}//	Self operator++(int) {//		//使用了拷贝构造,我们没有写拷贝构造,编译器会默认生成一个浅拷贝的拷贝构造,//		//我们新创建一个iteartor对象也是需要指向这个对象的,使用浅拷贝也能完成//		//并不是有指针就需要深拷贝,而是看指针指向的资源是不是属于我的,指针指向的资源是属于链表的//		Self tmp(*this);//		_node = _node->_next;//		return tmp;//	}//	Self operator--(int) {//		Self tmp(*this);//		_node = _node->_prev;//		return tmp;//	}//	bool operator!=(const Self& s) const//	{//		return _node != s._node;//	}//	bool operator==(const Self& s) const//	{//		return _node == s._node;//	}//};template<class T,class Ref,class Ptr>struct list_iterator {typedef list_node<T> Node;typedef list_iterator<T,Ref,Ptr> Self;list_iterator(Node* node):_node(node){}Node* _node;Ref operator*() {return _node->_data;}Ptr operator->() {return &_node->_data;}Self& operator++() {_node = _node->_next;return *this;}Self& operator--() {_node = _node->_prev;return *this;}Self operator++(int) {//使用了拷贝构造,我们没有写拷贝构造,编译器会默认生成一个浅拷贝的拷贝构造,//我们新创建一个iteartor对象也是需要指向这个对象的,使用浅拷贝也能完成//并不是有指针就需要深拷贝,而是看指针指向的资源是不是属于我的,指针指向的资源是属于链表的Self tmp(*this);_node = _node->_next;return tmp;}Self operator--(int) {Self tmp(*this);_node = _node->_prev;return tmp;}bool operator!=(const Self& s) const{return _node != s._node;}bool operator==(const Self& s) const{return _node == s._node;}};template<class T>class list{typedef list_node<T> Node;public://typedef list_iterator<T> iterator;//普通迭代器//typedef list_const_iterator<T> const_iterator;//const迭代器typedef list_iterator<T, T&, T*> iterator;//普通迭代器typedef list_iterator<T, const T&, const T*> const_iterator;//const迭代器/*iterator begin() {iterator it(_head->_next);return it}*//*iterator begin() {return iterator(_head->_next);}*/iterator begin() {return _head->_next;}iterator end() {return _head;}const_iterator begin() const{return _head->_next;}const_iterator end() const{return _head;}void empty_init(){_head = new Node();_head->_next = _head;_head->_prev = _head;_size = 0;}list() {empty_init();}//用n个val个构造      			   list(int n, const T& val = T()){empty_init();for (int i = 0; i < n; i++){push_back(val);}}//迭代器区间构造template<class iterator>list(iterator first, iterator last){empty_init();while (first != last){push_back(*first);//尾插数据,会根据不同类型的迭代器进行调用++first;}}//initializer_list构造list(initializer_list<int> il){empty_init();for (auto& e : il) {push_back(e);}}//lt2<lt1>,拷贝构造函数list(const list<T>& lt){empty_init();for (auto& e : lt) {push_back(e);}}//lt1=lt3 赋值运算符重载list<T>& operator=(list<T> lt) {swap(lt);return *this;}void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}void clear(){auto it = begin();while (it != end()) {it = erase(it);}}~list(){clear();delete _head;_head = nullptr;}void push_back(const T& x) {//Node* newnode = new Node(x);  //创建一个新节点//Node* tail = _head->_prev;  //找尾//tail->_next = newnode;//newnode->_prev = tail;//newnode->_next = _head;//_head->_prev = newnode;//++_size;insert(end(), x);}void push_front(const T& x){insert(begin(), x);}void insert(iterator pos, const T& x){Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);newnode->_next = cur;newnode->_prev = prev;prev->_next = newnode;cur->_prev = newnode;++_size;}void pop_back() {erase(--end());}void pop_front() {erase(begin());}iterator erase(iterator pos){assert(pos != end());Node* prev = pos._node->_prev;Node* next = pos._node->_next;prev->_next = next;next->_prev = prev;delete pos._node;--_size;return iterator(next);}size_t size() const{return _size;}size_t empty() const{return _size == 0;}private:Node* _head;size_t _size;};void test1() {list<int> mylist1;mylist1.push_back(1);mylist1.push_back(2);mylist1.push_back(3);mylist1.push_back(4);list<int>::iterator it = mylist1.begin();while (it != mylist1.end()) {cout << *it << " ";++it;}cout << endl;}void test2() {list<int> mylist1;//测试尾插mylist1.push_back(1);mylist1.push_back(2);mylist1.push_back(3);mylist1.push_back(4);//测试头插mylist1.push_front(10);mylist1.push_front(20);//测试迭代器和运算符重载list<int>::iterator it = mylist1.begin();while (it != mylist1.end()) {cout << *it << " ";++it;}cout << endl;//测试插入list<int>::iterator it1 = mylist1.begin();mylist1.insert(++it1, 999);//测试删除list<int>::iterator it2 = mylist1.begin();++it2;mylist1.erase(++it2);for (auto ele : mylist1) {cout << ele << " ";}cout << endl;//测试头删mylist1.pop_front();//测试尾删mylist1.pop_back();for (auto ele : mylist1) {cout << ele << " ";}cout << endl;}struct AA {int _a1=1;int _a2=1;};void test3() {list<AA> listA;listA.push_back(AA());listA.push_back(AA());listA.push_back(AA());listA.push_back(AA());list<AA>::iterator it = listA.begin();while (it != listA.end()) {//cout << (*it)._a1 << ":" << (*it)._a2 << endl;/*cout << it->_a1 << ":" << it->_a2 << endl;*/cout << it.operator->()->_a1 << ":" << it.operator->()->_a2 << endl;++it;}}template<class container>void print_container(const container& con) {typename container::const_iterator it = con.begin();while (it != con.end()) {cout << *it << " ";++it;}cout << endl;}void test4() {list<int> mylist1;mylist1.push_back(1);mylist1.push_back(2);mylist1.push_back(3);mylist1.push_back(4);//print_container(mylist1);}void test5(){list<int> mylist1;mylist1.push_back(10);mylist1.push_back(20);mylist1.push_back(30);mylist1.push_back(40);list<int> mylist2(mylist1);print_container(mylist1);print_container(mylist2);}void test6(){list<int> mylist1;mylist1.push_back(10);mylist1.push_back(20);mylist1.push_back(30);mylist1.push_back(40);list<int> mylist2;mylist2.push_back(1);mylist2.push_back(2);mylist2.push_back(3);print_container(mylist2);mylist2 = mylist1;print_container(mylist2);}void test7() {list<int> mylist1(6,1);print_container(mylist1);}void test8() {string s1("hello world");list<char> mylist1(s1.begin(),s1.end());print_container(mylist1);}void test9() {list<int> mylist1({1,2,3,4,5,6});print_container(mylist1);}}

21.2 test.cpp

#include"List.h"
int main() {//zx::test1();//zx::test2();//zx::test3();//zx::test4();//zx::test5();//zx::test6();//zx::test7();//zx::test8();//zx::test9();return 0;
}

http://www.dtcms.com/a/600766.html

相关文章:

  • 湖南火电建设有限公司网站龙采哈尔滨建站公司
  • 【PHP反序列化】css夺旗赛
  • ServletLess架构简介
  • 安卓C语言编译器的选择与使用技巧 | 优化C语言编程体验,提升开发效率
  • (三)自然语言处理笔记——Transformer
  • iOS性能分析工具,有UI卡顿、app启动、内存、webview等性能优化解析
  • 电商网站建设 数商云招商码头无忧查询系统
  • 开源 Objective-C IOS 应用开发(三)第一个iPhone的APP
  • (11)(2.2.2) BLHeli32,AM32, and BLHeli_S ESCs(二)
  • Google Chrome v142.0.7444.135 便携增强版
  • [Windows] PDF文件浏览OCR工具1.0
  • 2025人形机器人产业链全景分析报告:核心技术与市场趋势|附130+份报告PDF、数据、可视化模板汇总下载
  • 长春教做网站带维护的培训机构淮安网站建设
  • 图文详述:MySQL的下载、安装、配置、使用
  • 把课本内容抄到PPT上就行吗?会不会太乱?
  • MySQL XtraBackup 使用文档(全量 + 增量备份与恢复)
  • 在k8s中seaweedfs中,weed 命令详细举例说明
  • 动易 网站统计 首次打开阿里云服务器学生
  • 【底层奥秘与性能艺术】让 RTOS 在 48 MHz MCU 上跑出 0.5 µs 上下文切换——一场从零开始的嵌入式“时间革命”
  • Win11找不到组策略编辑器(gpedit.msc)
  • [智能体设计模式]第2章-路由(Route)
  • [智能体设计模式] 第五章 :函数调用
  • PixPin(截图工具) v2.2.0.0
  • 2023年混沌学堂JAVA课程(1-7期)+专题课
  • 备战算法专家--要点 1
  • 湖南服装网站建设东方财富网官方网站首页
  • 物业网站建设方案开发一个直播app
  • 设计模式实战篇(一):彻底搞懂 Singleton 单例模式
  • 什么是电子商务网站建设网站建设的一些背景图片
  • 一个有 IP 的服务端监听了某个端口,那么他的 TCP 最大链接数是多少