C++STL第二篇(vector的原理用法)

ivanlee717發表於2024-03-10

vector

vector的資料安排以及操作方式,與array非常相似,兩者的唯一差別在於空間的運用的靈活性。Array是靜態空間,一旦配置了就不能改變,要換大一點或者小一點的空間,可以,一切瑣碎得由自己來,首先配置一塊新的空間,然後將舊空間的資料搬往新空間,再釋放原來的空間。Vector是動態空間,隨著元素的加入,它的內部機制會自動擴充空間以容納新元素。因此vector的運用對於記憶體的合理利用與運用的靈活性有很大的幫助,我們再也不必害怕空間不足而一開始就要求一個大塊頭的array了。

Vector的實現技術,關鍵在於其對大小的控制以及重新配置時的資料移動效率,一旦vector舊空間滿了,如果客戶每新增一個元素,vector內部只是擴充一個元素的空間,實為不智,因為所謂的擴充空間(不論多大),一如剛所說,是”配置新空間-資料移動-釋放舊空間”的大工程,時間成本很高,應該加入某種未雨綢繆的考慮,稍後我們便可以看到vector的空間配置策略。

image-20240307143601112

Vector維護一個線性空間,所以不論元素的型別如何,普通指標都可以作為vector的迭代器,因為vector迭代器所需要的操作行為,如operaroe*, operator->, operator++, operator--, operator+, operator-, operator+=, operator-=, 普通指標天生具備。Vector支援隨機存取,而普通指標正有著這樣的能力。所以vector提供的是隨機訪問迭代器(Random Access Iterators).

#include <vector> // 包含 vector 標頭檔案

// 建立一個儲存 int 型別的 vector
std::vector<int> intVector;

// 向 vector 尾部新增元素
intVector.push_back(42);

// 獲取 vector 的大小(元素個數)
int size = intVector.size();

// 獲取 vector 的容量(當前分配的儲存空間大小)
int capacity = intVector.capacity();

// 訪問 vector 中的元素
int element = intVector[0]; // 使用下標訪問

// 遍歷 vector 中的所有元素
for (int i = 0; i < intVector.size(); ++i) {
    std::cout << intVector[i] << " ";
}

// 使用迭代器進行遍歷
for (auto it = intVector.begin(); it != intVector.end(); ++it) {
    std::cout << *it << " ";
}

以上就是所有vector常用的語法,具體透過下述一個小例子來說。

	vector<int> regina;
	for (int i = 0; i < 10; i++) {
		regina.push_back(i);
		cout << regina.capacity() << endl;

	}
	int* start = &regina[0];
	int* end = &regina[regina.size() - 1];

	for (; start <= end; start++) {
		cout << *start << endl;
	}

image-20240307151258152

Vector所採用的資料結構非常簡單,線性連續空間,它以兩個迭代器_Myfirst和_Mylast分別指向配置得來的連續空間中目前已被使用的範圍,並以迭代器_Myend指向整塊連續記憶體空間的尾端

。所謂動態增加大小,並不是在原空間之後續接新空間(因為無法保證原空間之後尚有可配置的空間),而是一塊更大的記憶體空間,然後將原資料複製新空間,並釋放原空間。因此,對vector的任何操作,一旦引起空間的重新配置,指向原vector的所有迭代器就都失效了。這是程式設計師容易犯的一個錯誤,務必小心。

常用操作

assign(beg, end);//將[beg, end)區間中的資料複製賦值給本身。
assign(n, elem);//將n個elem複製賦值給本身。
vector&operator=(const vector  &vec);//過載等號運算子
swap(vec);// 將vec與本身的元素互換。
--------
size();//返回容器中元素的個數
empty();//判斷容器是否為空
resize(int num);//重新指定容器的長度為num,若容器變長,則以預設值填充新位置。如果容器變短,則末尾超出容器長度的元素被刪除。
resize(int num, elem);//重新指定容器的長度為num,若容器變長,則以elem值填充新位置。如果容器變短,則末尾超出容器長>度的元素被刪除。
capacity();//容器的容量
reserve(int len);//容器預留len個元素長度,預留位置不初始化,元素不可訪問。
-------------
at(int idx); //返回索引idx所指的資料,如果idx越界,丟擲out_of_range異常。
operator[];//返回索引idx所指的資料,越界時,執行直接報錯
front();//返回容器中第一個資料元素
back();//返回容器中最後一個資料元素
-------------
insert(const_iterator pos, int count,ele);//迭代器指向位置pos插入count個元素ele.
push_back(ele); //尾部插入元素ele
pop_back();//刪除最後一個元素
erase(const_iterator start, const_iterator end);//刪除迭代器從start到end之間的元素
erase(const_iterator pos);//刪除迭代器指向的元素
clear();//刪除容器中所有元素

