当前位置: 首页 > article >正文

【C++】:STL详解 —— vector类

目录

vector的概念

vector的构造函数 

vector的大小

size()和capacity()

empty()

resize()和reserver()

vector的插入

push_back()和emplace_back()

insert()和emplace()

vector的删除

pop_back()

earse()

clear()

vector的迭代器

迭代器类型

begin()和end()

rbegin()和end()

vector中元素的访问

[]+下标

at()

front()和back()

越界访问风险与防护

 性能与适用场景对比

vector迭代器失效问题

迭代器失效的场景

安全操作的最佳实践

迭代器失效规则总结


vector的概念

  1. vector是表示可变大小数组的序列容器
  2. vector就像数组一样,也采用的连续空间来存储元素,这也意味着可以采用下标对vector的元素进行访问
  3. vector与普通数组不同的是,vector的大小是可以动态改变
  4. 当vector需要重新分配大小时,其做法是,分配一个新的数组,然后将全部元素移到这个数组当中,并释放原来的数组空间
  5. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因此存储空间比实际需要的存储空间一般更大。不同的库采用不同的策略权衡空间的使用和重新分配,以至于在末尾插入一个元素的时候是在常数的时间复杂度完成的
  6. 由于vector采用连续的空间来存储元素,与其他动态序列容器相比,vector在访问元素的时候更加高效,在其末尾添加和删除元素相对高效,而对于不在其末尾进行的删除和插入操作效率则相对较低
     

vector的构造函数 

    // 默认构造
    vector<int> v1;

    // 指定元素个数和值
    vector<int> v2(3, 100);       // {100, 100, 100}

    // 迭代器范围构造
    list<int> lst = {1, 2, 3};
    vector<int> v3(lst.begin(), lst.end()); // {1, 2, 3}

    // 拷贝构造
    vector<int> v4(v3);           // {1, 2, 3}

    // 移动构造
    vector<int> v5(move(v4));      // v5 接管 v4 的内存,v4 变空

    // 初始化列表构造
    vector<int> v6 = {4, 5, 6};   // {4, 5, 6}

    // 类型推导构造(C++17)
    vector v7 = {7, 8, 9};        // vector<int>
构造函数形式作用描述时间复杂度C++版本要求
vector<T>()创建空 vectorO(1)C++98
vector<T>(n)创建 n 个默认元素的 vectorO(n)C++98
vector<T>(n, val)创建 n 个 val 值的 vectorO(n)C++98
vector<T>(first, last)迭代器范围初始化O(n)C++98
vector<T>(const vector&)深拷贝构造O(n)C++98
vector<T>(vector&&)移动构造(高效转移内存)O(1)C++11
vector<T>{elem1, elem2...}初始化列表构造O(n)C++11
vector{...}类型自动推导O(n)C++17

vector的大小

size()和capacity()

  • size():返回当前元素数量
  • capacity():返回当前预分配的内存容量(可容纳的最大元素数)

vector<int> v = {1, 2, 3};
cout << v.size();   // 输出 3
cout << v.capacity();   // 输出 3

empty()

判断vector是否为空(size==0)

vector<int> v = {1, 2, 3};
cout << v.empty();  // 输出 0(false)

resize()和reserver()

  • resize(n):调整元素数量为 n

    • 若 n > size(),新增元素默认初始化(如 int 初始化为 0)。

    • 若 n < size(),删除尾部多余元素。

  • reserve(n):预分配至少能容纳 n 个元素的内存(减少扩容次数)。

vector<int> v = {1, 2, 3};
v.resize(5);        // v = {1, 2, 3, 0, 0}
v.resize(2);        // v = {1, 2}
v.reserve(100);     // 预分配容量为 100
cout << v.capacity(); // 输出 100
操作/属性描述示例
size()当前元素数量v.size() → 3
capacity()当前预分配内存可容纳的元素数量v.capacity() → 5
resize(n)调整元素数量(可能增删元素)v.resize(5) → 5 个元素
reserve(n)预分配内存容量(不改变元素数量)v.reserve(10)

vector的插入

push_back()和emplace_back()

  • push_back(const T& value)       
    • 在尾部插入元素(拷贝构造)。
  • emplace_back(Args&&... args)(C++11 起)
    • 在尾部直接构造元素(避免临时对象拷贝)

