C++ ——vector數組筆記

   vector 是 C++ 標準庫中的一個動態數組容器(Sequence Container),它可以自動管理內存大小,可以在運行時根據需要動態增長或縮小。它是一個非常常用且強大的容器,用於存儲一系列元素。可以簡單的認爲,vector是一個能夠放任意類型的動態數組。

  下面詳細介紹 vector 的使用方法,並提供相應的代碼案例。

1,基本函數實現

1.1  構造函數

  構造函數是初始化一個數組。vector本質是類模板,可以存儲任何類型的數據。vector在聲明前需要加上數據類型,而vector則通過模板參量設定類型。

vector():創建一個空vector

vector(int nSize):創建一個vector,元素個數爲nSize

vector(int nSize,const t& t):創建一個vector,元素個數爲nSize,且值均爲t

vector(const vector&):複製構造函數

vector(begin,end):複製[begin,end)區間內另一個數組的元素到vector中

  示例:

vector<int> vec;  // 創建一個空數組vec

vector<int> vec(1, 2, 3, 4, 5, 6);//vec中的內容爲1, 2, 3, 4, 5, 6

vector<int> vec(4);  // 開闢四個空間,值默認爲0

vector<int> vec(5, 4);  //創建5個值爲4的數組

vector<int> vec(a);//聲明並用a向量初始化vec向量

vector<int> vec(a.begin(), a.end()); // 將a的值從頭開始到尾複製

vector<int> vec(a.rbegin(), a.rend()); // 將a的值從尾到頭複製

int a[5]={1,2,3,4,5};
vector<int> vec(a,a+5);//將a數組的元素用來初始化vector向量

vector<int> vec(&a[1],&a[4]);//將a[1]-a[4]範圍內的元素作爲vec的初始值

 

1.2  增加函數

  即向vector中插入元素。其中emplace_back的效果和push_back一樣,都是尾部插入元素。二者的差別在於底部實現的機制不同;push_back是將這個元素拷貝或移動到容器中(如果是拷貝的話,事後會自行銷燬先前創建的這個元素);而emplace_back在實現時,則是直接在容器尾部創建這個元素,省去了拷貝或移動元素的過程,所以emplace_back的速度更快。

void push_back(const T& x):向量尾部增加一個元素X

iterator insert(iterator it,const T& x):向量中迭代器指向元素前增加一個元素x

iterator insert(iterator it,int n,const T& x):向量中迭代器指向元素前增加n個相同的元素x

iterator insert(iterator it,const_iterator first,const_iterator last):向量中迭代器指向元素前插入另一個相同類型向量的[first,last)間的數據

  示例:

//在vector的末尾插入新元素
vec.push_back(1);

//在迭代器的前面插入新元素
vector<int>::iterator it;
it=vec.begin();
vec.insert(it,5);//在第一個元素前面插入5

//在vector中加入3個1元素,同時清除掉以前的元素
vec.assign(3,1);//現在vector中只有3個1

  assgin修改vector,和insert操作類似,不過insert是從尾部插入,而assign則將整個vector改變。

 

1.3  刪除函數

  erase是通過迭代器刪除某個或某個範圍的元素,並返回下一個元素的迭代器。

iterator erase(iterator it):刪除向量中迭代器指向元素

iterator erase(iterator first,iterator last):刪除向量中[first,last)中元素

void pop_back():刪除向量中最後一個元素

void clear():清空向量中所有元素

  示例:

//刪除最後一個元素
vec.pop_back();

//刪除指定位置的元素
vec.erase(vec.begin());//刪除第一個位置的元素值

//清除所有元素
vec.clear();

//判斷該數組是否爲空
vec.empty();

  

1.4  遍歷函數

reference at(int pos):返回pos位置元素的引用

reference front():返回首元素的引用

reference back():返回尾元素的引用

iterator begin():返回向量頭指針,指向第一個元素

iterator end():返回向量尾指針,指向向量最後一個元素的下一個位置

