用 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.pswp.cn/diannao/97230.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/97230.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/97230.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

[肥用云計算] Serverless 多環境配置

前言 在 Serverless 應用開發中&#xff0c;多環境配置是一個繞不開的話題。從開發、測試到生產&#xff0c;每個環境都有其特定的配置需求。阿里云 Serverless Devs 雖然提供了官方的 env 命令來管理多環境&#xff0c;但在實際使用中&#xff0c;我發現官方方案存在一些局限…

LeetCode算法日記 - Day 25: 數組中的第K個最大元素、庫存管理III

目錄 1 數組中的第K個最大元素 1.1 題目解析 1.2 解法 1.3 代碼實現 2. 庫存管理III 2.1 題目解析 2.2 解法 2.3 代碼實現 1 數組中的第K個最大元素 215. 數組中的第K個最大元素 - 力扣&#xff08;LeetCode&#xff09; 給定整數數組 nums 和整數 k&#xff0c;請返…

10分鐘快速搭建 SkyWalking 服務

從 0 開始入門 SkyWalking&#xff0c;搭建 SkyWalking 服務&#xff0c;并接入 Java 項目中實現分布式鏈路追蹤。 Tags 目錄&#xff1a; 1. 概述2. 搭建 SkyWalking 單機環境3. 搭建 SkyWalking 集群環境4. 告警5. 注意事項6. Spring Boot 使用示例 1. 概述 1.1 概念 …

IDEA之GO語言開發

最近因為接到了需求&#xff0c;說是先把目前公司的JAVA服務慢慢替換成GO語言&#xff0c;于是去了解了一下。 但在開發之前&#xff0c;因為用習慣了IDEA&#xff0c;就想著能不能在IDEA上進行開發&#xff0c;結果真讓我找到了。 作為學習記錄一下 注意&#xff1a;基于IDEA…

rapid_table v3.0.0發布了

更新日志 rapid_table v3.0.0 主要更新是支持 batch 推理&#xff0c;模型并沒有升級哈&#xff01; 因為版本號是根據語義化版本號來走的&#xff0c;這次更改了接口的返回值。因此從 v2.0.3 升級到了 v3.0.0。 返回值具體變化如下&#xff1a; # v2.0.3 class RapidTableO…

若依微服務一鍵部署(RuoYi-Cloud):Nacos/Redis/MySQL + Gateway + Robot 接入(踩坑與修復全記錄)

本文記錄我把 高仙&#xff08;Gaussian&#xff09;機器人對接項目 從“本機能跑”遷到 Docker 一鍵部署 的全過程&#xff1a; 包含 四個后端服務&#xff08;gateway/auth/system/robot&#xff09;、前端 Nginx、MySQL/Redis、Nacos 配置中心、Sentinel 控制臺 的改造要點、…

React 業務場景使用相關封裝(hooks 使用)

React 業務場景相關方法封裝&#xff08;hooks 使用&#xff09; React 中常用的三方 hooks 庫 庫名特點常見場景官方文檔ahooks&#xff08;阿里出品&#xff09;豐富實用的 Hooks&#xff0c;和 Ant Design 配合最佳useRequest&#xff08;請求管理&#xff09;、useDeboun…

[高并發系統設計] - 搭建高并發高可用的系統 - 學習與探究

1.應用場景 主要用于高并發系統設計的架構演進和架構思路。 2.學習/操作 1.文檔閱讀 搭建高并發、高可用的系統 | Laravel China 社區 高并發&#xff0c; 你真的理解透徹了嗎&#xff1f; - 知乎 PHP實戰經驗之系統如何支撐高并發-51CTO.COM PHP高并發和大流量解決方案整理 …

【小白筆記】Visual Studio 在 2025年7月更新的功能說明(英文單詞記憶)

這是NVIDIA軟件中關于數據收集&#xff08;Usage Collection&#xff09;的選項。術語解釋NVIDIA Nsight Visual Studio Edition&#xff1a;這是一款由NVIDIA開發的工具&#xff0c;專門用于在Visual Studio這個集成開發環境&#xff08;IDE&#xff09;中進行GPU調試和性能分…

THM Whats Your Name WP

信息收集[2025-08-28 21:41:30] [SUCCESS] 端口開放 10.10.208.188:80[2025-08-28 21:41:30] [SUCCESS] 端口開放 10.10.208.188:22[2025-08-28 21:41:31] [SUCCESS] 端口開放 10.10.208.188:8081[2025-08-28 21:41:31] [SUCCESS] 服務識別 10.10.208.188:22 > [ssh] 版本:8…

