【C++詳解】STL-vector使用底層剖析和實現

文章目錄

  • vector介紹
  • vector和string的區別
  • 補充知識
    • initializer_list
    • emplace_back
    • 結構化綁定
  • vector的使用
    • 構造
    • 析構
    • 遍歷+修改
    • insert
    • find
    • 流插入/流提取
    • vector\<vector>(楊輝三角)
  • vector模擬實現
    • 淺品STL源碼
    • 構造函數
    • 拷貝構造
    • 多參數構造
    • 迭代器區間構造
    • n個val初始化
    • swap
    • operator=
    • reserve
    • size
    • capacity
    • push_back(補充解釋源碼里為什么要用定位new)
    • 迭代器
    • pop_back
    • insert(補充迭代器失效)
    • erase
    • clear
    • resize
    • 深層次的深拷貝問題
  • 源碼


vector介紹

vector底層和string一樣也是數組,類似數據結構我們介紹的順序表。
它和string的區別是string的元素類型只能是字符類型,vector有模板可以是任何類型,包括自定義類型。

vector和string的區別

既然vector和string底層都是數組,那存char類型數據的vector<char>和string有什么區別呢?
1、雖然vector也有date接口返回底層數組首元素的指針,但是vector<char>末尾沒有\0,無法兼容c語言。
2、vector插入數據只有push_back插入一個數據,find也只能找一個數據,但是string有字符串的概念,所以它有append,operator+=插入字符串,find也能找子串。

補充知識

initializer_list

在這里插入圖片描述

  • 這是C++11引入的新語法,initializer_list是一種類型,使用它需要包<initializer_list>頭文件。
  • 我們把一些數據用大括號括起來一起賦值給i1,這個i1類型就是initializer_list。
    它是底層在棧上開了一塊空間,把常量數組存起來,有一個指針指向開始,有一個指針指向結束,所以它支持迭代器,有size()和范圍for,后面講vector多參數構造的底層實現會用到。

用法:

  • typid()可以打印一個對象或者類型的真實類型。
  • initializer_list支持迭代器,所以可以用迭代器遍歷。
    在這里插入圖片描述

用途:

  • C++11要引入這個新語法是一是為了支持用任意多個值直接構造初始化容器,操作如下:
	vector<int> v1({ 10, 20, 30 });vector<double> v2({ 10.1, 20.4, 30.7, 40.6, 50.5 });

這就是直接調用構造函數。
但是我們實際更喜歡用下面這種操作:

	vector<int> v1 = { 10, 20, 30 };vector<double> v2 = { 10.1, 20.4, 30.7, 40.6, 50.5 };

這里會隱式類型轉換,initializer_list會先構造一個vector,再拷貝構造給給對象,編譯器會優化為直接構造。

  • 二是可以用于多參數構造函數的隱式類型轉換:
	struct A{A(int a1, int a2):_a1(a1),_a2(a2){}int _a1;int _a2;};vector<A> v1;A aa1(1, 1);//有名對象v1.push_back(aa1);//匿名對象v1.push_back(A(2, 3));//多參數隱式類型轉換v1.push_back({4,5});

emplace_back

這里小編只能介紹它的用法,理解他還需要后面的知識。
它是模板的可變參數,在上面多參數隱式類型轉換的時候像下面這樣用,效率會更高。

	A1.emplace_back(4,5);

結構化綁定

	auto [x, y] = aa1;for (auto [X, Y] : v1){cout << X << ':' << Y << endl;}

當對象是一個結構對象比如結構體體時,可以用上面這種方法訪問對象的成員。

vector的使用

這里小編不會一一介紹,和string基本一樣的接口就跳過了,和string有區別的會挑出來介紹。

構造

在這里插入圖片描述

	//默認構造vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);//10個1構造vector<int> v2(10, 1);//迭代器區間構造vector<int> v3(v2.begin(), v2.end())

析構

		~vector(){if (_first){delete[] _first;_first = _finish = _endofstorage = nullptr;}}

_first不為空再delete,delete后記得把指針置空。

遍歷+修改

	//使用size()for (int i = 0; i < v1.size(); i++){cout << v1[i] << " ";}cout << endl;//范圍forfor (auto e : v2){cout << e << " ";}cout << endl;//迭代器vector<int>::iterator it = v3.begin();while (it != v3.end()){cout << (*it) << " ";it++;}cout << endl;

