C++基礎(13)——list類的模擬實現

?

目錄

一、接口函數和類總覽

二、節點結構體的實現

構造函數

三、迭代器結構體的實現

迭代器模版參數

構造函數

重載++運算符

重載--運算符

重載==運算符

重載*運算符

重載->運算符

四、list的模擬實現

默認成員函數

構造函數

拷貝構造函數

賦值運算符重載函數

析構函數

迭代器相關函數

訪問容器的相關函數

插入和刪除函數

insert

erase

push_back和pop_back

push_front和pop_front

其他函數

size

clear

empty

swap


一、接口函數和類總覽

我們想要實現list類,需要一個節點的結構體,一個迭代器結構體,還要有一個list類。

總覽:

#include <cstddef>
#include <initializer_list>
#include <iostream>
#include <assert.h>namespace xywl {// 首先,我們需要一個節點類,帶模版參數的template<class T>struct list_node {T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T& data = T()) : _data(data), _next(nullptr), _prev(nullptr) {}};// list的迭代器template<class T, class Ref, class Ptr>struct list_iterator {typedef list_node<T> Node;typedef list_iterator<T, Ref, Ptr> Self;Node* _node;list_iterator(Node* node):_node(node){}Ref operator*();Ptr operator->();Self& operator++();Self& operator--();Self& operator++(int);Self& operator--(int);bool operator!=(const Self& s) const;bool operator==(const Self& s) const;};// list類的實現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;iterator begin();iterator end();const_iterator begin() const;const_iterator end() const;void empty_init();list();list(std::initializer_list<T> in);list(const list<T>& t);list<T>& operator=(list<T> t);~list();void clear();void swap(list<T>& t);void push_back(const T& x);void push_front(const T& x);iterator insert(iterator pos, const T& x);void pop_back();void pop_front();iterator erase(iterator pos);size_t size() const;bool empty() const;Node* _head;size_t _size;};
}

二、節點結構體的實現

我們首先要明確一點,就是我們實現的list類在底層是一個帶頭節點的雙向循環鏈表。所以我們實現節點的時候要有前驅和后繼指針。如圖:

所以我們在實現節點類的時候,就需要存儲如下幾個信息:數據,前驅節點的地址和后繼節點的地址。

所以說我們這個結構體只需要實現一個構造函數即可:

構造函數

這里我們默認給兩個指針傳入空指針,數據默認是指定類型的默認構造函數傳入的值:

template<class T>
struct list_node 
{T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T& data = T()) : _data(data), _next(nullptr), _prev(nullptr) {}
};

三、迭代器結構體的實現

我們這里就會十分好奇,之前我們也沒有單獨實現一個迭代器結構體,為什么這里要實現呢?

這是因為之前我們實現的string類和vector類都是在一個連續的空間里面的,我們可以通過原生的指針實現自增、自減和解引用的操作。但是我們實現list是鏈表,鏈表的節點在內存中的位置可是隨機的,我們不可能通過使用節點指針的自增和自減操作來實現對節點的數據進行操作的。所以我們這里需要實現迭代器結構體。

所以我們這里需要對于節點指針進行封裝,對于節點操作的各個運算符進行合理地重載。

迭代器模版參數

我們這里可以看到我們在實現迭代器結構體的模板參數列表中傳入了三個模板參數:

template<class T, class Ref, class Ptr>

我們這里T就是我們數據類型,Ref(reference)是引用類型,Ptr(pointer)是指針類型。

同時我們的list類的模擬實現里面,有兩種迭代器類型:

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

我們這里之所以要實現兩種,是因為我們要滿足不同的使用需求。

我們還需要說明一下,我們在實現迭代器對象的時候也將參數模版重定義了:

typedef list_iterator<T, Ref, Ptr> Self;

構造函數

我們這里實現的迭代器結構體實際上就是分裝了一個節點的指針,歸根到底還是對指針的操作。所以我們這里的構造函數的實現就是對于指針的賦值了:

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

重載++運算符

