list介绍

list的本质是一个带头的双向循环链表。

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另 一个是存储下一个结点地址的指针域。

​ 相较于vector的连续线性空间,list就显得负责许多,它的好处是每次插入或者删除一个元素,就只配置或者释放一个元素的空间。因此,list对于空间的运用有绝对的精准, 一点也不浪费。而且,对于任何位置的元素插入或元素的移除,list永远是常数时间。

​ List和vector是两个最常被使用的容器。 List容器是一个双向链表。

  • 采用动态存储分配,不会造成内存浪费和溢出
  • 链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素
  • 链表灵活,但是空间和时间额外耗费较大
  • list有一个重要的性质,插入和删除操作都不会造成原有的list迭代器失效

概述

list容器

  • 数据结构:双向循环链表
  • 迭代器:双向迭代器
  • 常用API
    • 构造
    • 数据元素的插入和删除
    • 容器大小操作
    • 赋值操作
    • 数据的存取
    • 反转和排序
  • 动态存储分配(链表的插入和删除)
  • 注意:list容器不能使用常用的sort,只能使用自己的sort
  • list容器插入和删除很方便,但是不支持任意位置的随机访问

list常见的接口

list的构造函数

list<T> lstT;//list采用采用模板类实现,对象的默认构造形式
list(beg,end);//构造函数将[beg, end)区间中的元素拷贝给本身
list(n,elem);//构造函数将n个elem拷贝给本身
list(const list &lst);//拷贝构造函数
void test()
{
list<int> lt1;// 无参构造
list<int> lt2(10, 5);// 用n个val构造一个list对象
list<int> lt3(lt2);// 拷贝构造
list<int> lt4(lt2.begin(), lt2.end());// 用一段区间的元素构造list
}

list中的迭代器

  • begin + end: 获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置的iterator/const_iterator(最后一个数据的下一个位置就是第一个数据的位置)
  • rbegin + rend: 获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置的reverse_iterator(第一个数据的前一个位置就是最后一个数据的位置)
  • list容器是一个双向的循环链表

list的迭代器遍历

1.迭代器遍历正向遍历

