【C++ STL核心】vector:最常用的动态数组容器(第九天核心)

作者:C++新手入门笔记

专栏:C++从入门到精通

标签:C++、STL、vector、动态数组、容器用法、迭代器、内存管理

简介:零基础吃透vector的核心用法、底层原理、常用接口与性能优化,从初始化到增删改查,从迭代器使用到内存管理,结合实战案例解决90%的vector高频使用场景,适配笔试/面试/项目开发需求。

前言

在前八天掌握了const修饰别名、模板、内部类、匿名对象等核心知识点后,第九天我们正式进入C++ STL(标准模板库)的学习------从最常用、最基础的容器vector开始。

STL是C++的"宝藏工具库",而vector是STL中最常用的容器,没有之一。它本质是动态数组 ,既能像普通数组一样随机访问,又能自动扩容,完美解决了普通数组"长度固定、扩容麻烦"的痛点。无论是笔试中的算法题(如排序、查找),还是项目中的数据存储,vector都是首选工具。

本文将从「底层原理→核心用法→迭代器→内存管理→实战案例→避坑指南」,全方位拆解vector,帮你从"会用"到"吃透",轻松掌握这个STL入门必备容器。

一、vector的核心定义与底层原理

1. 什么是vector?

vector是C++ STL中的序列式容器 ,底层实现是动态开辟的连续内存空间(和普通数组一样是连续存储),但支持自动扩容------当存储的元素超过当前容量时,会自动申请更大的内存空间,将原有元素拷贝过去,释放旧空间。

简单理解:vector = 普通数组 + 自动扩容功能,兼顾了数组的高效访问和链表的灵活扩容。

2. 底层核心结构(必记)

vector的底层由三个核心指针维护,这也是理解它所有特性的关键:

  • start:指向vector底层内存的起始地址(第一个元素的地址);

  • finish:指向vector中最后一个元素的下一个地址(标记有效元素的末尾);

  • end_of_storage:指向vector底层内存的末尾地址(标记总容量的末尾)。

核心公式(高频考点):

  • 有效元素个数:size() = finish - start

  • 总容量:capacity() = end_of_storage - start

  • 扩容时机:当size() == capacity()时,插入元素会触发扩容。

3. 扩容机制(重点!笔试常考)

vector的扩容不是"按需扩容"(增加1个元素就扩容1个),而是"倍增扩容",不同编译器扩容倍数不同(vs是1.5倍,gcc是2倍),核心目的是减少扩容次数,提高效率

扩容的完整流程(以gcc为例,扩容2倍):

  1. 当插入元素导致size() == capacity(),触发扩容;

  2. 申请一块大小为"原容量×2"的新内存;

  3. 将原内存中的所有元素,拷贝到新内存中;

  4. 释放原内存空间;

  5. 更新startfinishend_of_storage三个指针,指向新内存。

注意:扩容会触发元素拷贝,频繁扩容会影响性能(这是vector的唯一缺点)。

二、vector的核心用法(必会,结合代码)

使用vector前,必须包含头文件:#include <vector>,并使用std命名空间(using namespace std;),否则需写std::vector

1. 初始化(5种常用方式)

vector的初始化方式灵活,根据场景选择,以下是最常用的5种:

复制代码
cpp 复制代码
#include <vector>
using namespace std;

int main() {
    // 方式1:默认初始化,空vector,size=0,capacity=0(vs下默认capacity=1)
    vector<int> v1;
    
    // 方式2:初始化n个元素,每个元素默认值为0(int类型)
    vector<int> v2(5); // size=5,capacity=5,元素:0,0,0,0,0
    
    // 方式3:初始化n个元素,每个元素为指定值(如3)
    vector<int> v3(5, 3); // size=5,capacity=5,元素:3,3,3,3,3
    
    // 方式4:用普通数组/其他vector初始化(迭代器方式,最灵活)
    int arr[] = {1,2,3,4,5};
    vector<int> v4(arr, arr+5); // 从arr[0]到arr[4],size=5,元素:1,2,3,4,5
    vector<int> v5(v4); // 用v4初始化v5,完全拷贝
    
    // 方式5:列表初始化(C++11及以上支持,最简洁)
    vector<int> v6 = {1,2,3}; // 等价于vector<int> v6{1,2,3};
    return 0;
}
    

2. 核心接口(增删改查,必记)

vector的接口非常多,重点掌握以下常用接口,覆盖90%的使用场景,按"增→删→改→查→其他"分类整理:

