【C++ 学习 ⑬】- 详解 list 容器

news2024/10/7 16:21:59

目录

一、list 容器的基本介绍

二、list 容器的成员函数

2.1 - 迭代器

2.2 - 修改操作

三、list 的模拟实现

3.1 - list.h

3.2 - 详解 list 容器的迭代器

3.2 - test.cpp


 


一、list 容器的基本介绍

list 容器以类模板 list<T>(T 为存储元素的类型)的形式定义在 <list> 头文件中,并位于 std 命名空间中

template < class T, class Alloc = allocator<T> > class list;    

list 是序列容器,允许在序列内的任意位置高效地插入和删除元素(时间复杂度是 O(1) 常数阶),其迭代器类型为双向迭代器(bidirectional iterator)

list 容器的底层是以双向链表的形式实现的

list 容器与 forward_list 容器非常相似,最主要的区别在于 forward_list 容器的底层是以单链表的形式实现的,其迭代器类型为前向迭代器(forward iterator)

与其他标准序列容器(array、vector 和 deque)相比,list 容器在序列内已经获得迭代器的任意位置进行插入、删除元素时通常表现得更好

与其他序列容器相比,list 容器和 forward_list 容器的最大缺点是不支持任意位置的随机访问,例如:要访问 list 中的第 6 个元素,必须从已知位置(比如头部或尾部)迭代到该位置,这需要线性阶的时间复杂度的开销


二、list 容器的成员函数

2.1 - 迭代器

begin:

      iterator begin();
const_iterator begin() const;

end:

      iterator end();
const_iterator end() const;

rbegin:

      reverse_iterator rbegin();
const_reverse_iterator rbegin() const;

rend:

      reverse_iterator rend();
const_reverse_iterator rend() const;

示例

#include <iostream>
#include <list>
using namespace std;
​
int main()
{
    list<int> l;
    l.push_back(0);
    l.push_back(1);
    l.push_back(2);
    l.push_back(3);
    l.push_back(4);
​
    for (list<int>::iterator it = l.begin(); it != l.end(); ++it)
    {
        cout << *it << " ";
    }
    // 0 1 2 3 4
    cout << endl;
​
    for (list<int>::reverse_iterator rit = l.rbegin(); rit != l.rend(); ++rit)
    {
        cout << *rit << " ";
    }
    // 4 3 2 1 0
    cout << endl;
    return 0;
}

 

2.2 - 修改操作

push_front:

void push_front(const value_type& val);

注意:value_type 等价于 T

pop_front:

void pop_front();

push_back:

void push_back(const value_type& val);

pop_back:

void pop_back();

insert:

// C++ 98
single element (1) iterator insert(iterator position, const value_type& val);
          fill (2)     void insert(iterator position, size_type n, const value_type& val);
         range (3) template <class InputIterator>
                       void insert(iterator position, InputIterator first, InputIterator last);

相较于 vector,执行 list 的 insert 操作不会产生迭代器失效的问题

示例一

#include <iostream>
#include <list>
using namespace std;
​
int main()
{
    list<int> l;
    l.push_back(0);
    l.push_back(1);
    l.push_back(2);
    l.push_back(3);
    l.push_back(4);
​
    // 要求:在第三个元素前面插入元素 100
    // l.insert(l.begin() + 2, 100);  // error
    // 因为 list 对应的迭代器类型为双向迭代器,所以不支持加法操作,即没有重载该运算符
​
    // 解决方案:
    list<int>::iterator it = l.begin();
    for (size_t i = 0; i < 2; ++i)
    {
        ++it;
    }
    l.insert(it, 100);
​
    for (auto e : l)
    {
        cout << e << " ";
    }
    // 0 1 100 2 3 4
    cout << endl;
    return 0;
}

erase:

iterator erase(iterator position);
iterator erase(iterator first, iterator last);

因为节点被删除后,空间释放了,所以执行完 list 的 erase 操作,迭代器就失效了,而解决方案依然是通过返回值对迭代器进行重新赋值

示例二

#include <iostream>
#include <list>
using namespace std;
​
int main()
{
    list<int> l;
    l.push_back(0);
    l.push_back(1);
    l.push_back(2);
    l.push_back(3);
    l.push_back(4);
​
    // 删除 list 中所有值为偶数的元素
    list<int>::iterator it = l.begin();
    while (it != l.end())
    {
        if (*it % 2 == 0)
            it = l.erase(it);  // 直接写 l.erase(it); 会报错
        else
            ++it;
    }
​
    for (auto e : l)
    {
        cout << e << " ";
    }
    // 1 3
    cout << endl;
    return 0;
}


