【C++ 學習 ?】- 詳解 list 容器

目錄

一、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 4cout << endl;
?for (list<int>::reverse_iterator rit = l.rbegin(); rit != l.rend(); ++rit){cout << *rit << " ";}// 4 3 2 1 0cout << 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 4cout << 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 3cout << 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; ?// 4yzz::list<int> l2(l1);for (yzz::list<int>::iterator it = l2.begin(); it != l2.end(); ++it){cout << *it << " ";}// 1 2 3 4cout << endl;
?l1.push_front(10);l1.push_front(20);l1.push_front(30);l1.push_front(40);cout << l1.size() << endl; ?// 8yzz::list<int> l3;l3 = l1;for (auto& e : l3){cout << e << " ";}// 40 30 20 10 1 2 3 4cout << endl;
?l1.pop_back();l1.pop_back();l1.pop_front();l1.pop_front();cout << l1.size() << endl; ?// 4Print1(l1);// 20 10 1 2
?l1.clear();cout << l1.size() << endl; ?// 0cout << 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.pswp.cn/news/39928.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/39928.shtml
英文地址,請注明出處:http://en.pswp.cn/news/39928.shtml

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

相關文章

【第二階段】kotlin函數引用

針對上篇傳入函數參數我們也可以重新定義一個函數&#xff0c;然后在main中調用時傳入函數對象 lambda屬于函數類型的對象&#xff0c;需要把普通函數變成函數類型的對象&#xff08;函數引用&#xff09;&#xff0c;使用“&#xff1a;&#xff1a;” /*** You can edit, ru…

DRF 緩存

應用環境 django4.2.3 &#xff0c;python3.10 由于對于服務而言&#xff0c;有些數據查詢起來比較費時&#xff0c;所以&#xff0c;對于有些數據&#xff0c;我們需要將其緩存。 最近做了一個服務&#xff0c;用的時 DRF 的架構&#xff0c;剛好涉及緩存&#xff0c;特此記…

webSocket 筆記

1 認識webSocket WebSocket_ohana&#xff01;的博客-CSDN博客 一&#xff0c;什么是websocket WebSocket是HTML5下一種新的協議&#xff08;websocket協議本質上是一個基于tcp的協議&#xff09;它實現了瀏覽器與服務器全雙工通信&#xff0c;能更好的節省服務器資源和帶寬…

centos 7.9 部署django項目

1、部署框架 主要組件&#xff1a;nginx、uwsgi、django項目 訪問頁面流程&#xff1a;nginx---》uwsgi---》django---》uwsgi---》nginx 2、部署過程 操作系統&#xff1a;centos 7.9 配置信息&#xff1a;4核4G 50G 內網 eip &#xff1a;10.241.103.216 部署過程&…

深入學習SpringCloud Alibaba微服務架構,揭秘Nacos、Sentinel、Seata等核心技術,助力構建高效系統!

課程鏈接&#xff1a; 鏈接: https://pan.baidu.com/s/1hRN0R8VFcwjyCTWCEsz-8Q?pwdj6ej 提取碼: j6ej 復制這段內容后打開百度網盤手機App&#xff0c;操作更方便哦 --來自百度網盤超級會員v4的分享 課程介紹&#xff1a; &#x1f4da;【第01階段】課程簡介&#xff1a;全…

Android FrameWork 層 Handler源碼解析

Handler生產者-消費者模型 在android開發中&#xff0c;經常會在子線程中進行一些耗時操作&#xff0c;當操作完畢后會通過handler發送一些數據給主線程&#xff0c;通知主線程做相應的操作。 其中&#xff1a;子線程、handler、主線程&#xff0c;其實構成了線程模型中經典的…

STM32存儲左右互搏 I2C總線FATS讀寫EEPROM ZD24C1MA

STM32存儲左右互搏 I2C總線FATS讀寫EEPROM ZD24C1MA 在較低容量存儲領域&#xff0c;EEPROM是常用的存儲介質&#xff0c;可以通過直接或者文件操作方式進行讀寫。不同容量的EEPROM的地址對應位數不同&#xff0c;在發送字節的格式上有所區別。EEPROM是非快速訪問存儲&#xf…

vue2+Spring Boot2.7 大文件分片上傳

之前我們文章 手把手帶大家實現 vue2Spring Boot2.7 文件上傳功能 將了上傳文件 但如果文件很大 就不太好處理了 按正常情況甚至因為超量而報錯 這里 我弄了個足夠大的文件 我們先搭建 Spring Boot2.7 環境 首先 application.yml 代碼編寫如下 server:port: 80 upload:path:…

【佳佳怪文獻分享】使用點云從半監督到全監督房間布局估計

標題&#xff1a;From Semi-supervised to Omni-supervised Room Layout Estimation Using Point Cloud 作者&#xff1a;Huan-ang Gao, Beiwen Tian, Pengfei Li, Xiaoxue Chen, Hao Zhao, Guyue Zhou , Yurong Chen and Hongbin Zha 來源&#xff1a;2023 IEEE Internation…

