目录
1,push_back、push_front、pop_back、pop_front的使用
5. pop_back、push_front、pop_front 的实现
有了vector,为何还需list
为什么会有 list ?
因为 list 补充 vector 的缺点而存在的。
vector的缺点:
1、头部和中部的插入删除效率低。0(N), 因为需要挪动数据。
2、插入数据空间不够需要增容。增容需要开新空间、拷贝数据、释放旧空间,会付出很大的代价。
vector的优点:
支持下标的随机访问。 间接的就很好的支持排序、二分查找、堆算法等等。
list的产生就是为了解决vector的缺陷而存在的。
list的优点:
1、list头部、中间插入不再需要挪动数据,效率高。0(1)。
2、list插入数据是新增节点,不需要增容。
list的缺点:
不支持随机访问。
所以实际使用中vector和list是相辅相成的两个容器。
list的使用
list 的使用方法在一定程度上与 vector 类似。若熟悉 vector 的使用,那么在使用 list 时也会感到有一定的相似性。以下是list最常使用的接口,比较简单。
1,push_back、push_front、pop_back、pop_front的使用
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <list>
void test1()
{
list<int> lt;
//push_back 尾插
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
//pop_back 尾删
lt.pop_back();
//push_front 头插
lt.push_front(0);
lt.push_front(-1);
//pop_front 头删
lt.pop_front();
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
test1();
return 0;
}
2,正向、反向、const正向、const反向迭代器的使用
正向、反向迭代器的使用
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <list>
void test2()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
lt.push_back(6);
//1,正向迭代器
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;
}
cout << endl;
}
int main()
{
test2();
return 0;
}
const正向、const反向迭代器的使用
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <list>
//1,const正向迭代器
void Print_list(const list<int>& lt) //const对象在传参的时候会产生
{
list<int>::const_iterator it = lt.begin(); // lt是 const 会去调用const的begin
while (it != lt.end())
{
//*it = 1; 不能修改
cout << *it << " ";
++it;
}
cout << endl;
}
//2,const反向迭代器
void Print_reverse_list(const list<int>& lt)
{
list<int>::const_reverse_iterator rit = lt.rbegin();
while (rit != lt.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
}
void test3()
{
list<int> lt;
lt.push_back(10);
lt.push_back(20);
lt.push_back(30);
lt.push_back(40);
lt.push_back(50);
lt.push_back(60);
//1,const正向迭代器
Print_list(lt);
//2,const反向迭代器
Print_reverse_list(lt);
}
int main()
{
test3();
return 0;
}
3,operator= 赋值
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <list>
void test4()
{
list<int> lt1;
lt1.push_back(1);
lt1.push_back(2);
lt1.push_back(3);
lt1.push_back(4);
lt1.push_back(5);
list<int> lt2;
lt2.push_back(10);
lt2.push_back(20);
lt2.push_back(30);
lt2.push_back(40);
//赋值
lt1 = lt2;
//只要一个容器支持迭代器就可以支持范围for的操作
for (auto e : lt1)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
test4();
return 0;
}
4,insert、erase 任意位置的插入、删除
list不支持随机访问,vector支持,list空间都不连续,加的不是你想要的位置。
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
lt.push_back(6);
//4前插入 30
lt3.insert(lt3.begin() + 3, 30); //这样是错误的写法
怎么写呢,需要用到算法里面的 find
insert 任意位置的插入
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <list>
#include <algorithm>
void test5()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
lt.push_back(6);
//4 前插入 30
//lt3.insert(lt3.begin() + 3, 30); //错误写法
list<int>::iterator pos = find(lt.begin(), lt.end(), 4);
//这里必须做一个判断,因为如果没有找到就不会去调用insert
// 如果不做判断返回的是end的位置,由于没有找到值为 4 的元素,
// pos会指向end(),直接插入就会在末尾插入新元素。
if(pos != lt.end())
{
lt.insert(pos, 30);
}
//打印
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
test5();
return 0;
}
erase 任意位置的删除
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <list>
#include <algorithm>
void test5()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
lt.push_back(6);
list<int>::iterator pos = find(lt.begin(), lt.end(), 4);
if(pos != lt.end())
{
//为什么重新获取pos
//因为迭代器已经失效了,erase会返回删除元素的下一个位置,所以需要重新获取
pos = lt.erase(pos);
}
//打印
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
test5();
return 0;
}
5,迭代器失效(★)
情景一:
为什么 vector 报错了,而 list 不报错呢?
迭代器我们都想象成指针一样的东西。
在vector中,进行插入操作的时候,pos迭代器已经不是 3下标了,或者扩容,迭代器还指向旧地址空间,指针直接失效了。
在list中,insert 不会失效,因为这个链表,pos迭代器指向不受增容的影响。
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <list>
#include <vector>
#include <algorithm>
void test6()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
lt.push_back(6);
list<int>::iterator pos = find(lt.begin(), lt.end(), 4);
if (pos != lt.end())
{
lt.insert(pos, 30); // 插入以后pos迭代器失效了吗?
lt.erase(pos); //删除 4
}
//打印
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
}
void test7()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.push_back(6);
vector<int>::iterator pos = find(v.begin(), v.end(), 4);
if (pos != v.end())
{
v.insert(pos, 30); // 插入以后pos迭代器失效了吗?
v.erase(pos); //删除 4
}
//打印
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
test6();
//test7();
return 0;
}
图解如下: vector 和 list 的对比
如何解决:
重新获取迭代器 pos
情景二:
删除所有的偶数,当 erase 之后,it 指向释放的空间,对野指针进行++或者解引用操作会导致程序崩溃。
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <list>
#include <vector>
#include <algorithm>
void test8()
{
list<int> lt;
lt.push_back(3);
lt.push_back(2);
lt.push_back(1);
lt.push_back(5);
lt.push_back(4);
lt.push_back(6);
list<int>::iterator it = lt.begin();
while (it != lt.end())
{
if (*it % 2 == 0)
{
lt.erase(it); // it 变成了野指针,
cout << *it << endl; //失效了以后也不能进行解引用操作
}
++it; //对野指针进行++
}
}
int main()
{
test8();
return 0;
}
如何解决:
代码中,已经修复了迭代器失效问题,为什么不能直接输出呢
- 迭代器指向的不确定性:虽然
erase
返回了下一个有效的迭代器,但我们不能确定这个迭代器指向的元素一定是可以安全输出的。例如,如果在删除一个偶数元素后,下一个元素仍然是偶数,并且也满足删除条件,那么这个新的迭代器实际上指向的是即将被删除的元素。在这种情况下输出该元素的值是不安全的,属于未定义行为。 - 可能的迭代器失效场景:即使在当前情况下看起来似乎没有问题,但在更复杂的场景中,比如容器中存在其他并发操作或者其他条件可能导致迭代器在输出之前就已经失效。
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include <list>
#include <vector>
#include <algorithm>
void test8()
{
list<int> lt;
lt.push_back(3);
lt.push_back(2);
lt.push_back(1);
lt.push_back(5);
lt.push_back(4);
lt.push_back(6);
list<int>::iterator it = lt.begin();
while (it != lt.end())
{
if (*it % 2 == 0)
{
it = lt.erase(it); // erase会返回下一个位置
//cout<<*it <<endl; 这里不能直接输出
}
else
{
++it;
}
}
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
test8();
return 0;
}
小结:
对于 vector 而言,insert 和 erase 都会失效。
对于 list 而言,insert不会失效,erase 才会失效,erase之后空间被释放了指针还指向这块空间。失效了是指不能去取访问它,去访问失效迭代器才会有问题。
list的模拟实现
源码的引入
这段源码是主干部分,我们看完该源码是如何搭建的主干部分的,接下来我们就实现一个类似的。
其中有个注意的地方:
self& operator++(){
node =(link_type)((*node).next); //node是void*类型,转成__list_node* 类型
return *this;
}1,对node进行解引用拿到该对象(节点/结构体),对该对象进行 . 操作拿到里面的数据或者指针,然后再强转成 __list_node* 类型。
即:(*node).data,, node是节点的指针,解引用返回节点,.data 取节点里面的值
2, (link_type)((*node).next),需要得到下一个节点的指针也就是节点指向下一个位置。拿到该节点,返回类型是void*, 强转成节点类型 __list_node<T>*在 list 中,原生指针的行为已经不是我们想要的了,我们可以用自定义类型去封装原生指针,通过运算符重载来构建我们想要的行为。
其它细节在接下来的实现中会一一叙述。
template<class T>
struct __list_node {
typedef void* void_pointer;
void_pointer next;
void_pointer prev;
T data;
};
template<class T, class Ref, class Ptr>
struct __list_iterator {
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
typedef __list_iterator <T, Ref, Ptr> self;
typedef __list_node<T>* link_type;
link_type node;
__list_iterator(link_type x): node(x){}
bool operator==(const self&x)const{return node==x.node;}
bool operator!=(const self& x)const {return node!= x.node;}
reference operator*()const {return(*node).data;}
self& operator++(){
node =(link_type)((*node).next);
return *this;
}
self operator++(int) {
self tmp = *this;
++*this;
return tmp;
}
self& operator--()
{
node=(link type)((*node).prev);
return *this;
}
self operator--(int){
self tmp *this;
--*this:
return tmp;
}
};
template<class T,class Alloc=alloc>
class list
{
protected:
typedef void* void_pointer;
typedef __list_node<T> list_node;
typedef list_node* link_type;
public:
typedef T value_type;
public:
typedef list_iterator<T, T&, T*> iterator;
protected:
link_type node;
}
1.基本结构的搭建
__list_node
源码是定义了void*类型,需要强转,这里我们直接这样定义,我们不需要强转类型。
struct __list_node //对象<类型>
{
__list_node<T>* _next;
__list_node<T>* _prev;
T _data;
};
list
我们发现,在 list 初始化的时候,当我们 new 一个节点的时候需要进行初始化该节点,所以__list_node,需要进行初始化,即实现构造函数
template<class T>
class list
{
typedef __list_node<T> Node; //给节点取别名为 Node
public:
//带头双向循环链表
list()
{
_head = new Node; //除了开空间,还会去调用Node的构造函数完成初始化
_head->_next = _head;
_head->_prev = _head;
}
private:
Node* _head; //一个指针用来初始化虚拟头结点
};
__list_node 的完善
template<class T>
struct __list_node //对象<类型>
{
__list_node<T>* _next;
__list_node<T>* _prev;
T _data;
//Node类型的构造函数完成初始化
__list_node(const T& x = T()) //初始化头结点可以给值,也可以给缺省
:_data(x)
, _next(nullptr)
, _prev(nullptr)
{}
};
push_back的实现
这是我们在数据结构链表所学的,一摸一样的,有节点和没有节点的情况下都是满足的。
template<class T>
class list
{
typedef __list_node<T> Node;
public:
//带头双向循环链表
list()
{
_head = new Node; //除了开空间,还会去调用Node的构造函数完成初始化
_head->_next = _head;
_head->_prev = _head;
}
void push_back(const T& x)
{
Node* tail = _head->_prev;
Node* newnode = new Node(x);
tail->_next = newnode;
newnode->_prev = tail;
newnode->_next = _head;
_head->_prev = newnode;
}
private:
Node* _head;
};
有了这些,我们需要打印链表插入的数据,需要用到迭代器。
__list_itertaor
template<class T>
struct __list_iterator
{
typedef __list_node<T> Node;
Node* _node; //核心还是一个指针
__list_iterator(Node* node) //构造函数用于初始化
:_node(node)
{}
// *it
T& operator*()
{
return _node->_data;
}
__list_iterator<T>& operator++() //返回值还是一个迭代器
{
_node = _node->_next;
return *this;
}
// it != end()
bool operator!=(const __list_iterator<T>& it)
{
return _node != it._node;
}
};
有了迭代器,我们需要在 list 中使用 iterator,一个取begin(), 一个取end()
template<class T>
class list
{
typedef __list_node<T> Node;
public:
typedef __list_iterator<T> iterator;
iterator begin()
{
return iterator(_head->_next); // __list_iterator(_head->_next)
}
iterator end()
{
return iterator(_head);
}
//带头双向循环链表
list()
{
_head = new Node; //除了开空间,还会去调用Node的构造函数完成初始化
_head->_next = _head;
_head->_prev = _head;
}
void push_back(const T& x)
{
Node* tail = _head->_prev;
Node* newnode = new Node(x);
tail->_next = newnode;
newnode->_prev = tail;
newnode->_next = _head;
_head->_prev = newnode;
}
private:
Node* _head;
};
基本结构的整体代码
list.h
#pragma once
namespace my_list
{
template<class T>
struct __list_node //对象<类型>
{
__list_node<T>* _next;
__list_node<T>* _prev;
T _data;
//Node类型的构造函数完成初始化
__list_node(const T& x = T()) //初始化头结点可以给值,也可以给缺省
:_data(x)
, _next(nullptr)
, _prev(nullptr)
{}
};
template<class T>
struct __list_iterator
{
typedef __list_node<T> Node;
Node* _node; //核心还是一个指针
__list_iterator(Node* node) //构造函数用于初始化
:_node(node)
{}
// *it
T& operator*()
{
return _node->_data;
}
__list_iterator<T>& operator++() //返回值还是一个迭代器
{
_node = _node->_next;
return *this;
}
// it != end()
bool operator!=(const __list_iterator<T>& it)
{
return _node != it._node;
}
};
template<class T>
class list
{
typedef __list_node<T> Node;
public:
typedef __list_iterator<T> iterator;
iterator begin()
{
return iterator(_head->_next); // __list_iterator(_head->_next),类似于vector(1);
}
iterator end()
{
return iterator(_head);
}
//带头双向循环链表
list()
{
_head = new Node; //除了开空间,还会去调用Node的构造函数完成初始化
_head->_next = _head;
_head->_prev = _head;
}
void push_back(const T& x)
{
Node* tail = _head->_prev;
Node* newnode = new Node(x);
tail->_next = newnode;
newnode->_prev = tail;
newnode->_next = _head;
_head->_prev = newnode;
}
private:
Node* _head;
};
void test_list()
{
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;
}
}
test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include "list.h"
int main()
{
my_list::test_list();
return 0;
}
迭代器是如何执行起来的
2.迭代器的进一步理解
源码中的 typedef __list_iterator<T, T&, T*> iterator;
iterator就是__list_iterator<T, T&, T*> ,因为 list 迭代器不像 vector 空间是连续的,所以不能构建指针,只能去自定义一个类去实现该迭代器。
int* p1 = new int; *p1 拿到了 int
Date* p2 = new Date; *p2;拿到这个对象,(*p2)._year或者 p2->_year,拿到里面的数据。
//定义一个简单的日期类
struct Date
{
int _year = 0;
int _month = 1;
int _day = 1;
};
void test_list()
{
list<Date> lt;
lt.push_back(Date());
lt.push_back(Date());
list<Date>::iterator it = lt.begin();
while (it != lt.end())
{ //Date是别人写的,就没写输出,如何应对?
//cout << *it << " "; //解引用出来是Date自定义类型,不支持输出,没有重载operator输出
cout << it->_year << "-" << it->_month << "-" << it->_day << endl; //我们没有重载operator->,所以需要去实现operator->
++it;
}
cout << endl;
}
还可以这样输出,先拿到 it 指向的对象 Date,再去访问该对象里面的数据
cout << (*it)._year << "-" << (*it)._month << "-" << (*it)._day << endl;
3.const 迭代器的实现
以下代码在print_list中,lt 是 const,调用begin的时候,只能调 const 的 begin,所以给begin 加 const。
下面的代码看似没有问题,实际呢,遍历这个迭代器的时候还把我改了,这个迭代器不合理啊
const对象去调用const 返回的是普通迭代器,普通迭代器是可读可写的。
void print_list(const list<int>& lt)
{
list<int>::iterator it = lt.begin(); //const < 传给 list* this 加const=> const list * this
while (it != lt.end())
{
*it = 1;
cout << *it << " ";
++it;
}
cout << endl;
}
void test_list()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
print_list(lt);
}
const迭代器的实现
const迭代器的唯一需求只有一个,可以遍历,可以读,可以加加,但是不能改写迭代器指向的数据/内容,不是迭代器本身不能修改,而是迭代器指向的内容不能修改。
迭代器指向的内容是通过 * 和 -> 来修改的.
*it 和 it->怎么实现呢??
把整个迭代器类拷贝出来,改两个地方,* 和 -> 的返回类型变成const T& 和 const T*
把迭代器拷贝一份, 把类名改成__list_ const _iterator,并给operator* 和operator->,加上const。
但是仔细想想,当我们拷贝完之后就改两个地方,其它都一样,这样整体代码重复率就很高,代码显得很挫啊。所以我们别这样搞,我们这里可以增加两个模板参数来处理这种问题。
把迭代器的模版参数修改成:template<class T, class Ref, class Ptr>, 因为迭代器只需修改两个地方,我们就增加两个参数,一个是接收引用,一个是接收指针。
这模板参数部分开始第一次接触看来很复杂,其实很简单,就是对着图里的这个步骤走下,就很好理解了。
#pragma once
namespace my_list
{
template<class T>
struct __list_node //对象<类型>
{
__list_node<T>* _next;
__list_node<T>* _prev;
T _data;
//Node类型的构造函数完成初始化
__list_node(const T& x = T()) //初始化头结点可以给值,也可以给缺省
:_data(x)
, _next(nullptr)
, _prev(nullptr)
{}
};
template<class T, class Ref, class Ptr>
struct __list_iterator
{
typedef __list_node<T> Node;
Node* _node; //核心还是一个指针
__list_iterator(Node* node) //构造函数用于初始化
:_node(node)
{}
// *it
Ref operator*()
{
return _node->_data;
}
//operator->
Ptr operator->()
{
return &_node->_data;
}
__list_iterator<T, Ref, Ptr>&operator++() //返回值还是一个迭代器,里面模板参数也变成了三个
{
_node = _node->_next;
return *this;
}
// it != end()
bool operator!=(const __list_iterator<T, Ref, Ptr>& it)
{
return _node != it._node;
}
};
template<class T>
class list
{
typedef __list_node<T> Node;
public:
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
const_iterator begin() const
{
return const_iterator(_head->_next);
}
const_iterator end() const
{
return const_iterator(_head);
}
//带头双向循环链表
list()
{
_head = new Node; //除了开空间,还会去调用Node的构造函数完成初始化
_head->_next = _head;
_head->_prev = _head;
}
void push_back(const T& x)
{
Node* tail = _head->_prev;
Node* newnode = new Node(x);
tail->_next = newnode;
newnode->_prev = tail;
newnode->_next = _head;
_head->_prev = newnode;
}
private:
Node* _head;
};
void print_list(const list<int>& lt)
{
list<int>::const_iterator it = lt.begin(); //const < 传给 list* this 加const=> const list * this
while (it != lt.end())
{
//*it = 1; 现在就不能修改了
cout << *it << " ";
++it;
}
cout << endl;
}
void test_list()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
print_list(lt);
}
}
补充一点:
类型决定了对空间的解释权
假设有 Node* cur, iterator it
当他们都指向同一个节点,那么在物理内存中他们都存的是这个节点地址,物理上是一样的
但是他们的类型不一样,他们的意义就不一样。
比如*cur是一个指针的解引用,取到的值是节点。
比如 *it 是去调用迭代器的operator*,返回值是节点中存的值。
4.insert 和 erase 的实现
接下来的的实现都很简单,因为都是数据结构所学的。
insert的实现
void insert(iterator pos, const T& x)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(x);
// prev newnode cur
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
}
erase的实现
void erase(iterator pos)
{
assert(pos != end());
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
delete cur;
prev->_next = next;
next->_prev = prev;
}
5. pop_back、push_front、pop_front 的实现
复用上面的代码
void pop_back()
{
//erase(iterator(_head->_prev));
erase(--end());
}
void push_front(const T& x)
{
insert(begin(), x);
}
void pop_front()
{
erase(begin());
}
6.拷贝构造
拷贝构造也是构造函数的一种,用来初始化,需要先创建一个新的头节点,然后把数据全尾插到该对象中,在list中可直接使用迭代器,在里面可以直接使用, 不需要指定哪个类域和加类域符号,即 list<int>:: 。
//拷贝构造
list(const list<T>& lt)
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
//const_iterator it = lt.begin();
//while (it != lt.end())
//{
// //this->push_back(*it);
// push_back(*it);
// ++it;
//}
for (auto e : lt)
{
push_back(e);
}
}
7.析构函数
clear 用来清除所有的数据,释放所有的节点,但是不处理头节点。
void clear()
{
iterator it = begin();
while (it != end())
{
erase(it++);
}
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
8.operator= 赋值
传统写法中,先清空所有的数据,然后把赋值过来的数据全部尾插到该对象中。
现代写法中,传参的时候就,直接拷贝构造一份一摸一样的链表,拷贝构造是深拷贝(已经实现了),然后把该对象和这个拷贝出来的两者头结点一交换,就达到了目的。
//operator= 赋值
传统写法
//list<T>& operator=(const list<T>& lt)
//{
// if (this != <)
// {
// clear(); //清空所以的数据
// for (auto e : lt)
// {
// push_back(e);
// }
// }
// return *this;
//}
//赋值-现代写法
list<T>& operator=(list<T> lt)
{
//clear(); 不加也可以
swap(_head, lt._head);
return *this;
}
9.深浅拷贝问题
和vector 深浅拷贝问题都是一样的,我们需要手动实现,完成深拷贝。operator= 也是同理。
10.整体代码
list.h
#pragma once
#include <assert.h>
namespace my_list
{
template<class T>
struct __list_node //对象<类型>
{
__list_node<T>* _next;
__list_node<T>* _prev;
T _data;
//Node类型的构造函数完成初始化
__list_node(const T& x = T()) //初始化头结点可以给值,也可以给缺省
:_data(x)
,_next(nullptr)
,_prev(nullptr)
{}
};
//迭代器
// __list_iterator<T, T&, T*> --> iterator;
// __list_iterator<T, const T&, const T*> ---> const_iterator;
template<class T, class Ref, class Ptr>
struct __list_iterator
{
typedef __list_node<T> Node;
typedef __list_iterator<T, Ref, Ptr> Self;
Node* _node; //核心还是一个指针
__list_iterator(Node* node)
:_node(node)
{}
// *it
Ref operator*()
{
return _node->_data;
}
//operator->
Ptr operator->()
{
return &_node->_data;
}
//++it
Self& operator++()
{
_node = _node->_next;
return *this;
}
//it++ 后置
Self operator++(int)
{
Self tmp(*this);
//_node = _node->_next;
++(*this);
return tmp;
}
//--it
Self& operator--()
{
_node = _node->_prev;
return *this;
}
Self operator--(int)
{
Self tmp(*this);
//_node = _node->_prev;
--(*this);
return *this;
}
// it != end()
bool operator!=(const Self& it)
{
return _node != it._node;
}
// it ==
bool operator==(const Self& it)
{
return _node == it._node;
}
};
template<class T>
class list
{
typedef __list_node<T> Node;
public:
typedef __list_iterator<T, T&, T*> iterator;
//const 迭代器
typedef __list_iterator<T, const T&, const T*> const_iterator;
iterator begin()
{
return iterator(_head->_next); // __list_iterator(_head->_next),类似于vector(1);
}
iterator end()
{
return iterator(_head);
}
const_iterator begin() const
{
return const_iterator(_head->_next);
}
const_iterator end() const
{
return const_iterator(_head);
}
//带头双向循环链表
list()
{
_head = new Node; //除了开空间,还会去调用Node的构造函数完成初始化
_head->_next = _head;
_head->_prev = _head;
}
//拷贝构造
list(const list<T>& lt)
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
//const_iterator it = lt.begin();
//while (it != lt.end())
//{
// //this->push_back(*it);
// push_back(*it);
// ++it;
//}
for (auto e : lt)
{
push_back(e);
}
}
//operator= 赋值
//list<T>& operator=(const list<T>& lt)
//{
// if (this != <)
// {
// clear(); //清空所以的数据
// for (auto e : lt)
// {
// push_back(e);
// }
// }
// return *this;
//}
//赋值-现代写法
list<T>& operator=(list<T> lt)
{
//clear(); 不加也可以
swap(_head, lt._head);
return *this;
}
//析构函数
void clear()
{
iterator it = begin();
while (it != end())
{
erase(it++);
}
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
void push_back(const T& x)
{
/*Node* tail = _head->_prev;
Node* newnode = new Node(x);
tail->_next = newnode;
newnode->_prev = tail;
newnode->_next = _head;
_head->_prev = newnode;*/
insert(end(), x);
}
void pop_back()
{
//erase(iterator(_head->_prev));
erase(--end());
}
void push_front(const T& x)
{
insert(begin(), x);
}
void pop_front()
{
erase(begin());
}
void insert(iterator pos, const T& x)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(x);
// prev newnode cur
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
}
void erase(iterator pos)
{
assert(pos != end());
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
delete cur;
prev->_next = next;
next->_prev = prev;
}
private:
Node* _head; //虚拟头结点dummyHead
};
void test_list1()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
list<int>::iterator it = lt.begin();
while (it != lt.end()) //调用的是 operator!=()
{
cout << *it << "->";
++it;
}
cout << "nullptr" << endl;
}
//定义一个日期类
struct Date
{
int _year = 0;
int _month = 1;
int _day = 1;
};
void test_list2()
{
list<Date> lt;
lt.push_back(Date());
lt.push_back(Date());
list<Date>::iterator it = lt.begin();
while (it != lt.end())
{ //Date是别人写的,就没写输出,如何应对?
//cout << *it << " "; //解引用出来是Date自定义类型,不支持输出,没有重载operator输出
//(*it)._year 这样也可以
cout << it->_year << "-" << it->_month << "-" << it->_day << endl;
cout << (*it)._year << "-" << (*it)._month << "-" << (*it)._day << endl;
++it;
}
cout << endl;
}
void print_list(const list<int>& lt)
{
list<int>::const_iterator it = lt.begin(); //const < 传给 list* this 加const-> const list * this
while (it != lt.end())
{
//*it = 1;
cout << *it << " ";
++it;
}
cout << endl;
}
void test_list3()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
lt.push_back(6);
lt.pop_back();
lt.pop_front();
print_list(lt);
}
//拷贝构造
void test_list4()
{
list<int> lt1;
lt1.push_back(1);
lt1.push_back(2);
lt1.push_back(3);
lt1.push_back(4);
list<int> lt2(lt1);
print_list(lt2);
}
}
test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include "list.h"
int main()
{
/*my_list::test_list1();
my_list::test_list2();*/
//my_list::test_list3();
my_list::test_list4();
return 0;
}
常见面试题
1,vector 和 list的区别?
vector是一个可动态增长的数组
优点:随机访问 operator[] ->很好的支持排序、二分查找、堆算法的等等
缺点:头部或者中间的插入删除效率低 +空间不够了以后增容的代价较大
list是一个带头双向循环的链表
优点:任意位置插入删除数据效率高,0(1)
缺点:不支持随机访问
总结:vector和list是两个相辅相成,互补的容器
2,vector 和 list 底层是如何实现的 ?
vector 可随机访问,当vector插入数据的时候,需要挪动数据,空间不够的时候,开新的空间,拷贝数据,释放旧空间,这个过程代价有点大,list 不支持随机访问,需要遍历整个链表找到某个值。节点的内存不是连续分配的,是随机分配的,链表插入数据,不需要挪动数据,不需进行大量的内存赋值和重新分配操作,每个节点需要单独分配空间,节点被插入或者删除的时候,效率较高 O(1)
3,vector 是如何增容的?
容量不够的时候,开新空间,拷贝数据,释放旧空间。
4,什么是迭代器失效?
标签:node,iterator,STL,list,back,C++,lt,push From: https://blog.csdn.net/m0_63207201/article/details/141801426迭代器失效就是在对容器进行特定操作(如插入、删除元素等)后,原来能用的迭代器可能就不能用了。
vector中的迭代器,当vector需要增容的时候,开了新空间,迭代器还指向旧空间,迭代器便失效了
list 中进行了erase操作,去使用该迭代器的时候,此时,操作该迭代器就相对于操作野指针,因为该空间已经释放了。