日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

详解list容器(应用+模拟实现)

發(fā)布時間:2023/11/30 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 详解list容器(应用+模拟实现) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

list容器

帶頭結點的雙向循環(huán)鏈表

list操作

list容器的概念及其操作

構造和銷毀

list<int>L1;list<int>L2(10, 5);vector<int>v{ 1, 2, 3, 4, 5, 6, 7, 8, 9 };list<int>L3(v.begin(), v.end());list<int>L4(L3);

元素訪問

cout << L3.front() << endl; cout << L2.back() << endl;

容量

元素修改

list<int>L; L.push_back(1); L.push_back(2); L.push_back(3); L.push_back(4);cout << L.size() << endl; L.push_front(0); cout << L.front() << endl;L.pop_front(); cout << L.front() << endl;L.pop_back(); cout << L.back() << endl;//查找元素 auto it = find(L.begin(), L.end(), 2); //插入元素1,2,3 if (it != L.end())L.insert(it, 5);L.erase(it);

迭代器

auto it = L2.begin();while (it != L2.end()){cout << *it << " ";++it;}cout << endl;for (auto& e : L3){cout << e << " ";}cout << endl;cout << L3.front() << endl;cout << L2.back() << endl;auto rit = L4.rbegin();while (rit != L4.rend()){cout << *rit << " ";++rit;}cout << endl;

一般容器遍歷的時候都是左閉右開的區(qū)間
一般begin()都在要訪問元素的第一個位置,而end()則是最后一個元素向后一個的位置,對于list而言也就是頭結點。而逆向打印時,迭代器位置正好反過來,在當前位置時,打印前一個結點

list 特殊操作

list<int>L{ 9, 1, 2, 2, 3, 4, 2, 6, 8 };L.sort();//相鄰重復的元素才會被刪除//所以使用時必須保證list有序L.unique();L.reverse();

迭代器失效

//List中迭代器失效的問題---迭代器指向的結點不存在 void TestListIterator() {list<int>L{ 1, 2, 3, 4 };auto it = L.begin();//刪除之后it已經不存在了//所以使用迭代器時,一但有刪除元素要小心L.erase(it);//解決失效問題//重新賦值itit = L.begin();while (it!=L.end()){cout << *it << " ";++it;}cout << endl; }

以上的應用只給出了一部分,具體上面有個鏈接,專門講解list的應用,本文重點在模擬實現(xiàn)list的一些相關操作

模擬實現(xiàn)list

我們要模擬實現(xiàn)list應用中的幾個模塊

list結構定義

list是一個帶頭結點的雙向循環(huán)鏈表,所以我們要有一個頭結點的指針指向這個鏈表,因為在構造中,我們不管是什么構造都需要先創(chuàng)建頭結點,所以我們將創(chuàng)建頭結點封裝成一個成員函數,提高代碼復用性。
雙向循環(huán)鏈表初始化就是頭結點的下個結點指向自己,上一個結點也指向自己
至于迭代器問題,我們稍后再來進行模擬

定義結點類型

// list: 帶頭結點雙向循環(huán)鏈表 template<class T> struct ListNode {ListNode(const T& data = T()): _pNext(nullptr), _pPre(nullptr), _data(data){}ListNode<T>* _pNext;ListNode<T>* _pPre;T _data; }; class list{typedef ListNode<T> Node;public:typedef list_iterator<T> iterator;typedef list_reverse_iterator<iterator, T> reverse_iterator;private:void CreateHead(){_pHead = new Node;_pHead->_pNext = _pHead;_pHead->_pPre = _pHead;}protected:Node* _pHead;};

構造與析構

首先是默認構造,默認構造只需要創(chuàng)建出頭結點,即可。

list(){CreateHead();}

有參構造,有兩個參數,一個是創(chuàng)建多少個結點,第二個是每個結點的值。隨后先創(chuàng)建頭結點,然后進行尾插,具體實現(xiàn)將放在push_back函數中,提高代碼復用性

list(int n, const T& data ){CreateHead();for (int i = 0; i < n; ++i){push_back(data);}}

區(qū)間構造,我們需要用迭代器,因為給出的區(qū)間不一定就是list容器的區(qū)間,有可能是其他容器的區(qū)間,所以我們要使用類模板,然后先創(chuàng)建頭結點,然后當first沒有走到末尾也就是last時,不斷的尾插*first里的元素,然后first++,直到給所有元素賦完值

