C++初阶学习第十一弹——探索STL奥秘(六)——深度刨析list的用法和核心点

简介: C++初阶学习第十一弹——探索STL奥秘(六)——深度刨析list的用法和核心点

前言:

在前面,我们已经学习了STL中的string和vector,现在就来讲解STL中的最后一个部分——list的使用及其相关知识点,先说明一点,因为我们之前已经讲过了string和vector的接口函数等用法,list的这些用法与它们相差不大,所以我们讲解的重心就不再是如何使用list上,而是后面list的模拟实现和一些细节点


一、list的使用

1.1 list的简单接口函数

首先我们需要先明确list的底部实际上是类似一个带头双向链表的,结构如下图所示:

因此list非常便于插入和删除数据,下面我们就先来看一下list的一些重要的接口函数

初始化列表:

std::list<int> myList = {1, 2, 3, 4, 5};

通过迭代器访问元素:

std::list<int>::iterator it = myList.begin();
while (it != myList.end()) {
    std::cout << *it << std::endl;
    ++it;
}

在链表尾部插入元素:

myList.push_back(6);

在链表头部插入元素:

myList.push_front(0);

删除元素:

myList.remove(3); // 删除值为3的元素
myList.erase(it); // 删除迭代器指向的元素

排序链表:

myList.sort();

反转链表:

myList.reverse();

上面这些就是list经常使用的一些接口函数,没啥难度,有不理解的地方可以私信我或者到网上搜一下

1.2 list的注意事项

  • 迭代器失效: 在list进行插入和删除操作时,不仅操作的元素所在的迭代器会失效,所有指向链表的迭代器、指针和引用都会失效。因此,在进行操作后,需要重新获取有效的迭代器。(vector的使用也要注意这个问题)
  • 内存效率: list的内存效率相对较高,因为它不需要像数组那样连续分配内存,但是它的插入和删除操作的时间复杂度为O(1),这是因为链表的每个元素都需要存储指向前后节点的指针。
  • 没有容量概念: list没有容量(capacity)这个概念,它总是根据需要动态分配内存。
  • 元素唯一性: list中的元素是不重复的,如果尝试插入已经存在的元素,该元素将被覆盖。
  • 操作顺序: 由于list是双向链表,因此插入和删除操作会保持元素的相对顺序,即元素在链表中的位置不会改变。

使用list时,应该根据具体需求选择合适的操作,并注意迭代器的管理,以确保程序的正确性。

特别强调一下迭代器失效的问题,list的迭代器失效问题一般只有在删除元素的时候会出现,因为它插入数据的时候都是开辟的新空间,不同数据之间一般不是连接在一起的


二、list的模拟实现

list的模拟实现上与前面的vector和string也极为相似,这里我们主要想讲一下list的迭代器的模拟实现,首先我们要知道,因为我们期待迭代器能像指针那样发挥作用,所以它的模拟实现需要包含以下几点:


1. 指针可以解引用,迭代器的类中必须重载operator*()


2. 指针可以通过->访问其所指空间成员,迭代器类中必须重载oprator->()


3. 指针可以++向后移动,迭代器类中必须重载operator++()与operator++(int)


至于operator--()/operator--(int)释放需要重载,根据具体的结构来抉择,双向链表可以向前 移动,所以需要重载,如果是forward_list就不需要重载--


4. 迭代器需要进行是否相等的比较,因此还需要重载operator==()与operator!=()

list迭代器也要分为两种:正向迭代器和反向迭代器

因为list正反向迭代器的应用都要实现,所以还是比较麻烦的,下面我们直接来看一下实现

#include <iostream>
using namespace std;
#include <assert.h>
namespace zda
{
  // List的节点类
  template<class T>
  struct ListNode
  {
    ListNode(const T& val = T())
      : _prev(nullptr)
      , _next(nullptr)
      , _val(val)
    {}
 
    ListNode<T>* _prev;
    ListNode<T>* _next;
    T _val;
  };
 
