vector 手動實現 及遇到的各種細節問題

之前對vector的一些功能使用了一下 接下來手動實現一下vector? vector的實現和string還是有不小區別的? ?有很多地方都有細節的問題

不同于string的成員變量一個指針一個size一個capacity的成員變量? vector里面存的是三個迭代器iterator? 這的迭代器其實就是模版T的指針? 這樣就可以存各種類型的數據了

三個指針? statr指向第一個位置 finish指向最后一個元素的后一個位置? end_of_storage指向能存儲容量的后一個位置

因為用到了模版 所以不能分兩個文件來寫 就都寫到vector.h里面

接下來一步步實現vector的功能 在實際的過程中遇到了各種的問題 一步步分析并解決

容量操作

首先先實現簡單的一些容量的接口

size和capacity只需要分別返回finish-start和_end_of_storage? ?empty只需要判斷finish是否等于start

reserve

如果按照string那樣的方式如下圖的方式來寫reserve會出問題

這里的三個成員變量都是指針? 擴容開新空間釋放原來空間之后 需要讓三個成員變量進行更新 _start和_end_of_storage按照上面寫的沒有什么問題? 但是_finish的更新需要依靠size這個函數如下? 此時_finish是更新前的? 而_start是更新后的 此時相減就出問題了

解決方法

1.? 先更新_finish 再更新start? 先更新finish的話 此時的finish和start都指向原來的空間 size可以正常算 那么finish的就可以指向更新后的位置了

2. finish更新位置需要用到size 那么在更新start之前就提前算一下size并存起來到old_size中

有了reserve之后 我們就可以寫尾插的函數了? 先判斷空間滿沒有 滿了先用reserve來擴容??然后進行尾插并更新finish?

?形參用引用是因為?用傳值傳參的方式會進行拷貝構造?如果傳的是自定義類型的話拷貝代價比較大? 同時如果實參傳遞的時候傳的不是變量而是1? 2 直接的數字這種方式 此時的實參具有常性 如果這里引用的不用const修飾的話 相當于權限放大了 會報錯

然后再重載[]之后我們就可以用下標的方式進行打印了

如下實現四個迭代器之后 我們就可以通過迭代器的方式和范圍for的方式進行打印了 這些都和之前一樣

insert

在string那里insert插入我們實現的是下標的方式 在上次vector使用中我們發現標準庫中的insert只有迭代器的方式 所以這里也用迭代器的方式

如果按下圖的方式進行insert插入的操作就會出問題 之后再對pos其實出的問題和之前reserve中_finish更新的問題相似 不過這里是pos的問題

下面來分析一下為什么會出現上述的問題

在insert插入之前 如果空間不夠需要先擴容? 擴容操作通過reserve實現開新空間和將三個迭代器進行更新? 但是pos沒有更新還是指向之前空間的某個位置如下圖

而且擴容操作會把原來的空間給釋放掉? 此時pos位置指向的是被是否掉的空間 類似野指針?*pos=x操作是在原來空間的位置進行的 新的空間數據將原來數據拷貝之后?但是finish正常進行了++ 就導致還是最終會多打印一個數據? 但這個數據為隨機數

問題解決

我們知道出問題的原因和reserve那里類似 pos的位置還指向原來的空間

所以我們需要對pos的位置進行更新? 在reserve更新之前存pos和_start相減的值 通過reserve更新空間及三個迭代器之后 再更新pos的位置 這樣就可以正常的運行了

find可以查找傳的兩個迭代器區間內傳的第三個參數? ?并返回該位置的迭代器

迭代器失效

有了insert之外再結合find? 我們就可以選擇在想要的位置進行插入了 如下圖 find找到2的位置并返回給了pos insert再根據這個pos進行插入 但是在插入之后將pos進行*10的操作 沒有將2*10 而是新插入的6*10了

這還是剛剛類似的問題 在insert函數里面pos位置插入數據后pos指向的還是原來的位置 這時候這個位置的數據是新插入的6了? 也就是之前的通過find得到的pos失效了 pos的意義改變了

另一種失效問題