我們這里需要實現兩種++操作,分別是前置++和后置++:

前置++:

我們實現前置++就是返回自增后的數據,實現起來也非常簡單:

 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& s) const {return _node == s._node;
}

重載*運算符

我們使用借用的時候就是要獲取到這個位置的數據內容即可,也就是返回當前接待的指針所指向的數據即可:

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

重載->運算符

我們在使用迭代器的時候有可能會使用到->運算符,比如我們的list容器存儲的類型不是內置類型而是自定義類型,我們在拿到了一個位置的迭代器的時候,就需要使用->來訪問成員:

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

這里我們有可能會有一個疑問,就是我們重載了->符號,但是我們只是獲得了對應數據的地址,也就是說我們還要調用一次->符號才能獲取到正真的數據(第一個箭頭是調用了重載函數返回指針,第二個箭頭才是指針取訪問對象中的成員),但是事實上我們只需要使用一個箭頭,這是因為我們的編譯器做了識別處理,為了增加可讀性省略了一個箭頭,但是我們還是要明白這里的過程。

四、list的模擬實現

默認成員函數

構造函數

這里就是我們之前學習到的只是了,我們在創建一個雙向循環的鏈表的時候,初始化的狀態是申請了一個頭節點,然后讓這個節點的前驅指針和后繼指針都是指向自己的,如圖:

void empty_init() {_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;
}
list() {empty_init();
}

注意:我們這里為了讓代碼更加美觀,就把初始化動作單獨封裝成了一個函數。

拷貝構造函數

這里的實現邏輯比較簡單,就是先初始化出來一個節點,然后我們遍歷傳進來的list的每一個節點,然后將這每一個節點都尾插到開始創建的對象里面:

list(std::initializer_list<T> in) {empty_init();for(auto& s : in) {push_back(s);}
}

賦值運算符重載函數

我們的賦值運算符重載函數有兩種常見的寫法,這里和我們之前的string類的模擬實現可以說是一模一樣了:
寫法一:傳統寫法

這里的實現邏輯比較容易理解,就是先將原來的對象清空,然后將傳入的對象里面的數據一個一個的尾插到我們清空的對象里面:

lsit<T>& operator=(const list<T>& in) {if(this != &in) {clear();for(auto& s : in) {push_back(s);}}return *this;
}

寫法二:現代寫法

這里也是用到了swap函數來進行操作,這種寫法的原理和之前是想string的原理如出一轍就不多說了。

list<T>& operator=(list<T> t) {swap(t);return *this;
}

析構函數

析構函數就是將對象中的每一個節點都釋放,然后將頭節點置空即可:
?

~list() {clear();delete _head;_head = nullptr;
}

迭代器相關函數

我們首先需要實現的就是獲取begin和end這兩個迭代器,我們根據帶頭雙向循環鏈表的基本定義,我們就可以知道begin就是頭節點的下一個節點,而我們的end就是最后一個有效數據的下一個節點也就是頭節點了。

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

我們這里還需要實現一個const對象的相關函數:

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

訪問容器的相關函數

我們這里主要是實現back和front這兩個函數,一個返回第一個有效數據,一個返回最后一個有效數據即可:

T& front() {return *begin();
}T& back() {return *(--end());
}

插入和刪除函數

insert

這里的實現邏輯需要一些之前學習鏈表的知識了,為了簡化操作,我們可以保存住當前位置的指針和前驅指針,當然了如果你不嫌麻煩也可以不保存直接實現對應的邏輯,最后要返回新的節點的迭代器,我們可以根據下面的圖來嘗試:

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;
}

erase

我們首先要建立沒有該位置節點的連接關系,然后刪除該節點,最后返回下一個位置的迭代器:
?

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

push_back和pop_back

其實這里的兩個操作完全可以復用我們實現的insert函數和erase函數,也就是下面的代碼:

void push_back(const T& x) {insert(end(), x);
}
void pop_back() {erase(--end());
}

push_front和pop_front

這里的實現也是可以完全的復用之前的insert和erase函數:

void push_front(const T& x) {insert(begin(), x);
}
void pop_front() {erase(begin());
}

其他函數

size

我們這里就是要返回有效的數據個數,而我們在定義成員的就考慮到了這個情況,所以我們只需要返回_size:

size_t size() const {return _size;
}

clear

這個函數就是清空對象中的節點的函數,我們只需要遍歷一遍,然后刪除每一個節點就行了:

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

empty

這個函數就是判斷是不是空,我們這里因為提前定義了_size,所以只需要判斷_size是不是空的:
?

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

swap

這個函數就是用來交換我們的頭節點指針和我們的有效數據個數的,我們這里需要在前面加上std::的作用域限定符,告訴編譯器我們是用的庫函數提供的swap而不是自己實現的:

void swap(list<T>& t) {std::swap(_head, t._head);std::swap(_size, t._size);
}

五、總的實現代碼

#include <cstddef>
#include <initializer_list>
#include <iostream>
#include <assert.h>namespace xywl {// 首先,我們需要一個節點類,帶模版的template<class T>struct list_node {T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T& data = T()) : _data(data), _next(nullptr), _prev(nullptr) {}};// list的迭代器template<class T, class Ref, class Ptr>struct list_iterator {typedef list_node<T> Node;typedef list_iterator<T, Ref, Ptr> Self;Node* _node;list_iterator(Node* node):_node(node){}Ref operator*() {return _node->_data;}Ptr operator->() {return &_node->_data;}Self& operator++() {_node = _node->_next;return *this;}Self& operator--() {_node = _node->_prev;return *this;}Self& operator++(int) {Self temp(*this);_node = _node->_next;return temp;}Self& operator--(int) {Self temp(*this);_node = _node->_prev;return temp;}bool operator!=(const Self& s) const {return _node != s._node;}bool operator==(const Self& s) const {return _node == s._node;}};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;iterator begin() {return _head->_next;}iterator end() {return _head;}const_iterator begin() const{return _head->_next;}const_iterator end() const {return _head;}void empty_init() {_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}list() {empty_init();}list(std::initializer_list<T> in) {empty_init();for(auto& s : in) {push_back(s);}}list(const list<T>& t) {empty_init();for(auto& s : t) {push_back(s);}}list<T>& operator=(list<T> t) {swap(t);return *this;}~list() {clear();delete _head;_head = nullptr;}void clear() {auto it = begin();while(it != end()) {it = erase(it);}}void swap(list<T>& t) {std::swap(_head, t._head);std::swap(_size, t._size);}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;}void pop_back() {erase(--end());}void pop_front() {erase(begin());}iterator erase(iterator pos) {assert(pos != end());Node* prev = pos._node->_prev;Node* next = pos._node->_next;next->_prev = prev;prev->_next = next;delete pos._node;_size--;return next;}size_t size() const {return _size;}bool empty() const {return _size == 0;}private:Node* _head;size_t _size;};
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/97902.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/97902.shtml
英文地址,請注明出處:http://en.pswp.cn/web/97902.shtml

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

相關文章

從 APP 界面設計到用戶體驗優化:如何讓你的應用脫穎而出?

作為一個經驗豐富的設計師&#xff0c;在產品優化方面我踩過不少坑&#xff0c;也見過很多團隊在界面設計和用戶體驗上的誤區。APP 的外觀決定了用戶的第一印象&#xff0c;但能不能留住用戶、讓他們愿意持續使用&#xff0c;最終還是看體驗。今天就結合自己的經驗&#xff0c;…

Kafka如何配置生產者攔截器和消費者攔截器

Kafka 的生產者攔截器和消費者攔截器允許你在消息發送前后以及消息消費前后嵌入自定義邏輯&#xff0c;用于實現監控、審計、消息修改等功能。本文我們就用一個最常見的傳遞TraceId的案例來說明下這兩類攔截器如何來使用。 生產者發送攔截器 生產者攔截器需要實現 org.apache.k…

vue表單彈窗最大化無法渲染復雜組件內容