  //正向迭代器
  template<class T, class Ref, class Ptr>
  class ListIterator
  {
    typedef ListNode<T> Node;
    typedef ListIterator<T, Ref, Ptr> Self;
 
    // Ref 和 Ptr 类型需要重定义下,实现反向迭代器时需要用到
  public:
    typedef Ref Ref;
    typedef Ptr Ptr;
  public:
    //
    // 构造
    ListIterator(Node* node = nullptr)
      : _node(node)
    {}
 
    //
    // 具有指针类似行为
    Ref operator*()
    {
      return _node->_val;
    }
 
    Ptr operator->()
    {
      return &(operator*());
    }
 
    // 迭代器支持移动
    Self& operator++()
    {
      _node = _node->_next;
      return *this;
    }
 
    Self operator++(int)
    {
      Self temp(*this);
      _node = _node->_next;
      return temp;
    }
 
    Self& operator--()
    {
      _node = _node->_prev;
      return *this;
    }
 
    Self operator--(int)
    {
      Self temp(*this);
      _node = _node->_prev;
      return temp;
    }
 
    // 迭代器支持比较
    bool operator!=(const Self& l)const
    {
      return _node != l._node;
    }
 
    bool operator==(const Self& l)const
    {
      return _node != l._node;
    }
 
    Node* _node;
  };
 
  //反向迭代器
  template<class Iterator>
  class ReverseListIterator
  {
  public:
    typedef typename Iterator::Ref Ref;
    typedef typename Iterator::Ptr Ptr;
    typedef ReverseListIterator<Iterator> Self;
  public:
    // 构造
    ReverseListIterator(Iterator it)
      : _it(it)
    {}
 
    // 具有指针类似行为
    Ref operator*()
    {
      Iterator temp(_it);
      --temp;
      return *temp;
    }
 
    Ptr operator->()
    {
      return &(operator*());
    }
 
    // 迭代器支持移动
    Self& operator++()
    {
      --_it;
      return *this;
    }
 
    Self operator++(int)
    {
      Self temp(*this);
      --_it;
      return temp;
    }
 
    Self& operator--()
    {
      ++_it;
      return *this;
    }
 
    Self operator--(int)
    {
      Self temp(*this);
      ++_it;
      return temp;
    }
 
    // 迭代器支持比较
    bool operator!=(const Self& l)const
    {
      return _it != l._it;
    }
 
    bool operator==(const Self& l)const
    {
      return _it != l._it;
    }
 
    Iterator _it;
  };
}


三、list和vector的区别

1、任意位置插入删除时:list可以随意插入删除,但是vector任意位置的插入删除效率低,需要挪动元素,尤其是插入时有时候需要异地扩容,就需要开辟新空间,拷贝元素,释放旧空间,效率很低

2、访问元素时:vector支持随机访问,但是list不支持随机访问

3、迭代器的使用上:vector可以使用原生指针,但是list需要对原生指针进行封装

4、空间利用上:vector使用的是一个连续的空间,空间利用率高,而list使用的是零碎的空间,空间利用率低

四、总结

以上就是学习list的一些重点内容和基本操作,这些内容当然是不全的,剩下有很多内容需要自己再去学习一下,后期我也会有针对的再加一些内容进来

感谢大佬观看,创作不易,还请各位大佬一键三连!!!

