当前位置: 首页 > ds >正文

用 C++ 创建单向链表 forward list

文章目录

  • 前言
  • 1. 源码 forward_list.hpp
  • 2. 使用示例


前言

用 C++ 创建了一个单向链表,用于练习使用现代 C++ 的特性,包括 3 点:

  1. 对于容器,使用 std::initializer_list 作为参数创建构造函数。
    C++ Core Guidelines 中,推荐使用花括号 {} 初始化对象,并且对容器推荐使用 std::initializer_list 创建构造函数。
  2. 使用智能指针 unique_ptr 。
  3. 使用了 copy and move (即 copy and swap 惯用法的改进版)。
    关于 copy and move ,可参见我的另一篇文章
    《C++ 的 copy and swap 惯用法》https://blog.csdn.net/drin201312/article/details/149204977

1. 源码 forward_list.hpp

/*
自定义单向链表。包含功能:
1. 创建链表。
2. 头部插入元素,任意位置的前向插入。
3. 除最后一个元素之外,任意位置的后向插入。
4. 遍历链表并显示。
5. 根据输入位置,删除元素。
6. 根据输入的数值,删除链表中所有相同的元素。
7. 复制链表。
8. 清空链表。
*/#ifndef FORWARD_LIST_H_
#define FORWARD_LIST_H_
#include <algorithm>
#include <cstddef>
#include <iostream>
#include <iterator>
#include <memory>
#include <stdexcept>
#include <string>
#include <stack>
#include <utility>// 链表的节点。
template<typename T>
class Node {public:Node() = default;// 常函数如果返回引用,则必须返回 const 引用。const T& get_data() const {  // 返回节点内的数据。return data_;  }// 给节点输入数据。void set_data(const T& data) {data_ = data;}// 返回下一个节点地址的 reference。std::unique_ptr<Node<T>>& get_next_ptr() {return ptr_next_;}// 夺取下一个节点的 unique_ptrvoid set_next_ptr(std::unique_ptr<Node<T>>&& next_node_ptr) { ptr_next_ = std::move(next_node_ptr);  }private:T data_{};  // 节点内的数据。整数会默认初始化为 0 。std::unique_ptr<Node<T>> ptr_next_;  // 下一个节点的地址。
};template<typename T>  // 必须设置 T,用作 Node 的类型。
class ForwardList {public:  // 该部分必须考虑如何处理 7 个函数:4 个构造函数,2 个赋值操作符,1 个析构函数。ForwardList() = default;  // 允许使用无括号 ForwardList foo 的形式创建对象。// 用 initializer_list 接收任意数量的参数。ForwardList(std::initializer_list<T> args) {std::cout << "Default initializer_list called.\n";// for (auto each : args | std::views::reverse) {  // C++ 20 使用此方式逆序。//   std::cout << "each= " << each << "\n";//   push_front(each);// }//  C++ 14 使用下面 std::rbegin 的方式逆序。for (auto it = std::rbegin(args); it != std::rend(args); ++it) {push_front(*it);}}// 拷贝构造函数,必须手动把链表复制过来。ForwardList(const ForwardList& other) {   std::cout << "Copy constructor called.\n";if (other.nodes_quantity_ != 0) {std::stack<T> data_record;  // 记录 other 中的所有 dataNode<T>* running_node_ptr{other.front_node_ptr_.get()};for (size_t index = 0; index < other.nodes_quantity_; ++index) {data_record.emplace(running_node_ptr->get_data());running_node_ptr = running_node_ptr->get_next_ptr().get();}while (!data_record.empty()) {  // 根据记录的 data 新建整个链表。push_front(data_record.top());data_record.pop();}}}// 下面使用 copy and move 方法,把两个赋值运算符函数合二为一。// 转移 other 的资源,并且 other 应该是用 copy 或 move 新建的一个对象。void move_resource(ForwardList&& other) noexcept {std::cout << "move_resource called.\n";front_node_ptr_ = std::move(other.front_node_ptr_);nodes_quantity_ = other.nodes_quantity_;// 清理 other 中的资源。对于内置的数值类型, std::move 并不会将其归零,必须手动修改。other.nodes_quantity_ = 0;}// 移动构造函数,把链表资源转移到当前对象。ForwardList(ForwardList&& other) noexcept {std::cout << "Move constructor called.\n";// 初始化列表不能完成所有的转移工作,所以直接调用函数,不使用初始化列表。move_resource(std::move(other));  };// 赋值运算符,把拷贝赋值运算符和移动赋值运算符进行二合一,并且能避免自赋值问题。ForwardList& operator=(ForwardList other) {  // other 必须用值传递。std::cout << "operator= called.\n";move_resource(std::move(other));  return *this;}~ForwardList() {clear();  }// 头部插入一个 node。void push_front(const T& data) {  std::cout << "@push_front" << "\tdata= " << data << "\n";auto new_node_ptr = create_node(data);  // 这里会进行 NRVO 。new_node_ptr->set_next_ptr(std::move(front_node_ptr_));// 每个新节点,都会成为首节点。front_node_ptr_ = std::move(new_node_ptr);}// 在指定 node 位置的后面再插入一个 node 。void insert_after(const size_t node_index, const T& data) { std::cout << "@insert_after, node_index= " << node_index << "\n";check_index(node_index); if (nodes_quantity_ == 0) {push_front(data);} else {auto new_node_ptr = create_node(data);  // 这里会进行 NRVO 。Node<T>& current_node = goto_node(node_index);// 如果返回值是一个 reference,则该函数返回结果属于左值。// 下面两个输入参数都是左值,需用 std::move 转换为右值引用。new_node_ptr->set_next_ptr(std::move(current_node.get_next_ptr())); current_node.set_next_ptr(std::move(new_node_ptr));std::cout << "current_node.get_data()= " << current_node.get_data() << "\n";}}// 在指定 node 的位置,前面再插入一个 node 。void insert_before(size_t node_index, const T& data) { std::cout << "@insert_before, node_index= " << node_index << "\n";if (node_index == 0) {push_front(data);} else {  // 第 n 个位置向前插入一个节点,等于第 n-1 位置向后插入一个节点。insert_after(node_index - 1, data);}}void show_list() const {  // 显示整个链表。std::cout << "\nshowing the whole forward list: \n";if (nodes_quantity_ == 0) {std::cout << "Empty forward list.\n";} else {// 用到原始指针时,尽量缩小范围。因为只限于这个函数内部,生命周期极短,不会发生内存泄漏。Node<T>* running_node_ptr = front_node_ptr_.get();for (size_t i = 0; i < nodes_quantity_; ++i) {std::cout << "node[" << i << "].data= " << running_node_ptr->get_data() << "\n";running_node_ptr = running_node_ptr->get_next_ptr().get();}}}// 根据输入的位置 index ,删除元素。void delete_by_index(size_t node_index) {std::cout << "@delete_by_index, node_index= " << node_index << "\n";check_index(node_index); if (nodes_quantity_ != 0) {  // 避开空链表。if (node_index == 0) {front_node_ptr_ = std::move(front_node_ptr_->get_next_ptr());} else {  // 把前一个节点链接到后一个节点即可。Node<T>& previous_node = goto_node(node_index - 1);previous_node.set_next_ptr(std::move(previous_node.get_next_ptr()->get_next_ptr()));}--nodes_quantity_;}}// 根据输入的值,删除链表中所有具有相同值的 node 。void delete_by_value(const T& value) {std::cout << "@delete_by_value, value= " << value << "\n";std::stack<size_t> index_record;  // 使用一个后进先出的容器来记录 index 。Node<T>* running_node_ptr = front_node_ptr_.get();for (size_t index = 0; index < nodes_quantity_; ++index) {if (running_node_ptr->get_data() == value) {index_record.emplace(index);  // 记录当前 index。}running_node_ptr = running_node_ptr->get_next_ptr().get();}std::cout << "Found " << index_record.size() << " of " << value << "\n";while (!index_record.empty()) {size_t index = index_record.top();  // 先删除大的索引,再删除小的索引。index_record.pop();  // 必须同时删除对应的 index 。delete_by_index(index);}}// 返回链表的大小。size_t size() const {return nodes_quantity_;}// 清除链表数据。void clear() {std::cout << "@clear, clearing the whole forward list.\n";while (front_node_ptr_) {  // unique_ptr 只要指向新的资源,原有资源就被释放。front_node_ptr_ = std::move(front_node_ptr_->get_next_ptr());}nodes_quantity_ = 0;}private:// 链表状态有变化时,下面两个成员变量,必须同步更新。size_t nodes_quantity_ = 0;// 链表为动态大小,因此要把数据放在堆区。std::unique_ptr<Node<T>> front_node_ptr_;// 移动到指定的节点位置,并且返回该节点。从 0 开始计算 node_index 。// node_index 可以是 0 ,也可以是最后一个 node 。Node<T>& goto_node(size_t node_index) {  if (size() == 0) {  // 处理空链表的情况。throw std::range_error("The forward list is empty!");}// 使用 get 返回的原始指针。因为原始指针只存活在这个函数内,生命周期极短,不会造成内存泄漏。check_index(node_index);Node<T>* running_node_ptr_ = front_node_ptr_.get();for (size_t i = 0; i < node_index; ++i) {// 获取下一个节点的原始指针。running_node_ptr_ = (running_node_ptr_->get_next_ptr()).get();}return *running_node_ptr_;  // 只能返回堆区对象,不可返回原始指针,避免对象的生命周期管理混乱。}// 使用输入的数据,创建一个新的 node 。std::unique_ptr<Node<T>> create_node(const T& data){auto new_node_ptr{std::make_unique<Node<T>>()};new_node_ptr->set_data(data);++nodes_quantity_; return new_node_ptr;  // 返回局部变量,编译器进行 NRVO 。}// 不允许索引大于节点数量。void check_index (const size_t node_index) const{  // 始终允许 node_index 为 0 ,无须判断节点数量。if ((node_index != 0) && (node_index >= nodes_quantity_)) {throw std::range_error("node_index + 1 > nodes_quantity_ \nnode_index= " + std::to_string(node_index) + " , nodes_quantity_= " + std::to_string(nodes_quantity_));}}
};
#endif  // FORWARD_LIST_H_