在C++的STL中,capacity()size()vector類的兩個成員函式,用於獲取vector物件的容量和大小。

  • capacity()函式返回vector物件在不重新分配記憶體的情況下能夠容納的元素數量。換句話說,capacity()表示vector當前分配的記憶體空間大小,而不是vector實際包含的元素數量。當vector中的元素數量達到當前容量時,如果需要繼續新增元素,vector會分配更大的記憶體空間,並將原有元素複製到新的記憶體空間中。
  • size()函式返回vector物件當前包含的元素數量。換句話說,size()表示vector中實際儲存的元素數量,而不考慮vector實際分配的記憶體空間大小。

舉個例子,假設你有一個vector物件regina,初始時capacity()可能為10,而size()為0。這意味著regina的記憶體空間能夠容納10個元素,但實際上它目前並沒有儲存任何元素。當你向regina中新增元素時,size()會逐漸增加,直到等於capacity(),此時vector可能會重新分配更大的記憶體空間。

總結一下:

  • capacity():表示vector當前分配的記憶體空間大小,不考慮實際儲存的元素數量。
  • size():表示vector實際儲存的元素數量,不考慮分配的記憶體空間大小。
vector<int> regina;
for (int i = 0; i < 100000; i++) {
	regina.push_back(i);
}

cout << "capacity:" << regina.capacity() << endl;
cout << "size:" << regina.size() << endl;

//此時 透過resize改變容器大小
regina.resize(10);

cout << "capacity:" << regina.capacity() << endl;
cout << "size:" << regina.size() << endl;

//容量沒有改變
vector<int>(regina).swap(regina);
/*在建立臨時vector物件時,它會使用剛好
足夠的記憶體來儲存regina的元素,並且不會有
額外的記憶體佔用。然後透過swap函式,regina
會將自己的記憶體與臨時vector進行交換,
從而達到釋放多餘記憶體的目的。*/

cout << "capacity:" << regina.capacity() << endl;
cout << "size:" << regina.size() << endl;

image-20240307155400151

在這段程式碼中,我們首先向regina中新增了10萬個元素,然後呼叫resize(10)regina的大小改變為10。接著透過建立臨時vector物件並與regina交換來釋放多餘的記憶體。讓我解釋一下容量是如何變小的。

  1. 初始狀態:
    • 在向regina中新增10萬個元素後,capacity()可能會大於或等於10萬,因為vector在需要時會分配比實際所需更多的記憶體,以減少頻繁的重新分配和複製。
  2. 呼叫resize(10)
    • 當呼叫resize(10)時,regina的大小被改變為10,但它的容量仍然可能保持不變。這是因為resize函式通常只改變vector的大小,而不會改變其容量,除非指定了新的容量值。
  3. 透過臨時vector物件和swap釋放多餘記憶體:
    • 接著使用了一個巧妙的技巧,建立了臨時的vector物件,並透過swap函式釋放了regina中多餘的記憶體。這樣做會使regina的容量變得剛好足夠儲存當前的元素數量,沒有額外的記憶體佔用。

vector VS valarray VS array

  1. std::vector
    • std::vector 是標準庫中最常用的動態陣列容器。
    • 它可以動態增長和縮小,即在執行時可以新增或刪除元素。
    • vector 內部使用動態記憶體分配來儲存元素,因此可以根據需要動態調整其大小。
    • 支援隨機訪問,插入和刪除操作效率較高。
  2. std::valarray
    • std::valarray 代表值陣列,是用於執行數學運算的陣列。
    • valarray 提供了一些數學運算函式,如對每個元素進行操作、求平方根、求平均值等。
    • valarray 的設計旨在提供高效能的數學運算,但在某些情況下可能不夠靈活。
    • 在實際開發中,valarray 往往被認為是一個在數值計算方面更專業的工具,而不是通用的容器。
  3. std::array
    • std::array 是固定大小的陣列容器,其大小在編譯時確定。
    • array 在記憶體中是連續儲存的,類似於原生陣列,但提供了更多的功能和安全性。
    • array 提供了陣列的基本功能,如隨機訪問、迭代器等,但大小固定,不能動態增長或縮小。
#include <iostream>
#include <vector>
#include <valarray>
#include <array>

int main() {
    // 1. std::vector 例子
    std::vector<int> vec = {1, 2, 3, 4, 5};
    vec.push_back(6); // 新增元素
    for (int i : vec) {
        std::cout << i << " ";
    }
    std::cout << "\n";

    // 2. std::valarray 例子
    std::valarray<int> valarr = {1, 2, 3, 4, 5};
    valarr += 5; // 對每個元素加5
    for (int i : valarr) {
        std::cout << i << " ";
    }
    std::cout << "\n";

    // 3. std::array 例子
    std::array<int, 5> arr = {1, 2, 3, 4, 5};
    for (int i : arr) {
        std::cout << i << " ";
    }
    std::cout << "\n";

    return 0;
}

image-20240310211149852

相關文章