C++ vector 使用詳解(含C++20新特性)

目錄

介紹兩個關鍵詞

元素訪問

迭代器

容量

修改操作

emplace() & emplace_back()

std::erase & std::erase_if (std::vector)


簡介

C++vector本質上是一個動態數組,它的元素是連續存儲的,這意味着不僅可以通過迭代器訪問元素,還可以使用指向元素的常規指針來對其進行訪問。可以將指向vector元素的指針傳遞給任何需要指向數組元素的指針的函數。

vector的存儲是自動處理的,可以根據需要進行擴展和收縮。vector通常比靜態數組佔用更多的空間,因爲分配了更多的內存來處理將來的增長。這樣,vector不必在每次插入元素時都重新分配,而僅在附加內存耗盡時才需要重新分配。可以使用capacity()函數查詢已分配的內存總量。可以通過調用rinkle_to_fit()將額外的內存返回給系統。

就性能而言,重新分配空間通常是費時的操作。如果元素的數目是預先已知的,調用reserve()函數可以消除重新分配。

 

介紹兩個關鍵詞

(1)constexpr是C++11中新增的關鍵字,其語義是"常量表達式",也就是在編譯期可求值的表達式。最基礎的常量表達式就是字面值或全局變量/函數的地址或sizeof等關鍵字返回的結果,而其它常量表達式都是由基礎表達式通過各種確定的運算得到的。constexpr值可用於enum、switch、數組長度等場合。

constexpr所修飾的變量一定是編譯期可求值的,所修飾的函數在其所有參數都是constexpr時,一定會返回constexpr。

constexpr int Inc(int i) {
    return i + 1;
}

constexpr int a = Inc(1);         // ok
constexpr int b = Inc(cin.get()); // error!
constexpr int c = a * 2 + 1;      // ok

constexpr的好處:

(1)是一種很強的約束,更好地保證程序的正確語義不被破壞。

(2)編譯器可以在編譯期對constexpr的代碼進行非常大的優化,比如將用到的constexpr表達式都直接替換成最終結果等。

(3)相比宏來說,沒有額外的開銷,但更安全可靠。

 

(2)noexcept關鍵字告訴編譯器,函數中不會發生異常,這有利於編譯器對程序做更多的優化。如果在運行時,noexecpt函數向外拋出了異常(如果函數內部捕捉了異常並完成處理,這種情況不算拋出異常),程序會直接終止,調用std::terminate()函數,該函數內部會調用std::abort()終止程序。

 

元素訪問

at通過邊界檢查訪問指定的元素

reference       at( size_type pos );
const_reference at( size_type pos ) const;

返回對指定位置的元素的引用pos,並進行邊界檢查。如果pos不在容器範圍內,則會引發類型爲std::out_of_range的異常。

 

operation[]用於訪問指定的元素

reference       operator[]( size_type pos );
const_reference operator[]( size_type pos ) const;

返回對指定位置的元素的引用pos。不執行邊界檢查。表明我們可以像使用數組一樣使用vector。

#include <vector>
#include <iostream>
 
int main()
{
    std::vector<int> numbers {2, 4, 6, 8};
    std::cout << "Second element: " << numbers[1] << '\n';
    numbers[0] = 5;
    std::cout << "All numbers:";
    for (auto i : numbers) {
        std::cout << ' ' << i;
    }
    std::cout << '\n';
}

 

front用於訪問第一個元素

reference front();
const_reference front() const;

返回對容器中第一個元素的引用未定義front在空容器上的調用。

 

back用於訪問最後一個元素

reference back();
const_reference back() const;

返回對容器中最後一個元素的引用空容器調用back會導致未定義的行爲

#include <vector>
#include <iostream>

int main()
{
    std::vector<char> letters {'o', 'm', 'g', 'w', 't', 'f'};
    if (!letters.empty()) {
        std::cout << "The first character is: " << letters.front() << '\n';
        std::cout << "The last character is: " << letters.back() << '\n';
    }  
}

 

data用於直接訪問基礎數組

constexpr T* data() noexcept;                      (since C++20)
constexpr const T* data() const noexcept;          (since C++20)