reverse_iterator rbegin():反向迭代器,指向最後一個元素

reverse_iterator rend():反向迭代器,指向第一個元素之前的位置

  遍歷數組示例:

//向數組一樣利用下標進行訪問
vector<int> a;
for(int i=0;i<a.size();i++){
     cout<<a[i];
}

//利用迭代器進行訪問
vector<int>::iterator it;
for(it=a.begin();it!=a.end();it++){
   cout<<*it;
}

  

1.5  vector容量和大小

  顧名思義,size表示當前有多少個元素,capacity是可容納的大小,因爲vector是順序存儲的,那麼和數組一樣,有一個初始容量,在vector裏就是capacity。capacity必然大於等於size,每次擴容時會改變,具體大小和vector底層實現機制有關。

  max_size 是可存儲的最大容量,和實際的編譯器,系統有關,使用的比較少。

  empty就是判斷vector是否爲空,其實就是判斷size是否等於0。定義vector時設定了大小,resize修改大小等操作,vector都不爲空,clear後,size=0,那麼empty判斷就爲空。

bool empty() const:判斷向量是否爲空,若爲空,則向量中無元素

int size() const:返回向量中元素的個數

int capacity() const:返回當前向量所能容納的最大元素值

int max_size() const:返回最大可允許的vector元素數量值

  

2,容器特性和屬性

2.1  容器的特性

2.1.1,順序序列(Sequential Container)

  順序容器中的元素按照嚴格的線性順序排序。可以通過元素在序列中的位置訪問對應的元素。

  • std::vector 是C++標準庫中的一種順序序列容器,這意味着它按照元素的順序進行存儲和訪問。
  • 每個元素都有一個唯一的位置(索引),可以通過索引直接訪問,同時支持迭代器用於循環訪問。

2.1.2,動態數組(Dynamic Array)

  支持對序列中的任意元素進行快速直接訪問,甚至可以通過指針算述進行該操作。提供了在序列末尾相對快速地添加/刪除元素的操作。

  • std::vector 是一個動態數組容器,它在內部使用動態分配的數組來存儲元素。
  • 它能夠動態調整數組的大小,可以在運行時根據需要增加或減少元素的數量,而無需預先指定數組的大小。

2.1.3,能夠感知內存分配器的(Allocator-aware)

  容器使用一個內存分配器對象來動態地處理它的存儲需求。

  • std::vector 是 allocator-aware 的,這意味着它能夠感知並與特定的內存分配器協同工作。
  • 使用者可以通過傳遞自定義的內存分配器(通常是一個模板參數),以實現對內存分配和釋放過程的控制。

   以下是一個簡單的示例,演示了 std::vector 的這三個特性:

#include <iostream>
#include <vector>

int main() {
    // 創建一個空的vector
    std::vector<int> myVector;

    // 添加元素
    myVector.push_back(10);
    myVector.push_back(20);
    myVector.push_back(30);

    // 遍歷並輸出元素
    for (const auto& num : myVector) {
        std::cout << num << " ";
    }

    return 0;
}

  在這個例子中,std::vector 被用作順序序列,存儲了動態數組,並且它的內存管理是由C++標準庫的分配器機制處理的。

 

2.2  容器的屬性

  下面介紹一下 std::vector 的基本屬性,以及一些示例。

2.2.1 動態調整大小

   std::vector是一個動態大小的容器,可以在運行時根據需要自動調整大小。這意味着你可以隨時向vector中添加或刪除元素,而無需擔心數組大小的固定性。它會自動處理內存管理。

  示例:

#include <iostream>
#include <vector>

int main() {
    std::vector<int> myVector;  // 創建一個空的vector

    myVector.push_back(1);     // 添加元素
    myVector.push_back(2);
    myVector.push_back(3);

    std::cout << "Vector的大小: " << myVector.size() << std::endl;  // 輸出vector的大小

    return 0;
}

  

