【C++】从零实现 C++ 自定义 list 容器:双向链表与迭代器深度解析

news2024/11/15 11:29:09

个人主页: 起名字真南的CSDN博客

个人专栏:

  • 【数据结构初阶】 📘 基础数据结构
  • 【C语言】 💻 C语言编程技巧
  • 【C++】 🚀 进阶C++
  • 【OJ题解】 📝 题解精讲

目录

  • 📌 1. 引言
  • 📌 2. 内容概要
  • 📌 3. `list` 容器结构设计
    • ✨ 3.1 节点结构设计
    • ✨ 3.2 自定义迭代器的实现
  • 📌 4. `list` 容器的实现
    • ✨ 4.1 核心成员函数
  • 📌 5. 代码示例和测试
    • 🚀5.1 `test_list1()` - 测试基本的增删操作与遍历
    • 🚀5.2 `test_list2()` - 测试插入、修改与迭代器失效问题
    • 🚀5.3 `test_list3()` - 测试拷贝构造和赋值操作
  • 📌 6. 性能分析与适用场景
  • 📌 7. 总结与扩展阅读
  • 📌 8. 互动与讨论


📌 1. 引言

在 C++ 中,STL list 容器提供了双向链表的实现,适用于需要频繁插入和删除的场景。本文将手把手带你实现一个自定义的 list 容器,详细解析双向链表的节点设计、迭代器实现、增删操作等。希望通过本文的学习,你能对链表的底层原理有更深入的理解,并学会如何用 C++ 实现一个高效的 list 容器。

📌 2. 内容概要

  • 双向链表的基本结构设计
  • 自定义迭代器的实现原理
  • 核心方法实现:push_backinserterase
  • 内存管理与析构函数
  • 性能分析与应用场景

📌 3. list 容器结构设计

✨ 3.1 节点结构设计

在双向链表中,每个节点包含数据域和两个指针域,分别指向前后节点。我们首先定义节点的结构体 list_node,用于存储数据和链接信息。

template<class T>
struct list_node {
    T _data;
    list_node<T>* _prev;
    list_node<T>* _next;

    list_node(const T& data = T()) : _data(data), _prev(nullptr), _next(nullptr) {}
};
  • 代码解读
    • _data:存储节点的数据。
    • _prev_next:分别指向前一个和后一个节点,形成双向链接。

✨ 3.2 自定义迭代器的实现

list 容器需要一个迭代器来支持前向和后向遍历。我们设计一个 list_iterator,封装节点指针,并重载 *->++-- 等操作符。

template<class T, class Ref, class Ptr>
struct list_iterator {
    typedef list_node<T> Node;
    Node* _node;

    list_iterator(Node* node = nullptr) : _node(node) {}

    Ref operator*() { return _node->_data; }
    Ptr operator->() { return &_node->_data; }

    list_iterator& operator++() { _node = _node->_next; return *this; }
    list_iterator operator++(int) { list_iterator tmp(*this); _node = _node->_next; return tmp; }
    list_iterator& operator--() { _node = _node->_prev; return *this; }
    list_iterator operator--(int) { list_iterator tmp(*this); _node = _node->_prev; return tmp; }

    bool operator!=(const list_iterator& other) const { return _node != other._node; }
    bool operator==(const list_iterator& other) const { return _node == other._node; }
};
  • 代码解读
    • operator*operator->:分别返回节点的值和地址。
    • ++--:支持前后遍历。
    • ==!=:判断两个迭代器是否指向相同节点。

📌 4. list 容器的实现

✨ 4.1 核心成员函数

  • 构造函数:初始化一个空的链表,并设置头节点。
  • push_backpush_front:在链表尾部或头部插入元素。
  • inserterase:在指定位置插入或删除元素。
  • 析构函数:清理所有节点,防止内存泄漏。
template<class T>
class list {
    typedef list_node<T> Node;
public:
    typedef list_iterator<T, T&, T*> iterator;
    typedef list_iterator<T, const T&, const T*> const_iterator;

    list() { _head = new Node(); _head->_next = _head; _head->_prev = _head; _size = 0; }
    ~list() { clear(); delete _head; }

    iterator begin() { return _head->_next; }
    iterator end() { return _head; }

    void push_back(const T& x) { insert(end(), x); }
    void push_front(const T& x) { insert(begin(), x); }

