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; // 0
std::cout<<vec.size()<<std::endl; // 5
std::cout<<vec.max_size()<<std::endl; // 2305843009213693951
std::cout<<vec.capacity()<<std::endl; // 5
vec.reserve(50);
std::cout<<vec.capacity()<<std::endl; // 50
vec.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; // 3
std::cout<<vec.at(3)<<std::endl; // 3
std::cout<<vec.front()<<std::endl; // 0
std::cout<<vec.back()<<std::endl; // 5
int* 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 9
for(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 object
std::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; // 10
for(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 5
vec.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: John
for (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};
// 使用拷贝构造函数创建一个新的 vector
std::vector<int> copy(original);
// 输出原始 vector
std::cout << "Original vector: ";
for (int num : original) {
std::cout << num << " ";
}
std::cout << std::endl;
// 输出拷贝后的 vector
std::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() {
// 创建一个临时 vector
std::vector<int> temp = {1, 2, 3, 4, 5};
// 使用移动构造函数创建一个新的 vector
std::vector<int> moved(std::move(temp));
// 输出原始 vector
std::cout << "Original vector: ";
for (int num : temp) {
std::cout << num << " ";
}
std::cout << std::endl;
// 输出移动后的 vector
std::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; // 2
std::vector<int> vec1 = {1, 2, 3};
std::cout<<(vec == vec1)<<std::endl; // 1
std::cout<<(vec != vec1)<<std::endl; // 0
std::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
是一个高效的动态数组容器,特别适用于需要随机访问和尾部插入删除的场景。
然而,在频繁进行中间插入和删除操作时,可能会影响性能,因此需要根据实际需求选择合适的容器类型。