【C++基础】std::vector详解

本文详细介绍了C++标准库中的std::vector容器,包括其底层实现、成员函数如迭代器操作、容量管理、元素访问、赋值与增删操作,以及构造函数、析构函数和运算符重载。同时讨论了vector在不同场景下的性能优缺点。
摘要由CSDN通过智能技术生成

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::findstd::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::dequestd::list 等数据结构。

4、内存管理: std::vector 会自动管理内存,动态地分配和释放内存以存储元素。当元素数量超过当前分配的内存空间时,std::vector 会自动重新分配更大的内存空间,并将原有元素复制到新的内存空间中。这种动态内存管理可能会导致插入操作的时间复杂度增加。

5、预留空间: std::vector 允许预留一定的空间,以避免频繁的内存重新分配。通过调用 reserve 函数可以预先分配一定大小的内存空间,从而减少插入操作的时间复杂度。

6、迭代器: std::vector 提供了双向迭代器和随机访问迭代器,可以高效地遍历元素。

std::vector 是一个高效的动态数组容器,特别适用于需要随机访问和尾部插入删除的场景。
然而,在频繁进行中间插入和删除操作时,可能会影响性能,因此需要根据实际需求选择合适的容器类型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值