2.2.2 快速隨機訪問

  std::vector支持隨機訪問,即通過索引直接訪問元素。這使得在訪問和修改元素時效率非常高。 由於元素的連續存儲(底層實現是數組),std::vector 支持快速的隨機訪問。你可以通過索引直接訪問向量中的元素。

  示例:

#include <iostream>
#include <vector>

int main() {
    std::vector<std::string> names = {"Alice", "Bob", "Charlie"};

    // 隨機訪問元素
    std::cout << "第二個元素是: " << names[1] << std::endl;

    return 0;
}

  

2.2.3 動態內存管理

  std::vector負責管理其內部的動態內存,因此你無需手動分配或釋放內存。當元素被添加或刪除時,vector會自動處理內存的分配和釋放。當向量的大小超過其當前容量時,會動態分配更多的內存以容納更多的元素。這可以確保在添加元素時不會頻繁重新分配內存。

#include <iostream>
#include <vector>

int main() {
    std::vector<double> prices;

    // 添加元素
    prices.push_back(10.5);
    prices.push_back(20.0);
    prices.push_back(15.75);

    // 遍歷元素並輸出
    for (const auto& price : prices) {
        std::cout << "價格: " << price << std::endl;
    }

    return 0;
}

  

  總結: std::vector是C++中一個強大且靈活的容器,它提供了動態大小、隨機訪問、動態內存管理和豐富的功能。通過合理利用std::vector,你可以更輕鬆地處理動態數據集合,而無需手動管理內存或擔心固定數組大小的限制。除此之外,還有下面屬性:

  • 1,連續內存存儲:std::vector中的元素在內存中是連續存儲的,這意味着可以通過指針算術直接訪問元素,並且對於許多算法來說,這種存儲方式是高效的,有助於提高訪問效率。
  • 2,尾部插入和刪除: 向量支持在尾部進行快速的元素插入和刪除操作。
  • 3,快速尾部操作: 由於元素的尾部插入和刪除是快速的,std::vector 適用於需要在序列的末尾執行大量操作的場景。
  • 4,STL 算法和迭代器支持: std::vector 與 C++ 標準模板庫(STL)的其他部分無縫集成,可以使用算法和迭代器對向量進行操作。你可以使用迭代器來遍歷 std::vector 中的元素。

 

2.3  如何獲取vector的內存

  在C++中,std::vector 是一個封裝了動態數組的容器,它自動處理內存的分配和釋放。要獲取 std::vector 的底層內存地址,你可以使用 data() 成員函數,它返回指向容器第一個元素的指針。

  以下是一個示例:

#include <iostream>
#include <vector>

int main() {
    std::vector<int> myVector = {1, 2, 3, 4, 5};

    // 獲取底層內存地址
    int* ptr = myVector.data();

    // 輸出每個元素及其內存地址
    for (size_t i = 0; i < myVector.size(); ++i) {
        std::cout << "元素 " << myVector[i] << " 的內存地址: " << &myVector[i] << std::endl;
    }

    // 輸出整個vector的底層內存地址
    std::cout << "Vector的底層內存地址: " << ptr << std::endl;

    return 0;
}

  請注意,通過 data() 獲取的指針指向的是 vector 中第一個元素的地址。如果 vector 是空的,data() 返回的是一個合法但未定義的指針,因此在使用之前應確保 vector 不爲空。

  注意:一般情況下,直接使用 data() 獲取 vector 的底層內存並進行操作可能不是一個良好的實踐,因爲 std::vector 提供了許多高級的函數和方法,可以更安全和方便地訪問元素。直接訪問內存可能會引發未定義的行爲,特別是在修改元素時。

 

2.3.1  疑問:獲取內存使用 myVector.data() 和 myVector[0]的區別是什麼?

  myVector.data()myVector[0] 返回的是不同類型的指針,並且有不同的用途。