三、list 的模拟实现

3.1 - list.h

#pragma once
​
#include <iostream>
#include <assert.h>
​
namespace yzz
{
    template<class T>
    struct __list_node
    {
        __list_node<T>* _prev;
        __list_node<T>* _next;
        T _data;
​
        __list_node(const T& val = T())
            : _prev(0), _next(0), _data(val)
        { }
    };
​
​
    template<class T, class Ref, class Ptr>
    struct __list_iterator
    {
        typedef __list_iterator<T, Ref, Ptr> self;
        typedef __list_node<T> list_node;
        list_node* _pnode;  // 节点指针
​
        __list_iterator(list_node* p = 0)
            : _pnode(p)
        { }
​
        self& operator++()
        {
            _pnode = _pnode->_next;
            return *this;
        }
​
        self operator++(int)
        {
            self tmp(*this);
            _pnode = _pnode->_next;
            return tmp;
        }
​
        self& operator--()
        {
            _pnode = _pnode->_prev;
            return *this;
        }
​
        self operator--(int)
        {
            self tmp(*this);
            _pnode = _pnode->_prev;
            return tmp;
        }
​
        Ref operator*() const
        {
            return _pnode->_data;
        }
​
        Ptr operator->() const
        {
            return &_pnode->_data;
        }
​
        bool operator!=(const self& it) const
        {
            return _pnode != it._pnode;
        }
​
        bool operator==(const self& it) const
        {
            return _pnode == it._pnode;
        }
    };
​
​
    template<class T>
    class list
    {
    private:
        typedef __list_node<T> list_node;
​
        void empty_initialize()
        {
            _phead = new list_node;
            _phead->_prev = _phead;
            _phead->_next = _phead;
        }
​
    public:
        /*-------- 构造函数和析构函数 --------*/
        list()
        {
            empty_initialize();
        }
​
        list(const list<T>& l)  // 实现深拷贝
        {
            empty_initialize();
            for (auto& e : l)
            {
                push_back(e);
            }
        }
​
        ~list()
        {
            clear();
            delete _phead;
            _phead = 0;
        }
​
        /*-------- 赋值运算符重载 --------*/
        // 利用上面写好的拷贝构造函数实现深拷贝
        void swap(list<T>& l)
        {
            std::swap(_phead, l._phead);
        }
​
        list<T>& operator=(list<T> tmp)
        {
            swap(tmp);
            return *this;
        }
​
        /*-------- 迭代器 --------*/
        typedef __list_iterator<T, T&, T*> iterator;
        typedef __list_iterator<T, const T&, const T*> const_iterator;
        
        iterator begin()
        {
            return _phead->_next;
            // 等价于:return iterator(_phead);
            // 返回的过程中发生了隐式类型转换
        }
​
        iterator end()
        {
            return _phead;
        }
​
        const_iterator begin() const
        {
            return _phead->_next;
            // 等价于:return const_iterator(_phead->_next);
        }
​
        const_iterator end() const
        {
            return _phead;
        }
​
        /*-------- 容量操作 --------*/
        size_t size() const
        {
            size_t sz = 0;
            list_node* cur = _phead->_next;
            while (cur != _phead)
            {
                ++sz;
                cur = cur->_next;
            }
            return sz;
        }
​
        bool empty() const
        {
            return _phead->_next == _phead;
        }
​
        /*-------- 修改操作 --------*/
        iterator insert(iterator pos, const T& val)
        {
            list_node* newnode = new list_node(val);
            newnode->_prev = pos._pnode->_prev;
            newnode->_next = pos._pnode;
​
            pos._pnode->_prev->_next = newnode;
            pos._pnode->_prev = newnode;
            return newnode;
        }
​
        void push_back(const T& val)
        {
            // 方法一:
            /*list_node* newnode = new list_node(val);
            newnode->_prev = _phead->_prev;
            newnode->_next = _phead;
​
            _phead->_prev->_next = newnode;
            _phead->_prev = newnode;*/
​
            // 方法二(直接复用):
            insert(end(), val);
        }
​
        void push_front(const T& val)
        {
            // 方法一:
            /*list_node* newnode = new list_node(val);
            newnode->_prev = _phead;
            newnode->_next = _phead->_next;
​
            _phead->_next->_prev = newnode;
            _phead->_next = newnode;*/
​
            // 方法二(直接复用):
            insert(begin(), val);
        }
​
        iterator erase(iterator pos)
        {
            assert(pos != end());  // 前提是 list 非空
            list_node* prev_pnode = pos._pnode->_prev;
            list_node* next_pnode = pos._pnode->_next;
            prev_pnode->_next = next_pnode;
            next_pnode->_prev = prev_pnode;
            delete pos._pnode;
            return iterator(next_pnode);
        }
​
        void pop_back()
        {
            erase(--end());
        }
​
        void pop_front()
        {
            erase(begin());
        }
​
        void clear()
        {
            list_node* cur = _phead->_next;
            while (cur != _phead)
            {
                list_node* tmp = cur;
                cur = cur->_next;
                delete tmp;
            }
            _phead->_prev = _phead->_next = _phead;
        }
​
    private:
        list_node* _phead;  // 头指针
    };
}