void test01()
{
list<int> lt;
//尾插
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
//头插
lt.push_front(0);
lt.push_front(-1);
lt.push_front(-2);
list<int>::iterator it = lt.begin();
while (it != lt.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}

2.范围for

for (auto e : lt)
{
cout << e << " ";
}
cout << endl;

3.迭代器反向遍历

list<int>::reverse_iterator rit = lt.rbegin();
while (rit != lt.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
}

输出结果如下:

list的增删改查

assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身
assign(n, elem);//将n个elem拷贝赋值给本身
push_back(elem);//在容器尾部加入一个元素
pop_back();//删除容器中最后一个元素
push_front(elem);//在容器开头插入一个元素
pop_front();//从容器开头移除第一个元素
insert(pos,elem);//在pos位置插elem元素的拷贝,返回新数据的位置
insert(pos,n,elem);//在pos位置插入n个elem数据,无返回值
insert(pos,beg,end);//在pos位置插入[beg,end)区间的数据,无返回值
clear();//移除容器的所有数据
erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置
erase(pos);//删除pos位置的数据,返回下一个数据的位置
remove(elem);//删除容器中所有与elem值匹配的元素
swap(lst);//将lst与本身的元素互换
list<int> mylist;
mylist.push_back(19);
mylist.push_back(29);
mylist.push_back(39);
mylist.push_back(49);
mylist.push_back(59);
mylist.push_front(100);
mylist.push_front(200);
mylist.push_front(300);
mylist.push_front(400); vector<int> v;
v.push_back(1000);
v.push_back(2000);
v.push_back(3000); mylist.insert(mylist.begin(), v.begin(), v.end());
printList(mylist); mylist.remove(300);
//删除大于300的数据
mylist.remove_if(myfunc);

list的大小和头尾元素的读取

size();//返回容器中元素的个数
empty();//判断容器是否为空
resize(num);//重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除
resize(num, elem);//重新指定容器的长度为num,若容器变长,则以值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除

list迭代器失效

迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。

第一种情况:插入

list<int> mylist;
mylist.push_back(19);
mylist.push_back(29);
mylist.push_back(39);
mylist.push_back(49);
mylist.push_back(59);
list<int>::iterator it = mylist.begin();
mylist.insert(it,3);

运行结果没有问题,不会报错

第二种情况:删除

list<int> mylist;
mylist.push_back(19);
mylist.push_back(29);
mylist.push_back(39);
mylist.push_back(49);
mylist.push_back(59);
list<int>::iterator it = mylist.begin();
while( it! = mylist.end())
{
mylist.erase(it);
++it;
}

总结:插入数据不会导致迭代器失效,删除数据会导致迭代器失效。相比vector容器,vector容器插入数据是会导致迭代器失效,因为vector涉及增容问题,而list却不存在增容问题,所以迭代器指向的位置是有效的。删除数据会导致迭代器指向的位置是无效的,所以迭代器会失效。

解决方法:和vector一样,对迭代器进行赋值

list<int> mylist;
mylist.push_back(19);
mylist.push_back(29);
mylist.push_back(39);
mylist.push_back(49);
mylist.push_back(59);
list<int>::iterator it = mylist.begin();
while( it! = mylist.end())
{
it = mylist.erase(it);//erase()返回值是指向被删元素的下一元素的指针(也就是迭代器)
}

list模拟实现

list整体框架

list是由节点组成,所以定义一个节点的类,然后list的类中成员只需要一个头结点的指针即可。

template<class T>
struct __list_node
{
__list_node<T>* _prev;
__list_node<T>* _next;
T _data;
__list_node(const T& x = T())
:_next(nullptr)
, _prev(nullptr)
, _data(x)
{}
};
template<class T>
class list
{
typedef __list_node<T> Node;
public:
private:
Node* _head;
};

list的构造函数

构造函数要做的任务就是开一个头结点,所以我们可以封装出一个具体的函数来实现创建头结点的这个过程

创建头结点:

void CreatHead()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
}

构造函数的实现:

list()
{
CreatHead();
}

list迭代器的实现

list相比vector的迭代器而言,不再是一个简单的指针,它相对而言更复杂一些,list的迭代器为了实现一些简单的功能,我们把它封装成了一个类。看下面源码实现:

我们自己来模拟实现一下简单的。

迭代器的小框架(里面有一个成员变量——节点指针)

struct __list_iterator
{
typedef __list_node<T> Node;
__list_iterator(Node* node = nullptr)
:_node(node)
{}
Node* _node;
}

由于迭代器分普通迭代器和const 迭代器,为了不造成代码冗余,我们设计出来三个模板参数,根据传入的模板参数确定是那种迭代器。

// __list_iterator<T, T&, T*>  ->  普通迭代器
// __list_iterator<T, const T&, const T*> -> const迭代器
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 = nullptr)
:_node(node)
{}
__list_iterator(const Self& l)
:_node(l._node)
{}
// *it T&
Ref operator*()
{
return _node->_data;
}
// it-> T*
Ptr operator->()
{
return &_node->_data;
}
Self& operator++()
{
_node = _node->_next;
return *this;
}
Self& operator--()
{
_node = _node->_prev;
return *this;
}
Self operator++(int)
{
Self tmp(*this);
//_node = _node->_next;
++(*this); return tmp;
}
Self operator--(int)
{
Self tmp(*this);
//_node = _node->_prev;
--(*this); return tmp;
}
Self operator+(int count)
{
Self tmp(*this);
while (count--)
{
++tmp;
} return tmp;
}
Self operator-(int count)
{
Self tmp(*this);
while (count--)
{
--tmp;
} return tmp;
}
bool operator!=(const Self& it)
{
return _node != it._node;
}
};

我们还要在list里面做这样一个操作(堆两种迭代器进行重命名,方便我们认识):

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