如下圖先尾插4個數據 再在2的位置插入一個6 再對pos位置*10 這次這里什么都沒有變 這里是在insert函數里面空間不夠通過reserve擴容了? 擴容了之后pos指向的還是原來的舊空間? 剛剛我們更新pos是在insert函數里面對形參pos進行的 對于實參pos還是指向舊的空間? ?這種的其實就類似野指針一樣

所以pos再使用一次之后就不要再次直接使用了

實際上對于pos的二次使用標準庫中的vector在vs下這樣都會直接報錯(如下圖)? ?linux下gcc編譯器不會報錯 會和我們上面自己寫的一樣可以運行?

解決方法

錯誤方式

既然是在里面形參改變沒有影響實參? 那么如果采用加引用的方式呢

采取這種方式? 對于右下這種正常的寫法就不能使用了(函數返回的臨時對象具有常性 這里傳過來相當于權限放大)

正確解決方法? ?

insert返回pos+1的位置? pos每次使用之后更新它的位置? 如下圖就可以正常進行了 并符合我們的預期

在類外寫一個Printf_vector函數 這里的x是被const修飾的? 調用begin時候也是調用的const的版本返回值也為const類型的迭代器??

但是這樣只能打印int類型的 所以這里可以用模版函數的方式 如下圖

但是這里會報如下圖的錯誤

這是因為在類外用域作用符::訪問類里面時候 可能訪問的是類型 也可能是類的靜態成員變量 編譯器不知道是什么? 這是規定 沒有實例化的類模板里面取東西,編譯器不能區分這里const_iterator

解決方法

在前面加上typename就是告訴編譯器我們訪問的是類型? 如果是要訪問靜態的那后面也不會有變量i

除了在前面加typename之外 也可以用auto來自動識別類型的方式解決

另外打印還可以如下 這樣不只是vector可以打印 任意類型的容器用迭代器方式都可以打印

erase實現

如下圖要用erase刪除全部的偶數? 迭代器it從begin開始每次判斷該位置的值是不是偶數是就用erase刪去然后it++? 一直到最后一個位置? ?但是結果不和我們預期的一樣 會有以下兩種問題

第一個報錯了? 第二個沒有刪干凈?

原因是這個代碼如果該位置是偶數的話會把這個數給刪掉然后后面的數在函數里面會向左移動?然后此時it指向的已經是下一個數據了?此時再++就跳過這個數據了 第二種情況就是這樣

如果最后一個是偶數的話 把最后一個刪掉之后此時it指向的就是finish了 但是還會++就跳過finish了之后再走也永遠不會等于finish 第一種錯誤就是這樣

解決

因為刪除之后再里面就會自動移位了 所以如果刪除的話不需要再移動it? 為了分清也可以讓erase返回pos位置的然后每次更新一下it 這樣我們會更容易控制代碼

resize

第二個參數之前string那里我們只需要一個字符? 但這里是模版類的形式? 第二個參數可能是內置類型的int double char都有可能?或者是自定義類型的 所以這里固定缺省值為某一個類型是不行的

這里缺省值用到了匿名對象的方式 對于自定義類型 這種方式就會調用他們的默認構造函數? 但是這樣子如果是內置類型怎么辦呢? 內置類型有構造嗎?

本來內置類型是沒有的 但是為了支持類似這樣的寫法 就讓內置類型也支持構造了? 如下

??

而對于里面的處理??

分為三種情況 n<size? ?size<n<capacity? ? n>capacity

對于第一種情況 直接改變_finish指向的位置就可以了

對于第二三中情況 不管哪種都直接reserve反正對于第三種情況也不會縮容? ?然后空間夠了之后直接用迭代器finish位置到start+n之前都用val來填充就額可以了 在結束的時候finish的位置也剛好是它正確的位置

拷貝構造還是需要我們自己來寫 不然默認的是淺拷貝 v2和v1指向的是同一塊的空間??在我們寫了析構函數之后 對于同一塊空間就會析構兩次就會崩掉? 所以拷貝構造函數需要我們自己來寫實現深拷貝

拷貝構造有以下兩種方式來實現 第一種和之前的類似?先開空間再更新對應的成員變量 然后拷貝數據? 第二種方式是用范圍for的方式 范圍for每一次從v1取一個值然后尾插給新的要創建的