背景&#xff1a;最大化后選然后復雜組件內容丟失&#xff0c;如下拉框、圖片上傳組件修復方案&#xff1a;使用深拷貝核心代碼this.maximizeDialog {visible: true,title: 患者申請 - 最大化查看,formModel: JSON.parse(JSON.stringify(this.formModel || [])),formLogic: JS…

經典俄羅斯方塊游戲 | 安卓三模式暢玩,暫時無廣告!

大家好&#xff0c;今天想跟大家分享一款安卓版的俄羅斯方塊游戲。適合無聊的時候玩玩&#xff0c;換換腦子&#xff0c;這款游戲太經典。80、90都玩過這個游戲。之前我也給大家推薦過一些離線小游戲&#xff0c;但有些用著用著就開始出現彈窗廣告&#xff0c;這就有點煩&#…

今天開始學習新內容“服務集群與自動化”--crond服務、--syslog服務以及DHCP協議

一.crond簡介1、基本介紹crond是linux下用來周期性的執行某種任務或等待處理某些事件的一個守護進程&#xff0c;與windows下的計劃任務類似&#xff0c;當安裝完成操作系統后&#xff0c;默認會安裝此服務工具&#xff0c;并且會自動啟動crond進程&#xff0c;crond進程每分鐘…

從go語言出發,搭建多語言云原生場景下全鏈路觀測體系

一、方案背景 在公司內部devops平臺的微服務化改造過程中&#xff0c;我們遇到了典型的分布式系統觀測難題&#xff1a;服務間調用鏈路復雜、性能瓶頸難以定位、故障排查效率低下。特別是在生產環境出現問題時&#xff0c;往往需要花費大量時間在各個服務的日志中尋找蛛絲馬跡。…

Vue 進階實戰:從待辦清單到完整應用(路由 / 狀態管理 / 性能優化全攻略)

Vue 進階實戰&#xff1a;從待辦清單到完整應用&#xff08;路由 / 狀態管理 / 性能優化全攻略&#xff09; 在上一篇博客里&#xff0c;我們一起實現了能本地存儲的待辦清單&#xff0c;不少朋友留言說&#xff1a;“學會了基礎&#xff0c;但遇到‘登錄后才能訪問頁面’‘多…

uniApp開發XR-Frame微信小程序 | 動態加載與刪除模型

在使用xr-frame開發3D小程序時&#xff0c;我們經常需要根據需求去動態加載模型或刪除模型&#xff0c;在官方的說明中&#xff0c;提到了相關方法&#xff0c;但并不太明確&#xff0c;也沒有確切的實例。 我們先來看一下官方給出的說明。 一. Shadow元素 我們需要用代碼動…

把多個 PPT 合并在一起,三步告別復制粘貼

制作部門匯報分冊、項目階段文件等工作需要將多個零散的PPT合并為一份完整文檔。手動復制粘貼不僅效率低下&#xff0c;還容易導致格式錯亂、動畫丟失。本文介紹一種高效方法&#xff0c;三步操作即可將多個PPT文件快速合并為單一文檔。無論是整合匯報材料&#xff0c;還是準備…

安卓旋轉屏幕后如何防止數據丟失-ViewModel入門

Android ViewModel 入門教程 在日常開發中&#xff0c;當 Activity 因為旋轉屏幕或內存回收被銷毀重建時&#xff0c;UI 中的數據也會丟失。 這時候&#xff0c;Android Jetpack 提供的 ViewModel 就能幫我們解決這個問題。 1. 什么是 ViewModel ViewModel 是一種架構組件。它專…

Linux 下的 Vim 使用與網絡安全配置詳解

目錄 引言 一、Vim 編輯器的使用 1. Vim 的模式 2. 常用操作命令 3. 保存與退出 4. 多窗口與 Shell 切換 二、Linux 網絡基礎 1. 網絡分類 2. IP 地址與分類 三、網絡配置與工具 1. ifconfig 2. netstat 3. wget 4. 主機名與 IP 映射 四、Linux 防火墻與安全設置…