MySQL底層數據結構與算法淺析

1、概述 MySQL中&#xff0c;當我們發現某個sql的執行時間很長時&#xff0c;最先想到的就是給表加索引&#xff0c;加了索引之后&#xff0c;查詢性能就會有顯著的提升。 為了知其所以然&#xff0c;那么只有去了解MySQL的底層儲存結構和索引的查詢算法&#xff0c;只有這樣才…

VisualStudio 將xlsx文件嵌入到資源中訪問時變String?

如題&#xff0c;就是這么詭異&#xff0c;時至如今已經是visual studio 2022了&#xff0c;你通過界面導入xlsx文件到資源中&#xff0c;它的類型就是String而且沒法修改! 即使將文件壓縮成zip再導入&#xff0c;依然是String&#xff01; 三哥的騷操作問你服不服! 然而&#…

【視頻講解】R語言海七鰓鰻性別比分析:JAGS貝葉斯分層邏輯回歸MCMC采樣模型應用

全文鏈接&#xff1a;https://tecdat.cn/?p43774 原文出處&#xff1a;拓端抖音號拓端tecdat 分析師&#xff1a;Yifei Liu 【視頻講解】R語言海七鰓鰻性別比分析&#xff1a;JAGS貝葉斯分層邏輯回歸引言&#xff1a;生態人都懂的痛——樣本少、結果被質疑&#xff0c;咋辦&am…

Android14 USB子系統的啟動以及動態切換相關的init.usb.rc詳解

init.usb.rc的作用是在Android系統啟動和運行時&#xff0c;通過監聽屬性&#xff08;sys.usb.config和sys.usb.configfs, sys.usb.typec.mode&#xff09;變化動態&#xff0c;通過寫入內核接口 /sys/class/android_usb/ 來配置USB模式。1 USB子系統的啟動1.1 on init階段的配…

宜春城區SDH網圖分析

一、SDH網圖展示 圖片來源&#xff1a; 本地網傳輸網組SDH網圖(2014年12月) - 百度文庫 SDH就是Synchronous Digital Hierarchy&#xff0c;同步數字體系的意思。 從分布圖可以看出&#xff0c;城區網和工業網一樣&#xff0c;是環狀結構&#xff0c;保障數據傳輸的穩定。我的…

lwIP MQTT 心跳 Bug 分析與修復

一、背景在使用 lwIP 內置 MQTT 客戶端時&#xff0c;如果你用的是 2.2.0 之前的版本&#xff0c;很可能會遇到一個惱人的問題&#xff1a;客戶端和服務器正常連接&#xff0c;但一段時間后 會話被 broker 踢掉。比如常見的現象&#xff1a;Mosquitto / EMQX 日志顯示客戶端超時…

Golang 面試題「中級」

以下是 100 道 Golang 中級面試題及答案&#xff0c;涵蓋并發編程、內存管理、接口實現、標準庫深入應用等核心知識點&#xff1a; 一、并發編程基礎與進階問題&#xff1a;Golang 的 GPM 調度模型中&#xff0c;G、P、M 分別代表什么&#xff1f;它們的協作關系是怎樣的&#…

沃爾瑪AI系統Wally深度拆解:零售業庫存周轉提速18%,動態定價爭議與員工轉型成熱議點

最近去沃爾瑪購物&#xff0c;發現以前總斷貨的那款早餐麥片居然常年擺在最顯眼的貨架上&#xff0c;而且價格每周末都會微調——這可不是巧合&#xff0c;背后藏著零售業最硬核的AI操作。沃爾瑪去年推出的智能系統Wally&#xff0c;正悄悄改變著我們買東西的體驗和商家的運營邏…

AutoDL算力云上傳文件太慢了如何解決?

----------------------------------------------------------------------------------------------- 這是我在我的網站中截取的文章&#xff0c;有更多的文章歡迎來訪問我自己的博客網站rn.berlinlian.cn&#xff0c;這里還有很多有關計算機的知識&#xff0c;歡迎進行留言或…

【智慧城市】2025年中國地質大學(武漢)暑期實訓優秀作品(2):智慧城市西安與一帶一路

PART 01 項目背景01政策與時代背景近年來&#xff0c;隨著科技的飛速發展和政策的積極推動&#xff0c;我國新型智慧城市建設取得了顯著成效。在“十四五”國家信息化規劃中&#xff0c;明確提出要打造智慧高效的城市治理體系&#xff0c;推動城市管理精細化、服務智能化。同時…