返回值:指向基礎元素存儲的指針。對於非空容器,返回的指針等於第一個元素的地址。如果size()爲0,則data()可能會或可能不會返回空指針。

 

迭代器

begin() & cbegin()

iterator begin() noexcept;
const_iterator begin() const noexcept;
const_iterator cbegin() const noexcept;

返回指向vector第一個元素迭代器。如果的vector值爲空,則返回的迭代器將等於end()

 

 

end() & cend()

iterator end() noexcept;
const_iterator end() const noexcept;
const_iterator cend() const noexcept;

返回指向vector的最後一個元素之後的迭代器。此元素充當佔位符;嘗試訪問它會導致未定義的行爲。

#include <iostream>
#include <vector>
#include <string>
 
int main()
{
	std::vector<int> ints {1, 2, 4, 8, 16};
	std::vector<std::string> fruits {"orange", "apple", "raspberry"};
	std::vector<char> empty;
 
	// Sums all integers in the vector ints (if any), printing only the result.
	int sum = 0;
	for (auto it = ints.cbegin(); it != ints.cend(); it++)
		sum += *it;
	std::cout << "Sum of ints: " << sum << "\n";
 
	// Prints the first fruit in the vector fruits, without checking if there is one.
	std::cout << "First fruit: " << *fruits.begin() << "\n";
 
	if (empty.begin() == empty.end())
		std::cout << "vector 'empty' is indeed empty.\n";
}

輸出:
Sum of ints: 31
First fruit: orange
vector 'empty' is indeed empty.

 

rbegin() & crbegin()

reverse_iterator rbegin() noexcept;
const_reverse_iterator rbegin() const noexcept;
const_reverse_iterator crbegin() const noexcept;

將反向迭代器返回給reversed的第一個元素vector。它對應於non-reversed的最後一個元素vector。如果向量爲空,則返回的迭代器等於rend()

 

 

rend() & crend()

reverse_iterator rend() noexcept;
const_reverse_iterator rend() const noexcept;
const_reverse_iterator crend() const noexcept;

返回反向迭代器,該迭代器返回reversed的最後一個元素之後的元素vector。它對應於non-reversed的第一個元素之前的元素vector。該元素充當佔位符,嘗試訪問它會導致未定義的行爲。

 

容量

empty() 檢查容器沒有元素,即是否begin () == end ()

constexpr bool empty() const noexcept;
#include <vector>
#include <iostream>
 
int main()
{
    std::cout << std::boolalpha;
    std::vector<int> numbers;
    std::cout << "Initially, numbers.empty(): " << numbers.empty() << '\n';
 
    numbers.push_back(42);
    std::cout << "After adding elements, numbers.empty(): " << numbers.empty() << '\n';
}

輸出:
Initially, numbers.empty(): true
After adding elements, numbers.empty(): false

 

size()返回容器中元素的數量,即std::distance( begin (), end ())

size_type size() const noexcept;
#include <vector>
#include <iostream>
 
int main()
{ 
    std::vector<int> nums {1, 3, 5, 7};
 
    std::cout << "nums contains " << nums.size() << " elements.\n";
}

輸出:
nums contains 4 elements.

 

max_size()返回由於系統或庫實現限制,容器可以容納的最大元素數

size_type max_size() const noexcept;
#include <iostream>
#include <vector>
 
int main()
{
    std::vector<char> s;
    std::cout << "Maximum size of a 'vector' is " << s.max_size() << "\n";
}

輸出:
Maximum size of a 'vector' is 9223372036854775807s

 

reserve()將vector的容量增加到大於或等於的值new_cap

void reserve( size_type new_cap );

如果new_cap大於當前的capacity(),則分配新的存儲,否則該方法不執行任何操作。reserve()不會更改vector元素個數,如果new_cap大於capacity(),則所有迭代器(包括過去的迭代器)以及對元素的所有引用都將無效。否則,沒有迭代器或引用無效。

#include <cstddef>
#include <new>
#include <vector>
#include <iostream>
 
// minimal C++11 allocator with debug output
template <class Tp>
struct NAlloc {
    typedef Tp value_type;
    NAlloc() = default;
    template <class T> NAlloc(const NAlloc<T>&) {}
 
