C++ Vector容器的详细指南

C++中的vector是一个非常常用的序列容器,它能够存放各种类型的元素,并且其长度可以动态变化。本文将详细介绍vector容器的特点、使用方法以及内部实现机制。

vector的特点

vector使用连续的存储空间来存储元素,这意味着可以像数组一样通过指针偏移来访问元素。然而,与数组不同的是,vector的大小可以动态变化,并且这一变化由容器自动处理。当存储空间不足时,vector会自动申请更多的内存,并将原有的数据复制到新的内存区域。

容量管理

  • 容量重要性:一旦内存重新分配,vector中元素相关的所有引用、指针和迭代器都会失效,并且内存重新分配非常耗时。
  • 容量不会缩减:即使删除元素,引用、指针和迭代器仍然有效,指向删除前的位置。然而,插入操作可能会导致引用、指针和迭代器失效,因为插入可能导致vector重新分配内存。

缩减vector

有两种方法可以缩减vector的容量:

  1. coll.shrink_to_fit();
  2. coll.swap(vector);

迭代器的有效性

vector的迭代器在以下两种情况下可能会失效:

  1. 在一个较小索引位置上插入或移除元素。
  2. 由于容量变化引起内存重新分配。

内部实现

vector的底层实现为一块连续的内存区域,管理一个动态分配的数组。

  • 连续内存存储vector保证其元素在内存中是连续存储的,这使得它在随机访问时具有恒定时间复杂度 (O(1))。
  • 容量管理vector会在需要时自动扩展容量。当容量不足以容纳新元素时,会分配一个更大的内存块,通常是当前容量的两倍,然后将旧数据复制到新的内存块中。
  • 动态扩展:当向vector添加新元素且当前容量不足时,会触发重新分配。这一过程包括分配新的内存、将旧数据复制到新位置以及释放旧内存。

代码示例

以下是一个类q_vector的示例,展示了vector的一些基本操作:

class q_vector {
public:
  q_vector() {
    std::vector<int> vec1(10, 5);  // 创建一个包含10个int类型元素的vector,每个元素的值为5
    std::vector<int> vec2 = {1, 2, 3, 4, 5};  // 创建一个包含5个int类型元素的vector,初始值分别为1, 2, 3, 4, 5

    std::vector<int> v3 = {1, 2, 3, 4, 5}; // 使用初始化列表创建vector
    std::vector<int> v5(std::move(v3)); // 移动v3的数据到v5

    std::vector<int> v7;
    v7 = v5; // 复制v5的内容到v7

    std::vector<ContainerEntry> vector(100); // 调用100个构造函数

    vec2.size();  // 返回vector中的元素数量
    int32_t max = vec2.max_size();  // 返回vector可容纳的最大元素数量

    v3.resize(7, 0);  // 将v3的大小调整为7,新增的元素初始化为0
    v3.capacity();  // 返回vector在不重新分配内存的情况下可以容纳的元素数量
    v3.shrink_to_fit();  // 释放多余的容量
    v3.assign(4, 5);  // 用4个5替换v3的内容

    v3.assign({6, 7, 8, 9});  // 用初始化列表替换v3的内容
    v3.insert(v3.begin() + 1, 15);  // 在第二个位置插入15
    v3.insert(v3.end(), 2, 25);  // 在末尾插入2个25

    v3.erase(v3.begin());  // 移除第一个元素
    v3.erase(v3.begin(), v3.begin() + 2);  // 移除前两个元素
    v3.emplace(v3.begin(), 42);  // 在第一个位置原地构造元素42
    v3.emplace_back(43);  // 在末尾原地构造元素43
    v3.resize(6, 99);  // 将大小调整为6,新增的元素初始化为99
  }

  void Insert() {
    ContainerEntry entry(100);
    ContainerEntry entry1(100);

    std::vector<ContainerEntry> vector1 { entry };
    vector1.push_back(entry1);

    vector1.shrink_to_fit();  // 降低容量,使得size() == capacity(),C++11新特性

    ContainerEntry entry2(100);
    vector1.emplace_back(entry2);

    ContainerEntry entry3;
    if (vector1.size() >= 3) {
      vector1.insert(vector1.begin() + 3, entry3);  // 在第4个位置插入entry3
    } else {
      vector1.resize(4);  // 填充到至少有4个元素
      vector1.insert(vector1.begin() + 3, entry3);
    }
  }