(1)增:插入元素
  • push_back(val):在vector末尾插入一个元素val(最常用);

  • insert(pos, val):在迭代器pos位置插入一个元素val(效率较低,需移动后续元素);

  • insert(pos, n, val):在迭代器pos位置插入n个元素val;

  • insert(pos, first, last):在迭代器pos位置插入[first, last)区间的元素(如其他vector的一段)。

cpp 复制代码
vector<int> v = {1,2,3};
v.push_back(4); // v:[1,2,3,4]
v.insert(v.begin()+1, 5); // 在第2个位置插入5,v:[1,5,2,3,4]
v.insert(v.end(), 2, 6); // 在末尾插入2个6,v:[1,5,2,3,4,6,6]
    
(2)删:删除元素
  • pop_back():删除vector末尾的元素(最常用,效率高);

  • erase(pos):删除迭代器pos位置的元素(效率低,需移动后续元素);

  • erase(first, last):删除[first, last)区间的元素;

  • clear():删除所有元素,size变为0,但capacity不变(仅清空元素,不释放内存)。

cpp 复制代码
vector<int> v = {1,5,2,3,4,6,6};
v.pop_back(); // 删除末尾6,v:[1,5,2,3,4,6]
v.erase(v.begin()+1); // 删除第2个元素5,v:[1,2,3,4,6]
v.erase(v.begin()+2, v.end()); // 删除从第3个元素到末尾,v:[1,2]
v.clear(); // 清空所有元素,v:[],size=0,capacity不变
    
(3)改:修改元素

vector支持随机访问(和普通数组一样用[]访问),修改元素直接通过下标或迭代器:

cpp 复制代码
vector<int> v = {1,2,3,4};
v[0] = 10; // 通过下标修改,v:[10,2,3,4]
v.at(1) = 20; // 通过at()修改(和[]类似,会做越界检查),v:[10,20,3,4]

// 通过迭代器修改
vector<int>::iterator it = v.begin()+2;
*it = 30; // v:[10,20,30,4]
    

注意:[]不做越界检查(越界会崩溃),at()会做越界检查(越界抛异常),日常开发建议用at(),笔试为了效率可用[]