    iterator insert(iterator pos, const T& x) {
        Node* cur = pos._node;
        Node* prev = cur->_prev;
        Node* newnode = new Node(x);
        newnode->_next = cur; cur->_prev = newnode;
        prev->_next = newnode; newnode->_prev = prev;
        ++_size;
        return newnode;
    }

    iterator erase(iterator pos) {
        Node* cur = pos._node;
        Node* prev = cur->_prev;
        Node* next = cur->_next;
        prev->_next = next; next->_prev = prev;
        delete cur;
        --_size;
        return next;
    }

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

    size_t size() const { return _size; }
    bool empty() const { return _size == 0; }

private:
    Node* _head;
    size_t _size;
};

📌 5. 代码示例和测试

这些测试函数覆盖了自定义 list 容器的基本功能,包括增删操作、迭代器遍历、插入与删除、拷贝构造、赋值运算等。在实际测试中,使用 print_container 输出链表内容,便于观察操作结果。


🚀5.1 test_list1() - 测试基本的增删操作与遍历

void test_list1() {
    list<int> lt;
    lt.push_back(1);
    lt.push_back(2);
    lt.push_back(3);
    lt.push_back(4);
    lt.pop_back();  // 删除最后一个元素
    lt.pop_front(); // 删除第一个元素

    // 使用迭代器遍历链表,并将每个元素加10
    list<int>::iterator it = lt.begin();
    while (it != lt.end()) { 
        *it += 10; // 将每个元素的值加 10
        cout << *it << " ";
        ++it;
    }
    cout << endl;

    // 使用自定义的print_container函数输出链表内容
    print_container(lt);
}

解释

  • 初始化一个链表 lt 并插入元素。
  • 删除链表的尾部和头部元素。
  • 使用迭代器遍历链表,并对元素加 10 后输出。
  • print_container 函数打印链表的最终内容。

示例输出

12 13 
12 13 

🚀5.2 test_list2() - 测试插入、修改与迭代器失效问题

void test_list2() {
    list<int> lt;
    lt.push_back(1);
    lt.push_back(2);
    lt.push_back(3);
    lt.push_back(4);

    list<int>::iterator it = lt.begin();
    lt.insert(it, 10);  // 在开头插入10
    *it += 100;         // 将第一个元素加100
    print_container(lt);

    // 遍历链表,删除所有偶数元素
    it = lt.begin();
    while (it != lt.end()) {
        if (*it % 2 == 0) {
            it = lt.erase(it);  // 删除偶数元素,并接收下一个有效迭代器
        } else {
            ++it;
        }
    }
    print_container(lt);
}

解释

  • 在链表开头插入 10,然后修改第一个元素的值。
  • 通过迭代器遍历链表,删除所有偶数元素。
  • 使用 erase 时注意接收返回的迭代器,以防迭代器失效。

示例输出

10 101 2 3 4 
101 3 

🚀5.3 test_list3() - 测试拷贝构造和赋值操作

void test_list3() {
    list<int> lt1;
    lt1.push_back(1);
    lt1.push_back(2);
    lt1.push_back(3);
    lt1.push_back(4);

    list<int> lt2(lt1);  // 使用拷贝构造函数初始化lt2
    print_container(lt1);
    print_container(lt2);

    list<int> lt3;
    lt3.push_back(10);
    lt3.push_back(20);
    lt3.push_back(30);
    lt3.push_back(40);

    // 测试赋值操作
    lt1 = lt3;
    print_container(lt1);
    print_container(lt3);
}

解释

  • 创建链表 lt1 并插入元素,使用 lt1 初始化链表 lt2(测试拷贝构造函数)。
  • 创建链表 lt3 并赋值给 lt1(测试赋值运算符的实现)。
  • 最后输出 lt1lt3 的内容,验证 lt1 是否成功拷贝了 lt3 的数据。

示例输出

1 2 3 4 
1 2 3 4 
10 20 30 40 
10 20 30 40 

📌 6. 性能分析与适用场景

  • 适用场景:自定义 list 容器适用于需要频繁插入和删除的场景,尤其是在链表头部和尾部操作较多时。
  • 性能分析:由于链表结构的特性,inserterase 操作在已知位置的时间复杂度为 O(1),但随机访问的效率低,适合顺序访问或遍历操作。

📌 7. 总结与扩展阅读

本文详细介绍了如何从零实现一个 C++ 双向链表 list 容器,包括节点结构、迭代器设计、增删操作等。通过这篇文章的学习,希望你对 list 的底层实现原理有了更深入的理解。推荐阅读以下文章进一步学习C++标准库和数据结构实现的相关知识:

  • C++数据结构与算法:链表详解
  • C++ 迭代器设计模式与实现技巧