  void Delete() {
    std::vector<ContainerEntry> vector1;
    vector1.pop_back();  // 删除最后一个元素
    vector1.clear();  // 清空vector
  }

  void Update() {
    // coll.assign(n, elem);  // 复制n个elem,赋值给coll
  }

  void Select() {
    std::vector<ContainerEntry> vector1;
    vector1.capacity();
    vector1.size();
    ContainerEntry entryTest = vector1.front();  // 获取第一个元素,不会检查第一元素是否存在
    ContainerEntry entryTest2 = vector1[0];
  }

  void Iterator() {}

  void task1() {
    std::vector<ContainerEntry> vector;
    vector.reserve(100);  // 预分配不会调用元素构造函数
  }

   // 想vector中放1000个元素时。这个过程中vector v要扩容10次左右,要知道,vector容器的每一次扩容,就意味着一次重新分配,
   // 这包含着重新分配空间、复制内容、迭代器的重新指向操作,开销比较大。这时,一个reserve(1000)就会免掉这些开销。
   // 其次,由于重新分配,迭代器也会重置,程序中迭代中的迭代器很有可能失效
  void reserve() {
    std::vector<int> vector;
    int32_t type = 100;
    // 调用vector的reserve成员函数,预先分配足够的内存来存储100个int元素。这不会改变vector的当前大小(即元素数量仍然是0),
    // 但会保证vector有足够的容量来存储100个元素而不需要重新分配内存。
    vector.reserve(type);
  }

  /**
   * 大小(Size):当前std::vector中实际存储的元素数量。
   * 容量(Capacity):当前std::vector分配的内存可以容纳的元素数量。
   *
   * vector使用的是指数增长策略,即每次扩容时容量通常会成倍增加。具体的增长因子没有在标准中固定,常见的实现是每次扩容时将容量加倍。这样做的目的是为了在频繁插入操作时减少重新分配和数据拷贝的次数,提高性能。
   *
   * 自动扩容会带来一些性能上的开销,主要体现在以下几个方面:
   *    内存重新分配:每次扩容需要分配新的内存块。
   *    元素拷贝:将旧内存中的元素拷贝到新内存中。
   *    内存释放:释放旧的内存块。
   *  为了尽量减少这些开销,建议在可以预估元素数量时,使用std::vector::reserve方法预先分配足够的内存。
   */
  void autoExpansion(){
    //扩容过程,假设当前std::vector的容量为C,大小为N,且需要插入新元素,但当前容量不足,扩容过程如下:
    // 计算新容量:根据实现的策略(通常是C * 2),计算新容量。
    // 分配新内存:为新容量分配内存。
    // 拷贝旧元素:将现有的元素从旧内存拷贝到新分配的内存。
    // 销毁旧内存:释放旧的内存空间。
    // 更新容量指针:将std::vector的内存指针指向新分配的内存。
    std::vector<int> vec;
    int capacity = vec.capacity();

    std::cout << "Initial capacity: " << capacity << std::endl;

    for (int i = 0; i < 20; ++i) {
      vec.push_back(i);
      if (vec.capacity() != capacity) {
        capacity = vec.capacity();
        std::cout << "New capacity: " << capacity << std::endl;
      }
    }
  }

  ~q_vector() {}
private:
};

异常处理

vector仅支持最低限度的逻辑错误检查。at()是唯一一个被C++标准认可得以抛出异常的函数。此外,C++标准规定,只有一般标准异常或者被用户自定义的异常才可能发生。以下是vector的一些异常处理规则:

  1. 如果push_back插入元素时发生异常,函数不产生效用。
  2. 如果元素移除/复制操作不抛出异常,那么insert/emplace等操作要么成功,要么不抛出异常。
  3. pop_back绝对不会抛出任何异常。
  4. 如果元素移除/复制操作不抛出异常,erase也不会抛出异常。
  5. swapclear不会抛出异常。
  6. 如果元素移除/复制操作不抛出异常,那么所有操作不是成功,就是不产生任何效果,包括不抛出异常。

总结

C++的vector容器提供了一种高效、灵活的序列存储方式。理解其内部实现机制以及正确使用方法,可以显著提升程序的性能和可靠性。在需要频繁插入和删除操作时,合理利用vector的容量管理特性,并通过预先分配内存来减少性能开销,是非常重要的优化手段。

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值