insert

在這里插入圖片描述

vector的插入重載要比string少很多,并且vector是以迭代器為標準插入的,string是size_t pos為標準。

	//頭插v1.insert(v1.begin(), 1000);//下標為3的位置前面插入v1.insert(v1.begin() + 3, 1000);

find

在這里插入圖片描述

  • vector并沒有自己的find,它用的是算法庫的find,它是在一段迭代器區間查找。
  • string要自己實現find是因為string需要從某一位置開始向后查找還要查找子串。

在這里插入圖片描述

算法庫的find是通過一段迭代器區間找,因為迭代器區間是左閉右開的,所以找到了就返回第一個找到的元素的迭代器,沒找到就返回last。

	auto it = find(v1.begin(), v1.end(), 10);if (it != v1.end()){v1.insert(it, 100);}

流插入/流提取

  • vector以及后面的容器都不再提過輸入輸出,要輸入輸出自己通過范圍for或者迭代器實現。
  • string提供因為會經常輸入輸出字符串。

vector<vector>(楊輝三角)

在介紹vector<vector>之前我們先看一道題:
題目鏈接:楊輝三角
在這里插入圖片描述

這道題就需要用到vector<vector>來實現C語言類似二維數組的功能。
要理解vector我們先理解它是如何實例化的,我們以vector<vector<int>>為例:

在這里插入圖片描述

這里和C語言單純的二維數組不同,vector<vector>是對象數組。

當我們要讀寫vector<vector<int>>需要采用和C語言一樣的方法,但是底層原理已經完全不同了,C語言的指針的偏移和解引用,這里是兩個函數的調用,下面代碼兩種寫法是等價的,第一個是vector<vector>里的operator[],返回值的vector<int>&,返回值是可以修改的,第二個是vector<int>里的operator[],返回值是int&。有些讀者可能會有疑問,兩個函數調用不會拉低效率嗎?內聯函數的作用就體現出來了,operator[]很短小,所以會直接內聯展開替換成指令,不會調用函數。

vv[i][j];
vv.operator[](i).operator[](j);

完整代碼:

class Solution {
public:vector<vector<int>> generate(int numRows) {//定義vector<vector>+開空間vector<vector<int>> vv;//開numRows行個空間,resize第二個參數要傳對象,這里是匿名對象vv.resize(numRows, vector<int>());for(int i = 0; i < numRows; i++){//每一列數據個數是列數加1vv[i].resize(i + 1, 1);}//控制行//前兩行不用動,i從2開始for(int i = 2; i < vv.size(); i++){//控制列//每列的第一個和最后一個不用動for(int j = 1; j < vv[i].size() - 1; j++){vv[i][j] = vv[i - 1][j - 1] + vv[i - 1][j];}}return vv;}
};

vector模擬實現

首先我們要明確這里我們只用兩個文件來實現,因為vector要嚴格用模板來實現,模板的聲明和定義不能分離到兩個文件。

淺品STL源碼

在自己模擬實現之前,先看一下它的源碼吧,但是小編目前功力不足,只能帶大家看一部分。 首先看它的成員函數:

iterator start;
iterator finish;
iterator end_of_storage;

嗯?三個迭代器?這和string完全不一樣啊,快去看看這些迭代器是什么:

typedef T value_type;
typedef value_type* iterator;

哦,原來就是原始指針啊,那就猜一下start就是指向數組首元素的指針,finish是指向有效數據結尾下一個位置的指針,end_of_storage是指向空間末尾的指針,那事實是不是這樣呢,接下來應該去找begin()和end()和capacity():

iterator begin() { return start; }
iterator end() { return finish; }
size_type capacity() const { return size_type(end_of_storage - begin()); }

看來猜的大差不差,接下來就開始模擬實現vector吧。

構造函數

vector()
{ }

拷貝構造