(4)查:访问元素
  • v[i]:随机访问第i个元素(下标从0开始);

  • v.at(i):随机访问第i个元素,越界抛异常;

  • v.front():访问第一个元素;

  • v.back():访问最后一个元素;

  • find(first, last, val):查找val在[first, last)区间的位置(需包含头文件#include <algorithm>),找到返回迭代器,找不到返回v.end()

cpp 复制代码
#include <algorithm> // find函数需要包含这个头文件
vector<int> v = {10,20,30,40};
cout << v[0] << endl; // 输出:10
cout << v.front() << endl; // 输出:10
cout << v.back() << endl; // 输出:40

// 查找元素30
vector<int>::iterator it = find(v.begin(), v.end(), 30);
if (it != v.end()) {
    cout << "找到元素:" << *it << endl; // 输出:找到元素:30
} else {
    cout << "未找到元素" << endl;
}
    
(5)其他常用接口
  • size():返回有效元素个数(重点);

  • capacity():返回当前总容量(重点);

  • empty():判断vector是否为空(size==0返回true,否则false);

  • resize(n, val):将size调整为n,多余元素删除,不足元素补val(不改变capacity);

  • reserve(n):手动设置capacity为n(提前预留内存,避免频繁扩容,重点优化手段);

  • swap(v):交换两个vector的内容(效率高,仅交换三个底层指针)。

三、vector的迭代器(核心工具,必学)

1. 什么是迭代器?

迭代器是STL容器的"通用访问工具",本质是"智能指针",可以像指针一样访问容器中的元素,同时适配不同容器的底层结构(vector、list、map等都可用迭代器访问)。

vector的迭代器是随机访问迭代器(和普通指针用法几乎一致),支持++、--、+、-等操作。

2. 迭代器的类型与用法

vector常用的4种迭代器,按场景分类:

|------------|---------------------------------------|----------------|
| 迭代器类型 | 语法 | 核心用途 |
| 普通迭代器 | vector<int>::iterator | 访问、修改元素 |
| const迭代器 | vector<int>::const_iterator | 仅访问元素,不可修改 |
| 反向迭代器 | vector<int>::reverse_iterator | 反向访问元素(从末尾到开头) |
| const反向迭代器 | vector<int>::const_reverse_iterator | 反向访问,不可修改 |

3. 迭代器实战示例(遍历vector)

遍历是vector最常用的操作,推荐3种方式,优先用迭代器(通用、适配所有容器):

cpp 复制代码
vector<int> v = {1,2,3,4,5};

// 方式1:普通迭代器(可修改元素)
for (vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
    cout << *it << " "; // 输出:1 2 3 4 5
    *it *= 2; // 修改元素,v变为[2,4,6,8,10]
}
cout << endl;

// 方式2:const迭代器(仅访问,不可修改)
for (vector<int>::const_iterator it = v.begin(); it != v.end(); ++it) {
    cout << *it << " "; // 输出:2 4 6 8 10
    // *it = 1; ❌ 错误:const迭代器不可修改
}
cout << endl;

// 方式3:反向迭代器(反向遍历)
for (vector<int>::reverse_iterator it = v.rbegin(); it != v.rend(); ++it) {
    cout << *it << " "; // 输出:10 8 6 4 2
}
cout << endl;

// 方式4:C++11及以上:范围for(最简洁,底层也是迭代器)
for (int val : v) {
    cout << val << " "; // 输出:2 4 6 8 10
}
    

4. 迭代器失效问题(重点!笔试必考)

vector的迭代器会因为扩容、删除元素而失效(迭代器指向的内存地址无效),使用时必须避免,否则会导致程序崩溃。

常见的迭代器失效场景及解决方法:

  1. 扩容导致失效

    1. 场景:push_back/insert时触发扩容,原迭代器指向旧内存(已被释放);

    2. 解决:扩容后重新获取迭代器,或提前用reserve()预留足够容量。

  2. 删除元素导致失效

    1. 场景:erase(pos)后,pos及后续的迭代器全部失效(元素移动,地址变化);

    2. 解决:erase()会返回"删除位置的下一个有效迭代器",用迭代器接收返回值。

cpp 复制代码
// 错误示例:删除元素后迭代器失效
vector<int> v = {1,2,3,4,5};
for (auto it = v.begin(); it != v.end(); ++it) {
    if (*it == 3) {
        v.erase(it); // it失效,后续++it会崩溃
    }
}

// 正确示例:接收erase()的返回值,更新迭代器
vector<int> v = {1,2,3,4,5};
for (auto it = v.begin(); it != v.end(); ) { // 不写++it,在循环内控制
    if (*it == 3) {
        it = v.erase(it); // 接收返回值,it指向删除位置的下一个元素
    } else {
        ++it; // 未删除,迭代器正常++
    }
}
// 最终v:[1,2,4,5]
    

四、vector的内存管理(优化技巧,面试常考)

1. 内存分配的特点

  • vector的内存是连续分配的,支持随机访问(O(1)效率);

  • capacity >= size,多余的内存是"预留空间",用于后续插入元素,避免频繁扩容;

  • clear()仅清空元素(size=0),不释放内存(capacity不变);

  • vector不会自动缩小容量(即使size远小于capacity),需手动释放。

2. 内存优化技巧(必学)

  1. 提前预留容量(reserve()): 如果知道vector要存储的元素个数,提前用reserve(n)设置容量,避免频繁扩容(减少元素拷贝)。

    // 未优化:频繁扩容(假设插入10000个元素,会扩容多次) ``vector<int> v1; ``for (int i = 0; i < 10000; ++i) { `` v1.push_back(i); ``} `` ``// 优化:提前预留10000容量,无扩容 ``vector<int> v2; ``v2.reserve(10000); // 手动设置capacity=10000 ``for (int i = 0; i < 10000; ++i) { `` v2.push_back(i); // 无扩容,效率更

  2. 手动释放多余内存 : 当vector的size远小于capacity时,可用"swap技巧"释放多余内存(不影响有效元素)。``vector<int> v = {1,2,3,4,5}; ``v.reserve(100); // capacity=100,size=5,有95个多余内存 ``cout << "优化前:capacity=" << v.capacity() << endl; // 输出:100 `` ``// swap技巧:创建临时vector,拷贝有效元素,临时对象销毁时释放内存 ``vector<int>(v).swap(v); ``cout << "优化后:capacity=" << v.capacity() << endl; // 输出:5(和size一致) ``

五、vector实战案例(笔试/项目常用)

案例1:vector存储自定义类型(如结构体)

cpp 复制代码
#include <vector>
#include <iostream>
#include <string>
using namespace std;

// 自定义结构体
struct Student {
    string name;
    int id;
    int score;
};

int main() {
    vector<Student> v;
    // 插入自定义对象
    v.push_back({"张三", 202501, 90});
    v.push_back({"李四", 202502, 85});
    v.push_back({"王五", 202503, 95});
    
    // 遍历输出
    for (auto& s : v) { // 用引用,避免拷贝,效率高
        cout << "姓名:" << s.name << ",学号:" << s.id << ",成绩:" << s.score << endl;
    }
    return 0;
}
    

案例2:vector排序(结合algorithm库)

cpp 复制代码
#include <vector>
#include <iostream>
#include <algorithm> // sort函数需要包含这个头文件
using namespace std;

int main() {
    vector<int> v = {3,1,4,2,5};
    
    // 1. 默认升序排序
    sort(v.begin(), v.end());
    for (int val : v) {
        cout << val << " "; // 输出:1 2 3 4 5
    }
    cout << endl;
    
    // 2. 自定义降序排序(用lambda表达式,C++11及以上)
    sort(v.begin(), v.end(), [](int a, int b) {
        return a > b; // 降序:a>b时交换
    });
    for (int val : v) {
        cout << val << " "; // 输出:5 4 3 2 1
    }
    return 0;
}
    

案例3:vector去重(笔试高频)

cpp 复制代码
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;

int main() {
    vector<int> v = {1,2,2,3,3,3,4,5,5};
    
    // 去重步骤:先排序→再去重→再删除多余元素
    sort(v.begin(), v.end()); // 第一步:排序,相同元素聚集在一起
    // 第二步:去重,返回去重后最后一个元素的迭代器
    auto it = unique(v.begin(), v.end());
    // 第三步:删除去重后的多余元素
    v.erase(it, v.end());
    
    // 输出去重结果
    for (int val : v) {
        cout << val << " "; // 输出:1 2 3 4 5
    }
    return 0;
}
    

六、vector核心避坑指南(笔试/面试高频)

  1. 迭代器失效陷阱

    1. 扩容、删除元素会导致迭代器失效,删除时必须接收erase()的返回值;

    2. 避免在遍历过程中直接修改vector的容量(如push_back/erase)。

  2. 内存浪费陷阱

    1. clear()不释放内存,仅清空元素,需用swap技巧手动释放;

    2. 避免频繁扩容,提前用reserve()预留容量。

  3. 越界访问陷阱

    1. \]不做越界检查,越界会崩溃,日常开发优先用at();

  4. 效率陷阱

    1. insert(pos)在非末尾位置插入元素,效率低(需移动后续元素),优先用push_back();

    2. 遍历自定义类型vector时,用引用(&)避免拷贝,提高效率。

  5. 初始化陷阱

    1. vector<int> v(5):初始化5个0,不是初始化一个元素5;

    2. vector<int> v{5}:C++11及以上,初始化一个元素5(列表初始化)。

