vector的模擬實現

什么是vector

vector是一個封裝了動態大小數組的順序容器跟任意其它類型容器一樣,它能夠存放各種類型的對象。

模擬實現

實現前的準備

在實現vector之前,為了和庫里的區分開需要將實現的vector放在一個自定義的命名空間里。而且vector需要實現成模版的方式,所以需要我們傳模版參數,模版參數也就是順序容器所存的數據類型。而且根據庫里的vector,有三個關鍵的成員變量,分別指向順序容器的起始位置,數據容量位置,空間容量位置。

namespace cr
{template<class T>class vector{public:private:iterator _start;iterator _finish;iterator _endofstorage;};
}

迭代器的相關實現?

typedef T* iterator;
typedef const T* const_iterator;
iterator begin()
{return _start;
}
iterator end()
{return _finish;
}
const iterator begin() const
{return _start;
}
const iterator end() const
{return _finish;
}

這里要提醒的就是,_finish指向的是最后一個有效數據的下一個位置,_endofstorage指向的是對象的最大容量位置處的下一個位置。

下標引用operator[]

T& operator[](size_t pos)
{assert(pos < size());return _start[pos];
}

這里需要注意的是:不要忘記斷言

容器大小

size_t capacity() const
{return _endofstorage - _start;
}
size_t size() const 
{return _finish - _start;
}

為了保證const對象調用該函數時也可以正常通過,所以對該函數進行const修飾。

析構函數

~vector()
{delete[] _start;
}

擴容reserve