myVector.data()

  • myVector.data() 返回指向 vector 第一個元素的指針,是一個原始指針(T* 類型,其中 T 是 vector 中元素的類型)。
  • 這個指針允許你直接訪問 vector 的底層內存,但是需要小心使用,因爲它沒有邊界檢查和安全保障。
  • 在對 data() 返回的指針進行操作時,務必確保 vector 不爲空。
int* ptr = myVector.data();
// 對 ptr 進行直接內存操作

  

myVector[0]

  • myVector[0] 是 vector 的運算符重載,它返回 vector 中第一個元素的引用。
  • 這個引用允許你直接訪問 vector 的第一個元素,並且通過引用操作進行修改。它提供了邊界檢查,確保你訪問的是有效的元素。
int& ref = myVector[0];
// 對 ref 進行直接操作,修改會影響 vector 中的元素

  

選擇使用的場景:

  • 如果你需要直接訪問 vector 中第一個元素的底層內存,並且要進行原始指針的操作,可以使用 myVector.data()
  • 如果你只是想獲取第一個元素的值,並且可能會進行修改,使用 myVector[0] 更直觀且更安全,因爲它提供了引用而不是裸指針,並且有邊界檢查。

 

 3,C++ vector 底層實現機制

  這裏參考的是:https://c.biancheng.net/view/6901.html

  STL 衆多容器中,vector是最常用的容器之一,其底層所採用的數據結構非常簡單,就只是一段連續的線性內存空間。

3.1  vector源碼分析

  通過分析vector容器的源碼就可以看到,他就是使用三個迭代器來表示:

//_Alloc 表示內存分配器,此參數幾乎不需要我們關心
template <class _Ty, class _Alloc = allocator<_Ty>>
class vector{
    ...
protected:
    pointer _Myfirst;
    pointer _Mylast;
    pointer _Myend;
};

  其中,_Myfirst 指向的是 vector 容器對象的起始字節位置;_Mylast 指向當前最後一個元素的末尾字節;_myend 指向整個 vector 容器所佔用內存空間的末尾字節。
  下圖演示了以上這 3 個迭代器分別指向的位置。

   如上圖所示,通過這 3 個迭代器,就可以表示出一個已容納 2 個元素,容量爲 5 的 vector 容器。

  在此基礎上,將 3 個迭代器兩兩結合,還可以表達不同的含義,例如:

    • _Myfirst 和 _Mylast 可以用來表示 vector 容器中目前已被使用的內存空間;
    • _Mylast 和 _Myend 可以用來表示 vector 容器目前空閒的內存空間;
    • _Myfirst 和 _Myend 可以用表示 vector 容器的容量。
    • 對於空的 vector 容器,由於沒有任何元素的空間分配,因此 _Myfirst、_Mylast 和 _Myend 均爲 null。

  通過靈活運用這 3 個迭代器,vector 容器可以輕鬆的實現諸如首尾標識、大小、容器、空容器判斷等幾乎所有的功能,比如:

template <class _Ty, class _Alloc = allocator<_Ty>>
class vector{
public:
    iterator begin() {return _Myfirst;}
    iterator end() {return _Mylast;}
    size_type size() const {return size_type(end() - begin());}
    size_type capacity() const {return size_type(_Myend - begin());}
    bool empty() const {return begin() == end();}
    reference operator[] (size_type n) {return *(begin() + n);}
    reference front() { return *begin();}
    reference back() {return *(end()-1);}
    ...
};

  

3.2  vector 擴大容量的本質

  上面有說到果,vector作爲容器有着動態數組的功能,當加入的數據大於vector容量(capacity)時會自動擴容,系統會自動申請一片更大的空間,把原來的數據拷貝過去,釋放原來的內存空間。

  另外需要指明的是,當 vector 的大小和容量相等(size==capacity)也就是滿載時,如果再向其添加元素,那麼 vector 就需要擴容。vector 容器擴容的過程需要經歷以下 3 步:

  1. 完全棄用現有的內存空間,重新申請更大的內存空間;
  2. 將舊內存空間中的數據,按原有順序移動到新的內存空間中;
  3. 最後將舊的內存空間釋放。

  這也就解釋了,爲什麼 vector 容器在進行擴容後,與其相關的指針、引用以及迭代器可能會失效的原因

  由此可見,vector 擴容是非常耗時的。爲了降低再次分配內存空間時的成本,每次擴容時 vector 都會申請比用戶需求量更多的內存空間(這也就是 vector 容量的由來,即 capacity>=size),以便後期使用。vector 容器擴容時,不同的編譯器申請更多內存空間的量是不同的。以 VS 爲例,它會擴容現有容器容量的 50%。

 