3.2 - 详解 list 容器的迭代器

我们可以通过循序渐进的方式来了解 list 容器的迭代器:

  1. 首先,不能使用原生态指针直接作为 list 容器的正向迭代器,即

    typedef list_node* iterator;

    否则当正向迭代器进行 ++/-- 操作时,无法让它指向下一个或上一个节点,并且进行解引用 * 操作时,无法直接获得节点的值,所以需要对原生态指针进行封装,然后对这些操作符进行重载,即

    typedef __list_iterator<T> iterator;
  2. 其次,不能按以下方式直接定义 list 容器的常量正向迭代器,即

    typedef const __list_iterator<T> const_iterator;

    否则常量正向迭代器就无法进行 ++/-- 操作,因为 const 类对象只能去调用 const 成员函数,并且 operator* 的返回值类型为 T&,即仍然可以在外部修改 list 容器

    可以重新定义一个常量正向迭代器 __list_const_iterator,但需要修改的地方仅仅是 operatr* 的返回值,即将其修改为 const T&,显然这样的解决方案会造成代码的冗余,所以在 __list_iterator 类模板中增加一个类型参数 Ref,将 operator* 的返回值修改为 Ref,即

    typedef __list_iterator<T, T&> iterator;
    typedef __list_iterator<T, const T&> const_iterator;
  3. 最后,在重载 -> 操作符时,对于正向迭代器,返回值类型应该是 T*,对于常量正向迭代器,返回值类型应该是 const T*,所以再增加一个类型参数 Ptr,将 operator-> 的返回值类型修改为 Ptr,即

    typedef __list_iterator<T, T&, T*> iterator;
    typedef __list_iterator<T, const T&, const T*> const_iterator;

3.2 - test.cpp