相关文章
|
4月前
|
存储 Java 索引
(Python基础)新时代语言!一起学习Python吧!(二):字符编码由来;Python字符串、字符串格式化;list集合和tuple元组区别
字符编码 我们要清楚,计算机最开始的表达都是由二进制而来 我们要想通过二进制来表示我们熟知的字符看看以下的变化 例如: 1 的二进制编码为 0000 0001 我们通过A这个字符,让其在计算机内部存储(现如今,A 字符在地址通常表示为65) 现在拿A举例: 在计算机内部 A字符,它本身表示为 65这个数,在计算机底层会转为二进制码 也意味着A字符在底层表示为 1000001 通过这样的字符表示进行转换,逐步发展为拥有127个字符的编码存储到计算机中,这个编码表也被称为ASCII编码。 但随时代变迁,ASCII编码逐渐暴露短板,全球有上百种语言,光是ASCII编码并不能够满足需求
235 4
|
存储 安全 编译器
第二问:C++中const用法详解
`const` 是 C++ 中用于定义常量的关键字,主要作用是防止值被修改。它可以修饰变量、指针、函数参数、返回值、类成员等,确保数据的不可变性。`const` 的常见用法包括:
|
算法 C++ 容器
模拟实现c++中的list模版
模拟实现c++中的list模版
|
存储 C++ 容器
【C++】map、set基本用法
本文介绍了C++ STL中的`map`和`set`两种关联容器。`map`用于存储键值对,每个键唯一;而`set`存储唯一元素,不包含值。两者均基于红黑树实现,支持高效的查找、插入和删除操作。文中详细列举了它们的构造方法、迭代器、容量检查、元素修改等常用接口,并简要对比了`map`与`set`的主要差异。此外,还介绍了允许重复元素的`multiset`和`multimap`。
374 3
【C++】map、set基本用法
|
编译器 C语言 C++
【c++丨STL】list模拟实现(附源码)
本文介绍了如何模拟实现C++中的`list`容器。`list`底层采用双向带头循环链表结构,相较于`vector`和`string`更为复杂。文章首先回顾了`list`的基本结构和常用接口,然后详细讲解了节点、迭代器及容器的实现过程。 最终,通过这些步骤,我们成功模拟实现了`list`容器的功能。文章最后提供了完整的代码实现,并简要总结了实现过程中的关键点。 如果你对双向链表或`list`的底层实现感兴趣,建议先掌握相关基础知识后再阅读本文,以便更好地理解内容。
305 2
|
算法 C语言 C++
【c++丨STL】list的使用
本文介绍了STL容器`list`的使用方法及其主要功能。`list`是一种双向链表结构,适用于频繁的插入和删除操作。文章详细讲解了`list`的构造函数、析构函数、赋值重载、迭代器、容量接口、元素访问接口、增删查改操作以及一些特有的操作接口如`splice`、`remove_if`、`unique`、`merge`、`sort`和`reverse`。通过示例代码,读者可以更好地理解如何使用这些接口。最后,作者总结了`list`的特点和适用场景,并预告了后续关于`list`模拟实现的文章。
438 7
|
存储 编译器 C++
C++ initializer_list&&类型推导
在 C++ 中,`initializer_list` 提供了一种方便的方式来初始化容器和传递参数,而右值引用则是实现高效资源管理和移动语义的关键特性。尽管在实际应用中 `initializer_list&&` 并不常见,但理解其类型推导和使用方式有助于深入掌握现代 C++ 的高级特性。
210 4
第十三问:C++中静态变量的用法有哪些?
本文介绍了 C++ 中静态变量和函数的用法及原理。静态变量包括函数内的静态局部变量和类中的静态成员变量,前者在函数调用间保持值,后者属于类而非对象。静态函数不能访问非静态成员,但可以通过类名直接调用。静态链接使变量或函数仅在定义文件内可见,避免命名冲突。
|
安全 Java
java线程之List集合并发安全问题及解决方案
java线程之List集合并发安全问题及解决方案
1596 1
|
运维 关系型数据库 Java
PolarDB产品使用问题之使用List或Range分区表时,Java代码是否需要进行改动
PolarDB产品使用合集涵盖了从创建与管理、数据管理、性能优化与诊断、安全与合规到生态与集成、运维与支持等全方位的功能和服务,旨在帮助企业轻松构建高可用、高性能且易于管理的数据库环境,满足不同业务场景的需求。用户可以通过阿里云控制台、API、SDK等方式便捷地使用这些功能,实现数据库的高效运维与持续优化。