Docker 容器傳輸文件的常用方法

Docker 容器傳輸文件的常用方法 在 Docker 日常使用中&#xff0c;經常需要在主機與容器之間傳輸文件&#xff08;如配置文件、代碼包、日志等&#xff09;。以下是四種最常用的實現方式&#xff0c;覆蓋臨時傳輸、持久共享、構建集成等不同場景。 1. 使用 docker cp 命令&…

視頻轉音頻在線工具大比拼,哪家體驗更勝一籌?

最近工作上遇到了個挺有意思的需求&#xff0c;需要從幾個教學視頻里提取出音頻內容&#xff0c;方便做成播客形式&#xff0c;讓學員能隨時隨地學習。一開始&#xff0c;我以為這活兒挺簡單的&#xff0c;不就是把視頻里的聲音單獨弄出來嘛&#xff0c;結果一上手才發現&#…

KafKa02:Kafka配置文件server.properties介紹

一、配置文件位置二、配置文件介紹默認下&#xff1a;9092 是處理消息隊列核心業務&#xff08;客戶端與 broker 交互&#xff09;的端口9093 是集群內部控制器通信的端口# 指定節點角色&#xff0c;這里同時作為 broker&#xff08;消息代理&#xff09;和 controller&#xf…

哈爾濱云前沿服務器租用托管

黑龍江前沿數據&#xff0c;始建于2005年&#xff0c;多年的歷史&#xff0c;專業從事域名注冊&#xff0c;虛擬主機&#xff0c;服務器租用&#xff0c;云主機&#xff0c;網站建設等互聯網服務。電信/聯通/雙線/機房/眾多機房供您選擇&#xff0c;總有一個適合您的服務器&…

Qt開發經驗 --- Qt 修改控件樣式的方式(16)

文章目錄[toc]1 概述2 Qt Style Sheets (QSS)3 使用 QStyle 和 QProxyStyle4 設置 Palette (調色板)5 使用預定義的 QStyle6 直接設置控件屬性7 自定義控件繪制更多精彩內容&#x1f449;內容導航 &#x1f448;&#x1f449;Qt開發經驗 &#x1f448;1 概述 Qt 提供了多種修改…

Vue3》》Svg圖標 封裝和使用

SVG 安裝插件 npm i vite-plugin-svg-icons // vite.config.ts import { defineConfig } from vite import vue from vitejs/plugin-vue import { createSvgIconsPlugin } from vite-plugin-svg-icons import { resolve } from path export default defineConfig({//配置路徑別…

【04】AI輔助編程完整的安卓二次商業實戰-尋找修改替換新UI首頁圖標-菜單圖標-消息列表圖標-優雅草伊凡

【04】AI輔助編程完整的安卓二次商業實戰-尋找修改替換新UI首頁圖標-菜單圖標-消息列表圖標-優雅草伊凡引言本次二開布局沒有變&#xff0c;但是下一次整體布局會有變&#xff0c;不過本次開發發現朋友圈跳轉功能的流程步驟也做了一定的變化。原生項目復雜就復雜于就算一個顏色…

龍蜥8.10中spark各種集群及單機模式的搭建spark3.5.6(基于hadoop3.3.6集群)

先說最終的訪問端口&#xff0c;如我這里ip為172.20.94.37、172.20.94.38、172.20.94.39&#xff0c;主機名分別為&#xff1a;hadoop37、hadoop38、hadoop39. 最終訪問&#xff08;默認端口&#xff09;&#xff1a; hadoop webui 172.20.94.37:9870 hdfs 端口 8020 yarn 172.…

關于我重新學習 react 的第一遍

今天是25年9月11號&#xff0c;很久很久沒有學習前端知識了&#xff0c;坦誠來說還清楚記得在大學里因為前端技術第一次獲獎的心情&#xff0c;也清晰記得寫完第一篇博客后的心情&#xff0c;工作和運動給我最大程度的成就感。 打破自己 重新開始 完全地 版本一 25.9.11 文章目…