📌 8. 互动与讨论

你在项目中使用过 list 吗?你对本实现有其他的优化建议吗?欢迎在评论区讨论,分享你的想法和见解!


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

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

相关文章

Python Excel XLS或XLSX转PDF详解:七大实用转换设置

目录 使用工具 Python将Excel文件转换为PDF Python将Excel文件转换为带页码的PDF Python将Excel文件转换为特定页面尺寸的PDF Python将Excel文件转换为PDF并将内容适应到一页 Python将Excel文件转换为PDF/A Python将Excel文件中的工作表转换为单独的PDF Python将Excel工…

Centos 7 安装wget

Centos 7 安装wget 最小化安装Centos 7 的话需要上传wget rpm包之后再路径下安装一下。rpm包下载地址&#xff08;http://mirrors.163.com/centos/7/os/x86_64/Packages/&#xff09; 1、使用X-ftp 或者WinSCP等可以连接上传的软件都可以首先连接服务器&#xff0c;这里我用的…

LabVIEW 使用 Snippet

在 LabVIEW 中&#xff0c;Snippet&#xff08;代码片段&#xff09; 是一个非常有用的功能&#xff0c;它允许你将 一小段可重用的代码 保存为一个 图形化的代码片段&#xff0c;并能够在不同的 VI 中通过拖放来使用。 什么是 Snippet&#xff1f; Snippet 就是 LabVIEW 中的…

A3超级计算机虚拟机,为大型语言模型LLM和AIGC提供强大算力支持

热门大语言模型项目地址&#xff1a;www.suanjiayun.com/mirrorDetails?id66ac7d478099315577961758 近几个月来&#xff0c;我们目睹了大型语言模型&#xff08;LLMs&#xff09;和生成式人工智能强势闯入我们的视野&#xff0c;显然&#xff0c;这些模型在训练和运行时需要…

开源音乐分离器Audio Decomposition:可实现盲源音频分离,无需外部乐器分离库,从头开始制作。将音乐转换为五线谱的程序

今天给大家分析一个音频分解器&#xff0c;通过傅里叶变换和信封匹配分离音乐中的各个音符和乐器&#xff0c;实现音乐到乐谱的转换。将音乐开源分离为组成乐器。该方式是盲源分离&#xff0c;从头开始制作&#xff0c;无需外部乐器分离库。 相关链接 代码&#xff1a;https:…

35.3K+ Star!PhotoPrism:一款基于AI的开源照片管理工具

PhotoPrism 简介 PhotoPrism[1] 是一个为去中心化网络设计的AI照片应用,它利用最新技术自动标记和查找图片,实现自动图像分类与本地化部署,你可以在家中、私有服务器或云端运行它。 项目特点 主要特点 浏览所有照片和视频,无需担心RAW转换、重复项或视频格式。 使用强大的…

基于Spider异步爬虫框架+JS动态参数逆向+隧道代理+自定义中间件的猎聘招聘数据爬取

在本篇博客中&#xff0c;我们将介绍如何使用 Scrapy 框架结合 JS 逆向技术、代理服务器和自定义中间件&#xff0c;来爬取猎聘网站的招聘数据。猎聘是一个国内知名的招聘平台&#xff0c;提供了大量的企业招聘信息和职位信息。本项目的目标是抓取指定城市的招聘信息&#xff0…

计算机网络 (2)计算机网络的类别

计算机网络的类别繁多&#xff0c;根据不同的分类原则&#xff0c;可以得到各种不同类型的计算机网络。 一、按覆盖范围分类 局域网&#xff08;LAN&#xff09;&#xff1a; 定义&#xff1a;局域网是一种在小区域内使用的&#xff0c;由多台计算机组成的网络。覆盖范围&#…

超好用shell脚本NuShell mac安装

利用管道控制任意系统 Nu 可以在 Linux、macOS 和 Windows 上运行。一次学习&#xff0c;处处可用。 一切皆数据 Nu 管道使用结构化数据&#xff0c;你可以用同样的方式安全地选择&#xff0c;过滤和排序。停止解析字符串&#xff0c;开始解决问题。 强大的插件系统 具备强…

【C#设计模式(9)——组合模式(Component Pattern)】

