网站设计开发维护,做外贸学网站,网站源码整站打包,帝国cms做企业网站目录
前言
一、list 的使用 1、构造函数
2、迭代器
3、增删查改
4、其他函数使用
二、list 的模拟实现 1、节点的创建 2、push_back 和 push_front 3、普通迭代器 4、const 迭代器 5、增删查改(insert、erase、pop_back、pop_front) 6、构造函数和析构函数 6.1、默认构造…目录
前言
一、list 的使用 1、构造函数
2、迭代器
3、增删查改
4、其他函数使用
二、list 的模拟实现 1、节点的创建 2、push_back 和 push_front 3、普通迭代器 4、const 迭代器 5、增删查改(insert、erase、pop_back、pop_front) 6、构造函数和析构函数 6.1、默认构造 6.2、构造 n 个 val 的对象 6.3、拷贝构造 6.4、迭代器区间构造 6.5、 赋值运算符重载 6.6、析构函数
三、list 模拟实现源代码
四、list 的迭代器失效
五、list 和 vector的对比 前言
list是可以在常数范围内在任意位置进行插入和删除的序列式容器并且该容器可以前后双向迭代。list的底层是双向链表结构双向链表中每个元素存储在互不相关的独立节点中在节点中通过指针指向 其前一个元素和后一个元素。list 与 forward_list 非常相似最主要的不同在于 forward_list 是单链表只能朝前迭代已让其更简单高效。与其他的序列式容器相比(arrayvectordeque)list 通常在任意位置进行插入、移除元素的执行效率更好。与其他序列式容器相比list和forward_list最大的缺陷是不支持任意位置的随机访问.
一、list 的使用 1、构造函数
构造函数接口说明list (size_type n, const value_type val value_type())构造的list中包含n个值为val的元素list()构造空的listlist (const list x拷贝构造函数list (InputIterator first, InputIterator last)用[first, last)区间中的元素构造list
int main()
{// 默认构造listint lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);// 拷贝构造listint lt2(lt);// 构造 n 个节点listint lt3(5, 1);// 迭代器区间构造listint lt4(lt.begin(), lt.end());return 0;
}
2、迭代器
函数声明接口说明begin end返回第一个元素的迭代器返回最后一个元素下一个位置的迭代器rbegin rend返回第一个元素的reverse_iterator,即end位置返回最后一个元素下一个位置的 reverse_iterator,即begin位置
int main()
{int a[] { 1,2,3,4,5,6,7,8,9 };listint lt(a, a 9);auto it lt.begin();while (it ! lt.end()){cout *it ;it;}cout endl;return 0;
}
迭代器一般是用来遍历和查找的
而反向迭代器的使用是类似的只不过调用的函数换成了 rbegin 和 rend 。
注意反向迭代器的迭代使用的也是。但迭代器区间一样是[rbegin, rend
3、增删查改
函数声明接口说明push_front在list首元素前插入值为 val 的元素pop_front删除 list 中第一个元素push_back在list尾部插入值为 val 的元素pop_back删除 list 中最后一个元素insert在list position 位置中插入值为 val 的元素erase删除list position 位置的元素swap交换两个 list 中的元素clear清空 list 中的有效元素
int main()
{vectorint v { 1,2,3,4,5,6,7,8,9 };listint lt(v.begin(), v.end());for (auto e : lt) cout e ;cout endl;lt.push_front(10);lt.push_back(20);for (auto e : lt) cout e ;cout endl;lt.pop_front();lt.pop_back();for (auto e : lt) cout e ;cout endl;auto pos find(lt.begin(), lt.end(), 5);lt.insert(pos, 50);for (auto e : lt) cout e ;cout endl;pos find(lt.begin(), lt.end(), 8);lt.erase(pos);for (auto e : lt) cout e ;cout endl;return 0;
}
4、其他函数使用
函数声明接口说明empty检测 list 是否为空是返回 true 否则返回 falsesize返回 list 中有效节点的个数front返回 list 的第一个节点中值的引用back返回 list 的最后一个节点中值的引用
二、list 的模拟实现 1、节点的创建
templateclass T
struct list_node//节点
{list_nodeT* _next;list_nodeT* _prev;T _data;// 构造函数list_node(const T x T()):_next(nullptr), _prev(nullptr), _data(x){}
}; 由于节点存储的数据可能是任意类型所以我们需要将将节点定义为模板类。这里我们需要写一个给缺省值的默认构造函数便于之后在主类中new一个新节点时直接初始化同时将两个指针置为空将数据写入数据域中。 2、push_back 和 push_front
class list
{
public:typedef list_nodeT node;private:node* _head;
}
//尾插
void push_back(const T x) const
{node* new_node new node(x);node* tail _head-_prev;//链接节点之间的关系tail-_next new_node;new_node-_prev tail;new_node-_next _head;_head-_prev new_node;
}
//头插
void push_front(const T x)
{node* head _head-_next;node* new_node new node(x);_head-_next new_node;new_node-_prev _head;new_node-_next head;head-_prev new_node;
} 这里模拟的头插和尾插也很简单因为和我们之前在数据结构时候的双向循环链表是一样的只需要找到头或者尾然后链接四个节点间的关系即可。 3、普通迭代器
注意list 的迭代器是自定义类型不是原生指针node*。
迭代器为自定义类型其中*等都是通过运算符重载来完成的。
所以我们需要重载的符号*-前置后置前置--后置--!
templateclass T
struct __list_iterator
{typedef list_nodeT node;typedef __list_iteratorT self;node* _node;//构造函数__list_iterator(node* n):_node(n){}//重载*运算符T operator*(){return _node-_val;}T* operator-(){return _node-_data;}//重载前置运算符self operator(){_node _node-_next;return *this;}//重载后置运算符self operator(int){self tmp(*this);_node _node-_next;return tmp;}//重载前置--运算符self operator--(){_node _node-_prev;return *this;}//重载后置--运算符self operator--(int){self tmp(*this);_node _node-_prev;return tmp;}//重载!运算符bool operator!(const self s){return _node ! s._node;}//重载运算符bool operator(const self s){return _node s._node;}
}; 此处我实现了一个简单的正向迭代器使用一个模板参数T表示类型。 当普通迭代器封装好了之后我们需要在list类中来实现它的 begin() 和 end() 方法。由于迭代器的名字一般都是 iterator而且对于范围for来说也只能通过 iterator 来转换为迭代器进行遍历。所以这里我们将其typedef为iterator。
templateclass T
class list//链表
{typedef list_nodeT node;
public:typedef __list_iteratorT iterator;iterator begin(){return iterator(_head-_next);}iterator end(){return iterator(_head);}
private:node* _head;
}; 4、const 迭代器 const迭代器与普通迭代器的区别在于const迭代器指向的内容是不能修改的但是它的指向是可以修改的。
templateclass T
class list//链表
{typedef list_nodeT node;
public:typedef __list_const_iteratorT const_iterator;const_iterator begin(){return const_iterator(_head-_next);}const_iterator end(){return const_iterator(_head);}
private:node* _head;
}; 我们最好的做法就是在__list_iterator 的类模板中的添加两个模板参数然后再 list 类中 typedef 两份分别将第二个参数分别改成 T 和 const T 的类型本质上就是让编译器根据传入的 Ref 的不同来自动示例化出 const 迭代器类而我们还需要重载一个-运算符因为list中可能存储的是自定义类型这个自定义类型如果要是有多个成员变量的话我们就需要使用-来解引用访问成员变量同样还是要区分普通迭代器和const 迭代器所以就增加了另一个模版参数 Ptr。具体的解决做法如下
templateclass T, class Ref, class Ptr
struct __list_iterator
{typedef list_nodeT node;typedef __list_iteratorT, Ref, Ptr self;node* _node;__list_iterator(node* n):_node(n){}Ref operator*()//解引用{return _node-_data;}Ptr operator-(){return _node-_data;}...
};
然后最终在链表类中使用如下
templateclass T
class list//链表
{typedef list_nodeT node;
public:typedef __list_iteratorT, T, T* iterator;//普通迭代器typedef __list_iteratorT, const T, const T* const_iterator;//const迭代器iterator begin(){return iterator(_head-_next);//匿名对象的返回}const_iterator begin() const{return const_iterator(_head-_next);}iterator end(){return iterator(_head);}const_iterator end() const{return const_iterator(_head);}
private:node* _head;
}; 5、增删查改(insert、erase、pop_back、pop_front)
// 指定位置插入
void insert(iterator pos, const T x)
{node* cur pos._node;node* prev cur-_prev;node* new_node new node(x);prev-_next new_node;new_node-_prev prev;new_node-_next cur;cur-_prev new_node;
}
// 指定位置删除
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;return iterator(next);
}
// 尾删
void pop_back()
{erase(--end());
}
// 头删
void pop_front()
{erase(begin());
} 6、构造函数和析构函数 6.1、默认构造 由于后面会频繁对空进行初始化所以在这里对它进行了封装方便后面的调用。
void empty_init()//空初始化
{_head new node;_head-_next _head;_head-_prev _head;
}
list()
{empty_init();
} 6.2、构造 n 个 val 的对象
//用n个val构造对象
list(int n, const T val T())
{empty_init();for (int i 0; i n; i){push_back(val);}
}6.3、拷贝构造
//拷贝构造传统写法
list(const listT lt)
{empty_init();for (auto e : lt){push_back(e);}
}
//拷贝构造现代写法
list(const listT lt)
{empty_init();listT tmp(lt.begin(), lt.end());swap(tmp);
}6.4、迭代器区间构造
template class Iterator
list(Iterator first, Iterator last)
{empty_init();while (first ! last){push_back(*first);first;}
}6.5、 赋值运算符重载
//赋值运算符重载
listT operator(listT lt)//注意这里不能用引用
{swap(lt);return *this;
} 6.6、析构函数
//要全部清理掉
~list()
{clear();delete _head;_head nullptr;
}
//不释放头结点
void clear()
{iterator it begin();while (it ! end()){it erase(it);//这样也可以//erase(it);}
}
三、list 模拟实现源代码
templateclass T
struct list_node//节点
{list_nodeT* _next;list_nodeT* _prev;T _data;list_node(const T x T()):_next(nullptr), _prev(nullptr), _data(x){}
};
templateclass T, class Ref, class Ptr
struct __list_iterator
{typedef list_nodeT node;typedef __list_iteratorT, Ref, Ptr self;node* _node;__list_iterator(node* n):_node(n){}Ref operator*()//解引用{return _node-_data;}Ptr operator-(){return _node-_data;}//前置self operator(){_node _node-_next;return *this;}//后置self operator(int){self tmp(*this);_node _node-_next;return tmp;}//前置--self operator--(){_node _node-_prev;return *this;}//后置--self operator--(int){self tmp(*this);_node _node-_prev;return tmp;}bool operator!(const self s){return _node ! s._node;}bool operator(const self s){return _node s._node;}
};
templateclass T
class list//链表
{typedef list_nodeT node;
public:typedef __list_iteratorT, T, T* iterator;//普通迭代器typedef __list_iteratorT, const T, const T* const_iterator;//const迭代器iterator begin(){return iterator(_head-_next);//匿名对象的返回}const_iterator begin() const{return const_iterator(_head-_next);}iterator end(){return iterator(_head);}const_iterator end() const{return const_iterator(_head);}void empty_init()//空初始化{_head new node;_head-_next _head;_head-_prev _head;}list(){empty_init();}//迭代器区间构造template class Iteratorlist(Iterator first, Iterator last){empty_init();while (first ! last){push_back(*first);//push_back使用的前提是要有哨兵位的头结点first;}}// 交换函数void swap(listT tmp){std::swap(_head, tmp._head);}//现代拷贝构造list(const listT lt){listT tmp(lt.begin(), lt.end());swap(tmp);}//现代赋值写法listT operator(listT lt){swap(lt);return *this;}~list()//要全部清理掉{clear();delete _head;_head nullptr;}void clear()//不释放头结点{iterator it begin();while (it ! end()){it erase(it);//这样也可以//erase(it);}}void insert(iterator pos, const T x){node* cur pos._node;node* prev cur-_prev;node* new_node new node(x);prev-_next new_node;new_node-_prev prev;new_node-_next cur;cur-_prev new_node;}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;return iterator(next);}//尾插void push_back(const T x) const{//node* new_node new node(x);//node* tail _head-_prev;链接节点之间的关系//tail-_next new_node;//new_node-_prev tail;//new_node-_next _head;//_head-_prev new_node;insert(end(), x);}//头插void push_front(const T x){//node* head _head-_next;//node* new_node new node(x);//_head-_next new_node;//new_node-_prev _head;//new_node-_next head;//head-_prev new_node;insert(begin(), x);}//尾删void pop_back(){erase(--end());}//头删void pop_front(){erase(begin());}
private:node* _head;
};
四、list 的迭代器失效 当我们使用 erase 进行删除后此时指向删除位置的迭代器就失效了再次使用就会令程序崩溃。 因此若要多次删除则需要在使用后利用 erase 的返回值更新迭代器这样使用才不会出现错误。
int main()
{vectorint v { 1, 2,3,5,4,6 };listint lt(v.begin(), v.end());listint::iterator pos find(lt.begin(), lt.end(), 3);for (int i 0; i 3; i){pos lt.erase(pos); //利用erase的返回值更新迭代器}for (auto e : lt) cout e ;cout endl;return 0;
}
五、list 和 vector的对比
vectorlist底 层 结 构动态顺序表一段连续空间带头结点的双向循环链表随 机 访 问支持随机访问访问某个元素效率O(1)不支持随机访问访问某个元素 效率O(N)插 入 和 删 除任意位置插入和删除效率低需要搬移元素时间复杂 度为O(N)插入时有可能需要增容增容开辟新空 间拷贝元素释放旧空间导致效率更低任意位置插入和删除效率高不 需要搬移元素时间复杂度为 O(1)空 间 利 用 率底层为连续空间不容易造成内存碎片空间利用率 高缓存利用率高底层节点动态开辟小节点容易 造成内存碎片空间利用率低 缓存利用率低迭 代 器原生态指针对原生态指针(节点指针)进行封装迭 代 器 失 效在插入元素时要给所有的迭代器重新赋值因为插入 元素有可能会导致重新扩容致使原来迭代器失效删 除时当前迭代器需要重新赋值否则会失效插入元素不会导致迭代器失效 删除元素时只会导致当前迭代 器失效其他迭代器不受影响使 用 场 景需要高效存储支持随机访问不关心插入删除效率大量插入和删除操作不关心随机访问 本文要是有不足的地方欢迎大家在下面评论我会在第一时间更正。