目錄

1.vector的介紹及使用 ?
1.1 vector的介紹
1.2 vector的使用
1.2.1 vector的定義
1.2.2 vector iterator 的使用
1.2.3 vector 空間增長問題
1.2.3 vector 增刪查改
1.2.4 vector 迭代器失效問題。(重點)
1.2.5 vector 在OJ中的使用
2.vector深度剖析及模擬實現
使用memcpy拷貝問題
動態二維數組理解
模擬實現vector:
1. vector 是表示可變大小數組的序列容器。 2. 就像數組一樣, vector 也采用的連續存儲空間來存儲元素。也就是意味著可以采用下標對 vector 的元素進行訪問,和數組一樣高效。但是又不像數組,它的大小是可以動態改變的,而且它的大小會被容器自動處理。 3. 本質講, vector 使用動態分配數組來存儲它的元素。當新元素插入時候,這個數組需要被重新分配大小為了增加存儲空間。其做法是,分配一個新的數組,然后將全部元素移到這個數組。就時間而言,這是一個相對代價高的任務,因為每當一個新的元素加入到容器的時候,vector 并不會每次都重新分配大小。 4. vector 分配空間策略: vector 會分配一些額外的空間以適應可能的增長,因為存儲空間比實際需要的存儲空間更大。不同的庫采用不同的策略權衡空間的使用和重新分配。但是無論如何,重新分配都應該是對數增長的間隔大小,以至于在末尾插入一個元素的時候是在常數時間的復雜度完成的。 5. 因此, vector 占用了更多的存儲空間,為了獲得管理存儲空間的能力,并且以一種有效的方式動態增長。 6. 與其它動態序列容器相比( deque, list and forward_list ), vector 在訪問元素的時候更加高效,在末尾添加和刪除元素相對高效。對于其它不在末尾的刪除和插入操作,效率更低。比起list 和 forward_list統一的迭代器和引用更好1.2 vector的使用 1.2.1 vector的定義

以上就是vector的構造函數,重點關注1和3



1.2.3 vector 空間增長問題我們可以看到這里的iterator行為上就像指針一樣,但又不一定是指針?,而對于vector這里來說,這里的iterator就是一個原生指針,后面會講到迭代器失效的問題。

這里的resize和reserve和之前的string的含義一樣,這里就看看函數的使用方式即可:
capacity 的代碼在 vs 和 g++ 下分別運行會發現, vs下capacity是按1.5倍增長的,g++是按2倍增長的。 這個問題經常會考察,不要固化的認為, vector 增容都是 2 倍,具體增長多少是根據具體的需求定義的。 vs是PJ版本STL,g++是SGI版本STL 。( 也就是不同編譯器的實現方式不同,所以可能會導致有不同的結果) reserve 只負責開辟空間,如果確定知道需要用多少空間, reserve 可以緩解 vector 增容的代價缺陷問題。 resize 在開空間的同時還會進行初始化,影響 size 。
這是vs下的擴容:

reserve的優勢就在于如果我們知道要開多少空間,我們就可以一次性開好,就可以避免頻繁的擴容,因為我們知道頻繁擴容是要付出代價的,所以C++在這方面做的很好。
1.2.3 vector 增刪查改
為什么不推薦使用insert以及erase呢?因為我們知道數組刪除數據是要挪動數據的,這樣就導致花費更多時間,所以一般情況下不推薦使用。
上述內容很簡單,查網站就會使用,不過多講解,下面主要講解迭代器失效的問題;
1.2.4 vector 迭代器失效問題。(重點)迭代器的主要作用就是讓算法能夠不用關心底層數據結構,其底層實際就是一個指針,或者是對指針進行了封裝,比如:vector的迭代器就是原生態指針T* 。因此迭代器失效,實際就是迭代器底層對應指針所指向的空間被銷毀了,而使用一塊已經被釋放的空間,造成的后果是程序崩潰(即如果繼續使用已經失效的迭代器,程序可能會崩潰)。 為什么會這樣呢? 下面我們看幾個例子:1. 會引起其底層空間改變的操作,都有可能是迭代器失效 ,比如: resize 、 reserve 、 insert 、 assign 、push_back等。 拿擴容來說,reserve的底層實現是講原來的空間釋放掉,然后開辟新的空間,這樣迭代器所在的位置就發生了改變,所以最后就導致了迭代器失效了。 我們看看下面這段代碼:
#includeusing namespace std;
#includeint main()
{
vectorv{ 1,2,3,4,5,6 };
auto it = v.begin();
v.assign(100, 3);
//因為這里擴容過,所以迭代器肯定失效
while (it != v.end())
{
cout<< *it<< " ";
++it;
}
cout<< endl;
return 0;
} 結果正如我們所料:

解決這個辦法就是更新一下迭代器即可正常使用。
看看其他例子:
2. 指定位置元素的刪除操作--eraseint main()
{
int a[] = { 1, 2, 3, 4 };
vectorv(a, a + sizeof(a) / sizeof(int));
// 使用find查找3所在位置的iterator
vector::iterator pos = find(v.begin(), v.end(), 3);
// 刪除pos位置的數據,導致pos迭代器失效。
v.erase(pos);
cout<< *pos<< endl; // 此處會導致非法訪問
return 0;
}
erase
刪除
pos
位置元素后,
pos
位置之后的元素會往前搬移,
沒有導致底層空間的改變,理論上講迭代器不應該會失效
,但是:
如果pos剛好是最后一個元素,刪完之后pos剛好是end的位置,而end位置是沒有元素的,那么pos就失效了。因此刪除vector中任意位置上元素時,vs就認為該位置迭代器失效了。
我們可以看到
vs實現的方式是比較嚴格的
,如果不更新迭代器這就無法使用了。后面我們會模擬實現vector,后面我們模擬實現的就可以使用。
如果要
刪除vector中所有的偶數,我們應該怎么做呢?
下面這樣就是有問題的:
int main()
{
vectorv{ 1, 2, 3, 4 };
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
v.erase(it);
++it;
}
return 0;
} 不難發現這里肯定是有問題的,因為刪除完4之后it又++就已經越界訪問了。
我們可以在不是偶數的時候再++,同時更新一下it,是偶數的就不用++
int main()
{
vectorv{ 1, 2, 3, 4 };
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
it = v.erase(it);
else
{
++it;
}
}
for (auto& e : v)
{
cout<< e<< endl;
}
return 0;
} 
但是在Linux底下g++編譯器就沒有檢查的那么嚴格:
看看擴容這段代碼:
// 1. 擴容之后,迭代器已經失效了,程序雖然可以運行,但是運行結果已經不對了
int main()
{
vectorv{ 1,2,3,4,5 };
for (size_t i = 0; i< v.size(); ++i)
cout<< v[i]<< " ";
cout<< endl;
auto it = v.begin();
cout<< "擴容之前,vector的容量為: "<< v.capacity()<< endl;
// 通過reserve將底層空間設置為100,目的是為了讓vector的迭代器失效
v.reserve(100);
cout<< "擴容之后,vector的容量為: "<< v.capacity()<< endl;
// 經過上述reserve之后,it迭代器肯定會失效,在vs下程序就直接崩潰了,但是linux下不會
// 雖然可能運行,但是輸出的結果是不對的
while (it != v.end())
{
cout<< *it<< " ";
++it;
}
cout<< endl;
return 0;
} 
可以看到Linux底下是沒有報錯的,因為空間還是原來的空間,后序元素往前搬移了,it的位置還是有效的,但是對于剛剛那個偶數那題Linux下也會報錯:

4. 與vector類似,string在插入+擴容操作+erase之后,迭代器也會失效
最后說說如何解決這類問題:
迭代器失效解決辦法:在使用前,對迭代器重新賦值即可 1.2.5 vector 在OJ中的使用1. 力扣因為數組在實際的應用非常廣泛,所以C++的vector的運用也是非常多,而且嵌套vector遠遠比C語言中的二維數組好用,下面我們就來看看vector在oj中的應用:
這題楊輝三角實現起來其實并不是很難,但是如果使用C語言來做就很難受了,因為開辟空間那里就會讓你非常頭疼,但是對于C++來說這又不是一件難事了。這里就體現了嵌套vector的優點了。 思路: 先開辟好一個vector實現方式:>,這樣相當于C語言中的二維數組了。然后調整好空間,在第一行以及對角線的那一行初始化為1,其他的初始化為0,然后我們就可以遍歷這個嵌套vector,判斷是否0的元素,是就讓其加上上一行的前一個以及上一行的那個。
class Solution {
public:
vector>generate(int numRows) {
//先開辟空間
vector>vv;
vv.resize(numRows);
for(size_t i= 0 ;i 2.力扣
這道題如果使用哈希表的話就超出了這個空間復雜度了,用暴力查找的方式又不符合題意,這里有一個思路非常巧妙:
我們可以利用位運算來解決,因為這個除了這個數字其他的數字都出現過3次,我們可以把數組中的每一個數字的二進制位加起來,然后讓結果%3就可以得到我們所想要的二進制位了,(不論是0還是1,%3的結果都是我們想要的那個數的二進制位),最后我們用ret每次接受一下要的到二進制位,就可以得到答案。
代碼實現:
class Solution {
public:
int singleNumber(vector& nums) {
//我們可以把所有的二進制位全部加起來,然后%3就可以得到所求的數字的二進制位了
int ret = 0;//我們要求的數字
for(int i = 0;i<32;++i)
{
int sum = 0;
for(auto e:nums)
{
sum += ((e>>i) & 1);
}
//把要求的數字的二進制位找出來
if(sum%3)
{
ret |= (1< 3.數組中出現次數超過一半的數字_牛客題霸_牛客網
思路:因為所出現的元素的個數超過了一般半,我們就可以通過計數的方式記錄它,如果是這個元素就++,不是就--,最后得到的那個數一定是我們想要的數,另外,我們還可以拓展一下:如果這個數不一定存在那要怎么處理?
我們可以對這個記錄一下這個數,然后再遍歷一次數組,通過計數的方式去確認是否存在。
代碼實現:
class Solution {
public:
int MoreThanHalfNum_Solution(vectornumbers) {
//因為所出現的元素的個數超過了一般半,我們就可以通過計數的方式記錄它,如果是這個元素就++,不是就--
//這樣的話就可以在最后的時候找到這個元素
int tmp = numbers[0];//記錄我們要求的數據
int times = 1;
for(int i = 1;i 4.力扣
這一題的難度比較大,運用到了回溯算法
思路:
我們可以通過一個數組來記錄數字與字母之間的映射關系,然后利用回溯算法(遞歸)就解決像這種組合問題,下面通過這題來大概講解一下回溯:
這個很像二叉樹的遍歷,但又復雜一點,這個可以看成多叉樹的遍歷方式。我們可以通過每一層的遞歸來達到一個組合的效果。
代碼實現:
class Solution {
//映射數組
string numstr[10] = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
public:
//遞歸子函數
void combine(string& digits,int i,vector& vcombine,string retstr)
{
if(i == digits.size())
{
vcombine.push_back(retstr);
return;
}
//找到對應的字母
int num = digits[i]- '0';
string str = numstr[num];
//遍歷串中的所有字符,然后進入下一層
for(auto ch:str)
{
combine(digits,i+1,vcombine,retstr+ch);
}
}
vectorletterCombinations(string digits) {
vectorvcombine;
if(digits.empty())
{
return vcombine;
}
int i = 0;
string retstr;//用來每次加,然后最后加到vcombine中
//遞歸
combine(digits,i,vcombine,retstr);
return vcombine;
}
}; 下面通過畫遞歸展開圖再來看看其中的細節:
1. memcpy是內存的二進制格式拷貝,將一段內存空間中內容原封不動的拷貝到另外一段內存空間中 2. 如果拷貝的是自定義類型的元素, memcpy 既高效又不會出錯,但 如果拷貝的是自定義類型元素,并且自定義類型元素中涉及到資源管理時,就會出錯,因為memcpy的拷貝實際是淺拷貝。

這個和之前講拷貝構造的時候很像,都是淺拷貝導致的問題。?可能會引起內存泄漏甚至程序崩潰。
動態二維數組理解模擬實現vector:在前面就已經看過了二維數組的題了,這里不過多介紹,還是介紹一下memcpy也會使內存崩潰的結果:
要解決這個問題,我們還是要進行深拷貝,這是下面的reserve的模擬實現代碼來解決這個問題:
我們可以通過這個一個對象賦值的方式來進行深拷貝
templateclass vector
{
public:
// Vector的迭代器是一個原生指針
typedef T* iterator;
typedef const T* const_iterator;
iterator begin()
{
return _start;
}
iterator end()
{
return _end;
}
const_iterator cbegin()
{
return _start;
}
const_iterator cend() const
{
return _end;
}
// construct and destroy
vector()
:_start(nullptr)
,_end(nullptr)
,_endOfStorage(nullptr)
{}
vector(int n, const T& value = T())
:_start(nullptr)
, _end(nullptr)
, _endOfStorage(nullptr)
{
reserve(n);
for (int i = 0; i< n; ++i)
{
push_back(value);
}
}
templatevector(InputIterator first, InputIterator last)
:_start(nullptr)
, _end(nullptr)
, _endOfStorage(nullptr)
{
while (first != last)
{
push_back(*first);
++first;
}
}
vector(const vector& v)
:_start(nullptr)
, _end(nullptr)
, _endOfStorage(nullptr)
{
//找個工具人
vectortmp(v._start, v._end);
swap(tmp);
}
vector& operator= (vectorv)
{
swap(v);
return *this;
}
~vector()
{
delete[] _start;
_start = _end = _endOfStorage = nullptr;
}
// capacity
size_t size() const
{
return _end - _start;
}
size_t capacity() const
{
return _endOfStorage - _start;
}
void reserve(size_t n)
{
//堅持不縮容
if (n >capacity())
{
//開辟新的空間,然后賦值過去
T* tmp = new T[n];
size_t oldsize = size();
if (_start)
{
for (size_t i = 0; i< oldsize; ++i)
{
tmp[i] = _start[i];
}
delete[]_start;
}
_start = tmp;
_end = _start + oldsize;
_endOfStorage = _start + n;
}
}
void resize(size_t n, const T& value = T())
{
//要擴容
if (n >capacity())
{
int newcapacity = capacity() == 0 ? 4 : 2 * capacity();
reserve(newcapacity);
}
if (n >size())
{
while (_end != _start + n)
{
push_back(value);
}
}
else
{
_end = _start + n;
}
}
///access///
T& operator[](size_t pos)
{
assert(pos< size());
return _start[pos];
}
const T& operator[](size_t pos)const
{
assert(pos< size());
return _start[pos];
}
///modify/
void push_back(const T& x)
{
if (size() == capacity())
{
int newcapacity = capacity() == 0 ? 4 : 2 * capacity();
reserve(newcapacity);
}
*_end = x;
++_end;
}
void pop_back()
{
assert(_end >_start);
--_end;
}
void swap(vector& v)
{
std::swap(_start, v._start);
std::swap(_end, v._end);
std::swap(_endOfStorage, v._endOfStorage);
}
iterator insert(iterator pos, const T& x)
{
assert(_start<= pos);
assert(_end >pos);
//判斷增容
int distance = pos - _start;
if (_end == _endOfStorage)
{
int newcapacity = capacity() == 0 ? 4 : 2 * capacity();
reserve(newcapacity);
//這里的擴容不處理會導致迭代器失效
pos = _start + distance;
}
//挪動數據,插入
auto end = _end -1;
while (end >= pos)
{
*(end+1) = *end;
--end;
}
*pos = x;
++_end;
return pos;
}
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos< _end);
//挪動數據
iterator ret = pos + 1;
while (ret< _end)
{
*(ret - 1) = *ret;
++ret;
}
--_end;
return pos;
}
private:
iterator _start; // 指向數據塊的開始
iterator _end; // 指向有效數據的尾
iterator _endOfStorage; // 指向存儲容量的尾
}; 其中實現過程中最容易錯誤的就是insert以及erase擴容問題,這是最容易出錯的。
擴容是防止淺拷貝,所有把利用賦值來實現深拷貝。insert以及erase要注意更新迭代器,防止迭代器失效。
你是否還在尋找穩定的海外服務器提供商?創新互聯www.cdcxhl.cn海外機房具備T級流量清洗系統配攻擊溯源,準確流量調度確保服務器高可用性,企業級服務器適合批量采購,新人活動首月15元起,快前往官網查看詳情吧
網頁題目:C++vector-創新互聯
網站路徑:http://www.yijiale78.com/article6/dddpog.html
成都網站建設公司_創新互聯,為您提供建站公司、網站排名、營銷型網站建設、Google、云服務器、網站制作
聲明:本網站發布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創新互聯