vector<int> v = {1, 2};
v.push_back(3);          // v = {1, 2, 3}(拷贝)
v.emplace_back(4);       // v = {1, 2, 3, 4}(直接构造)

insert()和emplace()

  • insert(iterator pos, const T& value)  
    • 在迭代器 pos 指向的位置前插入元素(拷贝构造)。
  • emplace(iterator pos, Args&&... args)(C++11 起)
    • 在 pos 位置直接构造元素(更高效)。

vector<int> v = {1, 3};
auto it = v.begin() + 1;
v.insert(it, 2);         // v = {1, 2, 3}(在位置1插入2)
v.emplace(it, 0);        // v = {1, 0, 2, 3}(在位置1插入0)

vector的删除

pop_back()

移除最后一个元素

vector<int> v = {1, 2, 3};
v.pop_back();   // v = {1, 2}

earse()

erase(iterator pos)
删除迭代器 pos 指向的元素,返回指向下一个元素的迭代器。

vector<int> v = {1, 2, 3, 4};
auto it = v.begin() + 1;
it = v.erase(it);    // 删除元素 2,v = {1, 3, 4}
                     // it 指向 3(需更新迭代器)

erase(iterator first, iterator last)
删除迭代器范围 [first, last) 内的元素,返回指向 last 之后元素的迭代器。

vector<int> v = {1, 2, 3, 4, 5};
auto it = v.erase(v.begin() + 1, v.begin() + 3); 
// 删除元素 2 和 3,v = {1, 4, 5}
// it 指向 4

clear()

移除所有元素,size() 变为 0,但 capacity() 保持不变。

vector<int> v = {1, 2, 3};
v.clear();       // v 为空,size=0,capacity 仍为 3

vector的迭代器

迭代器类型

vector<T>::iterator:普通迭代器(可读写元素)。

vector<T>::const_iterator:常量迭代器(只读元素)。

vector<T>::reverse_iterator:反向迭代器。

vector<T>::const_reverse_iterator:常量反向迭代器。
vector<int> v = {1, 2, 3};

vector<int>::iterator it = v.begin();
*it = 10;  // 修改元素为 10

vector<int>::const_iterator cit = v.cbegin();
// *cit = 20;  // 错误!不可修改

begin()和end()

  • begin():返回指向第一个元素的迭代器。

  • end():返回指向最后一个元素之后位置的迭代器(尾后迭代器)。

iterator begin();
const_iterator begin() const;

iterator end();
const_iterator end() const;
vector<int> v = {1, 2, 3};

// 正向遍历
for (auto it = v.begin(); it != v.end(); ++it) {
    cout << *it << " ";  // 输出 1 2 3
}

rbegin()和end()

  • rbegin():返回指向最后一个元素之后位置的迭代器(尾后迭代器)。

  • rend():返回指向第一个元素的迭代器。

iterator rbegin();
const_iterator begin() const;

iterator rend();
const_iterator end() const;
// 反向遍历
for (auto rit = v.rbegin(); rit != v.rend(); ++rit) {
    cout << *rit << " "; // 输出 3 2 1
}

vector中元素的访问

[]+下标

  • 高效:时间复杂度 O(1)(直接计算内存偏移)。

  • 无边界检查:越界访问导致未定义行为(可能崩溃或数据损坏)。

reference operator[] (size_type n);
const_reference operator[] (size_type n) const;
vector<int> v = {10, 20, 30};
cout << v[0];    // 输出 10
v[1] = 200;      // 修改元素为 200
// cout << v[5];  // 危险!越界访问

at()

  • 边界检查:若 index >= size(),抛出 std::out_of_range 异常。

  • 性能略低:因额外检查,稍慢于 operator[]

vector<int> v = {1, 2, 3};
try {
    cout << v.at(2);  // 输出 3
    cout << v.at(5);  // 抛出异常
} catch (const out_of_range& e) {
    cerr << "越界错误:" << e.what(); // 捕获异常
}

front()和back()

  • front():返回第一个元素的引用。

  • back():返回最后一个元素的引用。

vector<int> v = {5, 6, 7};
cout << v.front();  // 5
cout << v.back();   // 7
v.front() = 0;      // v = {0, 6, 7}
// 若 vector 为空,行为未定义!

越界访问风险与防护