2. 使用示例

#include "forward_list.hpp"
#include <cctype>
#include <iostream>void insert_and_show() {ForwardList<int> foo;foo.push_front(2025);ForwardList<int> list{2025, 888};list.insert_before(0, 200);list.insert_before(0, 200);list.insert_after(3, 300);  list.insert_after(3, 300);  list.show_list();list.show_list();
}void delete_and_show() {ForwardList<int> list{100, 2025, 888, 2025};// list.delete_by_index(1);list.delete_by_index(0);list.show_list();list.delete_by_value(200);list.show_list();list.delete_by_value(2025);list.show_list();
}void copy_and_move() {ForwardList<int> list{2025, 888};ForwardList<int> foo;std::cout << "\nfoo = list\n";foo = list;  // 调用拷贝赋值运算符。std::cout << "\nfoo.show_list()= ";foo.show_list();ForwardList<int> bar;std::cout << "\nbar = std::move(list)\n";bar = std::move(list);  // 调用移动赋值运算符。std::cout << "\nbar.show_list()= ";bar.show_list();// 再查看原有链表。std::cout << "\nlist.show_list()= ";list.show_list();
}
void clear_and_show() {ForwardList<int> foo{2025, 888};std::cout << "\nfoo.show_list()= ";foo.show_list();foo.clear();std::cout << "\nfoo.show_list()= ";foo.show_list();
}int main() {insert_and_show();// delete_and_show();  // copy_and_move();  // clear_and_show();  return 0;
}