		vector(const vector<T>& v){reserve(v.capacity());for (auto e : v){push_back(e);}}

拷貝構造本質就是一個對象想要另一個對象一樣大的空間一樣的值,所以可以復用reserve來開空間,再用范圍for遍歷把數據全部拷貝過來。
但是這里要注意一個細節,在拷貝構造之前需要把成員變量初始化,如果不初始化的話在有些編譯器里就會默認是隨機值,然后capacity()來調用這些隨機值就會出錯,這里我們初始化方法推薦在成員變量處給隨機值。
那既然我們已經給了缺省值了,為什么前面還要實現一個默認構造函數呢,我們不寫它直接用缺省值不就行了嗎?其實就算它沒有實質用處,也還是要寫它,構造函數規定只要顯示寫了構造函數那么編譯器就不會生成默認構造了,我們寫了的拷貝構造就是構造函數,所以如果不寫前面那個默認構造函數的話我們就無法調用默認構造函數了。
除了自己寫之外我們還可以強制編譯器生成默認構造:

		vector() = default;

多參數構造

		vector(initializer_list<T> i1){reserve(i1.size());for (auto e : i1){push_back(e);}}

這里就可以用前面介紹的initializer_list來實現多參數構造。

迭代器區間構造

這里我們可以用函數模板,如果我們寫死為iterator的嗎那只有vector能調用這里的迭代器區間構造,如果我們寫成模板那其他類對象比如string的迭代器也能構造初始化,但是要數據類型匹配,比如字符就可以轉化成這里vector實例化出的整型。
也就是說類模板的成員函數還可以繼續寫成函數模板。

		template <class InputIterator>vector(InputIterator first, InputIterator last){reserve(last - first);while (first != last){push_back(*first);first++;}}

n個val初始化

這個構造用的也挺多,比如前面講的楊輝三角。
這里還要注意參數匹配,不匹配的話會調到迭代器區間初始化去。

		vector(int n, const T& val = T()){reserve(n);resize(n, val);}

swap

		void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);}

operator=

		vector<T>& operator=(vector<T>& v){swap(v);return *this;}

這里就是我們在string部分介紹的現代寫法。