list内部begin()和end()的实现(普通迭代器调用前两个,const迭代器调用后两个)

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的增删查改的实现

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;
} void pop_back()
{
assert(head != head->_next);
Node* tail = head->_prev;
Node* prevTail = tail->_prev;
delete tail;
tail = prevTail; tail->_next = head;
head->_prev = tail;
} void push_front(const T& x)
{
Node* newnode = new Node(x);
Node* firstNode = head->_next; head->_next = newnode;
newnode->_prev = head;
newnode->_next = firstNode;
firstNode->_prev = newnode;
} void pop_front()
{
assert(head->_next != head);
Node* firstNode = head->_next;
Node* secondNode = firstNode->_next; delete firstNode;
firstNode = nullptr; head->_next = secondNode;
secondNode->_prev = head;
} void insert(iterator pos, const T& x)
{
Node* cur = pos._node;
Node* prev = cur->_prev; Node* newnode = new Node(x); prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
} iterator erase(iterator pos)
{
assert(head->_next != head);
assert(pos != end()); Node* node = pos._node;
Node* prev = node->_prev;
Node* next = node->_next; delete node;
node = nullptr; prev->_next = next;
next->_prev = prev; return iterator(next);
} T front()
{
assert(head->_next != head);
return head->_next->data;
} T back()
{
assert(head->_next != head);
return head->_prev->data;
}

list中的析构函数和clear

1.clear 通过迭代器遍历,一个一个的删除节点

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

2.析构函数 可以先调用clear函数清理空间,然后再delete掉头结点

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

拷贝构造和operator=赋值重载

1.拷贝构造

list(const list<T>& lt)
{
CreatHead();
/*const_iterator it = lt.begin();
while (it != lt.end())
{
push_back(*it);
++it;
}*/
for (auto e : lt)
push_back(e);
}

2.operator= 直接利用swap和形参交换,形参会自己调用析构函数清理空间

list<T>& operator=(list<T> lt)
{
if (this != &lt)// 防止自己给自己赋值
{
swap(lt);
} return *this;
}

swap函数实现如下:

void swap(list<T>& lt)
{
::swap(head, lt.head);
}

C++初阶(list容器+模拟实现)的更多相关文章

  1. Nodejs初阶之express

    PS: 2014/09/24 更新<Express 4.X 启航指南>,欢迎阅读和评论:)   老规矩,开头部分都是些自娱自乐的随想,想到哪写到哪... 到今天俺已经在俺厂工作俩年零几天了 ...

  2. R语言实战(一)介绍、数据集与图形初阶

    本文对应<R语言实战>前3章,因为里面大部分内容已经比较熟悉,所以在这里只是起一个索引的作用. 第1章       R语言介绍 获取帮助函数 help(), ? 查看函数帮助 exampl ...

  3. 平衡树初阶——AVL平衡二叉查找树+三大平衡树(Treap + Splay + SBT)模板【超详解】

    平衡树初阶——AVL平衡二叉查找树 一.什么是二叉树 1. 什么是树. 计算机科学里面的树本质是一个树状图.树首先是一个有向无环图,由根节点指向子结点.但是不严格的说,我们也研究无向树.所谓无向树就是 ...

  4. 重温ASP.NET WebAPI(一)初阶

    重温ASP.NET WebAPI(一)初阶   前言 本文为个人对WebApi的回顾无参考价值.主要简单介绍WEB api和webapi项目的基本结构,并创建简单地webaapi项目实现CRUD操作. ...

  5. JAVA容器-模拟ArrayList的底层实现

    概述 ArrayList实质上就是可变数组的实现,着重理解:add.get.set.remove.iterator的实现,我们将关注一下问题. 1.创建ArrayList的时候,默认给数组的长度设置为 ...

  6. IOC容器模拟实现

    运用反射机制和自定义注解模拟实现IOC容器,使其具有自动加载.自动装配和根据全限定类名获取Bean的功能. 一. 实现原理 1-1 IOC容器的本质 IOC容器可理解为是一个map,其中的一个entr ...

  7. Android 初阶自定义 View 字符头像

    自己很少做自定义 View ,只有最开始的时候跟着郭神写了一个小 Demo ,后来随着见识的越来越多,特别是在开源社区看到很多优秀的漂亮的控件,都是羡慕的要死,但是拉下来的代码还是看不明白,而且当时因 ...

  8. zoj 2724 Windows Message Queue(使用priority_queue容器模拟消息队列)

    题目链接: http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=2724 题目描述: Message queue is the b ...

  9. JAVA容器-模拟LinkedList实现(双链表)

    概述 LinkedList实质上就是双向链表的拓展的实现,我们将关注一下问题.LinkedList 1.双向链表怎么来实现插入.删除.查询? 2.利用二分法提高查询效率. 3.不同步,线程不安全,需要 ...

  10. 《R语言实战》读书笔记--第三章 图形初阶(一)

    3.1使用图形 可以使用pdf等函数将图形直接保存在文件中.在运用attach和detach函数的使用中经常出现错误,比如命名重复的问题,所以,应该尽量避免使用这两个函数. plot是一般的画图函数, ...