    Tp* allocate(std::size_t n)
    {
        n *= sizeof(Tp);
        std::cout << "allocating " << n << " bytes\n";
        return static_cast<Tp*>(::operator new(n));
    }
 
    void deallocate(Tp* p, std::size_t n) 
    {
        std::cout << "deallocating " << n*sizeof*p << " bytes\n";
        ::operator delete(p);
    }
};
template <class T, class U>
bool operator==(const NAlloc<T>&, const NAlloc<U>&) { return true; }
template <class T, class U>
bool operator!=(const NAlloc<T>&, const NAlloc<U>&) { return false; }
 
int main()
{
    int sz = 100;
    std::cout << "using reserve: \n";
    {
        std::vector<int, NAlloc<int>> v1;
        v1.reserve(sz);
        for(int n = 0; n < sz; ++n)
            v1.push_back(n);
    }
    std::cout << "not using reserve: \n";
    {
        std::vector<int, NAlloc<int>> v1;
        for(int n = 0; n < sz; ++n)
            v1.push_back(n);
    }
}

輸出:
using reserve: 
allocating 400 bytes
deallocating 400 bytes
not using reserve: 
allocating 4 bytes
allocating 8 bytes
deallocating 4 bytes
allocating 16 bytes
deallocating 8 bytes
allocating 32 bytes
deallocating 16 bytes
allocating 64 bytes
deallocating 32 bytes
allocating 128 bytes
deallocating 64 bytes
allocating 256 bytes
deallocating 128 bytes
allocating 512 bytes
deallocating 256 bytes
deallocating 512 bytes

 

capacity()返回容器當前已分配空間的元素的個數。

size_type capacity() const noexcept;

 

shrink_to_fit()請求刪除未使用的容量capacity()減小爲size()

void shrink_to_fit();    (since C++11)

如果發生重新分配,則所有迭代器(包括過去的結束迭代器)以及對元素的所有引用都將無效。如果沒有發生重新分配,則沒有迭代器或引用無效。

#include <iostream>
#include <vector>

int main()
{
    std::vector<int> v;
    std::cout << "Default-constructed capacity is " << v.capacity() << '\n';
    v.resize(100);
    std::cout << "Capacity of a 100-element vector is " << v.capacity() << '\n';
    v.clear();
    std::cout << "Capacity after clear() is " << v.capacity() << '\n';
    v.shrink_to_fit();
    std::cout << "Capacity after shrink_to_fit() is " << v.capacity() << '\n';
}
輸出:
Default-constructed capacity is 0
Capacity of a 100-element vector is 100
Capacity after clear() is 100
Capacity after shrink_to_fit() is 0

 

修改操作

clear()清除元素

void clear() noexcept;

clear清除容器中的所有元素調用之後,size()返回零。使所有引用包含元素的引用,指針或迭代器無效。

 

insert()插入元素

(1)iterator insert( const_iterator pos, const T& value );
(2)iterator insert( const_iterator pos, T&& value );
(3)iterator insert( const_iterator pos, size_type count, const T& value );
(4)template< class InputIt >
   iterator insert( const_iterator pos, InputIt first, InputIt last );
(5)iterator insert( const_iterator pos, std::initializer_list<T> ilist );

(1-2)value在之前插入pos

(3)插入之前的count副本valuepos

(4)將[first, last)範圍內的元素插入pos之前。

(5)將ilist插入pos之前

如果新的size()大於舊的capacity()則導致重新分配。如果新的size()大於capacity(),則所有迭代器和引用均無效。否則,只有插入點之前的迭代器和引用保持有效。

#include <iostream>
#include <vector>
 
void print_vec(const std::vector<int>& vec)
{
    for (auto x: vec) {
         std::cout << ' ' << x;
    }
    std::cout << '\n';
}
 
int main ()
{
    std::vector<int> vec(3,100);
    print_vec(vec);
 
    auto it = vec.begin();
    it = vec.insert(it, 200);
    print_vec(vec);
 
    vec.insert(it,2,300);
    print_vec(vec);
 
    // "it" no longer valid, get a new one:
    it = vec.begin();
 
    std::vector<int> vec2(2,400);
    vec.insert(it+2, vec2.begin(), vec2.end());
    print_vec(vec);
 
    int arr[] = { 501,502,503 };
    vec.insert(vec.begin(), arr, arr+3);
    print_vec(vec);
}

輸出:
100 100 100
200 100 100 100
300 300 200 100 100 100
300 300 400 400 200 100 100 100
501 502 503 300 300 400 400 200 100 100 100

 

erase()從容器中刪除指定的元素

iterator erase( const_iterator pos );
iterator erase( const_iterator first, const_iterator last );

(1)刪除處的元素pos

(2)刪除範圍內的元素[first, last)。

點或點之後使迭代器和引用無效,包括end()迭代器。迭代器pos必須有效且可取消引用。因此,end()迭代器(有效,但不可取消引用)不能用作的值pos。first如果滿足以下條件,則不需要取消迭代器first==last:刪除空範圍是無操作的。

#include <vector>
#include <iostream>
 
 
int main( )
{
    std::vector<int> c{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    for (auto &i : c) {
        std::cout << i << " ";
    }
    std::cout << '\n';
 
    c.erase(c.begin());
 
    for (auto &i : c) {
        std::cout << i << " ";
    }
    std::cout << '\n';
 
    c.erase(c.begin()+2, c.begin()+5);
 
    for (auto &i : c) {
        std::cout << i << " ";
    }
    std::cout << '\n';
 
    // Erase all even numbers (C++11 and later)
    for (auto it = c.begin(); it != c.end(); ) {
        if (*it % 2 == 0) {
            it = c.erase(it);
        } else {
            ++it;
        }
    }
 
    for (auto &i : c) {
        std::cout << i << " ";
    }
    std::cout << '\n';
}

輸出:
0 1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9
1 2 6 7 8 9
1 7 9

 

push_back()將給定元素添加到容器末尾

void push_back( const T& value );   (1)
void push_back( T&& value );        (2)

(1)使用value的副本初始化新元素。

(2) value被移到新元素中。

如果新的size()大於capacity()則所有迭代器和引用(包括過去的迭代器)都將失效。否則,只有過去的迭代器是無效的。由於隱式調用了reserve(size()+ 1)的等效項,某些實現還會導致重新分配超過max_size時引發std::length_error

#include <vector>
#include <iostream>
#include <iomanip>
 
int main()
{
    std::vector<std::string> numbers;
 
    numbers.push_back("abc");
    std::string s = "def";
    numbers.push_back(std::move(s));
 
    std::cout << "vector holds: ";
    for (auto&& i : numbers) std::cout << std::quoted(i) << ' ';
    std::cout << "\nMoved-from string holds " << std::quoted(s) << '\n';
}

輸出:
vector holds: "abc" "def" 
Moved-from string holds ""

 

pop_back()刪除容器的最後一個元素

void pop_back();

刪除容器的最後一個元素,pop_back未定義在空容器上的調用。迭代器對最後一個元素的引用以及end()迭代器均無效。

#include <vector>
#include <iostream>
 
template<typename T>
void print(T const & xs)
{
    std::cout << "[ ";
    for(auto const & x : xs) {
        std::cout << x << ' ';
    }
    std::cout << "]\n";
}
 
int main()
{
    std::vector<int> numbers;
 
    print(numbers); 
 
    numbers.push_back(5);
    numbers.push_back(3);
    numbers.push_back(4);
 
    print(numbers); 
 
    numbers.pop_back();
 
    print(numbers); 
}

輸出:
[ ]
[ 5 3 4 ]
[ 5 3 ]

 

resize()調整容器

void resize( size_type count );                                (1)
void resize( size_type count, const value_type& value );       (2)

調整容器的大小以包含count元素。如果當前大小大於count,則容器將縮小爲其第一個count元素。如果當前大小小於count,(2)需要額外拷貝值value。在將大小調整爲更小時,vector容量不會減少,因爲這將使所有迭代器失效,而是等效調用pop_back()導致迭代器失效的情況  

#include <iostream>
#include <vector>
int main()
{
    std::vector<int> c = {1, 2, 3};
    std::cout << "The vector holds: ";
    for(auto& el: c) std::cout << el << ' ';
    std::cout << '\n';
    c.resize(5);
    std::cout << "After resize up to 5: ";
    for(auto& el: c) std::cout << el << ' ';
    std::cout << '\n';
    c.resize(2);
    std::cout << "After resize down to 2: ";
    for(auto& el: c) std::cout << el << ' ';
    std::cout << '\n';
}

輸出:
The vector holds: 1 2 3
After resize up to 5: 1 2 3 0 0
After resize down to 2: 1 2

 

swap()交換容器

void swap( vector& other ) noexcept;       (since C++17)

other容器完成交換。不對單個元素調用任何移動,複製或交換操作。

#include <vector>
#include <iostream>
 
void printVector(std::vector<int>& vec)
{
    for (int a : vec)
    {
        std::cout << a << " ";
    }
}
 
int main()
{
    std::vector<int> v1{1, 2, 3};
    std::vector<int> v2{7, 8, 9};
 
    std::cout << "v1: ";
    printVector(v1);
 
    std::cout << "\nv2: ";
    printVector(v2);
 
    std::cout << "\n-- SWAP\n";
    v2.swap(v1);
 
    std::cout << "v1: ";
    printVector(v1);
 
    std::cout << "\nv2: ";
    printVector(v2);
}

輸出:
v1: 1 2 3
v2: 7 8 9
-- SWAP
v1: 7 8 9
v2: 1 2 3

 

 

emplace() & emplace_back()

C++11新標準引入了三個新成員:emplace_front、emplace和emplace_back,這些操作構造而不是拷貝元素。這些操作分別對應push_front、insert和push_back,允許將元素放置在容器頭部、一個指定位置之前或容器尾部。

當調用push或insert成員函數時,我們將元素類型的對象傳遞給它們,這些對象被拷貝到容器中。而當我們調用一個emplace成員函數時,則是將參數傳遞給元素類型的構造函數。emplace成員使用這些參數在容器管理的內存空間中直接構造元素。

emplace函數的參數根據元素類型而變化,參數必須與元素類型的構造函數相匹配。emplace函數在容器中直接構造元素。傳遞給emplace函數的參數必須與元素類型的構造函數相匹配。

emplace()

template< class... Args > 
iterator emplace( const_iterator pos, Args&&... args );    (since C++11)

將新元素直接插入容器pos之前。元素是通過std::allocator_traits::construct構造的,通常使用placement-new在容器提供的位置就地構造元素。參數args...作爲std::forward < Args > ( args ) ...轉發給構造函數。如果新的size()大於capacity(),則所有迭代器和引用均無效。否則,只有插入點之前的迭代器和引用保持有效。

 

emplace_back()

template< class... Args >
reference emplace_back( Args&&... args );            (since C++17)

將新元素附加到容器的末尾。該元素是通過std::allocator_traits::construct構造的,通常使用placement-new在容器提供的位置就地構造該元素。參數args...作爲std::forward < Args > ( args ) ...轉發給構造函數。如果新的size()大於capacity()則所有迭代器和引用(包括過去的迭代器)都將失效。否則,只有過去的迭代器是無效的。 

 

處理基礎數據類型:

// reference: http://www.cplusplus.com/reference/vector/vector/emplace_back/
int test()
{
	{   /*
	       template <class... Args>
	       void emplace_back (Args&&... args);
	     */
		std::vector<int> myvector = { 10, 20, 30 };

		myvector.emplace_back(100);
		myvector.emplace_back(200);

		std::cout << "myvector contains:";
		for (auto& x : myvector)
			std::cout << ' ' << x;
		std::cout << '\n';
	}

	{   /*
	       template <class... Args>
	       iterator emplace (const_iterator position, Args&&... args);
	     */
		std::vector<int> myvector = { 10, 20, 30 };

		auto it = myvector.emplace(myvector.begin() + 1, 100);
		myvector.emplace(it, 200);
		myvector.emplace(myvector.end(), 300);

		std::cout << "myvector contains:";
		for (auto& x : myvector)
			std::cout << ' ' << x;
		std::cout << '\n';
	}
	return 0;
}

 

處理對象:

#include <vector>
#include <string>
#include <iostream>
 
struct President
{
    std::string name;
    std::string country;
    int year;
 
    President(std::string p_name, std::string p_country, int p_year)
        : name(std::move(p_name)), country(std::move(p_country)), year(p_year)
    {
        std::cout << "I am being constructed.\n";
    }
    President(President&& other)
        : name(std::move(other.name)), country(std::move(other.country)), year(other.year)
    {
        std::cout << "I am being moved.\n";
    }
    President& operator=(const President& other) = default;
};
 
int main()
{
    std::vector<President> elections;
    std::cout << "emplace_back:\n";
    elections.emplace_back("Nelson Mandela", "South Africa", 1994);
 
    std::vector<President> reElections;
    std::cout << "\npush_back:\n";
    reElections.push_back(President("Franklin Delano Roosevelt", "the USA", 1936));
 
    std::cout << "\nContents:\n";
    for (President const& president: elections) {
        std::cout << president.name << " was elected president of "
                  << president.country << " in " << president.year << ".\n";
    }
    for (President const& president: reElections) {
        std::cout << president.name << " was re-elected president of "
                  << president.country << " in " << president.year << ".\n";
    }
}

輸出:
emplace_back:
I am being constructed.
 
push_back:
I am being constructed.
I am being moved.
 
Contents:
Nelson Mandela was elected president of South Africa in 1994.
Franklin Delano Roosevelt was re-elected president of the USA in 1936.

 

emplace_back() 與 push_back()對比:

// reference: https://corecplusplustutorial.com/difference-between-emplace_back-and-push_back-function/
class Dat {
	int i;
	std::string ss;
	char c;
	public:
	Dat(int ii, std::string s, char cc) :i(ii), ss(s), c(cc) { }
	~Dat() { }
};

int test_emplace_4()
{
	std::vector<Dat> vec;
	vec.reserve(3);
	vec.push_back(Dat(89, "New", 'G')); // efficiency lesser
	//vec.push_back(678, "Newer", 'O'); // error,push_back can’t accept three arguments
	vec.emplace_back(890, "Newest", 'D'); // work fine, efficiency is also more

	return 0;
}

 

 

std::erase & std::erase_if (std::vector)

template< class T, class Alloc, class U >
constexpr typename std::vector<T,Alloc>::size_type
    erase(std::vector<T,Alloc>& c, const U& value);      (1) (since C++20)

template< class T, class Alloc, class Pred >
constexpr typename std::vector<T,Alloc>::size_type
    erase_if(std::vector<T,Alloc>& c, Pred pred);        (2) (since C++20)

(1)從容器中刪除所有等於value的元素。相當於:

auto it = std::remove(c.begin(), c.end(), value);
auto r = std::distance(it, c.end());
c.erase(it, c.end());
return r;

 

(2)pred從容器中擦除所有滿足謂詞的元素。相當於:

auto it = std::remove_if(c.begin(), c.end(), pred);
auto r = std::distance(it, c.end());
c.erase(it, c.end());
return r;
#include <iostream>
#include <numeric>
#include <vector>
 
void print_container(const std::vector<char>& c)
{
    for (auto x : c) {
        std::cout << x << ' ';
    }
    std::cout << '\n';
}
 
int main()
{
    std::vector<char> cnt(10);
    std::iota(cnt.begin(), cnt.end(), '0');
 
    std::cout << "Init:\n";
    print_container(cnt);
 
    std::erase(cnt, '3');
    std::cout << "Erase \'3\':\n";
    print_container(cnt);
 
    auto erased = std::erase_if(cnt, [](char x) { return (x - '0') % 2 == 0; });
    std::cout << "Erase all even numbers:\n";
    print_container(cnt);
    std::cout << "In all " << erased << " even numbers were erased.\n";
}

輸出:
Init:
0 1 2 3 4 5 6 7 8 9 
Erase '3':
0 1 2 4 5 6 7 8 9 
Erase all even numbers:
1 3 7 9
In all 5 even numbers were erased.

 

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