#include "list.h"
#include <iostream>
using namespace std;
​
void Print1(const yzz::list<int>& l)
{
    yzz::list<int>::const_iterator cit = l.begin();
    while (cit != l.end())
    {
        cout << *cit << " ";
        ++cit;
    }
    cout << endl;
}
​
void test_list1()
{
    yzz::list<int> l1;
    l1.push_back(1);
    l1.push_back(2);
    l1.push_back(3);
    l1.push_back(4);
    cout << l1.size() << endl;  // 4
    yzz::list<int> l2(l1);
    for (yzz::list<int>::iterator it = l2.begin(); it != l2.end(); ++it)
    {
        cout << *it << " ";
    }
    // 1 2 3 4
    cout << endl;
​
    l1.push_front(10);
    l1.push_front(20);
    l1.push_front(30);
    l1.push_front(40);
    cout << l1.size() << endl;  // 8
    yzz::list<int> l3;
    l3 = l1;
    for (auto& e : l3)
    {
        cout << e << " ";
    }
    // 40 30 20 10 1 2 3 4
    cout << endl;
​
    l1.pop_back();
    l1.pop_back();
    l1.pop_front();
    l1.pop_front();
    cout << l1.size() << endl;  // 4
    Print1(l1);
    // 20 10 1 2
​
    l1.clear();
    cout << l1.size() << endl;  // 0
    cout << l1.empty() << endl;  // 1
}
​
struct Point
{
    int _x;
    int _y;
​
    Point(int x = 0, int y = 0)
        : _x(x), _y(y)
    { }
};
​
void Print2(const yzz::list<Point>& l)
{
    yzz::list<Point>::const_iterator cit = l.begin();
    while (cit != l.end())
    {
        // 方法一:
        // cout << "(" << (*cit)._x << ", " << (*cit)._y << ")" << " ";
        
        // 方法二:
        cout << "(" << cit->_x << ", " << cit->_y << ")" << " ";
        // 注意:operator-> 是单参数,所以本应该是 cit->->_i 和 cit->->_j,
        // 但为了可读性,编译器做了优化,即省去一个 ->
        ++cit;
    }
    cout << endl;
}
​
void test_list2()
{
    yzz::list<Point> l;
    l.push_back(Point(1, 1));
    l.push_back(Point(2, 2));
    l.push_back(Point(3, 3));
    l.push_back(Point(4, 4));
    Print2(l);
    // (1, 1) (2, 2) (3, 3) (4, 4)
}
​
int main()
{
    // test_list1();
    test_list2();
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/870699.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ruoyi-vue-v3.8.6-搭建

一、准备工作 环境&#xff1a; win10、MySQL8、JDKjdk1.8.0_311 redis6.2.6 IDEA 2022.3.3 maven3.9 Node v18.14.2 npm 9.5.0 版本&#xff1a; 若依框架官方文档&#xff1a;http://doc.ruoyi.vip/ 官网导航&#xff1a;http://120.79.202.7/ 若依项目地址&#xff…

将数组(矩阵)旋转根据指定的旋转角度scipy库的rotate方法

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 将数组(矩阵)旋转 根据指定的旋转角度 scipy库的rotate方法 关于下列代码说法正确的是&#xff1f; import numpy as np from scipy.ndimage import rotate a np.array([[1,2,3,4], …

腾讯云服务器竞价实例是什么?适用于什么行业?有啥优惠?

腾讯云服务器CVM计费模式分为包年包月、按量计费和竞价实例&#xff0c;什么是竞价实例&#xff1f;竞价实例和按量付费相类似&#xff0c;优势是价格更划算&#xff0c;缺点是云服务器实例有被自动释放风险&#xff0c;腾讯云服务器网来详细说下什么是竞价实例&#xff1f;以及…

八、解析应用程序——分析应用程序(1)

文章目录 一、确定用户输入入口点1.1 URL文件路径1.2 请求参数1.3 HTTP消息头1.4 带外通道 二、确定服务端技术2.1 提取版本信息2.2 HTTP指纹识别2.3 文件拓展名2.4 目录名称2.5 会话令牌2.6 第三方代码组件 小结 枚举尽可能多的应用程序内容只是解析过程的一个方面。分析应用程…

Leetcode35 搜索插入位置

给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 解析&#xff1a;以示例2来举例&#xff0c;left 0,right 3,mid 1…

【CSS】CSS 布局——弹性盒子

Flexbox 是一种强大的布局系统&#xff0c;旨在更轻松地使用 CSS 创建复杂的布局。 它特别适用于构建响应式设计和在容器内分配空间&#xff0c;即使项目的大小是未知的或动态的。Flexbox 通常用于将元素排列成一行或一列&#xff0c;并提供一组属性来控制 flex 容器内的项目行…

SpringMVC的异常处理机制

1、简介 系统中异常包括两类&#xff1a;预期异常和运行时异常RuntimeException&#xff0c;前者通过捕获异常从而获取异常信息&#xff0c;后 者主要通过规范代码开发、测试等手段减少运行时异常的发生。 系统的Dao、Service、Controller出现都通过throws Exception向上抛出…

BFS 五香豆腐

题目描述 经过谢老师n次的教导&#xff0c;dfc终于觉悟了——过于腐败是不对的。但是dfc自身却无法改变自己&#xff0c;于是他找到了你&#xff0c;请求你的帮助。 dfc的内心可以看成是5*5个分区组成&#xff0c;每个分区都可以决定的的去向&#xff0c;0表示继续爱好腐败&…

【图像分类】CNN + Transformer 结合系列.4

介绍两篇利用Transformer做图像分类的论文&#xff1a;CoAtNet&#xff08;NeurIPS2021&#xff09;&#xff0c;ConvMixer&#xff08;ICLR2022&#xff09;。CoAtNet结合CNN和Transformer的优点进行改进&#xff0c;ConvMixer则patch的角度来说明划分patch有助于分类。 CoAtN…

多目标优化算法之樽海鞘算法(MSSA)

樽海鞘算法的主要灵感是樽海鞘在海洋中航行和觅食时的群聚行为。相关文献表示&#xff0c;多目标优化之樽海鞘算法的结果表明&#xff0c;该算法可以逼近帕雷托最优解&#xff0c;收敛性和覆盖率高。 通过给SSA算法配备一个食物来源库来解决第一个问题。该存储库维护了到目前为…

el-select 动态添加多个下拉框

实现的效果如下: 主要的代码如下: 这是formdata 的结构 主要的逻辑 在这个 methods

ubuntu supervisor 部署 python 项目

ubuntu supervisor 查看系统是否可用 cuda 初环境与设备安装 supervisor 环境创建 Supervisor 配置文件启动 Supervisor 服务管理项目 本篇文章将介绍 ubuntu supervisor 部署 python 项目 Supervisor 是一个用于管理和监控进程的系统工具。它的主要功能是确保系统中的进程持续…

Stable Diffusion WebUI 从零基础到入门

本文主要介绍Stable Diffusion WebUI的实际操作方法&#xff0c;涵盖prompt推导、lora模型、vae模型和controlNet应用等内容&#xff0c;并给出了可操作的文生图、图生图实战示例。适合对Stable Diffusion感兴趣&#xff0c;但又对Stable Diffusion WebUI使用感到困惑的同学&am…

在CentOS 8系统下搭建LNMP环境

操作场景&#xff1a;LNMP 环境是指在 Linux 系统下&#xff0c;由 Nginx MySQL/MariaDB PHP 组成的网站服务器架构。本文档介绍如何在腾讯云云服务器&#xff08;CVM&#xff09;上手动搭建 LNMP 环境。 进行手动搭建 LNMP 环境&#xff0c;您需要熟悉 Linux 命令&#xff0…

游戏行业实战案例 5 :玩家在线分布

【面试题】某游戏数据后台设有“登录日志”和“登出日志”两张表。 「登录日志」记录各玩家的登录时间和登录时的角色等级。 「登出日志」记录各玩家的登出时间和登出时的角色等级。 其中&#xff0c;「角色 id 」字段唯一识别玩家。 游戏开服前两天&#xff08; 2022-08-13 至…

【深度学习 | 反向传播】释放反向传播的力量: 让训练神经网络变得简单

&#x1f935;‍♂️ 个人主页: AI_magician &#x1f4e1;主页地址&#xff1a; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 &#x1f468;‍&#x1f4bb;景愿&#xff1a;旨在于能和更多的热爱计算机的伙伴一起成长&#xff01;&#xff01;&…

快速上手PyCharm指南

PyCharm简介 PyCharm是一种Python IDE&#xff08;Integrated Development Environment&#xff0c;集成开发环境&#xff09;&#xff0c;带有一整套可以帮助用户在使用Python语言开发时提高其效率的工具&#xff0c;比如调试、语法高亮、项目管理、代码跳转、智能提示、自动…

为什么String要设计成不可变的

文章目录 一、前言二、缓存hashcode缓存 三、性能四、安全性五、线程安全 一、前言 为什么要将String设计为不可变的呢&#xff1f;这个问题一直困扰着许多人&#xff0c;甚至有人直接向Java的创始人James Gosling提问过。在一次采访中&#xff0c;当被问及何时应该使用不可变…

Axure RP9小白安装教程

第一步&#xff1a; 打开&#xff1a;Axure中文学习网 第二步&#xff1a; 鼠标移动软件下载&#xff0c;点击Axure RP 9下载既可 第三步&#xff1a; 注意&#xff1a;Axure RP 9 MAC正式版为苹果版本&#xff0c;Axure RP 9 WIN正式版为Windows版本 中文汉化包&#xff…

春秋云镜 CVE-2022-0410

春秋云镜 CVE-2022-0410 WordPress plugin The WP Visitor Statistics SQLI 靶标介绍 WordPress plugin The WP Visitor Statistics (Real Time Traffic) 5.6 之前存在SQL注入漏洞&#xff0c;该漏洞源于 refUrlDetails AJAX 不会清理和转义 id 参数。 登陆账户&#xff1a;u…