void reserve(size_t n)
{if (n > capacity()){size_t sz = size();T* tmp = new T[n];if (sz != 0){memcpy(tmp, _start, sizeof(T) * sz);delete[] _start;//勿忘}_start = tmp;_finish = _start + sz;_endofstorage = _start + n;}
}

這里進行擴容時一定要注意判斷空間大小關系,尤其是要注意三個指針的指向問題,在此之前記錄好原空間中有效數據的個數,因為執行new操作都是異地擴容,所以_start存的地址肯定發生改變,如果此時通過_start求得的_finish就一定會出錯,所以需要提前記錄好位置關系但是該函數不一定正確

尾插數據push_back?

void push_back(const T& x) 
{if (_finish == _endofstorage)reserve(size() == 0 ? 4 : capacity() * 2);*_finish = x;_finish++;
}

push_back需要注意的地方就是當空間大小為0時需要擴的大小,以及_finish++。這里來驗證一下自內置類型的數據結果:

?再看看string類的數據結果:引發了異常: 讀取訪問權限沖突。

?其實這里的問題哦就是reserve的實現出了問題:

?當空間不夠時會發生擴容,就會調用memcpy函數,memcpy是庫里的,而且我們知道memcpy內部就是通過值拷貝將_start解引用進行拷貝給tmp(_start是一個string類的指針),所以此時拷貝好的數據相當于是淺拷貝,指向如下:

?然后調用析構函數,將_start空間內存釋放,但是_start內部數據是string類,所以會先調用string的析構函數,釋放string得空間,所以此時就是報錯所在:tmp內的string同樣也銷毀了。

修改reverse函數

void reserve(size_t n)
{if (n > capacity()){size_t sz = size();T* tmp = new T[n];if (sz != 0){//memcpy(tmp, _start, sizeof(T) * sz);for (int i = 0; i < sz; i++){tmp[i] = _start[i];//如果T是string,調用std::string的賦值重載}delete[] _start;//勿忘}_start = tmp;_finish = _start + sz;_endofstorage = _start + n;}
}

這種拷貝的方式就有效避免了淺拷貝的問題,如果模版參數T是string類的話,tmp[i]的類型就是string,所以此時的賦值就會調用string的賦值重載operator=()進行深拷貝,所以就不會出問題。

改變有效數據個數resize

void resize(size_t n, const T& x = T())//匿名對象
{if (n > size()){reserve(n);while (_finish != _endofstorage){*_finish = x;_finish++;}}else{_finish = _start + n;}}

這里要注意的就是T x給的缺省值T(),T()其實就是調用匿名對象的構造函數,然后在調用拷貝構造給x對象,而編譯器會優化成直接的構造。匿名對象具有常性(不能&(同一塊空間)不加const),出了該行就自動析構,const引用可以延長匿名對象的生命周期。但是如果是內置類型,這樣寫也是沒問題的,其實內置類型也是可以看做有構造函數的。就如下測試:

?構造函數

vector(size_t n=0,const T& val=T()):_start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{reserve(n);while (n--){push_back(val);}
}

拷貝構造

vector(const vector<T>& v):_start(new T[v.capacity()]),_finish(_start),_endofstorage(_start+v.capacity())
{for (auto ch : v){*_finish = ch;//如果T是string,調用std::string的賦值重載_finish++;}
}

通過迭代器區間初始化的構造函數

//在一個類模版里面還可以繼續寫模版函數
template<class InputIterator>
vector(InputIterator first, InputIterator last):_start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{while (first != last){push_back(*first);first++;}
}

這里迭代器區間的構造函數是需要實現成模版類型的,因為庫里面的STL說明不同的容器可以通過迭代器區間進行初始化,只不過有點不兼容的可以進行強制類型轉換:

但是當執行到構造函數時:

cr::vector<int> vv(3, 1);

會報錯非法的間接尋址??

原因:當我們調用構造函數時,會優先找最匹配的構造函數進行調用,所以此時以上的三種構造函數就會調用迭代器區間構造函數,而不是第一種


vector(size_t n=0,const T& val=T())//一般構造
vector(InputIterator first, InputIterator last)迭代器區間構造

調用第一種時會發生int類型到size_t類型的轉換,而第三種構造函數會直接調用,因為第三種構造有模版參數,而對于兩個參數(3,1)都是int類型,所以此時模版形參InputIterator就是替換成int型,所以此時第三種更匹配。

解決方法:

  1. 將第一個構造函數的形參改變成int類型
  2. 再生成一個構造函數形成重載
    vector(int n, const T& val = T()):_start(nullptr), _finish(nullptr), _endofstorage(nullptr)
    {reserve(n);while (n--){push_back(val);}
    }

賦值重載operator=?

vector<T>& operator=(vector<T> v)
{swap(_start, v._start);swap(_finish, v._finish);swap(_endofstorage, v._endofstorage);return *this;
}

這里是要一個返回值的,主要是避免連等的情況:a=b=c;

在某位置插入數據insert

void insert(iterator pos, T x)
{assert(pos >= _start && pos <= _finish);if (_finish == _endofstorage);{reserve(_start==nullptr?4:capacity() * 2);}iterator end = _finish-1;while (end >= pos){*(end + 1) = *end;end--;}*pos = x;_finish++;
}

以上代碼其實是有問題的,如下測試

調試時你會發現是在insert內部出了問題?而且會在下面代碼不斷循環

while (end >= pos)
{*(end + 1) = *end;end--;
}

所以問題極可能出現在上面的代碼段:?

此時pos依舊是nullptr,就相當于pos指向的依舊是原空間的地址,插入數據擴容時,空間都發生了變化,所以再拿原空間的指針和新空間的指針比較就不妥

修改代碼:其實就是記錄pos位置和_start之間的差距,好在擴容到新空間時,將差距補上

void insert(iterator pos, T x)//insert使用之后迭代器失效最好不要使用
{assert(pos >= _start && pos <= _finish);//不要忘記斷言if (_finish == _endofstorage);{size_t len = pos - _start;reserve(_start==nullptr?4:capacity() * 2);//異地擴容改變了_start,可能導致pos成為野指針pos = _start + len;}iterator end = _finish-1;while (end >= pos){*(end + 1) = *end;end--;}*pos = x;_finish++;
}

?雖然修改了代碼,編譯起來也沒問題,但是如果你執行下列操作還是跑不通:

?此時是斷言處出問題了,其實這種情況屬于迭代器失效因為你插入數據時發生了擴容了,所以同上面一樣,it指針指向的是原空間的首個位置,所以就會錯誤

解決方法:每次插入數據是重新調用迭代器v.begin()或v.end()來獲得迭代器的位置。

在某位置刪除數據erase

void erase(iterator pos)
{assert(pos < _finish&& pos >= _start);iterator end = pos + 1;while (end < _finish){*(end - 1) = *end;end++;}_finish--;
}

這里和inssert是不同的,erase不會發生擴容,所以也不存在迭代器失效,但是這erase要注意的就是,每次刪除一個數據時位置會發生挪動,所以一般在刷題時一定要注意這個小細節。

而VS下的erase是并不是這樣實現的,VS下的erase也只能使用一次,用完了該指針也就是失效了。盡管進行it++還是--來改變it再使用也是不可以的。

?所以可以借鑒insert的用法:

?但是VS下的erase其實是有返回值的,

其實返回的就是你刪除的那個數據的下一個位置,但是位置會發生挪動,所以,返回的也就是pos位置?


如有解釋不當,歡迎大家留言!

?

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

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

相關文章

論文閱讀 - Neutral bots probe political bias on social media

論文鏈接&#xff1a;Neutral bots probe political bias on social media | EndNote Click 試圖遏制濫用行為和錯誤信息的社交媒體平臺被指責存在政治偏見。我們部署中立的社交機器人&#xff0c;它們開始關注 Twitter 上的不同新聞源&#xff0c;并跟蹤它們以探究平臺機制與用…

超導熱催生meme,換湯不換藥的投機輪回

文/章魚哥 出品/陀螺財經 幣圈對炒作meme概念的熱情從未消亡過。 隨著一種名為LK-99的物質被發現&#xff0c;圍繞超導的興奮不僅激發了科學界&#xff0c;加密貨幣相關概念也與之沸騰。不出所料&#xff0c;與此前圍繞元宇宙、AI大肆炒作一樣&#xff0c;許多meme代幣已經出現…

關于MySQL中的binlog

介紹 undo log 和 redo log是由Inno DB存儲引擎生成的。 在MySQL服務器架構中&#xff0c;分為三層&#xff1a;連接層、服務層&#xff08;server層&#xff09;、執行層&#xff08;存儲引擎層&#xff09; bin log 是 binary log的縮寫&#xff0c;即二進制日志。 MySQL…

android開發之Android 自定義滑動解鎖View

自定義滑動解鎖View 需求如下&#xff1a; 近期需要做一個類似屏幕滑動解鎖的功能&#xff0c;右劃開始&#xff0c;左劃暫停。 需求效果圖如下 實現效果展示 自定義view如下 /** Desc 自定義滑動解鎖View Author ZY Mail sunnyfor98gmail.com Date 2021/5/17 11:52 *…

數據結構——線性表

文章目錄 線性表的定義和基本操作順序表線性表的鏈式表示 線性表的定義和基本操作 線性表是具有相同數據類型的(n≥0)個數據元素的有限序列&#xff0c;其中n為表長&#xff0c;當n0時線性表是一個空表。若用L命名線性表&#xff0c;則其中一般表示為&#xff1a;L(a1,a2,a3, …

.NET實現解析字符串表達式

一、引子功能需求 我們創建了一個 School 對象&#xff0c;其中包含了教師列表和學生列表。現在&#xff0c;我們需要計算教師平均年齡和學生平均年齡。 //創建對象 School school new School() {Name "小菜學園",Teachers new List<Teacher>(){new Teach…

CCLINK轉MODBUS-TCP網關cclink通訊接線圖 終端電阻

大家好&#xff0c;今天我們要聊的是生產管理系統中的CCLINK和MODBUS-TCP協議&#xff0c;它們的不同使得數據互通比較困難&#xff0c;但捷米JM-CCLK-TCP網關的出現改變了這一切。 1捷米JM-CCLK-TCP是一款自主研發的CCLINK從站功能的通訊網關&#xff0c;它的主要功能是將各種…

后端開發5.Redis的搭建

使用docker安裝 Redis【redis】(6379) 拉取Redis鏡像 docker pull redis:6.2.6 啟動Redis容器 docker run -di --name=redis -p 6379:6379 redis:6.2.6 啟動Redis容器并設置密碼 docker run -di --name=redis -p 6379:6379 redis:6.2.6 --requirepass "密碼" 測…

D455+VINS-Fusion+surfelmapping 稠密建圖(三)

繼續&#xff0c;由surfelmapping建立的點云生成octomap八叉樹柵格地圖 一、安裝OctomapServer 建圖包 安裝插件 sudo apt-get install ros-melodic-octomap-ros sudo apt-get install ros-melodic-octomap-msgs sudo apt-get install ros-melodic-octomap-server sudo apt-…

cubemx hal stm32 舵機 可減速 任意位置停止 驅動代碼

CubeMX配置 對于 STM32 F407VE 這里的84是來自APB1那路2倍頻得到&#xff1a; 代碼部分 兩個舵機都是180度的 servo.c #include "servo.h" #include "tim.h" #include "stdio.h"__IO uint32_t g_SteerUWT[2] {0}; uint16_t g_SteerDeg[…

React Native Maps的使用

介紹 React Native Maps是一個用于在React Native應用中顯示地圖的庫。它提供了許多功能&#xff0c;如顯示地圖、標記位置、繪制多邊形等。以下是React Native Maps的使用步驟&#xff1a; 使用 首先&#xff0c;你需要在你的React Native項目中安裝React Native Maps庫。可…

青大數據結構【2014】

一、單選 二、簡答 為了解決順序隊列的假溢出問題&#xff0c;提出了循環隊列&#xff0c;即把存儲隊列的表從邏輯上看成一個環 判別隊列空和滿有三種方法&#xff1a; 1&#xff09;采用計數器判別&#xff0c;空時&#xff0c;計數器為0&#xff1b;滿時&#xff0c;計數器…

【設計模式——學習筆記】23種設計模式——中介者模式Mediator(原理講解+應用場景介紹+案例介紹+Java代碼實現)

文章目錄 案例引入案例一普通實現中介者模式 案例二 介紹基礎介紹登場角色尚硅谷 《圖解設計模式》 案例實現案例一&#xff1a;智能家庭類圖實現 案例二&#xff1a;登錄頁面邏輯實現說明類圖實現 總結文章說明 案例引入 案例一 普通實現 在租房過程中&#xff0c;客戶可能…

css 實現 html 元素內文字水平垂直居中的N種方法

上一篇博文寫了div 中元素居中的N種常用方法&#xff0c;那么單個html元素&#xff1a;div&#xff08;塊級元素代表&#xff09;&#xff0c;span&#xff08;行內元素代表&#xff09;中的文字如何水平垂直都居中呢&#xff1f;實現方法如下&#xff1a; 本文例子使用的 html…

WebAPIs 第二天

DOM事件基礎 事件監聽事件類型事件對象 一.事件監聽 ① 概念&#xff1a;就是讓程序檢測是否有事件發生&#xff0c;一旦有事件觸發&#xff0c;就立即調用一個函數做出響應&#xff0c;也成為綁定事件或者注冊事件 ② 語法&#xff1a;元素對象.addEventListener(事件類型&…

機器學習---對數幾率回歸

1. 邏輯回歸 邏輯回歸&#xff08;Logistic Regression&#xff09;的模型是一個非線性模型&#xff0c; sigmoid函數&#xff0c;又稱邏輯回歸函數。但是它本質上又是一個線性回歸模型&#xff0c;因為除去sigmoid映射函 數關系&#xff0c;其他的步驟&#xff0c;算法都是…

從零開始,貪吃蛇小游戲系列專欄完美收官!

&#x1f3ae; 從零開始&#xff0c;貪吃蛇小游戲系列專欄完美收官&#xff01; &#x1f40d; 各位游戲開發探索者們&#xff0c;大家好&#xff01;我是[億元程序員]&#xff0c;一位擁有8年游戲開發經驗的主程。經過一段時間的努力&#xff0c;我很高興地宣布&#xff0c;我…

阿里云預裝LAMP應用導致MySQL不顯示訪問密碼如何解決

&#x1f600;前言 本篇博文是關于阿里云云服務器ECS部署MySQL過程中出現的一下坑&#xff0c;希望能夠幫助到您&#x1f60a; &#x1f3e0;個人主頁&#xff1a;晨犀主頁 &#x1f9d1;個人簡介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章可以幫助到大家…

SUB-1G SOC芯片DP4306F 32 位 ARM Cortex-M0+內核替代CMT2380F32

DP4306F是一款高性能低功耗的單片集成收發機&#xff0c;集成MO核MCU&#xff0c;工作頻率可覆蓋200MHiz^ 1000MHz。 支持230/408/433/470/868/915頻段。該芯片集成了射頻接收器、射頻發射器、頻率綜合器、GFSK調制器、GFSK解調器等功能模塊。通過SPI接口可以對輸出功率、頻道選…

gitlab-Runner搭建

root wget https://packages.gitlab.com/runner/gitlab-runner/packages/fedora/29/gitlab-runner-12.6.0-1.x86_64.rpm/download.rpm rpm -ivh download.rpm ---- 安裝 rpm -Uvh download.rpm -----更新升級 然后運行&#xff1a; gitlab-runner register --url https://git…