reserve

		void reserve(size_t n){//若外部調用該函數需要判斷if (n > capacity()){size_t old_size = size();//開新空間T* tmp = new T[n];//拷貝舊空間數據到新空間,并釋放舊空間//若_start為0(nullptr),無需拷貝if (_start){//memcpy會引發深層次的深拷貝問題//memcpy(tmp, _start, sizeof(T) * old_size);for (int i = 0; i < old_size; i++){tmp[i] = _first[i];}delete[] _start;}_start = tmp;_finish = _start + old_size;_endofstorage = _start + n;}}

這里要提前把舊對象size()值保存下來,避免后面調用size() {return _finish - _first;}其中一個是舊的數據,一個是更新后的數據,結果不是我們想要的。
注意拷貝數據時不能用nencpy,詳細解釋見深層次的深拷貝問題。

size

//不改變調用對象的函數加const
size_t size() const
{return _finish - _first;
}

capacity

size_t capacity() const
{return _endofstorage - _first;
}

push_back(補充解釋源碼里為什么要用定位new)

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

這里我們直接就把x的值賦給finish所在位置了,因為我申請的空間是直接new出來的,不僅開辟了空間,還調用了構造函數初始化這塊空間,所以不管x是內置類型還是自定義類型,都可以直接賦值。
但是源碼里的vector空間不是直接在堆上new的,而是在內存池里malloc的空間,所以空間是沒有初始化的,若空間里是自定義類型的變量,直接賦值就會出問題:
我們以string為例,先看我們實現的string的賦值運算符重載:

	string& string::operator=(const string& s) {if (*this != s){char* tmp = new char[s._size + 1];memcpy(tmp, s._str, s._size + 1);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;}

這里我們用沒初始化的s直接調用賦值運算符重載就會出問題,因為沒初始化的s的成員變量都是隨機值,比方說new char[s._size +
1]這里的_size就是隨機值,那么它最終new了多少數據就是未知的,和我們的想法違背。
所以源碼里還使用了定位new用來顯示調用構造函數,避免出現上面的情況。

迭代器

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

普通迭代器和const迭代器都要實現,以便const對象調用。

pop_back

		bool empty(){return _finish == _first;}void pop_back(){assert(!empty());_finish--;}

pop_back不用刪除數據,只用把_finish–就行了,但是要注意判斷數組是否為空。

insert(補充迭代器失效)

		iterator insert(iterator pos, const T& x){assert(pos >= _first && pos <= _finish);//擴容if (_finish == _endofstorage){size_t len = pos - _first;reserve(capacity() == 0 ? 4 : 2 * capacity());//更新pospos = _first + len;}//挪動數據iterator end = _finish;while (end >= pos){*(end + 1) = *end;end--;}//插入數據*pos = x;_finish++;return pos;}

這里返回值為什么是迭代器,在后面erase部分再展開講解。
乍一看這段代碼非常完美,其實潛藏了一個坑點,當數組空間不夠要擴容時,因為是堆上的空間,所以大概率會異地擴,那么擴完之后_first和_finish都指向新空間了,但是pos還指向舊空間,pos不就成野指針了嗎,其實這里說是野指針并不準確,官方來講應該是迭代器失效,因為這里迭代器恰好就是原生指針,其他容器并不一定。
解決方法:只需要每次擴容的時候更新pos就行了,運用指針的相對位置,先用len記錄pos到_first的距離,擴容后_first+len就得到更新后的pos了。

優化后:

		void pop_back(){assert(!empty());_finish--;}void insert(iterator pos, const T& x){assert(pos >= _first && pos <= _finish);//擴容if (_finish == _endofstorage){size_t len = pos - _first;reserve(capacity() == 0 ? 4 : 2 * capacity());//更新pospos = _first + len;}//挪動數據iterator end = _finish;while (end >= pos){*(end + 1) = *end;end--;}//插入數據*pos = x;_finish++;}

我們再來看下面這段代碼:

		int x;cin >> x;auto it = find(v.begin(), v.end(), 4);if (it != v.end()){v.insert(it, x * 10);//it是否還能用?}

意思是我們隨便輸入一個x,然后再v里找是否有x,有的話在它前面插入10x。 這里我們想想插入過后迭代器it是否還能用?(因為標準沒有規定何時擴容,所以這里一致認為insert后擴容了并且導致迭代器失效)
有的讀者可能會認為當然可以啊,it的數據都更新了,但是其實是不行的,因為更新的只是形參pos,這里只是傳值所以形參的改變不會影響實參。
那直接把insert的第一個參數類型改為iterator&不就行了嗎,其實這也是不對的,比如下面:

v.insert(v.begin(), 30);
v.insert(it + 2, 30);

用begin函數調用insert,我們前面實現begin函數是傳值返回的_first,所以返回的并不是_first本身,而是它的一份臨時拷貝,那如果這里insert引用了這個返回值,就會發生權限的放大,第二鐘情況也是同理,it + 2表達式的的結果也是臨時對象,具有常性的。所以小編在這里提醒大家,縱使引用有萬般好,也要視情況用。

erase

		void erase(iterator pos){assert(pos >= _first && pos < _finish);auto it = pos + 1;//挪動數據while (it != _finish){*(it - 1) = *it;it++;}_finish--;}//優化后iterator erase(iterator pos){assert(pos >= _first && pos < _finish);auto it = pos + 1;//挪動數據while (it != _finish){*(it - 1) = *it;it++;}_finish--;return pos;}

實現思路就是把目標位置以后的數據往前挪,開頭的assert也能檢查數組是否為空。

那erase后迭代器是否會失效呢?我們來看下面這段程序:

	std::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);//刪除偶數auto it = v.begin();while (it != v.end()){if ((*it) % 2 == 0){v.erase(it);//it是否失效?}it++;}

這段代碼會刪除v里面的的偶數數據。我們分析一下,第一次會刪除2,然后后面數據依次往前挪,以前指向2的迭代器就被動指向3了,這里就出現迭起器失效的問題了,因為指向的內容被改變了,因為vs對迭代器失效進行了嚴格檢驗,所以迭代器失效后再對這個迭代器訪問會直接報錯,但是在g++下就能通過,因為不同編譯器對迭代器失效的處理是不同的,總的來說,這里是有問題的。
并且這里還有一個隱藏問題,就是刪除2了之后,所有數據往前挪,那此時迭代器就指向3了,it++后就指向4了,相當于3沒判斷直接跳過了。
解決方法標準已經給出了,我們來看標準庫的erase:

在這里插入圖片描述

它給了一個返回值iterator,它返回的是刪除元素位置下一個位置的迭代器,這就是方便我們來賦值更新迭代器的,也就是用it接受這個返回值就達到了更新迭代器的目的,這樣就。完美解決了迭代器失效的問題。

優化后:

	auto it = v.begin();while (it != v.end()){if ((*it) % 2 == 0){it = v.erase(it);}else{it++;}}

總結:無論insert還是erase都會造成迭代器失效,因為它們有中間迭代器參數pos,解決辦法就是迭代器失效后及時更新迭代器。

clear

		void clear(){_finish = _start;}

這里只能把_start賦值給_finish,因為_start必須指向空間的開始,clear不用釋放空間,交給析構函數來釋放。

resize

在介紹resize之前,小編想先講一下C++標準因為支持模板,所以它還支持了內置類型的構造函數,所以下面這些代碼都是支持的:

	int i = 10;int j(10);int k = int();int m = int(10);

接下的resize的缺省參數就會用到。

		void resize(size_t n, T val = T()){if (n > size()){reserve(n);while (_finish != _start + n){(*_finish) = val;_finish++;}}else{_finish = _start + n;}}

這里resize第二個參數是模板,所以就不能像以前一樣用0充當缺省值,這里最好就是用匿名對象T()當缺省值,(其實匿名對象就是調用了默認構造,因為默認構造就是不傳實參也可以調用的構造)不管是內置類型還是自定義類型的默認構造的值都接近與0,int就是0,char就是ASCII碼為0的’\0’,這樣不管參數是內置類型還是自定義類型都可以調用。

深層次的深拷貝問題

	wusaqi::vector<string> v1;v1.push_back("1111111111111111111111");v1.push_back("1111111111111111111111");v1.push_back("1111111111111111111111");v1.push_back("1111111111111111111111");print(v1);

這段代碼是用string來實例化vector,我們分別來看vector擴容前和擴容后打印出來的值:

在這里插入圖片描述
在這里插入圖片描述

我們看到在擴容后數組的前四個vector被置成隨機值了,敏銳一點的讀者應該會感覺是淺拷貝之類的問題。事實確實是這樣。我們既然知道是擴容后發生的問題,那我們來看擴容接口:

		void reserve(size_t n){//若外部調用該函數需要判斷if (n > capacity()){size_t old_size = size();//開新空間T* tmp = new T[n];//拷貝舊空間數據到新空間,并釋放舊空間//若_start為0(nullptr),無需拷貝if (_start){memcpy(tmp, _start, sizeof(T) * old_size);delete[] _start;}_start = tmp;_finish = _start + old_size;_endofstorage = _start + n;}}

這里拷貝數據是memcpy,我們知道memcpy只會一個字節一個字節拷貝,相當于它會原封不動的把string的成員變量:_str _size _caoacity拷貝到tmp,也就是說tmp數組里的string成員變量_str和原數組里的string成員變量_str的值是一樣的,指向同一塊空間,那么調用delete釋放原數組后tmp數組里的值也被釋放了,所以就解釋了為什么擴容后前四個string為隨機值。
我們借用監視窗口可以看到底層數組確實是指向同一塊空間:

在這里插入圖片描述

這里問題就出在reserve的拷貝上,解決思路很簡單,我們不用memcpy,而是for循環遍歷把每一個數組里的元素然后用賦值運算符重載把元素依次賦給tmp數組,這樣內置類型正常淺拷貝賦值,自定義類型就會去調用自己的賦值運算符重載完成深拷貝。

		void reserve(size_t n){//若外部調用該函數需要判斷if (n > capacity()){size_t old_size = size();//開新空間T* tmp = new T[n];//拷貝舊空間數據到新空間,并釋放舊空間//若_start為0(nullptr),無需拷貝if (_start){//memcpy會引發深層次的深拷貝問題//memcpy(tmp, _start, sizeof(T) * old_size);for (int i = 0; i < old_size; i++){tmp[i] = _first[i];}delete[] _start;}_start = tmp;_finish = _start + old_size;_endofstorage = _start + n;}}

源碼

vector.h

#pragma once
#include <assert.h>
#include <string.h>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;namespace wusaqi
{template<class T>class vector{public: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;}vector(){}//vector() = default;vector(const vector<T>& v){reserve(v.capacity());for (auto e : v){push_back(e);}}void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);}vector<T>& operator=(vector<T>& v){swap(v);return *this;}vector(initializer_list<T> i1){reserve(i1.size());for (auto e : i1){push_back(e);}}template <class InputIterator>vector(InputIterator first, InputIterator last){reserve(last - first);while (first != last){push_back(*first);first++;}}vector(int n, const T& val = T()){reserve(n);resize(n, val);}vector(size_t n, const T& val = T()){reserve(n);resize(n, val);}~vector(){if (_start){delete[] _start;_start = _finish = _endofstorage = nullptr;}}void reserve(size_t n){//若外部調用該函數需要判斷if (n > capacity()){size_t old_size = size();//開新空間T* tmp = new T[n];//拷貝舊空間數據到新空間,并釋放舊空間//若_start為0(nullptr),無需拷貝if (_start){//memcpy會引發深層次的深拷貝問題//memcpy(tmp, _start, sizeof(T) * old_size);for (int i = 0; i < old_size; i++){tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = _start + old_size;_endofstorage = _start + n;}}T& operator[](size_t i){//斷言防止越界訪問assert(i < size());return _start[i];}//不改變調用對象的函數加constsize_t size() const{return _finish - _start;}size_t capacity() const{return _endofstorage - _start;}void push_back(const T& x){//size_t storage = capacity();//if (_finish == _endofstorage)//{//	size_t newstorage = storage == 0 ? 4 : 2 * storage;//	reserve(newstorge);//}//簡化:if (_finish == _endofstorage){reserve(capacity() == 0 ? 4 : 2 * capacity());}*_finish = x;_finish++;}bool empty(){return _finish == _start;}void pop_back(){assert(!empty());_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 : 2 * capacity());//更新pospos = _start + len;}//挪動數據iterator end = _finish;while (end >= pos){*(end + 1) = *end;end--;}//插入數據*pos = x;_finish++;return pos;}iterator erase(iterator pos){assert(pos >= _start && pos < _finish);auto it = pos + 1;//挪動數據while (it != _finish){*(it - 1) = *it;it++;}_finish--;return pos;}void clear(){_start = _finish;}void resize(size_t n, const T& val = T()){if (n > size()){reserve(n);while (_finish != _start + n){(*_finish) = val;_finish++;}}else{_finish = _start + n;}//size_t capacity = _endofstorage - _start;//size_t size = _finish - _start;//if (n < size)//{//	_finish = _start + n;//}//else//{//	if (n > capacity)//	{//		reserve(n);//		_endofstorage = _start + n;//	}//	iterator pos = _finish;//	while (pos != _start + n)//	{//		(*pos) = val;//		pos++;//	}//	_finish = _start + n;//}}private:iterator _start = nullptr;iterator _finish = nullptr;iterator _endofstorage = nullptr;};
}

test.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include "vector.h"template<class T>
void print(const T& v)
{for (auto ch : v){cout << ch << " ";}cout << endl;
}void test01()
{wusaqi::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.pop_back();for (int i = 0; i < v.size(); i++){cout << v[i] << " ";}cout << endl;for (auto ch : v){cout << ch << " ";}cout << endl;print(v);
}void test02()
{wusaqi::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);//v.push_back(5);v.insert(v.begin(), 30);print(v);//v.erase(v.begin() + 2);//print(v);int x;cin >> x;wusaqi::vector<int>::iterator it = find(v.begin(), v.end(), x);if (it != v.end()){v.erase(it);}print(v);}void test03()
{wusaqi::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(5);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(4);print(v);//刪除偶數auto it = v.begin();while (it != v.end()){if ((*it) % 2 == 0){it = v.erase(it);}else{it++;}}print(v);v.clear();print(v);
}void test04()
{wusaqi::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(5);v.push_back(2);v.push_back(3);v.push_back(4);v.push_back(5);v.push_back(4);print(v);v.resize(10, 100);print(v);//int i = 10;//int j(10);//int k = int();//int m = int(10);
}void test05()
{wusaqi::vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);print(v1);wusaqi::vector<int> v2(v1);print(v2);wusaqi::vector<int> v3 = { 1, 2, 3, 4, 5, 1, 1, 1 };print(v3);wusaqi::vector<int> v4(v3.begin() + 2, v3.end() - 2);print(v4);string s1("good morning");wusaqi::vector<int> v5(s1.begin() + 2, s1.end() - 2);print(v5);std::vector<int> v6(10, 1);print(v6);std::vector<size_t> v7(10, 1);print(v7);
}void test06()
{wusaqi::vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);print(v1);wusaqi::vector<int> v3;v3 = v1;print(v3);
}void test07()
{wusaqi::vector<string> v1;v1.push_back("1111111111111111111111");v1.push_back("1111111111111111111111");v1.push_back("1111111111111111111111");v1.push_back("1111111111111111111111");v1.push_back("1111111111111111111111");print(v1);
}int main()
{//test01();//cout << typeid(std::vector<int>::iterator).name() << endl;//cout << typeid(wusaqi::vector<int>::iterator).name() << endl;//test02();//test03();//test04();test05();//test06();//test07();return 0;
}

以上就是小編分享的全部內容了,如果覺得不錯還請留下免費的關注和收藏
如果有建議歡迎通過評論區或私信留言,感謝您的大力支持。
一鍵三連好運連連哦~~

在這里插入圖片描述

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

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

相關文章

MySql升級安裝、socket 及密碼重置

升級 項目需要使用Mysql8.0, 查看自己的ubuntu22.04上mysql版本為5.7&#xff0c; 使用以下命令自動升級到8.0版本。 sudo apt install Mysqlsock錯誤&#xff1a; Can’t connect to local MySQL server through socket 運行mysql -u -p 報以下錯誤&#xff1a; ERROR 200…

Python網絡爬蟲技術:從入門到實戰

在當今數字化時代&#xff0c;網絡爬蟲技術已經成為數據挖掘和信息收集的重要工具。通過網絡爬蟲&#xff0c;我們可以高效地從互聯網上獲取大量有價值的數據&#xff0c;用于數據分析、市場研究、學術研究等多種場景。本文將帶你從零開始&#xff0c;了解Python網絡爬蟲的基本…

偏微分方程初值問題求解

題目 問題 2. (a) u t + 3 u x ? 2 u y = x ; u t + x u x + y u y = x ; u_t + 3u_x - 2u_y = x; \quad u_t + xu_x + yu_y = x; ut?+3ux??2uy?=x;ut?+xux?+yuy?=x; u t + x u x ? y u y = x ; u t + y u x + x u y = x ; u_t + xu_x - yu_y = x; \quad u_t + yu_…

【專業梳理】PMP知識體系,以SIPOC流程圖為核心的質量工具擴展

??1. SIPOC流程圖:質量管理的起點?? SIPOC(Supplier-Input-Process-Output-Customer)是六西格瑪和流程管理中的核心工具,用于定義和優化跨職能流程。在PMBOK中,它與質量管理知識領域(尤其是質量規劃、質量保證)緊密關聯: ??質量規劃??:通過SIPOC明確流程邊界…

OpenCV指定pid和vid通過MSMF打開攝像頭

在基于OpenCV的項目中&#xff0c;實際開發過程會面臨設備上存在多個攝像頭&#xff0c;需要指定攝像頭的pid和vid打開攝像頭。在OpenCV通過MSMF打開攝像頭時&#xff0c;需要傳入攝像頭的index&#xff0c;因此需要在打開該攝像頭前需要找出攝像頭的index&#xff0c;下面給出…

STM32F103ZET6系統啟動過程

STM32F103ZET6系統啟動過程 一、概述 STM32F103ZET6啟動過程指硬件選擇啟動模式后,執行固件程序之前的一系列動作。對于系統存儲器模式,系統執行Bootloader程序升級狀態,檢測數據進行串口升級;對于內部Flash模式,系統執行啟動文件,設置堆棧大小,配置系統時鐘,最終調用…

[Data Pipeline] Kafka消息 | Redis緩存 | Docker部署(Lambda架構)

第七章&#xff1a;Kafka消息系統&#xff08;實時流處理&#xff09; 歡迎回到數據探索之旅&#xff01; 在前六章中&#xff0c;我們構建了強大的**批量處理流水線**。 通過Airflow DAG&#xff08;批量任務編排&#xff09;協調Spark作業&#xff08;數據處理&#xff09;…

jquery 賦值時不觸發change事件解決——仙盟創夢IDE

一、傳統方法jquey change $(#village_id).trigger(change);$("#village_id").val(99);$("#village_id").change(); 不生效 二、傳統方法jquey $(#village_id).trigger(change); 四、傳統方法jquey <input type"text" /> <button…

Android | 簽名安全

檢驗和簽名 校驗開發者在數據傳送時采用的一種校正數據的一種方式&#xff0c; 常見的校驗有:簽名校驗(最常見)、dexcrc校驗、apk完整性校驗、路徑文件校驗等。 通過對 Apk 進行簽名&#xff0c;開發者可以證明對 Apk 的所有權和控制權&#xff0c;可用于安裝和更新其應用。…

Android14 耳機按鍵拍照

在相機拍照預覽界面 通過耳機按鍵實現拍照功能 耳機按鍵定義 frameworks/base/core/java/android/view/KeyEvent.java public static final int KEYCODE_HEADSETHOOK 79;相機界面 拍照邏輯 DreamCamera2\src\com\android\camera\PhotoModule.java Override public bool…

【AI作畫】第2章comfy ui的一般輸入節點,文本框的類型和輸入形式

目錄 CLIP文本編碼器 條件輸出和文本輸出 轉換某一變量為輸入 展示作品集 在默認的工作流之外&#xff0c;我們如何自己添加節點呢&#xff1f; 一般我們用到的sampler采樣器在“鼠標右鍵——添加節點——采樣——K采樣器” 我們用的clip文本編碼器在“鼠標右鍵——添加節…

vue3仿高德地圖官網路況預測時間選擇器

<template><div class"time-axis-container"><div class"time-axis" ref"axisRef"><!-- 刻度線 - 共25個刻度(0-24) --><divv-for"hour in 25":key"hour - 1"class"tick-mark":class&…

ZArchiver:高效解壓縮,輕松管理文件

在數字時代&#xff0c;文件的壓縮與解壓已成為我們日常操作中不可或缺的一部分。無論是接收朋友分享的大文件&#xff0c;還是下載網絡資源&#xff0c;壓縮包的處理都極為常見。ZArchiver正是一款為安卓用戶精心打造的解壓縮軟件&#xff0c;它以強大的功能、簡潔的界面和高效…

1432.改變一個整數能得到的最大差值

貪心思想&#xff0c;為了得到最大差&#xff0c;想辦法變成一個最大的數和一個最小的數。 這里有規則&#xff0c;從最高位開始&#xff0c; 變成最大&#xff0c;如果<9&#xff0c;則將該數位代表的數都變成9&#xff0c;如果該數位已經是9了&#xff0c;則將下一個數位…

前端跨域解決方案(4):postMessage

1 postMessage 核心 postMessage 是現代瀏覽器提供的跨域通信標準 API&#xff0c;允許不同源的窗口&#xff08;如主頁面與 iframe、彈出窗口、Web Worker&#xff09;安全交換數據。相比其他跨域方案&#xff0c;它的核心優勢在于&#xff1a; 雙向通信能力&#xff1a;支持…

大語言模型指令集全解析

在大語言模型的訓練與優化流程中&#xff0c;指令集扮演著關鍵角色&#xff0c;它直接影響模型對任務的理解與執行能力。以下對常見指令集展開詳細介紹&#xff0c;涵蓋構建方式、規模及適用場景&#xff0c;助力開發者精準選用 為降低指令數據構建成本&#xff0c;學術界和工…

OpenCV CUDA模塊設備層-----用于封裝CUDA紋理對象+ROI偏移量的一個輕量級指針類TextureOffPtr()

操作系統&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 編程語言&#xff1a;C11 算法描述 TextureOffPtr<T, R> 是 OpenCV 的 CUDA 模塊&#xff08;opencv_cudev&#xff09;中用于封裝 CUDA 紋理對象 ROI 偏移量 的一個輕量級指…

Python 數據分析10

2.3.3其他 除了前面所介紹的常用語數據挖掘建模的庫之外&#xff0c;還有許多庫也運用于數據挖掘建模&#xff0c;如jieba、SciPy、OpenCV、Pillow等。 1.jieba jieba是一個被廣泛使用的Python第三方中文分詞庫。jieba使用簡單&#xff0c;并且支持Python、R、C等多種編程語言的…

css 制作一個可以旋轉的水泵效果

如圖&#xff0c;項目里面有一個小圖片可以旋轉&#xff0c;達到看起來像是一個在工作的水泵。我使用css旋轉動畫實現。 一、HTML結構部分 <div className"ceshixuanzhuan"><img src{lunkuo} className"lunkuo"/><img src{yepian} classN…

數據結構期末程序題型

一、 隊列 1、簡單模擬隊列排列 #include<bits/stdc.h> using namespace std; int main(){int n;cin>>n;queue<int>q;string str;while(true){cin>>str;if(str"#")break;if(str"In"){int t;cin>>t;if(q.size()<n){q.pu…