七、vector总结(核心必记)

vector是STL中最基础、最常用的容器,核心优势是"连续存储+随机访问+自动扩容",适合大多数场景的数据存储和访问。

  • 底层:连续内存空间,由三个指针维护size和capacity;

  • 核心接口:push_back、pop_back、erase、size、capacity、reserve(重点);

  • 迭代器:随机访问迭代器,注意扩容、删除导致的失效问题;

  • 优化技巧:提前reserve()预留容量,用swap()释放多余内存;

  • 适用场景:频繁访问、少量插入删除(末尾插入删除效率高)、需要随机访问的场景。

掌握vector,不仅能解决笔试中的算法题、项目中的数据存储问题,还能为后续学习list、map等其他STL容器打下基础------STL容器的用法有很多共性,学会vector,其他容器就能触类旁通。

相关推荐
仰泳的熊猫2 小时前
题目2308:蓝桥杯2019年第十届省赛真题-旋转
数据结构·c++·算法·蓝桥杯
菜鸟‍2 小时前
【后端项目】苍穹外卖day01-开发环境搭建
java·开发语言·spring boot
lzksword3 小时前
C++ Builder XE OpenDialog1打开多文件并显示xls与xlsx二种格式文件
java·前端·c++
青槿吖3 小时前
【保姆级教程】Spring事务控制通关指南:XML+注解双版本,避坑指南全奉上
xml·java·开发语言·数据库·sql·spring·mybatis
Yungoal3 小时前
B/S和C/S架构在服务端接收请求
c语言·开发语言·架构
niceffking3 小时前
C++内部类的ISO约定和语法细节
开发语言·c++
wjs20243 小时前
C# 常量
开发语言
Ma_Hong_Kai3 小时前
CMFCRibbonBar
开发语言·visualstudio·mfc
jaysee-sjc3 小时前
【练习十二】Java实现年会红包雨小游戏
java·开发语言·算法·游戏·intellij-idea