本文最后是模擬實現全部講解,文章穿插有彩色字體,是我總結的技巧和關鍵
?1.vector的介紹及使用
1.1 vector的介紹
https://cplusplus.com/reference/vector/vector/(vector的介紹)
了解
1. vector是表示可變大小數組的序列容器。
2. 就像數組一樣,vector也采用的連續存儲空間來存儲元素。也就是意味著可以采用下標方括號[]對vector的元素進行訪問,和數組一樣高效。但是又不像數組,它的大小是可以動態改變的,而且它的大小會被容器自動處理。
3. 本質講,vector使用動態分配數組來存儲它的元素。當新元素插入時候,這個數組需要被重新分配大小為了增加存儲空間。其做法是,分配一個新的數組,然后將全部元素移到這個數組。就時間而言,這是一個相對代價高的任務,因為每當一個新的元素加入到容器的時候,vector并不會每次都重新分配大小。
使用STL的三個境界:能用,明理,能擴展 ,那么下面學習vector,我們也是按照這個方法去學習
1.2 vector的使用
vector學習時一定要學會查看文檔:https://cplusplus.com/reference/vector/vector,vector在實際中非常的重要,在實際中我們熟悉常見的接口就可以,下面列出了哪些接口是要重點掌握的。
1.2.1 vector的定義
1.2.2 vector iterator 的使用
1.2.3 vector 空間增長問題
capacity的代碼在vs和g++下分別運行會發現,vs下capacity是按1.5倍增長的,g++是按2倍增長的。
這個問題經常會考察,不要固化的認為,vector增容都是2倍,具體增長多少是根據具體的需求定義的。vs是PJ版本STL,g++是SGI版本STL。
reserve只負責開辟空間,如果確定知道需要用多少空間,reserve可以緩解vector增容的代價缺陷問題。resize在開空間的同時還會進行初始化,影響size
// 如果已經確定vector中要存儲元素大概個數,可以提前將空間設置足夠
// 就可以避免邊插入邊擴容導致效率低下的問題了
void TestVector()
{vector<int> v;size_t sz = v.capacity();v.reserve(100); // 提前將容量設置好,可以避免一遍插入一遍擴容cout << "making bar grow:\n";for (int i = 0; i < 100; ++i) {v.push_back(i);if (sz != v.capacity()){sz = v.capacity();cout << "capacity changed: " << sz << '\n';}}
}
1.2.3 vector 增刪查改
1.2.4 vector 迭代器失效問題。(重點)
迭代器失效解決辦法:在使用前,對迭代器重新賦值即可
? ? ? 迭代器的主要作用就是讓算法能夠不用關心底層數據結構,其底層實際就是一個指針,或者是對指針進行了封裝,比如:vector的迭代器就是原生態指針T* 。因此迭代器失效,實際就是迭代器底層對應指針所指向的空間被銷毀了,而使用一塊已經被釋放的空間,造成的后果是程序崩潰(即如果繼續使用已經失效的迭代器,程序可能會崩潰)。
對于vector可能會導致其迭代器失效的操作有:
1. 會引起其底層空間改變的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign。push_back等。
#include <iostream> using namespace std; #include <vector> int main() {vector<int> v={1,2,3,4,5,6};auto it = v.begin();// 將有效元素個數增加到100個,多出的位置使用8填充,操作期間底層會擴容v.resize(100, 8);// reserve的作用就是改變擴容大小但不改變有效元素個數,操作期間可能會引起底層容量改變v.reserve(100);// 插入元素期間,可能會引起擴容,而導致原空間被釋放v.insert(v.begin(), 0);v.push_back(8);// 給vector重新賦值,可能會引起底層容量改變v.assign(100, 8);/*出錯原因:以上操作,都有可能會導致vector擴容,也就是說vector底層原理舊空間被釋放掉, 而在打印時,it還使用的是釋放之間的舊空間,在對it迭代器操作時,實際操作的是一塊已經被釋放的 空間,而引起代碼運行時崩潰。解決方式:在以上操作完成之后,如果想要繼續通過迭代器操作vector中的元素,只需給it重新 賦值即可。*/while(it != v.end()){cout<< *it << " " ;++it;}cout<<endl;return 0; }
結論:insert和erase形參pos都可能會失效(就當他失效了),所以原則上insert和erase過的迭代器不要使用
我們發現庫里邊的erase返回值是一個迭代器,將返回值拷貝給外部定義的it可以保證迭代器不失效,為什么會有失效的可能性呢?-》刪除數據后可能會異地縮容,返回的pos指針依舊指向被釋放過的區域,一定是不對的
比如下邊的我們調用庫里邊的vector,erase后沒有給迭代器賦值,出現了報錯
// 迭代器失效void test_vector6(){std::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(4);v.push_back(4);v.push_back(4);v.push_back(6);for (auto e : v){cout << e << " ";}cout << endl;// 要求刪除所有的偶數std::vector<int>::iterator it = v.begin();while (it != v.end()){if (*it % 2 == 0){v.erase(it);}else{++it;}}for (auto e : v){cout << e << " ";}cout << endl;}
當我們將迭代器賦值過后,就順利運行了
insert后迭代器失效和erase是一個道理,就不再贅述了。
就記住insert/erase后的迭代器重新賦值后再用
2.vector深度剖析及模擬實現
使用memcpy拷貝問題(memmove同理,要將模擬實現里用到這兩個淺拷貝的都換成賦值拷貝,因為自定義類型使用這兩個函數會淺拷貝,要賦值拷貝-》深拷貝才能防止同一塊空間析構兩次的風險)
假設模擬實現的vector中的reserve接口中,使用memcpy進行的拷貝,以下代碼會發生什么問題?
int main()
{bite::vector<bite::string> v;v.push_back("1111");v.push_back("2222");v.push_back("3333");return 0;
}
問題分析:
1. memcpy是內存的二進制格式拷貝,將一段內存空間中內容原封不動的拷貝到另外一段內存空間中
2. 如果拷貝的是自定義類型的元素,memcpy既高效又不會出錯,但如果拷貝的是自定義類型元素,并且自定義類型元素中涉及到資源管理時,就會出錯,因為memcpy的拷貝實際是淺拷貝
結論:如果對象中涉及到資源管理時,千萬不能使用memcpy進行對象之間的拷貝,因為memcpy是淺拷貝,否則可能會引起內存泄漏甚至程序崩潰。(要調用賦值拷貝深拷貝)
動態二維數組理解
這里力扣有一道題,可以搜一下楊輝三角,就是用vector寫的
// 以楊慧三角的前n行為例:假設n為5
void test2vector(size_t n)
{// 使用vector定義二維數組vv,vv中的每個元素都是vector<int>vector<vector<int>> vv(n);// 將二維數組每一行中的vecotr<int>中的元素全部設置為1for (size_t i = 0; i < n; ++i)vv[i].resize(i + 1, 1);// 給楊慧三角出第一列和對角線的所有元素賦值for (int i = 2; i < n; ++i){for (int j = 1; j < i; ++j){vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];}}
}
vector<vector<int>> vv(n);
構造一個vv動態二維數組,vv中總共有n個元素,每個元素都是vector類型的,每行沒有包含任何元素,如果n為5時如下所示:
vv中元素填充完成之后,如下圖所示:
vector模擬實現全部代碼
vector.h
#include<assert.h>namespace jzy
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;vector(){}/*vector(const vector<T>& v){_start = new T[v.capacity()];memcpy(_start, v._start, v.size()* sizeof(T));_finish = _start + v.size();_endofstorage = _start + v.capacity();}*/// v2(v1)vector(const vector<T>& v){reserve(v.capacity());for (const auto& e : v){push_back(e);}}template <class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}vector(size_t n, const T& val = T()){resize(n, val);}vector(int n, const T& val = T()){resize(n, val);}// 21:06void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);}// v1 = v3vector<T>& operator=(vector<T> v){swap(v);return *this;}~vector(){if (_start){delete[] _start;_start = _finish = _endofstorage = nullptr;}}iterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}// 20: 10繼續void reserve(size_t n){if (n > capacity()){size_t old = size();T* tmp = new T[n];if (_start){//memcpy(tmp, _start, old * sizeof(T));for (size_t i = 0; i < old; i++){tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = _start + old;_endofstorage = _start + n;}}void resize(size_t n, T val = T()){if (n > size()){reserve(n);while (_finish < _start + n){*_finish = val;++_finish;}}else{_finish = _start + n;}}void push_back(const T& x){if (_finish == _endofstorage){size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);}*_finish = x;++_finish;}void pop_back(){assert(size() > 0);--_finish;}iterator insert(iterator pos, const T& x){assert(pos >= _start && pos <= _finish);if (_finish == _endofstorage){size_t len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len;//這里發生內部的迭代器失效,需要重新賦值}//memmove(pos + 1, pos, sizeof(T) * (_finish - pos));iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;return pos;}iterator erase(iterator pos){assert(pos >= _start);assert(pos < _finish);iterator it = pos + 1;while (it < _finish){*(it - 1) = *it;++it;}_finish--;return pos;}size_t size() const{return _finish - _start;}size_t capacity() const{return _endofstorage - _start;}T& operator[](size_t pos){assert(pos < size());return _start[pos];}const T& operator[](size_t pos) const{assert(pos < size());return _start[pos];}private:iterator _start = nullptr;iterator _finish = nullptr;iterator _endofstorage = nullptr;};void print_vector(const vector<int>& v){for (auto e : v){cout << e << " ";}cout << endl;}}
test.cpp
#include<iostream>
using namespace std;
#include<string>
#include<vector>
#include<algorithm>
#include<list>
#include"vector.h"namespace jzy
{void test_vector1(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(4);v.push_back(4);v.push_back(4);vector<int>::iterator it = v.begin();while (it != v.end()){cout << *it << " ";++it;}cout << endl;for (auto e : v){cout << e << " ";}cout << endl;v[0]++;for (size_t i = 0; i < v.size(); i++){cout << v[i] << " ";}cout << endl;v.insert(v.begin(), 100);print_vector(v);v.insert(v.begin(), 100);print_vector(v);int i = 0;int j = int();int k = int(10);}void test_vector2(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(4);v.push_back(4);vector<int> v1 = v;for (auto e : v){cout << e << " ";}cout << endl;for (auto e : v1){cout << e << " ";}cout << endl;vector<int> v2;v2.push_back(11);v2.push_back(21);v2.push_back(31);v2.push_back(411);v2.push_back(41);v2.push_back(41);v1 = v2;for (auto e : v1){cout << e << " ";}cout << endl;for (auto e : v2){cout << e << " ";}cout << endl;}void test_vector3(){vector<int> v;v.reserve(10);v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(6);for (auto e : v){cout << e << " ";}cout << endl;v.resize(8);for (auto e : v){cout << e << " ";}cout << endl;v.resize(15, 1);for (auto e : v){cout << e << " ";}cout << endl;v.resize(3);for (auto e : v){cout << e << " ";}cout << endl;}void test_vector4(){vector<string> v;v.reserve(10);v.push_back("xxxx");v.push_back("xxxx");v.push_back("xxxx");v.push_back("xxxx");v.push_back("xxxx");v.push_back("xxxx");for (auto e : v){cout << e << " ";}cout << endl;v.resize(8);for (auto e : v){cout << e << " ";}cout << endl;v.resize(15, "yyyy");for (auto e : v){cout << e << " ";}cout << endl;v.resize(3);for (auto e : v){cout << e << " ";}cout << endl;}void test_vector5(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(6);for (auto e : v){cout << e << " ";}cout << endl;v.erase(v.begin() + 3);for (auto e : v){cout << e << " ";}cout << endl;}// 迭代器失效void test_vector6(){std::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(4);v.push_back(4);v.push_back(4);v.push_back(6);for (auto e : v){cout << e << " ";}cout << endl;// 要求刪除所有的偶數std::vector<int>::iterator it = v.begin();while (it != v.end()){if (*it % 2 == 0){it = v.erase(it);}else{++it;}}for (auto e : v){cout << e << " ";}cout << endl;}void test_vector7(){vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(6);v.push_back(7);v.push_back(8);v.push_back(9);for (auto e : v){cout << e << " ";}cout << endl;vector<string> vstr;vstr.push_back("1111");vstr.push_back("1111");vstr.push_back("1111");vstr.push_back("1111");vstr.push_back("1111");for (auto e : vstr){cout << e << " ";}cout << endl;}void test_vector8(){/*vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);vector<int> v2(v1.begin(), v1.end());for (auto e : v2){cout << e << " ";}cout << endl;list<int> lt;lt.push_back(10);lt.push_back(20);lt.push_back(30);lt.push_back(40);vector<int> v3(lt.begin(), lt.end());for (auto e : v3){cout << e << " ";}cout << endl;int a[] = { 100, 200, 300 };vector<int> v4(a, a+3);for (auto e : v4){cout << e << " ";}cout << endl;*/}void test_vector9(){vector<string> v1(5, "1111");for (auto e : v1){cout << e << " ";}cout << endl;vector<int> v2(5, 1);for (auto e : v2){cout << e << " ";}cout << endl;}
}int main()
{jzy::test_vector6();return 0;
}
3.vector oj題
136. 只出現一次的數字 - 力扣(LeetCode)
118. 楊輝三角 - 力扣(LeetCode)
26. 刪除有序數組中的重復項 - 力扣(LeetCode)
137. 只出現一次的數字 II - 力扣(LeetCode)
260. 只出現一次的數字 III - 力扣(LeetCode)
數組中出現次數超過一半的數字_牛客題霸_牛客網
17. 電話號碼的字母組合 - 力扣(LeetCode)
vector模擬實現過程詳解
這里迭代器很簡單,就是將類型重命名為iterator
const迭代器命名為const T*,意思是指針指向的對象不可修改
無參構造函數很簡單,c++11缺省值,初始化列表會用聲明處的缺省值,這三個指針為空就夠了(vector底層就是三個指針維護的,起始指針_start,有效字符結尾_finish,容量結尾_endofstorage)
拷貝構造,直接reserve擴容開空間,然后用范圍for將v1的內容給給e,再尾插到v2,完成拷貝構造
用區間構造,類型比較長是為了和庫里邊一樣,當迭代器指針不指向有效數據的下一個,尾插,迭代器++,完成構造
用n個val構造,不傳val是會初始化為默認構造
這里是防止調用歧義,當用n個整數初始化vector時,可能會調用迭代器區間構造,這個是為了防止調用歧義
交換邏輯,和string一樣,交換內置類型
賦值拷貝,v3先拷貝構造一個v,用v和v1交換三個指針,v1維護的就是v3的值,而且局部對象出作用域會調用析構,將v析構,剛好只剩v1和v3
析構函數,當指針指向非空,釋放空間并且置空
迭代器和const迭代器,很簡單,非const類型調用非const,返回非const可修改,const類型調用const迭代器,返回const對象,不可修改
reserve擴容邏輯,參數是要擴容的大小,只擴容不縮容。
要定義一個old保存舊size()是為了后邊賦值會改變_start,size()兩個隨機地址相減肯定錯,申請新空間,當原數組非空的時候,將舊空間的數據(自定義類型賦值拷貝)拷貝到新空間,并且釋放舊空間(如果是自定義類型會調用它的析構),最后賦值新的_start,_finish,和_endofstorage(注意這個要深拷貝,防止同一塊空間析構兩次)
只給數字,val是默認構造,也可以給數字和初始值
如果n大于size和capacity,擴容+尾插
如果n大于size,小于capacity,只尾插不擴容
如果n小于size,會將_finish設置為有效數據個數,不擴容
尾插,檢查擴容+尾插
尾刪,檢查size大于0,--finish
插入邏輯是先讓end指向最后一個數據的前一個,從右向左依次向后挪動數據,直到pos位置結束,然后再插入,尾插直接不會進入循環
插入,檢查擴容(如果擴容會開辟新空間,pos指向原來空間,需要重新賦值防止迭代器失效),下邊就是正常的挪動數據邏輯并且插入(不能用memmove因為會淺拷貝,造成兩次析構同一塊空間,要賦值拷貝進行深拷貝才行)
刪除,因為_finish下標是有效數據的下一個,所以刪除不能pos等于finish,定義it是要刪除的下一個,刪除是挪動覆蓋,it++直到finish結束
size和capacity都很簡單,size是有效數據個數,結尾指針減去起始指針
capacity是容量,容量指針減去起始指針
要斷言檢查越界,非const調用非const,返回可修改的引用,const調用const,返回const&不可修改
對應測試樣例
可以看到尾插+擴容,并且插入迭代器位置和數字,插入結果也很清楚
拷貝構造和賦值拷貝,結果跟我們預料的一樣
可以看到,正常打印的結果
當resize的值大于size小于capacity時,只尾插不擴容
大于capacity時,擴容+尾插
小于size,刪除數據,容量不變
這個和上邊差不多,無非存儲的數據換成string,(注意,string默認構造是空串,沒有傳參數時是構造空串)
刪除很簡單,就是刪除某個位置的元素,傳對應的指針過去
迭代器失效,很簡單,記住insert或者erase后的迭代器記得接收一下就行
這是很常規的尾插數字和字符串,注意尾插字符串時會先構造一個臨時string對象,然后賦值拷貝給vector對應位置的string對象
典型的用迭代器區間構造,不管是vector,list,還是原生數組指針都可以構造
用數字和參數構造,不傳參默認是這個類型的默認構造,比如string、默認構造是空字符串,int默認構造是0
感謝觀看,有不全面的地方希望大佬們交流,歡迎交流