前言 组合模型是将对象组合成树形结构以表示“整体-部分”的层次结构&#xff0c;使客户终端代码更加简洁和灵活。 代码 //目录抽象类 public abstract class Directory {protected string _name;public Directory(string name){_name name;}public abstract void Show(); } …

Ubuntu下Xshell连接腾讯云服务器

1.在腾讯云上买好服务器后&#xff0c;找到控制台&#xff0c;找到自己的服务器重置密码&#xff0c;默认用户名时ubuntu 2.在Xshell连接服务器 然后出现一个秘钥接受&#xff0c;直接接受就好了&#xff0c;然后就出现下面 然后就可以了 查看当前登录的用户 whoami 查看当前服…

初识算法 · 位运算(3)

目录 前言&#xff1a; 两整数之和 题目解析 算法原理 算法编写 只出现一次的数字II 题目解析 算法原理 算法编写 前言&#xff1a; ​本文的主题是位运算&#xff0c;通过两道题目讲解&#xff0c;一道是只出现一次的数字II&#xff0c;一道是两整数之和。 链接分别…

微信小程序——01开发前的准备和开发工具

文章目录 一、开发前的准备1注册小程序账号2安装开发者工具 二、开发者工具的使用1创建项目2 工具的使用3目录结构4各个页面之间的关系5 权限管理6提交审核和发布 一、开发前的准备 开发前需要进行以下准备&#xff1a; 1 注册小程序账号2激活邮箱3 信息登记4 登录小程序管理后…

使用热冻结数据层生命周期优化在 Elastic Cloud 中存储日志的成本

作者&#xff1a;来自 Elastic Jonathan Simon 收集数据对于可观察性和安全性至关重要&#xff0c;而确保数据能够快速搜索且获得低延迟结果对于有效管理和保护应用程序和基础设施至关重要。但是&#xff0c;存储所有这些数据会产生持续的存储成本&#xff0c;这为节省成本创造…

记录配置ubuntu18.04下运行ORBSLAM3的ros接口的过程及执行单目imu模式遇到的问题(详细说明防止忘记)

今天的工作需要自己录制的数据集来验证昨天的标定结果 用ORBSLAM3单目imu模式运行&#xff0c;mentor给的是一个rosbag格式的数据包&#xff0c;配置过程出了几个问题记录一下&#xff0c;沿配置流程写。 一.orbslam3编译安装 1.首先是安装各种依赖 这里不再赘述&#xff0…

vue2项目启用tailwindcss - 开启class=“w-[190px] mr-[20px]“ - 修复tailwindcss无效的问题

效果图 步骤 停止编译"npm run dev"安装依赖 npm install -D tailwindcssnpm:tailwindcss/postcss7-compat postcss^7 autoprefixer^9 创建文件/src/assets/tailwindcss.css&#xff0c;写入内容&#xff1a; tailwind base; tailwind components; tailwind utiliti…

实施工程师简历「精选篇」

【#实施工程师简历#】一份出色的实施工程师简历&#xff0c;是获得优质工作机会的重要跳板。那么&#xff0c;如何打造一份令人眼前一亮的实施工程师简历呢&#xff1f;以下是幻主简历网整理的实施工程师简历「精选篇」&#xff0c;欢迎大家阅读收藏&#xff01; 实施工程师简历…

Linux篇(权限管理命令)

目录 一、权限概述 1. 什么是权限 2. 为什么要设置权限 3. Linux中的权限类别 4. Linux中文件所有者 4.1. 所有者分类 4.2. 所有者的表示方法 属主权限 属组权限 其他权限 root用户&#xff08;超级管理员&#xff09; 二、普通权限管理 1. ls查看文件权限 2. 文件…

惊爆!72.1K star 的 Netdata:实时监控与可视化的超炫神器!

在当今复杂的 IT 环境中&#xff0c;实时监控与可视化对于保障系统的稳定运行和性能优化至关重要。 无论是服务器、应用程序&#xff0c;还是网络设备&#xff0c;及时获取性能数据能够帮助我们快速定位问题、优化资源配置。 Netdata&#xff0c;作为一个开源的实时监控工具&a…

姓名改成商标名称,李子柒已成身份证名字!

近日李子柒紫气东来&#xff0c;以中国非物质文化遗产“漆器”生动地展现了中国漆器的独特美学和工艺之美&#xff0c;这条视频在微博已超过1.3亿观看&#xff0c;在国外视频平台订阅超二千万粉丝成海外中文创作第一人。 李子柒原名李佳佳&#xff0c;在网上看到她已经正式将身…