這里要注意?如果我們自己寫了拷貝構造? 編譯器默認的構造就不能使用了 我們需要自己來寫一個默認構造函數(無參的或者全缺省的)? ? ?或者使用強制調用編譯器默認構造的方式

對于以下自己實現的無參構造函數? ??

這里的具體過程是? ?在聲明位置我們把三個成員變量都=nullptr的方式了? 在調用函數里面內容之前會先用初始化列表把三個成員變量都初始化為空指針 然后才會進入函數里面 如果大于0執行下面的邏輯 如果等于0再經過初始化列表全部初始化為空指針就結束了不會再進行下面操作

賦值

下面這種方式和構造類似? 不過之前可能有數據? 先用clear把原來的數據給清理掉

還用另一種的方式 如下圖??

直接把傳的形參和現在的進行交換? 這里要注意兩個點

? 一個是形參不能為引用了(這里要交換 不能把原來的實參給改變了)?

另一個是這個swap我們最好自己來實現一下 std庫里的swap為了讓各種類型都可以用 用模版的方式 在里面的實現用的是簡單粗暴的?創建新變量c存a 然后a=b b=c這種方式

對于內置類型沒有什么大的消耗 但是對于自定義類型也這樣的話 里面有很多的數據 需要進行三次拷貝構造 代價就很大了? 所以我們最好自己寫一個swap的函數? 如下圖二我們自己寫的 只是把兩個對象的三個指針進行了交換?

在類模版里面也可以寫一個函數模版 它既是類模版的成員函數也是函數模版? 如下面的函數模版寫在類模版中? 這樣就可以支持各種類型的迭代器來初始化vector對象

如下圖v1用vector類型的v的迭代器進行初始化? ?v2用list類型的l1進行初始化??

但是用其他類型迭代器初始化的時候 如這里的v2? ?l1里面需要存的也是int類型或者是可以轉換為int類型 這樣才可以進行初始化

像這樣在類模版里面再寫一個支持各種類型的迭代器使用的函數模版? ?如果我們想把list對象排序 在list中排序不方便我們就可以放到vector中這樣的函數模版 來排序?

寫了這個之后 我們使用之前寫的兩個參數的構造函數初始化的時候又會報錯

為什么呢

如下圖 兩個都是構造函數 如果是用左邊的 兩個形參類型可以直接都實例化為int 而對于右邊第一個參數需要從int到size_t類型的隱式轉換 所以會調用左邊的 而對于int類型在左邊進行解引用操作就會報錯了

解決方法

1.在傳值的時候指定類型 如這里在10后面加一個u? 代表該整數是unsigned類型 就會匹配第二個構造函數了?

2.再寫一個構造函數第一個參數為需要的類型 就會優先匹配這個構造函數了? 實際上的std庫中vector實現就是用到這種方法 寫了多個這樣的構造函數

.

最后reserve其實還存在一個問題

如上圖 如果vector里面存的是自定義類型? 在push_back里面空間不夠會調用reserve reserve里面擴容是通過開新空間拷貝數據釋放原來空間的方式 這里拷貝用到的是mercpy 是按字節拷貝的? 對于內置類型不會出什么問題

但是如果是自定義類型的話 只能進行淺拷貝 把string里面的指針給拷貝了過去? 然后釋放原來空間時候會先把原來空間中指向的字符串空間先給全部釋放了

如下圖? 原來vector存著四個string類型 每個string里面有一個指針指向堆區動態申請的空間? 在開新空間之后用memcpy的方式只會把每一個string對象的指針和size cpapcity給拷貝過去? 然后對于原來的空間會先把string對象str指向的空間給釋放掉再釋放掉舊的空間 此時新空間中存的指針指向的空間是已經被釋放掉的空間?

解決

不用memcpy的方式? 而是對vector中每一個數據直接賦值的方式? 對于自定義類型如string 就會調用string的賦值運算符 會自動開一塊新的空間 然后把內容拷貝過去

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

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

相關文章

OpenStack Neutron中的L2 Agent與L3 Agent:新手友好指南

引言&#xff1a;云網絡的幕后英雄 在當今的云計算世界中&#xff0c;OpenStack作為開源云平臺的佼佼者&#xff0c;為成千上萬的企業提供了靈活、可擴展的基礎設施服務。而在OpenStack的眾多組件中&#xff0c;Neutron&#xff08;網絡服務&#xff09;扮演著至關重要的角色—…

