stl源码剖析list,stl list遍历

  stl源码剖析list,stl list遍历

  Yyds干货库存

  写到这里,我们知道了STL的具体框架,你会发现里面的一些函数都是用同样的方法。这篇博客主要讲迭代器的子封装。我们皱眉串和向量的迭代器是原生指针,但是今天的不同。这里你会发现,既然我们的字符串和向量都可以支持下标访问,为什么迭代器还存在?今天的列表你会发现它不支持下标。为了统一,我们的STL支持迭代器。

  我们来看看list中的函数,其中最重要的是构造函数。这里先看用法,不要在意原理。这里就不说那些熟悉的功能了,后面实现的时候再说。

  构造函数一共有四个构造函数,其中我们可以只把比较常用的拿出来。

  构造器

  界面描述

  list (size_type n,常量值_类型val=值_类型())

  由构造的列表包含n个值为val的元素。

  列表()

  构建一个空列表

  列表(常量列表x)

  复制构造函数

  列表(输入运算符在前,输入运算符在后)

  使用[first,last] interval中的元素构建一个列表。

  首先,测试构造的列表是否包含n个带有val值的元素。

  #包括iostream

  #包含列表

  使用命名空间std

  int main()

  {

  list int l(10,1);

  返回0;

  }

  看看另一个没有参数的构造函数。

  #包括iostream

  #包含列表

  使用命名空间std

  int main()

  {

  list int l;

  返回0;

  }

  我们不会在这里演示其余部分。我们以前学习的时候用过,没必要浪费你的时间。

  迭代器这里,我们最好先和大家分享一下迭代器。这里有四种迭代器,比如正向迭代器。在用法上没有什么新意。在这里,我们最好演示一个。

  int main()

  {

  list int l(10,1);

  list int:iterator it=l . begin();

  而(它!=l.end())

  {

  cout * it“”;

  它;

  }

  返回0;

  }

  Reverse()这是一个反转函数,就像我们之前实现的字符串反转一样。下面我们来看看。

  #包括iostream

  #包含列表

  使用命名空间std

  int main()

  {

  list int l;

  for(int I=0;i 10我)

  {

  l . push _ back(I);

  }

  l . reverse();

  for (int val : l)

  {

  cout val“”;

  }

  cout endl

  返回0;

  }

  Sort()list他自己内置了一个排序函数,具体用法比较简单。先用一下,里面的比较器是给一个自定义类型的。我们以后再谈这个。

  #包括iostream

  #包含列表

  #包含时间. h

  使用命名空间std

  int main()

  {

  srand((无符号int)time(NULL));

  list int l;

  for(int I=0;i 20我)

  {

  int ret=rand()% 100;

  l . push _ back(ret);

  }

  for (int val : l)

  {

  cout val“”;

  }

  cout endl

  l . sort();

  for (int val : l)

  {

  cout val“”;

  }

  cout endl

  返回0;

  }

  list的排序函数性能有点低,我们一般不用。我会在这个博客上分享这个功能。

  merge()merge(()的本质是合并两个有序链表。记住是有序的,也就是我们最好先把两个链表快照,再合并。

  int main()

  {

  srand((无符号int)time(NULL));

  list int l1

  list int l2

  for(int I=0;i 20我)

  {

  int ret=rand()% 100;

  L1 . push _ back(ret);

  }

  for(int I=0;i 20我)

  {

  int ret=rand()% 100;

  L2 . push _ back(ret);

  }

  L1 . sort();

  L2 . sort();

  l1 .合并(L2);

  for (int val : l1)

  {

  cout val“”;

  }

  cout endl

  返回0;

  }

  注意,当我们使用merge函数时,知道哪个参数最多的列表被清除就足够了。

  int main()

  {

  srand((无符号int)time(NULL));

  list int l1

  list int l2

  for(int I=0;i 20我)

  {

  int ret=rand()% 100;

  L1 . push _ back(ret);

  }

  for(int I=0;i 20我)

  {

  int ret=rand()% 100;

  L2 . push _ back(ret);

  }

  L1 . sort();

  L2 . sort();

  l1 .合并(L2);

  cout L2 . size()endl;

  返回0;

  }

  unique()函数是一个重复数据删除函数,但是我们仍然需要对列表进行排序。这里演示了简单的重复数据删除。看看这种无序的情况。这可以理解为我们只是去一个区域复制。

  int main()

  {

  list int l;

  l . push _ back(1);

  l . push _ back(1);

  l . push _ back(2);

  l . push _ back(1);

  l . push _ back(3);

  for (int val : l)

  {

  cout val“”;

  }

  cout endl

  l . unique();

  for (int val : l)

  {

  cout val“”;

  }

  cout endl

  返回0;

  }

  排序性能list中排序的本质是归并排序,list中的排序数据是一种浪费。我们在想一件事。STL中我们的算法库中不是有排序算法吗?为什么这里有一个内置列表?我们先来看看算法库。

  #包含算法

  int main()

  {

  srand((无符号int)time(NULL));

  list int l;

  for(int I=0;i 20我)

  {

  int ret=rand()% 100;

  l . push _ back(ret);

  }

  for (int val : l)

  {

  cout val“”;

  }

  cout endl

  std:sort(l.begin(),l . end());

  for (int val : l)

  {

  cout val“”;

  }

  cout endl

  返回0;

  }

  迭代器分类我们发现了上面报道的一堆错误,这里就不要让大家多想了。我们可以在这里观察事物,我们称之为随机迭代器。这里我们有点困惑。迭代器还会分类吗?好像没见过面,主要是之前淡化了这个知识点。

  我们来观察一下string和vector的迭代器类型,顺便把迭代器类型拿出来。

  迭代器分类

  单迭代器,双向迭代器,随机迭代器,单迭代器

  先说这个迭代器。单迭代器只能,这个的stl对应下面几个。

  双向迭代器

  这里就不截图了,双向迭代器或者-,之类的东西还有很多,比如list,map,set。通常,如果底层物理结构不连续,就会出现这种情况。

  随机迭代器

  随机迭代器支持-、-和-。我们学的vector,string,加上后面要学的adapter deque,都是随机迭代器。

  排序性能从这里可以看出,算法中的排序是随机迭代器,而我们的链表只是双向迭代器,不太合适。我们可以将随机迭代器传递给双向迭代器,反之亦然,这就是为什么我们的列表有内置排序。

  我们需要测试sort的性能。在某种意义上,列表中的排序是无用的。我们先测试一下vector和list的排序比较。

  int main()

  {

  srand((无符号int)time(NULL));

  const int N=1024 * 1024

  list int l;

  向量int v;

  for(int I=0;I N;我)

  {

  int val=rand();

  l.push_back(瓦尔);

  v . push _ back(val);

  }

  int begin 1=clock();

  l . sort();

  int end 1=clock();

  int begin 2=clock();

  std:sort(v.begin()、v . end());

  int end 2=clock();

  “cout”列表:“(end 1-begin 1)endl;

  cout sort:“(end 2-begin 2)endl;

  返回0;

  }

  我们发现,如果数据量小于1w,两个系统的性能相差不大,但是随着数据量的扩大,两个系统的差别越来越大。

  我们再测试一次。如果我们按向量排序,然后在指南列表中去看他们的表现如何,你会发现我是按向量排序的,并对其进行了排名。我比你落后,那你是废物吗?

  int main()

  {

  srand((无符号int)time(NULL));

  const int N=1024 * 1024

  list int l1

  list int l2

  向量int v;

  for(int I=0;I N;我)

  {

  int val=rand();

  L1 . push _ back(val);

  l2 .推回(val);

  }

  //排序l1

  int begin 1=clock();

  L1 . sort();

  int end 1=clock();

  //移动前排序

  int begin 2=clock();

  五.储备(N);

  for (int val : l2)

  {

  v . push _ back(val);

  }

  std:sort(v.begin()、v . end());

  //再填回去

  L2 . clear();

  for (int val : v)

  {

  l2 .推回(val);

  }

  int end 2=clock();

  “cout”列表:“(end 1-begin 1)endl;

  cout vector:“(end 2-begin 2)endl;

  返回0;

  }

  我们可以在这里看看迭代器的封装。之前我们都是用原生指针。本质原因是它们的物理空间是连续的。在这里,list并不是所谓的连续空间,它们可以一个一个地放置节点。那么-,就有问题了,所以这里需要把迭代器单独拿出来。

  让我们先写简单的迭代器,然后再完善它。

  模板类T

  结构列表迭代器

  {

  typedef _list_node T节点;

  typedef _ list _ iterator T self//这是必需的,我们稍后会对此进行修改。如果使用了tepdef,则只能修改这一个。

  Node * _ node//我们给的是指针,节点的空间可能会大一些。

  _list_iterator(Node* node)

  :_node(节点)

  {

  }

  };大家想想,所谓的或者——不就是当前指针的节点指向next或者prev吗?对应的迭代器对象不变,但是里面的内容变了。

  int main()

  {

  list int l;

  l . push _ back(1);

  l . push _ back(2);

  l . push _ back(3);

  l . push _ front(10);

  list int:iterator it=l . begin();

  而(它!=l.end())

  {

  cout it endl

  它;

  }

  cout l . size()endl;

  返回0;

  }

  迭代器实现的一般结构如下。我们将第一个元素视为开始,将标记位视为结束。这其中还是有一些原因的,不过会在后面的博客中分析,其中涉及到反向迭代器。

  根据考试成绩分等级排列的投考者的名单

  {

  公共:

  typedef _list_node T节点;

  typedef _ list _ iterator T iterator;

  公共:

  列表()

  {

  //首先,新建一个头节点

  _head=新节点;

  _ head-_ next=_ head;

  _ head-_ prev=_ head;

  }

  私人:

  Node * _ head//哨兵位置

  }

  Begin() end()先实现常用的,后面const修改的危害需要单独讨论。

  迭代器begin()

  {

  返回迭代器(_ head-_ next);

  }

  迭代器结束()

  {

  返回迭代器(_ head);

  }

  接线员!=operator==这里我们知道只是改变了迭代器中的指针,就不说细节了。我们来加==和!=超载吧。这个只需要比较里面的指针。

  布尔运算符!=(const self t)常量

  {

  返回_节点!=t. _ node

  }

  布尔运算符==(常数self t)常数

  {

  回归!(*这个!=t);

  }

  operator的意思是把里面的指针指向下一个地址,本质上没什么可看的。

  //前面

  self运算符()

  {

  _ node=_ node-_ next;

  返回* this

  }

  //后部

  自身运算符(整数)

  {

  self cur=* this//调用复制构造。

  _ node=_ node-_ next;

  返回cur

  }

  算子——既然都实现了,也相对容易。

  //前面

  自运算符-()

  {

  _ node=_ node-_ prev;

  返回* this

  }

  //后部

  自运算符-(整数)

  {

  self cur(* this);

  _ node=_ node-_ prev;

  返回cur

  }

  操作员*在这里开始出现问题。这个操作符是解引用的,即获取节点中的数据。我们这里用T作为返回值,或者突破类域。这种突破类域的方法我们后面会讲。这是标准图书馆的惯例。

  测试运算符*()

  {

  return _ node-_ data;

  }

  运算符——既然提出了解决方案,那就先把所有运算符都写在这里,然后我们再修改。运算符——是编译器优化,本来应该是(迭代器。_node- \_date)。

  T*运算符-()

  {

  返回(_ node-_ data);

  }

  测试迭代器现在我们可以看到迭代器是如何工作的。在这里,我们将测试这种情况。这里最大的问题是后两个。

  先测试一下常用迭代器,但是后面的const迭代器有些问题。

  int main()

  {

  bit:list int l;

  l . push _ back(1);

  l . push _ back(2);

  l . push _ back(3);

  l . push _ back(4);

  l . push _ back(5);

  l . push _ back(6);

  l . push _ back(7);

  l . push _ front(10);

  bit:list int:iterator it=l . begin();

  而(它!=l.end())

  {

  cout * it endl

  它;

  }

  返回0;

  }

  结构A

  {

  A(int a=0,int b=0)

  {

  a1=a;

  a2=b;

  }

  int a1

  int a2

  };

  int main()

  {

  bit:列出一个l;

  l.push_back(A(1,2));

  l.push_back(A(1,2));

  l.push_back(A(1,2));

  bit:list A:iterator it=l . begin();

  而(它!=l.end())

  {

  cout it-a1 it-a2 endl;

  它;

  }

  返回0;

  }

  现在我们已经确认了普通迭代器在正常使用,我们需要看看const修改的迭代器。这里我们有两种方法。第一种方法是我们重写一个类,但是这种方法反复做轮子。我们不推荐。先把另外两个暂时放在一边,先看看我们遇到的问题。

  #include assert.h

  命名空间位

  {

  ////这是列表节点

  模板类T

  结构列表节点

  {

  _ list _ node T * _ next

  _ list _ node T * _ prev

  T _ data

  //先看看构造函数要不要写

  _list_node(const T t=T())

  :_next(nullptr)

  ,_prev(nullptr)

  ,_数据(吨)

  {

  }

  };

  模板类T

  结构列表迭代器

  {

  类型定义_列表_节点T节点;

  typedef _ list _ iterator测试自身

  Node * _ node

  _list_iterator(Node* node)

  :_node(节点)

  {

  }

  //析构拷贝都不需要

  布尔运算符==(常数自我测试)常数

  {

  回归!(*这个!=t);

  }

  T*运算符-()

  {

  return _ node-_ data;

  }

  测试运算符*()

  {

  return _ node-_ data;

  }

  布尔运算符!=(const self t)常量

  {

  返回_节点!=t. _节点

  }

  //前置

  自己运算符()

  {

  _ node=_ node-_ next;

  返回*这个

  }

  自运算符-()

  {

  _ node=_ node-_ prev;

  返回*这个

  }

  //后置

  自身运算符(整数)

  {

  自我约束=*这

  _ node=_ node-_ next;

  返回坏蛋

  }

  自运算符-(整数)

  {

  self cur(* this);

  _ node=_ node-_ prev;

  返回坏蛋

  }

  };

  模板类T //这个就是一个模板

  根据考试成绩分等级排列的投考者的名单

  {

  公共:

  类型定义_列表_节点T节点;

  typedef _ list _ iterator T iterator;

  typedef _ list _ iterator const T const _ iterator;

  //要有构造函数作为一个标记位

  列表()

  {

  //首先新的一个头节点

  _head=新节点;

  _ head-_ next=_ head;

  _ head-_ prev=_ head;

  }

  //插入不做说明的话我们插入节点的前面

  迭代器插入(迭代器位置,常量值)

  {

  //记录迭代器对应的节点指针

  Node* ppos=pos ._ node

  节点*当前=新节点(val);

  //记录前一个节点

  node * prev=ppos-_ prev;

  cur-_ next=ppos;

  ppos-_ prev=cur;

  cur-_ prev=prev;

  prev-_ next=cur;

  返回迭代器(cur);

  }

  无效推回(常量值)

  {

  insert(end(),val);

  }

  //迭代器

  迭代器开始()

  {

  返回迭代器(_ head-_ next);

  }

  const_iterator begin() const

  {

  返回const _ iterator(_ head-_ next);

  }

  const_iterator end() const

  {

  返回const _ iterator(_ head);

  }

  迭代器结束()

  {

  返回迭代器(_ head);

  }

  私人:

  节点* _头

  };

  }

  void func(const bit:list int l)

  {

  bit:list int:const _ iterator it=l . begin();

  而(它!=l.end())

  {

  cout * it结束

  它;

  }

  }

  int main()

  {

  bit:list int l;

  我。push _ back(1);

  我。push _ back(2);

  我。push _ back(3);

  我。push _ back(4);

  我。push _ back(5);

  我。push _ back(6);

  我。push _ back(7);

  func(l);

  返回0;

  }

  我们学到了模板,这里的编译器报的错误就会变多了,这里我们看看高手是怎么控制的。高手给他们重新添加了一个模板,主要就是返回值作用。

  模板类t,类裁判,类光电带读数机(photoelectric tape reader)

  结构列表迭代器

  {

  类型定义_列表_节点T节点;

  typedef _list_iterator T,Ref,Ptr self

  Node * _ node

  _list_iterator(Node* node)

  :_node(节点)

  {

  }

  布尔运算符==(常数自我测试)常数

  {

  回归!(*这个!=t);

  }

  光电带读数机(photoelectric tape reader)操作员-()

  {

  return _ node-_ data;

  }

  引用运算符*()

  {

  return _ node-_ data;

  }

  };

  根据考试成绩分等级排列的投考者的名单

  {

  公共:

  类型定义_列表_节点T节点;

  typedef _list_iterator T,T,T * iterator

  typedef _list_iterator T,const T,const T * const _ iterator

  }

  这样就可以了。关于目录的迭代器我们到这里就放一段落,后面等我们谈过栈,在和大家谈反向迭代器

  目录实现我们在C语言过程中学过部分数据结构,学了顺序表,也就是我们的向量,那么今天我们分享的本质就是链表,而且是带哨兵位的双向循环链表。我们先把这个节点给定义出来,一般这种节点我们都是用结构。

  //这个是目录节点

  模板类T

  结构列表节点

  {

  _ list _ node T * _ next

  _ list _ node T * _ prev

  T _ data

  //先看看构造函数要不要写

  _list_node(const T t=T())

  :_next(nullptr)

  ,_prev(nullptr)

  ,_数据(吨)

  {

  }

  };

  这里我们先把框架搭出来,我们最好把节点数据类型说明一下。

  模板类T

  根据考试成绩分等级排列的投考者的名单

  {

  公共:

  类型定义_列表_节点T节点;

  公共:

  列表()

  {

  //首先新的一个头节点

  _head=新节点;

  _ head-_ next=_ head;

  _ head-_ prev=_ head;

  }

  私人:

  Node * _ head//哨兵位

  };

  Insert()让我们先实现insert。后面的尾插页和头插页可以重用这个代码,就不重复轮了。这里你不必关心迭代器。我们稍后将讨论这个迭代器包。你只需要记住迭代器里有一个节点指针,就是当前节点地址。这里的返回值是插入到元素中的第一个节点的迭代器。我是按照标准库来的。

  迭代器插入(迭代器位置,常量值)

  {

  //记录迭代器对应的节点指针

  Node* ppos=pos。_ node

  Node* cur=新节点(val);

  //记录上一个节点

  node * prev=ppos-_ prev;

  cur-_ next=ppos;

  ppos-_ prev=cur;

  cur-_ prev=prev;

  prev-_ next=cur;

  返回迭代器(cur);

  }

  重用push_back() push_front()代码即可。

  void push_back(常量值)

  {

  insert(end(),val);

  }

  void push_front(常量值)

  {

  insert(begin(),val);

  }

  这个需要测试,这样才能证明之前的insrt是正确的。

  int main()

  {

  bit:list int l;

  l . push _ back(1);

  l . push _ back(2);

  l . push _ back(3);

  l . push _ back(4);

  l . push _ back(5);

  l . push _ back(6);

  l . push _ back(7);

  l . push _ front(10);

  bit:list int:iterator it=l . begin();

  而(它!=l.end())

  {

  cout * it“”;

  它;

  }

  cout endl

  返回0;

  }

  Erase()最后,我们将实现delete函数,其他函数将被忽略。主要是太简单,没有挑战性。如果你想看的话,我们后面会附上仓库的链接,里面实现的功能更多。

  迭代器擦除(迭代器位置)

  {

  Node* ppos=pos。_ node

  //无法删除标记位置。

  断言(位置!=_ head);

  node * prev=ppos-_ prev;

  node * next=ppos-_ next;

  prev-_ next=next;

  next-_ prev=prev;

  删除ppos

  返回迭代器(下一个);

  }

  Pop_back() pop_front这里再用一下吧。

  void pop_back()

  {

  erase(-end());

  }

  void pop_front()

  {

  erase(begin());

  }

  注意删除元素会导致迭代器失败。在这里,我们首先使用擦除测试。会有头删尾之类的问题,标准库中也有。

  int main()

  {

  bit:list int l;

  l . push _ back(1);

  l . push _ back(2);

  l . push _ back(3);

  l . push _ back(4);

  l . push _ back(5);

  l . push _ back(6);

  l . push _ back(7);

  l . push _ front(10);

  bit:list int:iterator it=l . begin();

  而(它!=l.end())

  {

  it=l . erase(it);

  }

  cout l . size()endl;

  返回0;

  }

  迭代器失败我们来测试一下list中迭代器失败的问题。这里很简单。我们先来看看insert。这个没有问题。

  int main()

  {

  *国际清单;

  l . push _ back(1);

  l . push _ back(2);

  l . push _ back(2);

  l . push _ back(4);

  l . push _ back(4);

  l . push _ back(6);

  l . push _ back(7);

  auto it=l . begin();

  而(它!=l.end())

  {

  if (*it % 2==0)

  {

  l.insert(it,* it * 10);

  }

  它;

  }

  for (int val : l)

  {

  cout val“”;

  }

  返回0;

  }

  但是erase会变成迭代器失效的问题。我们很容易理解擦除。一般我们删除数据的时候,这个会变成一个野指针。

  int main()

  {

  *国际清单;

  l . push _ back(1);

  l . push _ back(2);

  l . push _ back(2);

  l . push _ back(4);

  l . push _ back(4);

  l . push _ back(6);

  l . push _ back(7);

  auto it=l . begin();

  l.erase(它);

  * it

  返回0;

  }

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

相关文章阅读

  • office2010激活密钥大全 怎么永久激活office2010
  • project2010产品密钥免费_project2010激活密钥永久激活码
  • c语言调用退出函数 c语言退出整个程序怎么写
  • c语言中怎么给函数初始化 c语言的初始化语句
  • c语言编写函数计算平均值 c语言求平均函数
  • chatgpt是什么?为什么这么火?
  • ChatGPT为什么注册不了?OpenAI ChatGPT的账号哪里可以注册?
  • OpenAI ChatGPT怎么注册账号?ChatGPT账号注册教程
  • chatgpt什么意思,什么是ChatGPT ?
  • CAD中怎么复制图形标注尺寸不变,CAD中怎么复制图形线性不变
  • cad中怎么创建并使用脚本文件,cad怎么运行脚本
  • cad中快速计算器的功能,cad怎么快速计算
  • cad中快速修改单位的方法有哪些,cad中快速修改单位的方法是
  • cad中心点画椭圆怎么做,cad轴测图怎么画椭圆
  • CAD中常用的快捷键,cad各种快捷键的用法
  • 留言与评论(共有 条评论)
       
    验证码: