std::vector
是 C++ 标准库中的一个容器,提供了动态数组的功能。它的底层实现通常是使用连续的内存块来存储元素,因此可以通过指针算术来访问元素,并且支持常数时间的随机访问,并支持在容器末尾高效地添加和删除元素。
一、底层实现
std::vector
的底层通常由一个连续的内存块(数组)来存储其元素,内部的元素在内存中是依次排列的,可以通过指针算术或迭代器进行快速的随机访问。
当 std::vector
中的元素数量超过当前容量时,std::vector
会重新分配更大的内存块,并将元素从旧的内存块复制到新的内存块中。这通常涉及到动态内存分配和释放,但由于 std::vector
使用连续的内存存储元素,因此仅需要一次连续的内存分配,这有助于提高效率。
当创建一个空的 std::vector
对象时,它不分配任何内存,大小是0。当开始添加元素时,std::vector
会动态分配内存。通常情况下,第一次分配的内存大小由具体的实现决定,但通常是一个小的初始值,比如16或32个元素大小的内存块。
当元素数量超过当前内存容量时,std::vector
将重新分配内存并将元素从旧的内存块复制到新的内存块中。新的内存块的大小通常是当前元素数量的两倍或更多,具体取决于具体的实现和策略。重新分配内存时,可能会有一些额外的内存开销,因为需要考虑到内存对齐和内存管理的一些细节。
二、成员函数
std::vector 提供了一系列成员函数来操作和管理动态数组。
1、迭代器:
begin()
, end()
cbegin()
, cend()
rbegin()
, rend()
crbegin()
, crend()
std::vector<int> vec{1,2,3,4,5};std::cout<<"begin() -> end()"<<std::endl;for(auto it = vec.begin(); it != vec.end(); it++) {std::cout<<*it<<std::endl; // 1 2 3 4 5}std::cout<<"cbegin() -> cend()"<<std::endl;for(auto it = vec.cbegin(); it != vec.cend(); it++) {std::cout<<*it<<std::endl; // 1 2 3 4 5}std::cout<<"rbegin() -> rend()"<<std::endl;for(auto it = vec.rbegin(); it != vec.rend(); it++) {std::cout<<*it<<std::endl; // 5 4 3 2 1 }std::cout<<"crbegin() -> crend()"<<std::endl;for(auto it = vec.crbegin(); it != vec.crend(); it++) {std::cout<<*it<<std::endl; // 5 4 3 2 1 }
2、容量:
empty()
size()
max_size()
reserve(size_type new_cap)
capacity()
shrink_to_fit()
std::vector<int> vec{1,2,3,4,5};std::cout<<vec.empty()<<std::endl; // 0std::cout<<vec.size()<<std::endl; // 5std::cout<<vec.max_size()<<std::endl; // 2305843009213693951std::cout<<vec.capacity()<<std::endl; // 5vec.reserve(50);std::cout<<vec.capacity()<<std::endl; // 50vec.shrink_to_fit();std::cout<<vec.capacity()<<std::endl; // 5
resize(size_type count)
resize(size_type count, const value_type& value)
std::vector<int> vec{1,2,3};for(auto num:vec){std::cout<<num<<std::endl; // 1 2 3 }vec.resize(5);for(auto num:vec){std::cout<<num<<std::endl; // 1 2 3 0 0}
3、元素访问:
operator[]
at()
front()
back()
data()
std::vector<int> vec{0,1,2,3,4,5};std::cout<<vec[3]<<std::endl; // 3std::cout<<vec.at(3)<<std::endl; // 3std::cout<<vec.front()<<std::endl; // 0std::cout<<vec.back()<<std::endl; // 5int* ptr = vec.data();for (size_t i = 0; i < vec.size(); ++i) {std::cout << *(ptr + i) << " "; // 0 1 2 3 4 5}
4、赋值:
4.1、assign()
std::vector<int> vec{1,2,3,4};std::vector<int> vec_bak{5,6,7,8,9};vec.assign(vec_bak.begin(), vec_bak.end()); // 5 6 7 8 9for(auto num:vec){std::cout<<num<<std::endl;}vec.assign(4,50);for(auto num:vec){std::cout<<num<<std::endl; // 50 50 50 50}
4.2、swap(vector& other)
std::vector<int> vec{1,2,3,4};std::vector<int> vec_bak{5,6,7,8,9};vec.swap(vec_bak);for(auto num:vec){std::cout<<num<<std::endl; // 5 6 7 8 9}for(auto num:vec_bak){std::cout<<num<<std::endl; // 1 2 3 4 }
5、尾部增删:
push_back(const T& value)
push_back(T&& value)
pop_back()
emplace_back(Args&&... args)
std::vector<std::string> vec{"A","B","C"};for(auto num:vec){std::cout<<num<<std::endl; // A B C}vec.push_back("D");for(auto num:vec){std::cout<<num<<std::endl; // A B C D}vec.pop_back();for(auto num:vec){std::cout<<num<<std::endl; // A B C}vec.emplace_back(std::move("E"));for(auto num:vec){std::cout<<num<<std::endl; // A B C E}
PS:如果是普通元素则一样,如果元素是对象则有区别。
push_back
:添加的对象是一个左值,进行深拷贝。添加的对象是右值且有移动构造函数,执行移动操作。
emplace_back
:它不会执行深拷贝或移动操作,根据参数直接调用构造新的对象。
Demo : push_back 和 emplace_back 演示
#include <iostream>
#include <vector>class MyObject {
public:MyObject(int value) : value_(value) {std::cout << "Constructing MyObject with value: " << value_ << std::endl;}MyObject(const MyObject& other) {value_ = other.value_;std::cout << "Copying MyObject with value: " << value_ << std::endl;}MyObject(MyObject&& other) noexcept {value_ = other.value_;other.value_ = 0; // Reset the value of the moved objectstd::cout << "Moving MyObject with value: " << value_ << std::endl;}int getValue() const {return value_;}private:int value_;
};int main() {std::vector<MyObject> vec;// 使用 push_back 添加已构造好的对象MyObject obj1(10);vec.push_back(obj1); // 调用拷贝构造函数vec.push_back(MyObject(20)); // 直接通过临时对象构造新元素,调用移动构造函数std::cout << "Vector size after push_back: " << vec.size() << std::endl;// 使用 emplace_back 直接在容器内构造新对象vec.emplace_back(30); // 直接在容器内构造新元素,调用构造函数vec.emplace_back(40); // 直接在容器内构造新元素,调用构造函数std::cout << "Vector size after emplace_back: " << vec.size() << std::endl;return 0;
}
6、随机增删
6.1、insert
用于在指定位置插入一个或多个元素。其函数签名为:
iterator insert (const_iterator position, const T& value);
iterator insert (const_iterator position, T&& value);
iterator insert (const_iterator position, size_type n, const T& value);
template <class InputIterator>
iterator insert (const_iterator position, InputIterator first, InputIterator last);
iterator insert (const_iterator position, initializer_list<T> il);
第一个版本在指定位置插入一个值为 value
的元素。
第二个版本在指定位置插入一个右值引用 value
的元素。
第三个版本在指定位置插入 n
个值为 value
的元素。
第四个版本在指定位置插入从迭代器范围 [first, last)
的元素。
第五个版本在指定位置插入初始化列表 il
中的元素。
insert
函数返回一个迭代器,指向插入的第一个元素。
Demo:使用 insert
函数向 std::vector
插入元素:
std::vector<int> vec{1,2,3,4,5};for(auto num : vec) {std::cout<<num<<std::endl; // 1 2 3 4 5}auto it = vec.insert(vec.begin()+2,10);std::cout<<*it<<std::endl; // 10for(auto num : vec) {std::cout<<num<<std::endl; // 1 2 10 3 4 5}vec.insert(vec.begin(),3,30);for(auto num : vec) {std::cout<<num<<std::endl; // 30 30 30 1 2 10 3 4 5}std::vector<int> another = {100,200,300};vec.insert(vec.end(),another.begin(),another.end());for(auto num : vec) {std::cout<<num<<std::endl; // 30 30 30 1 2 10 3 4 5 100 200 300 }vec.insert(vec.begin(),{-2,-1});for(auto num : vec) {std::cout<<num<<std::endl; // -2 -1 30 30 30 1 2 10 3 4 5 100 200 300 }
6.2、 emplace
用于在向量中指定位置插入一个元素。与 insert
函数不同,emplace
函数可以直接在向量中就地构造新元素,而不需要提前创建一个对象。emplace
函数的参数用于构造新元素的构造函数参数。
它有两种重载形式:
iterator emplace(const_iterator position, Args&&... args)
:在指定位置插入一个新元素,并将构造函数参数传递给元素类型的构造函数。
void emplace_back(Args&&... args)
:在向量末尾插入一个新元素,并将构造函数参数传递给元素类型的构造函数。
Demo:emplace函数演示
#include <iostream>
#include <vector>class MyClass {
public:MyClass(int val) : value(val) {std::cout << "Constructor called with value: " << val << std::endl;}private:int value;
};int main() {std::vector<MyClass> vec;// 在向量末尾插入一个新元素vec.emplace_back(10);// 在向量的第一个位置插入一个新元素vec.emplace(vec.begin(), 20);return 0;
}
6.3、erase
用于从向量中删除一个或多个元素。
它有两个重载形式:
iterator erase(iterator position)
:从向量中删除指定位置的元素,并返回指向删除元素之后的元素的迭代器。
iterator erase(iterator first, iterator last)
:从向量中删除指定范围内的元素,并返回指向删除范围之后的元素的迭代器。
Demo:演示两种erase
std::vector<int> vec{1,2,3,4,5};for(auto num : vec) {std::cout<<num<<std::endl; // 1 2 3 4 5}vec.erase(vec.begin()+2);for(auto num : vec) {std::cout<<num<<std::endl; // 1 2 4 5}vec.erase(vec.begin(),vec.begin()+2);for(auto num : vec) {std::cout<<num<<std::endl; // 4 5}
6.4、clear
用于清空向量,即移除向量中的所有元素,使其成为空向量。
该函数没有返回值,调用后向量的大小变为0,但是向量仍然保留了一部分内存空间,以备后续添加元素时使用。
std::vector<int> vec{1,2,3,4,5};std::cout<<vec.size()<<" "<<vec.capacity()<<std::endl; // 5 5vec.clear();std::cout<<vec.size()<<" "<<vec.capacity()<<std::endl; // 0 5
7、查找排序
std::find
和 std::sort
函数来执行查找和排序操作。
std::find
函数用于在向量中查找特定值,并返回指向找到的元素的迭代器,如果未找到,则返回尾后迭代器。
#include <iostream>
#include <vector>
#include <algorithm>int main() {std::vector<int> vec = {1, 2, 3, 4, 5};auto it = std::find(vec.begin(), vec.end(), 3);if (it != vec.end()) {std::cout << "Found element: " << *it << std::endl; // Found element: 3} else {std::cout << "Element not found" << std::endl;}return 0;
}
std::sort
函数用于对向量中的元素进行排序,默认是升序排序。
Demo1:自定排序规则
struct MyStruct {int id;std::string name;
};bool compareByName(const MyStruct& a, const MyStruct& b) {return a.name < b.name;
}int main() {std::vector<MyStruct> vec = {{1, "John"},{2, "Alice"},{3, "Bob"}};std::sort(vec.begin(), vec.end(), compareByName);// ID: 2, Name: Alice// ID: 3, Name: Bob// ID: 1, Name: Johnfor (const auto& item : vec) {std::cout << "ID: " << item.id << ", Name: " << item.name << std::endl;}return 0;
}
Demo2:逆序排序
#include <iostream>
#include <vector>
#include <algorithm>int main() {std::vector<int> vec = {5, 2, 8, 1, 6};// 使用 lambda 表达式定义自定义比较函数,实现逆序排序std::sort(vec.begin(), vec.end(), [](int a, int b) {return a > b; // 逆序排序});// 打印逆序排序后的结果for (const auto& item : vec) {std::cout << item << " "; // 8 6 5 2 1}std::cout << std::endl;return 0;
}
三、构造函数与析构函数
1、构造函数:
vector()
:构造一个空的 vector。
vector(size_type count, const T& value)
:构造一个包含 count 个元素,每个元素的值都是 value 的 vector。
std::vector<int> vec1;
std::vector<std::string> vec2(5,"A");
vector(const vector& other)
:拷贝构造函数,深拷贝,会复制 other 中的元素,并将它们存储在新的内存位置
#include <iostream>
#include <vector>int main() {std::vector<int> original = {1, 2, 3, 4, 5};// 使用拷贝构造函数创建一个新的 vectorstd::vector<int> copy(original);// 输出原始 vectorstd::cout << "Original vector: ";for (int num : original) {std::cout << num << " ";}std::cout << std::endl;// 输出拷贝后的 vectorstd::cout << "Copied vector: ";for (int num : copy) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
vector(vector&& other) noexcept
:移动构造函数,将另一个已存在的 vector 对象的资源“移动”到新创建的 vector 中,而不是进行深拷贝。
这里 other 是另一个 vector 对象的右值引用,表示要移动的源对象。移动构造函数会将 other 中的资源(例如指向内存块的指针)转移给新创建的 vector,并使 other 保持有效但处于有效但无法访问的状态。移动构造函数的主要目的是提高性能,因为它避免了不必要的资源复制。
#include <iostream>
#include <vector>int main() {// 创建一个临时 vectorstd::vector<int> temp = {1, 2, 3, 4, 5};// 使用移动构造函数创建一个新的 vectorstd::vector<int> moved(std::move(temp));// 输出原始 vectorstd::cout << "Original vector: ";for (int num : temp) {std::cout << num << " ";}std::cout << std::endl;// 输出移动后的 vectorstd::cout << "Moved vector: ";for (int num : moved) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
2、析构函数:
~vector() noexcept
:析构函数负责释放其管理的所有元素,并释放动态分配的内存。具体来说,析构函数会调用每个元素的析构函数,然后释放分配给动态数组的内存。
在 C++ 标准库中,std::vector 的析构函数是隐式生成的,如果用户没有自定义,将会使用编译器默认生成的析构函数。默认情况下,析构函数会按顺序释放元素,然后释放分配的内存。因此,std::vector 在其生命周期结束时会自动执行析构函数,确保动态分配的内存被正确释放,从而避免内存泄漏。
四、运算符重载
[]
运算符:用于访问向量中指定位置的元素,例如 vec[i]
。
==
和 !=
运算符:用于比较两个向量是否相等或不相等。
<
, <=
, >
, >=
运算符:用于比较两个向量的大小关系,通常是按字典序比较。
std::vector<int> vec = {1, 2, 3};std::cout<<vec[1]<<std::endl; // 2std::vector<int> vec1 = {1, 2, 3};std::cout<<(vec == vec1)<<std::endl; // 1std::cout<<(vec != vec1)<<std::endl; // 0std::vector<int> vec2 = {1, 2, 4};std::cout<<(vec2 > vec1)<<std::endl; // 1
五、vector的性能问题
1、随机访问: std::vector
支持通过索引进行 O(1) 时间复杂度的随机访问。这是因为 std::vector
内部使用连续的内存存储元素,因此可以通过指针算术运算来快速访问元素。
2、尾部插入和删除: 在尾部进行插入和删除操作是非常高效的,平均时间复杂度为 O(1)。因为 std::vector
内部的元素是连续存储的,所以在尾部插入或删除元素只需要修改指向尾部的指针,不需要移动其他元素。
3、中间插入和删除: 在中间位置插入或删除元素可能会导致后续元素的移动,因此平均时间复杂度为 O(n)。如果需要在中间位置频繁地进行插入或删除操作,考虑使用 std::deque
或 std::list
等数据结构。
4、内存管理: std::vector
会自动管理内存,动态地分配和释放内存以存储元素。当元素数量超过当前分配的内存空间时,std::vector
会自动重新分配更大的内存空间,并将原有元素复制到新的内存空间中。这种动态内存管理可能会导致插入操作的时间复杂度增加。
5、预留空间: std::vector
允许预留一定的空间,以避免频繁的内存重新分配。通过调用 reserve
函数可以预先分配一定大小的内存空间,从而减少插入操作的时间复杂度。
6、迭代器: std::vector
提供了双向迭代器和随机访问迭代器,可以高效地遍历元素。
std::vector
是一个高效的动态数组容器,特别适用于需要随机访问和尾部插入删除的场景。
然而,在频繁进行中间插入和删除操作时,可能会影响性能,因此需要根据实际需求选择合适的容器类型。