调用 insert_and_show 的运行结果如下图:
请添加图片描述


—————————— 本文结束 ——————————

http://www.xdnf.cn/news/19226.html

相关文章:

  • “我店 + RWA”来袭:重构商业价值,解锁消费投资新密码
  • HarmonyOS权限管理应用
  • 【序列晋升】20 Spring Cloud Function 函数即服务(FaaS)
  • FPGA实现1553B BC控制器IP方案
  • LeetCode259~282题解
  • 吴恩达机器学习作业五:神经网络正向传播
  • 【前端教程】从性别统计类推年龄功能——表单交互与数据处理进阶
  • 【前端教程】从零开始学JavaScript交互:7个经典事件处理案例解析
  • C++Primer笔记——第六章:函数(下)
  • KNN算法(K近邻算法)
  • 互联网大厂AI大模型面试解析:从基础技术到场景应用
  • STL容器的连续性及其访问:vector和deque
  • 零基础上手:Cursor + MCP 爬取 YouTube 视频数据
  • 微信小程序中蓝牙打印机中文编码处理:使用iconv-lite库
  • Pytest 插件:pytest_runtest_protocol
  • 在Excel和WPS表格中隔一行插入多个空白行
  • nvm使用和node使用
  • 神经语言学视角:脑科学与NLP深层分析技术的交叉融合
  • YARN架构解析:深入理解Hadoop资源管理核心
  • Pycharm 登录 Github 失败
  • 从电网监控到油气分析:QtitanDataGrid 在能源领域的应用探索
  • Ubuntu下配置并远程连接MySQL
  • GVIM-您的化学多智能体助手
  • 如何用 Kotlin 在 Android 手机开发一个应用程序获取国家或地区信息
  • 瞬态数据表定义Fluent变量
  • [Godot] C#获取MenuButton节点索引
  • 将数据赋值到Word并下载
  • 2025.8.29总结
  • 从Cloudflare到EdgeOne:我的个人站点加速之旅与性能对比实测
  • Ubuntu 搭建 Solana 区块链开发环境 + Anchor 智能合约完整教程