根據源碼,模擬實現 RabbitMQ - 通過 SQLite + MyBatis 設計數據庫(2)

目錄 一、數據庫設計 1.1、數據庫選擇 1.2、環境配置 1.3、建庫建表接口實現 1.4、封裝數據庫操作 1.5、針對 DataBaseManager 進行單元測試 一、數據庫設計 1.1、數據庫選擇 MySQL 是我們最熟悉的數據庫&#xff0c;但是這里我們選擇使用 SQLite&#xff0c;原因如下&am…

手機出現 不讀卡 / 無信號時應該怎么辦?

當手機屏幕亮起&#xff0c;一般在屏幕最上方都會有代表手機卡狀態的顯示&#xff0c;其中網絡信號和讀卡狀態的標識&#xff0c;依舊有很多人分不太清&#xff0c;更不清楚改怎么辦了。 1、當我們的手機里有兩張卡時&#xff0c;則會有兩個信號顯示 2、信號狀態一般是由短到…

CSS自己實現一個步驟條

前言 步驟條是一種用于引導用戶按照特定流程完成任務的導航條&#xff0c;在各種分步表單交互場景中廣泛應用。例如&#xff1a;在HIS系統-門診醫生站中的接診場景中&#xff0c;我們就可以使用步驟條來實現。她的執行步驟分別是&#xff1a;門診病歷>遺囑錄入>完成接診…

ArcGIS Pro基礎入門、制圖、空間分析、影像分析、三維建模、空間統計分析與建模、python融合、案例全流程科研能力提升

目錄 第一章 入門篇 GIS理論及ArcGIS Pro基礎 第二章 基礎篇 ArcGIS數據管理與轉換 第三章 數據編輯與查詢、拓撲檢查 第四章 制圖篇 地圖符號與版面設計 第五章 空間分析篇 ArcGIS矢量空間分析及應用 第六章 ArcGIS柵格空間分析及應用 第七章 影像篇 遙感影像處理 第八…

Python random模塊用法整理

隨機數在計算機科學領域扮演著重要的角色&#xff0c;用于模擬真實世界的隨機性、數據生成、密碼學等多個領域。Python 中的 random 模塊提供了豐富的隨機數生成功能&#xff0c;本文整理了 random 模塊的使用。 文章目錄 Python random 模塊注意事項Python random 模塊的內置…

大語言模型控制生成的過程Trick:自定義LogitsProcessor實踐

前言 在大模型的生成過程中&#xff0c;部分原生的大語言模型未經過特殊的對齊訓練&#xff0c;往往會“胡說八道”的生成一些敏感詞語等用戶不想生成的詞語&#xff0c;最簡單粗暴的方式就是在大模型生成的文本之后&#xff0c;添加敏感詞庫等規則手段進行敏感詞過濾&#xf…

30行JS代碼帶你手寫自動回復語音聊天機器人

&#x1f942;(???)您的點贊&#x1f44d;?評論&#x1f4dd;?收藏?是作者創作的最大動力&#x1f91e; 前言 現如今生活中到處都是聊天機器人的身影&#xff0c;聊天機器人不僅僅能減少人工的聊天壓力&#xff0c;而且十分的可愛有趣&#xff0c;安卓系統的小AI&#xf…

Springboot整合Mybatis調用Oracle存儲過程

1、配置說明 Oracel11g+springboot2.7.14+mybatis3.5.13 目標:springboot整合mybatis訪問oracle中的存儲過程,存儲過程返回游標信息。 mybatis調用oracle中的存儲過程方式 2、工程結構 3、具體實現 3.1、在Oracle中創建測試數據庫表 具體數據可自行添加 create table s…

Lodash——使用與實例

1. 簡介 Lodash是一個一致性、模塊化、高性能的JavaScript實用庫。Lodash通過降低array、number、objects、string等等的使用難度從而讓JavaScript變得簡單。Lodash的模塊方法&#xff0c;非常適用于&#xff1a; 遍歷array、object 和 string對值進行操作和檢測創建符合功能的…

字符個數統計(同類型只統計一次)

思路&#xff1a;因為題目圈定出現的字符都是 ascii 值小于等于127的字符&#xff0c;因此只需要定義一個標記數組大小為128 &#xff0c;然后將字符作為數組下標在數組中進行標記&#xff0c;若數組中沒有標記過表示第一次出現&#xff0c;進行計數&#xff0c;否則表示重復字…

簡單線性回歸:預測事物間簡單關系的利器

文章目錄 &#x1f340;簡介&#x1f340;什么是簡單線性回歸&#xff1f;&#x1f340;簡單線性回歸的應用場景使用步驟&#xff1a;注意事項&#xff1a; &#x1f340;代碼演示&#x1f340;結論 &#x1f340;簡介 在數據科學領域&#xff0c;線性回歸是一種基本而強大的統…