【自用】JavaSE--特殊文件Properties與XML、日志技術

特殊文件概述使用特殊文件可以存儲多個有關系的數據&#xff0c;作為系統的配置信息屬性文件類似于鍵值對&#xff0c;一一對應存儲數據(比如用戶名與密碼)XML文件存儲多個用戶的多個屬性更適合&#xff0c;適合存儲更復雜的數據Properties注&#xff1a;這個屬性文件的后綴即使…

中本聰思想與Web3的困境:從理論到現實的跨越

一、中本聰思想的核心精髓中本聰通過比特幣白皮書提出的核心思想&#xff0c;可歸納為三大支柱&#xff1a;去中心化貨幣體系目標&#xff1a;擺脫中央機構控制&#xff0c;避免通貨膨脹和政治干預&#xff08;如2008年金融危機暴露的中心化風險&#xff09;。實現路徑&#xf…

Centos 用戶管理

一.創建用戶 在 root賬戶 或 sudo 權限下 1. 創建用戶 useradd xiaoyangzi2.為該用戶設置密碼或修改密碼 passwd xiaoyangzi3. 將用戶加入wheel用戶組 在 CentOS 中&#xff0c;屬于 wheel 組的用戶默認可以使用 sudo 權限。 查看所屬用戶組: groups xiaoyangzi將 xiaoyangzi 加…

C++枚舉算法習題

1. 3的倍數枚舉&#xff08;基礎&#xff09;題目&#xff1a;在之間有10和50多少個數是3的倍數&#xff1f;列舉這些數。 解析&#xff1a;枚舉10到50之間的數&#xff0c;判斷是否能被3整除。優化&#xff1a;計算第一個≥10的3的倍數&#xff08;1234&#xff09;&#xff0…

【SpringBoot系列-01】Spring Boot 啟動原理深度解析

【SpringBoot系列-01】Spring Boot 啟動原理深度解析 大家好&#xff01;今天咱們來好好聊聊Spring Boot的啟動原理。估計不少人跟我一樣&#xff0c;剛開始用Spring Boot的時候覺得這玩意兒真神奇&#xff0c;一個main方法跑起來就啥都有了。但時間長了總會好奇&#xff1a;這…

windows環境下使用vscode以及相關插件搭建c/c++的編譯,調試環境

windows下使用vscode搭建c/c的編譯、運行、調試環境&#xff0c;需要注意的是生成的是xxx.exe可執行文件。另外使用的編譯器是mingw&#xff0c;也就是windows環境下的GNU。 我參考的網址是&#xff1a;https://zhuanlan.zhihu.com/p/1936443912806962622 文章分為2種環境搭建…

標準瓦片層級0~20,在EPSG:4326坐標系下,每個像素點代表的度數

在 EPSG:4326&#xff08;WGS84經緯度坐標系&#xff09; 下&#xff0c;瓦片層級&#xff08;Zoom Level&#xff09;的分辨率以 度/像素 為單位&#xff0c;其計算遵循 TMS Global Geodetic 規范&#xff08;單位&#xff1a;度&#xff09;。以下是 標準層級 0 至 20 的分辨…

Unity高級剔除技術全解析

目錄 ?編輯層級剔除&#xff08;Layer Culling&#xff09;原理詳解 代碼示例 業務應用場景 距離剔除&#xff08;Distance Culling&#xff09;技術細節 進階實現 開放世界優化技巧 視口裁剪&#xff08;Viewport Culling&#xff09;多攝像機協作方案 高級應用場景 …

[Linux] Linux文件系統基本管理

目錄 識別文件系統和設備 Linux 中設備 Linux 文件系統 查看設備和文件系統 lsblk命令 df命令 du命令 案例&#xff1a;查看根文件系統中哪個文件占用了最大空間 環境準備 查找過程 掛載和卸載文件系統 環境準備 掛載文件系統 卸載文件系統 卸載失敗處理 lsof …

如何在 Ubuntu 24.04 Server 或 Desktop 上安裝 XFCE

在 Ubuntu 24.04 上更改當前桌面環境或添加新的桌面環境并不是一項艱巨的任務。大多數流行的 Linux 桌面環境,包括 XFCE,都可以通過默認的 Ubuntu 24.04 LTS 系統倉庫安裝。在本教程中,我們將學習如何使用 Tasksel 工具在 Ubuntu Linux 上安裝和配置 XFCE。 訪問終端并運行…

linux下用c++11寫一個UDP回顯程序

需求&#xff1a;1&#xff09;從2個UDP端口接收數據&#xff0c;并在同樣的端口回顯。echo2&#xff09;多個處理線程&#xff0c;多個發送線程&#xff1b;3&#xff09;使用條件變量喚醒&#xff1b;#include <stack> #include <mutex> #include <atomic>…

MySQL 深分頁優化與條件分頁:把 OFFSET 換成“游標”,再用覆蓋索引抄近路

MySQL 深分頁優化與條件分頁:把 OFFSET 換成“游標”,再用覆蓋索引抄近路 這不是“玄學調優”,而是可復制的方案。本文用可復現的 DDL/造數腳本,演示為什么 OFFSET 越大越慢,如何用 條件游標(Keyset Pagination) 替換它,并配上 覆蓋索引。還會教你看 EXPLAIN/EXPLAIN A…

Unity 繩子插件 ObjRope 使用簡記

Unity 繩子插件&#xff0c;是一個基于物理的、高度逼真且可交互的繩索模擬解決方案。 其性能良好&#xff0c;能夠運行在小游戲平臺。 一、插件基本 插件資源商店地址&#xff1a; Obi Rope | Physics | Unity Asset Store 官方文檔&#xff08;手冊&#xff09;&#xff…

demo 通訊錄 + 城市選擇器 (字母索引左右聯動 ListItemGroup+AlphabetIndexer)筆記

一、城市選擇器實現筆記1. 雙層 for 循環渲染數據結構interface BKCityContent {initial: string; // 字母索引cityNameList: string[]; // 城市列表 }核心實現// 外層循環&#xff1a;字母分組 - 遍歷城市數據&#xff0c;按字母分組顯示 ForEach(this.cityContentList, (item…

【總結型】c語言中的位運算

位運算包括 & | ^ ~ << >>按位與 將某些變量中的某些位清0同時保持其他位不變。也可以用來獲取變量中的某一位。 例如&#xff1a;將int型變量n低8位全置為0&#xff0c;其余位保持不變。 n n & 0xffffff00 如何判斷一個int型變量n的第七位。 n & 0x8…

如何在FastAPI中玩轉APScheduler,實現動態定時任務的魔法?

url: /posts/4fb9e30bb20956319c783e21897a667a/ title: 如何在FastAPI中玩轉APScheduler,實現動態定時任務的魔法? date: 2025-08-16T01:14:26+08:00 lastmod: 2025-08-16T01:14:26+08:00 author: cmdragon summary: APScheduler是Python中強大的任務調度庫,支持任務持久化…

GitHub的簡單使用方法----(5)

最后一篇簡單講講git管理遠程倉庫 1.目的 備份&#xff0c;實現代碼共享集中化管理 &#xff08;將本地倉庫同步到git遠程倉庫中&#xff09; git clone 倉庫地址 以下圖為示例&#xff0c;我打開了一個別人的項目倉庫&#xff0c;點擊code能看到倉庫地址 等待完成即可 如…

C++ STL-string類底層實現

摘要&#xff1a; 本文實現了一個簡易的string類&#xff0c;主要包含以下功能&#xff1a; 1. 默認成員函數&#xff1a;構造函數&#xff08;默認/參數化&#xff09;、拷貝構造、賦值重載和析構函數&#xff0c;采用深拷貝避免內存問題&#xff1b; 2. 迭代器支持&#xff1…

【LeetCode每日一題】

每日一題3. 無重復字符的最長子串題目總體思路代碼1.兩數之和題目總體思路代碼15. 三數之和題目總體思路代碼2025.8.153. 無重復字符的最長子串 題目 給定一個字符串 s &#xff0c;請你找出其中不含有重復字符的 最長 子串 的長度。 示例 1: 輸入: s “abcabcbb” 輸出: 3…