template<class Iterator> list(Iterator first, Iterator last) {CreateHead();while (first != last){push_back(*first);++first;} }

拷貝構造,傳進行類類型對象的引用,先創(chuàng)建頭結點,頭結點不能動,用臨時變量pCur指上來,當pCur沒有再次指向頭結點時,也就是沒有走完一圈,我們不斷的尾插給進來對象的數據,每插一個pCur往后走

list(const list<T>& L) {CreateHead();Node* pCur = L._pHead->_pNext; //從第一個元素走while (pCur != L._pHead){push_back(pCur->_data);pCur = pCur->_pNext;} }

賦值操作符重載,還是傳進來類類型對象的引用,首先看是不是自己給自己賦值,如果不是,我們先用clear()函數清除所有元素(后面會實現(xiàn)clear()函數),然后再重復拷貝構造里面的操作,最后返回*this即可

//L1=L2; list<T>& operator=(const list<T>& s) {if (this != &s){clear(); //先清空Node* pCur = L._pHead;while (pCur != _pHead){push_back(pCur->_data);pCur = pCur->_pNext;}}return *this; }

析構函數,我們先用clear()函數釋放掉所有元素,但是頭結點還在,所以我們再把頭結點釋放即可。

~list() {clear(); delete[]_pHead; }

元素訪問

這里的訪問操作,思想很簡單,返回第一個元素值,就是頭結點的下一個結點的值,最后一個元素的值,就是頭結點上一個元素的值。

T& front(){return _pHead->_pNext->_data;}const T& front()const{return _pHead->_pNext->_data;}T& back(){return _pHead->_pPre->_data;}const T& back()const{return _pHead->_pPre->_data;}

容量

返回當前容器中有效元素個數,我們定義一個臨時變量來記錄有效元素個數,遍歷一遍整個鏈表,每遍歷一個元素,個數就+1。

size_t size()const {size_t count = 0;Node* pCur = _pHead->_pNext;while (pCur != _pHead){++count;pCur = pCur->_pNext;}return count; }

判空操作就是當頭結點的下一個結點指向自己就為空

size_t empty()const {return _pHead->_pNext == _pHead; }

改變容量的操作,我們要傳進來新容量,還有如果新容量大于舊容量,我們要對擴容的元素就是值填充,填充元素的值我們也要給出來,如果沒有給我們就給出一個默認值。
當新容量大于舊容量時,就讓變量i從舊容量的末尾開始走,走到新容量的末尾,然后不斷的尾插我們給進來的data
當新容量小于舊容量時,就讓變量i從新容量開始走,走到舊容量的位置,不斷的pop_back()尾刪元素即可。

void resize(size_t newsize, const T& data = T()) {size_t oldsize = size();if (newsize > oldsize){// 節(jié)點增多for (size_t i = oldsize; i < newsize; ++i)push_back(data);}else{// 節(jié)點減少// oldsize:10 newsize:5for (size_t i = newsize; i < oldsize; ++i)pop_back();} }

元素修改

我們剛才不斷的使用到了push_back這個函數,我們的尾插函數只需要不斷的往尾部insert數據就行
insert()我們稍后實現(xiàn)

void push_back(const T& data) {insert(end(), data); }

pop_back()我們只需要把尾部元素刪除掉,就是end()的前一個位置

void pop_back() {erase(--end()); }

頭插,在begin()的位置insert數據data就行

void push_front(const T& data) {insert(begin(), data); }

頭刪,就是刪除begin()位置的元素就行

void pop_front() {erase(begin()); }

我們之前相當于一直在推卸責任,不斷的把具體實現(xiàn)功能往后推,現(xiàn)在終于要實現(xiàn)具體的功能

insert函數返回迭代器,在pos位置,插入T類型的數據data。

  • 我們插入數據,對于list而言也就是插入一個結點,所以我們要先創(chuàng)建一個結點
  • 新結點的前一個結點指向插入位置的前一個結點
  • 新結點下一個結點指向當前插入位置的結點
  • 新界點前一個結點的下一個結點指向新結點
  • 當前位置的結點的前一個結點指向新結點
  • 返回迭代器類型的新結點
  • iterator insert(iterator pos, const T& data) {Node* pNewNode = new Node(data);//鏈表為空刪不了Node* pCur = pos._pCur;pNewNode->_pPre = pCur->_pPre;pNewNode->_pNext = pCur;pNewNode->_pPre->_pNext = pNewNode;pCur->_pPre = pNewNode;return iterator(pNewNode); } iterator erase(iterator pos){Node* pDelNode = pos._pCur;if (pDelNode == _pHead)return end();Node* pRet = pDelNode->_pNext;//保存刪除結點的下一個,用于返回pDelNode->_pPre->_pNext = pDelNode->_pNext;pDelNode->_pNext->_pPre = pDelNode->_pPre;delete pDelNode;return iterator(pRet);}

    清除,就是一個一個把有效元素刪除,用頭刪法就行,當pCur沒有指向頭結點時,代表還沒有結束
    不斷的讓頭結點指向pCur的下一個結點,然后刪除pCur,再讓pCur指向頭結點的下一個結點

    void clear() {Node* pCur = _pHead->_pNext;// 頭刪法// []-->1-->2-->3...while (pCur != _pHead){_pHead->_pNext = pCur->_pNext;delete pCur;pCur = _pHead->_pNext;}_pHead->_pNext = _pHead;_pHead->_pPre = _pHead; }

    交換函數,此我們要交換兩個鏈表,我們只需要調用庫函數swap,將兩個鏈表的頭指針傳進去即可。

    void Swap(list<T>& L) {swap(_pHead, L._pHead); }

    迭代器

    迭代器的本身就是一個指針
    我們可以看到我們最重要的insert()函數和erase()函數都需要用到迭代器來進行實現(xiàn),而list的迭代器并非像vector那樣,一個簡單的指針就可以,如果是原生態(tài)指針,不能取到下一個結點。所以我們需將結點類型的指針重新封裝

    迭代器如果要當成指針的方式進行應用,我們必須在迭代器中提供如下的方法,讓它具備類似于指針的特性

  • 迭代器構造
  • 移動迭代器(++/–)
  • 兩個迭代器之間要可以進行比較(!=/==)
  • 重載*運算符和->運算符
  • // list迭代器:將節(jié)點類型的指針重新封裝template<class T>struct list_iterator{typedef ListNode<T> Node;typedef list_iterator<T> Self;public:list_iterator(Node* pCur): _pCur(pCur){}// 按照指針的方式進行應用T& operator*(){//返回數據本身return _pCur->_data;}T* operator->(){//返回數據的地址return &(_pCur->_data);}// 3. 移動Self& operator++(){_pCur = _pCur->_pNext;return *this;}Self operator++(int){Self temp(*this);_pCur = _pCur->_pNext;return temp;}Self& operator--(){_pCur = _pCur->_pPre;return *this;}Self operator--(int){Self temp(*this);_pCur = _pCur->_pPre;return temp;}// 4. 比較bool operator!=(const Self& s){return _pCur != s._pCur;}bool operator==(const Self& s){return _pCur == s._pCur;}Node* _pCur;};

    總結:
    如何給一個類定義迭代器:
    分析:該類的迭代器是原生態(tài)的指針還是需要對指針進行封裝
    取決于當前的數據結構(看是順序的結構還是其他的 結構,順序結構可以是原生態(tài)的指針,鏈式或者樹形的迭代器需要進行封裝)

    1.結合該類的數據結構,定義迭代器類(封裝指針) 2.將迭代器與該類進行結合:在容器類中--->typedef 迭代器類型 iterator 3.容器類中提供:begin()end()

    反向迭代器(了解)

    template<class Iterator, class T>struct list_reverse_iterator{typedef list_reverse_iterator<Iterator, T> Self;public:list_reverse_iterator(Iterator it): _it(it){}T& operator*(){Iterator temp = _it;--temp;return *temp;}T* operator->(){return &(operator*());}Self& operator++(){--_it;return *this;}Self operator++(int){Self temp(*this);_it--;return temp;}Self& operator--(){++_it;return *this;}Self operator--(int){Self temp(*this);_it++;return temp;}bool operator!=(const Self& s){return _it != s._it;}bool operator==(const Self& s){return _it == s._it;}Iterator _it;};

    測試

    #include<vector> void TestList1() {bite::list<int>L1;bite::list<int>L2(10, 5);vector<int>v{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };bite::list<int>L3(v.begin(),v.end());bite::list<int>L4(L3);auto it = L2.begin();while (it != L2.end()){cout << *it << " ";++it;}cout << endl; } 創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎

    總結

    以上是生活随笔為你收集整理的详解list容器(应用+模拟实现)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。