4,使用示例

  std::vector 是一個非常有用的標準庫容器,它提供了動態數組的功能,可以根據需要自動調整大小。以下是一些關於 std::vector 的基本示例:

4.1:引入頭文件

  首先,使用的話需要引入頭文件 <vector>

#include <vector>

  

4.2:創建 vector 對象

  直接使用vector 模板類來創建一個 vector 對象。可以創建存儲特定類型元素的 vector,格式爲: vector<數據類型> 名字。例如:

vector<int> myVector; // 創建一個存儲整數的 vector,名字爲myVector

vector<char> myVector; // 創建一個存儲字符的 vector,名字爲myVector

vector<string> myVector; // 創建一個存儲字符串的 vector,名字爲myVector
……

  

4.3:創建 vector並添加元素

  使用push_back()函數將元素添加到vector的末尾,默認且只能添加到末尾。

  代碼如下:

#include <iostream>
#include <vector>

int main() {
    // 創建一個空的 vector
    std::vector<int> numbers;

    // 向 vector 中添加元素
    numbers.push_back(1);
    numbers.push_back(2);
    numbers.push_back(3);

    // 訪問 vector 中的元素
    std::cout << "Vector elements: ";
    for (int i = 0; i < numbers.size(); ++i) {
        std::cout << numbers[i] << " ";
    }
    std::cout << std::endl;

    return 0;
}

  打印結果如下:

Vector elements: 1 2 3

  

4.4:使用範圍循環遍歷 vector

  代碼如下:

#include <iostream>
#include <vector>

int main() {
    // 創建一個 vector,並初始化
     std::vector<std::string> fruits = {"Apple", "Banana", "Orange"};

    // 也可以向 vector 中添加元素
    fruits.push_back("pear");

    // 使用範圍循環遍歷 vector
    std::cout << "Fruits: ";
    for (const auto& fruit : fruits) {
        std::cout << fruit << " ";
    }
    std::cout << std::endl;

    return 0;
}

  結果如下:

Fruits: Apple Banana Orange pear 

  

4.5:插入和刪除元素

#include <iostream>
#include <vector>

int main() {
    // 創建一個 vector,並初始化
    std::vector<double> prices = {10.5, 20.3, 15.0};

    // 直接在尾部插入元素
    prices.push_back(88);

    // 在指定位置插入元素
    prices.insert(prices.begin() + 1, 25.8);

    // 刪除指定位置的元素
    prices.erase(prices.begin() + 2);

    // 打印 vector 元素
    std::cout << "Prices: ";
    for (const auto& price : prices) {
        std::cout << price << " ";
    }
    std::cout << std::endl;

    return 0;
}

  結果:

Prices: 10.5 25.8 15 88 

  

4.6:使用迭代器訪問數組

  除了直接使用for循環遍歷訪問數組之外,我們也可以利用迭代器將容器中數組輸出。

  首先聲明一個迭代器,vector<int>::iterator it;  來訪問vector容器,目的是遍歷或者指向vector容器的元素。

#include <iostream>
#include <vector>

int main() {
    // 創建一個 vector,並初始化
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    // 使用迭代器訪問 vector 中的元素
    std::cout << "Vector elements: ";
    for (auto it = numbers.begin(); it != numbers.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;

    return 0;
}

  結果如下:

Vector elements: 1 2 3 4 5 

  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章