访问方法越界行为安全建议
operator[]未定义行为(崩溃/数据损坏)确保索引有效(如 index < v.size()
at()抛出异常使用 try-catch 处理异常
front()/back()空容器导致未定义行为先检查 !v.empty()

 性能与适用场景对比

方法性能安全性典型场景
operator[]无检查已知索引安全的快速访问
at()高(异常)需边界检查的调试或不确定索引
迭代器/范围 for依赖遍历逻辑顺序访问或修改元素
data()无检查与 C 风格 API 交互(如 memcpy

vector迭代器失效问题

迭代器的主要作用就是让我们在使用各个容器时不用关心其底层的数据结构,而vector的迭代器在底层实际上就是一个指针。迭代器失效就是指迭代器底层对应指针所指向的空间被销毁了,而指向的是一块已经被释放的空间,如果继续使用已经失效的迭代器,程序可能会崩溃。

迭代器失效的场景

1、插入操作(Insert)

导致扩容:插入元素时若超出当前容量(size >= capacity),vector会重新分配内存,导致所有迭代器、指针、引用失效

vector<int> v = {1, 2, 3};
auto it = v.begin();
v.push_back(4);  // 假设容量不足,触发扩容
// it 失效!不可使用

未扩容时:仅插入位置之后的迭代器失效。

vector<int> v = {1, 2, 3};
v.reserve(5);          // 预分配容量
auto it = v.begin() + 1;
v.insert(it, 4);       // 插入后,it 及其后的迭代器失效
// 必须使用 insert 返回的新迭代器
auto new_it = v.insert(v.begin() + 1, 4);

2、删除操作(Erase)

删除元素时,被删除元素之后的迭代器失效,包括end()

vector<int> v = {1, 2, 3, 4};
auto it = v.begin() + 2;  // 指向3
v.erase(it);             // 删除3,it失效
// 必须使用 erase 返回的迭代器(指向4)
it = v.erase(it);        // it 现在指向4

3、容量调整(Resize/Reserve)

  • reserve(n):若 n > capacity,重新分配内存,所有迭代器失效。

  • resize(n):若 n > capacity,同样触发扩容,导致所有迭代器失效

安全操作的最佳实践

使用迭代器时,永远记住一句话:每次使用前,对迭代器进行重新赋值

1、插入操作(Insert)

使用返回的新迭代器更新原迭代器:

vector<int> v = {1, 3, 4};
auto it = v.begin() + 1;
it = v.insert(it, 2);  // v = {1,2,3,4}, it指向2

2、删除操作(Erase)

在循环中结合erase的返回值更新迭代器:

vector<int> v = {2, 4, 6, 8};
for (auto it = v.begin(); it != v.end();) 
{
    if (*it % 2 == 0) 
    {
        it = v.erase(it);  // 更新迭代器
    } 
    else 
    {
        ++it;
    }
}

迭代器失效规则总结

操作迭代器失效范围内存是否重分配
push_back/emplace_back若扩容则全部失效;否则仅end()失效可能
insert/emplace若扩容则全部失效;否则插入点之后失效可能
erase/pop_back被删除元素之后失效
reserve(n > capacity)全部失效
resize(n > capacity)全部失效

http://www.kler.cn/a/561525.html

相关文章:

  • Seata分布式事务【详解分布式事务AT模式、2PC两阶段提交协议、Seata Server(TC)环境搭建,附有示例+代码】
  • 如何在后台表中查找SAP的数据修改记录?
  • 代理IP服务器实现游戏多账号管理
  • Linux环境基础开发工具的使用(apt、vim、gcc、g++、gdb、make/Makefile)
  • docker中配置redis
  • Linux之loop设备(Loop Devices in Linux)
  • MQ(Message Queue)
  • Perfectly Clear WorkBench深度解析:专业图像处理软件的高效应用
  • LeetCode - 24 两两交换链表中的节点
  • 基于JavaWeb开发的高校食堂点餐系统
  • 数据分析七大步骤
  • AIoT安全与隐私自动化建设:实践与展望
  • MYSQL之相关子查询
  • 【教程】使用docker+Dify搭建一个本地知识库
  • 利用Python爬虫获取VIP商品详情:实战案例指南
  • Linux 命名管道
  • Docker 搭建 Nginx 服务器
  • DeepSeek 助力 Vue 开发:打造丝滑的分割线(Divider)
  • 2024年第十五届蓝桥杯青少 图形化编程(Scratch)省赛中级组真题——截取递增数
  • RTSP中RTP/RTCP协议栈、NTP同步及QoS机制