随机推荐

  1. DFS算法-求集合的所有子集

    目录 1. 题目来源 2. 普通方法 1. 思路 2. 代码 3. 运行结果 3. DFS算法 1. 概念 2. 解题思路 3. 代码 4. 运行结果 4. 对比 1. 题目来源 牛客网,集合的所有子 ...

  2. 在kibana中查看nginx日志的Discover,Dashboards

    官方的操作: 1.安装filebeat,配置filebeat获取nginx日志,来源有两种: 第一种是使用自带的模块进行收集,在modules.d目录中启用模块配置,运行Filebeat时启用模块,在 ...

  3. Elasticsearch的mapping讲解

    映射是定义文档及其包含的字段的存储和索引方式的过程. 映射定义具有: 元字段 元字段用于自定义如何处理关联的文档元数据.包括文档 _index,_id和 _source领域. 字段或属性 映射包含pr ...

  4. 在CentO7系统上配置Springboot项目jar包开机自启动

    官方文档地址:https://docs.spring.io/spring-boot/docs/current/reference/html/deployment.html#deployment-ins ...

  5. day04-MySQL常用函数01

    5.MySQL常用函数 5.1合计/统计函数 5.1.1合计函数-count count 返回行的总数 Select count(*)|count (列名) from table_name [WHER ...

  6. 媒介查询兼容各种端口的响应式范围取值(移动端、PC端、ipad、移动端侧屏)

    !!!(chrome作者亲测)!!!数据仅供参考 /*ipad*/@media screen and (min-width:760px) and (max-width:1000px) /*移动端*/@ ...

  7. C++面向对象编程之reference

    1.声明 reference 一定要有初值,指针可以不用设初值 2. int& r = x; 表示 r 代表 x, r 用起来就是 x ,而且 reference 设完初值后再也不能代表其他变 ...

  8. 洛谷P4011 【网络流24题】 孤岛营救问题 (BFS+状压)

    一道妙题啊......(不知道为什么这道题的标签是网络流,不需要用网络流啊) 如果没有门和钥匙,连边(边权为1)求最短路就行了. 但是有这两个因素的限制,我们采用分层建图的思想,一共2p层,每层对应持 ...

  9. 微信DAT文件转JPG图片(图片恢复)

    微信电脑版现在已经是日常工作生活必不可少的工具,有时候删除了聊天记录或者被系统清理软件清理了,但还想查看曾经的微信聊天图片. 这个时候辛辛苦苦找到了文件,却发现无法查看,因为微信电脑版为了保护我们的隐 ...

  10. VLQ & Base64 VLQ 编码方式的原理及代码实现

    目录 VLQ Base64 VLQ VLQ VLQ (Variable-length quantity)是一种通用的,使用任意位数的二进制来表示一个任意大的数字的一种编码方式. 编码实现: ** 对数 ...