首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【C++标准库类型】深入理解vector类型(1):从基础到实践

【C++标准库类型】深入理解vector类型(1):从基础到实践

作者头像
byte轻骑兵
发布2026-01-21 15:55:14
发布2026-01-21 15:55:14
1330
举报

vector是C++标准模板库(STL)中的一种顺序容器,用于存储动态数组。与普通的C风格数组不同,vector的大小可以在运行时自动调整,不需要在编译时确定大小。vector本质上是一个模板类,可以存储任意类型的对象,只要这些对象满足可复制和可移动的要求。

一、vector 的本质与核心特性

动态数组:vector 是 C++ 标准库中的序列容器,本质是一个可动态扩展的数组。它在堆内存中分配连续空间,支持 O(1) 时间的随机访问。

核心优势

  • 动态扩展vector能够根据需要自动扩展其大小,插入新元素时,如果当前容量不足,会自动分配更大的内存空间,并将原有元素复制到新的内存空间中。
  • 随机访问:由于vector使用连续的内存空间存储元素,因此可以通过下标操作符[]高效地访问任意位置的元素,时间复杂度为O(1)。
  • 自动管理内存vector自动管理其内存空间,不需要手动释放内存,避免了内存泄漏的风险。
  • 迭代器vector支持多种迭代器,包括正向迭代器、反向迭代器和常量迭代器,方便对元素进行遍历和修改。

与原生数组对比

代码语言:javascript
复制
int arr[5];          // 固定大小
std::vector<int> v;  // 动态大小
v.reserve(5);        // 可预分配空间

二、使用方法

2.1. 定义和初始化

要使用 vector,需要包含 <vector> 头文件:

代码语言:javascript
复制
#include <vector>
using namespace std;
 
// 定义一个空的vector
vector<int> vec1;
 
// 定义一个包含5个元素的vector,每个元素初始化为10
vector<int> vec2(5, 10);
 
// 使用初始化列表定义一个vector
vector<int> vec3 = {1, 2, 3, 4, 5};
 
// 通过复制构造函数初始化
vector<int> vec4(vec3);

2.2. 插入和删除元素

  • 插入元素
代码语言:javascript
复制
vec1.push_back(10); // 在末尾插入元素
vec1.insert(vec1.begin() + 1, 20); // 在指定位置插入元素
  • 删除元素
代码语言:javascript
复制
vec1.pop_back(); // 删除末尾元素
vec1.erase(vec1.begin() + 2); // 删除指定位置的元素

2.3. 访问元素

  • 下标操作符
代码语言:javascript
复制
int value = vec1[0]; // 访问第一个元素
  • at方法:提供边界检查,若越界会抛出std::out_of_range异常。
代码语言:javascript
复制
try {
    int value = vec1.at(1); // 访问第二个元素
} catch (const std::out_of_range& e) {
    cerr << "Out of range: " << e.what() << endl;
}
  • frontback方法:分别返回第一个和最后一个元素的引用。
代码语言:javascript
复制
int first = vec1.front();
int last = vec1.back();

2.4. 迭代器与算法应用

迭代器类型

代码语言:javascript
复制
// 正向迭代器
for(auto it = nums.begin(); it != nums.end(); ++it) {
    std::cout << *it << " ";
}

// 反向迭代(C++11)
for(auto rit = nums.rbegin(); rit != nums.rend(); ++rit) {
    std::cout << *rit << " ";
}

// 常量迭代器
auto cit = nums.cbegin();  // 不可修改元素

结合标准算法

代码语言:javascript
复制
#include <algorithm>

// 查找元素
auto pos = std::find(nums.begin(), nums.end(), 999);

// 排序
std::sort(nums.begin(), nums.end());

// 遍历处理(C++11 lambda)
std::for_each(nums.begin(), nums.end(), [](int n) {
    std::cout << n * 2 << " ";
});

2.5. 容量和大小

  • size方法:返回当前元素的数量。
代码语言:javascript
复制
size_t numElements = vec1.size();
  • capacity方法:返回当前分配的存储空间能容纳的元素数量。
代码语言:javascript
复制
size_t capacity = vec1.capacity();
  • empty方法:判断vector是否为空。
代码语言:javascript
复制
bool isEmpty = vec1.empty();
  • resize方法:调整vector的大小,若新大小大于原大小,会用默认值填充新增位置;若新大小小于原大小,会删除多余元素。
代码语言:javascript
复制
vec1.resize(10, 0); // 调整大小为10,新增元素初始化为0
  • reserve方法:为vector预留一定的存储空间,避免频繁重新分配内存。
代码语言:javascript
复制
vec1.reserve(100); // 预留100个元素的存储空间
  • shrink_to_fit方法:请求vector减小其容量以匹配其大小,释放多余的内存。
代码语言:javascript
复制
vec1.shrink_to_fit();

、性能优化关键点

扩容机制

  • size() == capacity() 时触发扩容
  • 典型扩容策略:新容量 = 旧容量 * 2(实现依赖)
  • 扩容代价:需要拷贝所有元素到新内存空间

优化策略

代码语言:javascript
复制
// 预分配空间避免多次扩容
std::vector<BigObject> bigVec;
bigVec.reserve(1000);  // 预先分配足够空间

// 使用移动语义减少拷贝
std::vector<std::string> strVec;
strVec.emplace_back("hello");  // 直接构造,避免临时对象

四、实践应用

4.1. 动态数组

vector 最常见的用途之一是作为动态数组。例如,可以读取用户输入的一组整数,并将它们存储在 vector 中:

代码语言:javascript
复制
#include <vector>
#include <iostream>

int main() {
    std::vector<int> numbers;
    int num;

    std::cout << "Enter integers (enter -1 to stop):" << std::endl;
    while (true) {
        std::cin >> num;
        if (num == -1) {
            break;
        }
        numbers.push_back(num);
    }

    // 输出所有元素
    for (int i = 0; i < numbers.size(); ++i) {
        std::cout << numbers[i] << " ";
    }
    std::cout << std::endl;

    return 0;
}

4.2. 存储自定义类型

vector 可以存储自定义类型的对象。例如,定义一个 Person 类,并将多个 Person 对象存储在 vector 中:

代码语言:javascript
复制
#include <vector>
#include <iostream>
#include <string>

class Person {
public:
    Person(const std::string& name, int age) : name(name), age(age) {}

    void printInfo() const {
        std::cout << "Name: " << name << ", Age: " << age << std::endl;
    }

private:
    std::string name;
    int age;
};

int main() {
    std::vector<Person> people;

    // 添加 Person 对象
    people.emplace_back("Alice", 25);
    people.emplace_back("Bob", 30);

    // 输出所有 Person 对象的信息
    for (const auto& person : people) {
        person.printInfo();
    }

    return 0;
}

使用了 emplace_back() 来直接在 vector 的末尾构造 Person 对象,避免了不必要的拷贝。

4.3. 实现栈结构

代码语言:javascript
复制
template<typename T>
class Stack {
private:
    std::vector<T> elements;
public:
    void push(const T& val) { elements.push_back(val); }
    void pop() { elements.pop_back(); }
    T top() const { return elements.back(); }
};

4.4. 矩阵表示(二维 vector)

代码语言:javascript
复制
std::vector<std::vector<int>> matrix = {
    {1,2,3},
    {4,5,6},
    {7,8,9}
};

五、注意事项与陷阱

5.1. 迭代器失效问题

解释:迭代器是用于访问容器元素的对象,当对 vector 进行插入、删除等操作时,可能会导致迭代器失效,使得后续对迭代器的操作产生未定义行为

  • 插入元素:如果插入元素时 vector 的容量不足,会重新分配内存,将原有元素复制到新的内存区域,此时所有迭代器都会失效。
  • 删除元素:删除元素会使被删除元素及其之后的迭代器失效。

典型场景:

代码语言:javascript
复制
std::vector<int> vec = {1, 2, 3};
auto it = vec.begin() + 1;

// 插入元素(可能导致扩容)
vec.push_back(4);  // 可能使 it 失效!
// 错误:此时使用 it 是未定义行为
*it = 5;           // ❌ 危险操作!

解决办法:

  • 插入后重新获取迭代器
代码语言:javascript
复制
it = vec.begin() + 1;  // 重新计算迭代器位置
  • 优先使用索引代替迭代器
代码语言:javascript
复制
size_t index = 1;
vec[index] = 5;  // 安全操作

5.2. 容量和内存分配问题

解释:vector 的容量(capacity())是指它当前可以容纳的元素数量,当元素数量超过容量时,vector 会自动重新分配更大的内存空间,将原有元素复制到新的内存区域,这会带来性能开销。

示例代码:

代码语言:javascript
复制
#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec;
    for (int i = 0; i < 100; ++i) {
        vec.push_back(i);
        // 每次容量不足时都会重新分配内存
        std::cout << "Size: " << vec.size() << ", Capacity: " << vec.capacity() << std::endl;
    }
    return 0;
}

解决办法:在插入大量元素之前,可以使用 reserve() 方法预先分配足够的内存,避免多次重新分配内存

代码语言:javascript
复制
#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec;
    vec.reserve(100);  // 预先分配足够的内存
    for (int i = 0; i < 100; ++i) {
        vec.push_back(i);
        std::cout << "Size: " << vec.size() << ", Capacity: " << vec.capacity() << std::endl;
    }
    return 0;
}

5.3. 下标访问越界问题

解释:使用下标运算符 [] 访问 vector 元素时,不会进行边界检查,如果下标越界,会导致未定义行为。

示例代码:

代码语言:javascript
复制
#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec = {1, 2, 3};
    // 错误:越界访问,未定义行为
    std::cout << vec[3] << std::endl; 
    return 0;
}

解决办法:使用 at() 方法访问元素,它会进行边界检查,如果越界会抛出 std::out_of_range 异常

代码语言:javascript
复制
#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec = {1, 2, 3};
    try {
        std::cout << vec.at(3) << std::endl; 
    } catch (const std::out_of_range& e) {
        std::cerr << "Out of range error: " << e.what() << std::endl;
    }
    return 0;
}

5.4. 拷贝和移动语义问题

解释:vector 作为函数参数传递或赋值时,会涉及拷贝或移动操作。如果 vector 中存储的是大对象,拷贝操作会带来性能开销。

示例代码:

代码语言:javascript
复制
#include <iostream>
#include <vector>
#include <string>

void printVector(std::vector<std::string> vec) {
    for (const auto& str : vec) {
        std::cout << str << " ";
    }
    std::cout << std::endl;
}

int main() {
    std::vector<std::string> vec = {"apple", "banana", "cherry"};
    // 拷贝操作,可能带来性能开销
    printVector(vec); 
    return 0;
}

解决办法:

  • 传递引用:将函数参数改为引用类型,避免拷贝操作:
代码语言:javascript
复制
#include <iostream>
#include <vector>
#include <string>

void printVector(const std::vector<std::string>& vec) {
    for (const auto& str : vec) {
        std::cout << str << " ";
    }
    std::cout << std::endl;
}

int main() {
    std::vector<std::string> vec = {"apple", "banana", "cherry"};
    printVector(vec); 
    return 0;
}
  • 使用移动语义:在需要转移所有权时,使用 std::move() 进行移动操作:
代码语言:javascript
复制
#include <iostream>
#include <vector>
#include <string>

std::vector<std::string> getVector() {
    std::vector<std::string> vec = {"apple", "banana", "cherry"};
    return std::move(vec);
}

int main() {
    std::vector<std::string> newVec = getVector();
    return 0;
}

5.5. 异常安全性问题

解释:vector 进行插入、删除等操作时,如果发生异常,可能会导致 vector 处于不一致的状态。

示例代码:

代码语言:javascript
复制
#include <iostream>
#include <vector>

class BigObject {
public:
    BigObject() { /* 可能抛出异常的构造函数 */ }
};

int main() {
    std::vector<BigObject> vec;
    try {
        vec.push_back(BigObject());
    } catch (...) {
        // 插入操作可能失败,vec 状态不确定
    }
    return 0;
}

解决办法:使用 emplace_back() 代替 push_back()emplace_back() 直接在 vector 的末尾构造对象,减少不必要的拷贝和移动,并且在构造过程中如果发生异常,vector 的状态不会改变。

代码语言:javascript
复制
#include <iostream>
#include <vector>

class BigObject {
public:
    BigObject() { /* 可能抛出异常的构造函数 */ }
};

int main() {
    std::vector<BigObject> vec;
    try {
        vec.emplace_back();
    } catch (...) {
        // 插入操作失败,vec 状态不变
    }
    return 0;
}

5.6. vector<bool> 的特殊陷阱

核心问题vector<bool> 是空间优化的特化版本,存储方式不同于其他 vector<T>

典型错误:

代码语言:javascript
复制
std::vector<bool> flags{true, false};

// 尝试获取 bool 引用
bool& flag = flags[0];  // ❌ 编译错误!返回的是临时代理对象
flags[1] = true;        // ✔️ 允许通过代理对象赋值

替代方案

  • 需要传统数组行为:
代码语言:javascript
复制
std::vector<char> bools(10, 0);  // 用 char 模拟 bool
  • 位操作需求
代码语言:javascript
复制
std::bitset<100> bits;  // 固定大小的位集合

5.7. 容量与大小的混淆

常见错误:误用 reserve()resize()

错误示例:

代码语言:javascript
复制
std::vector<int> vec;
vec.reserve(10);      // 分配容量为 10,size 仍为 0

vec[0] = 1;           // ❌ 越界访问!size() 未改变
std::cout << vec[0];  // 未定义行为

vec.resize(10);       // ✔️ 正确调整大小
vec[9] = 10;          // 安全操作

关键区别:

方法

作用

时间复杂度

reserve(n)

仅预分配内存(容量)

O(n)

resize(n)

修改元素数量(改变大小)

O(n)

5.8. 元素生命周期管理

关键问题:存储原始指针导致内存泄漏。

危险代码:

代码语言:javascript
复制
std::vector<MyClass*> ptr_vec;
ptr_vec.push_back(new MyClass());

// 忘记释放内存...
// ptr_vec 析构时不会自动 delete 指针!

现代 C++ 解决方案:使用智能指针

代码语言:javascript
复制
std::vector<std::unique_ptr<MyClass>> safe_vec;
safe_vec.push_back(std::make_unique<MyClass>());  // 自动管理内存

5.9. 多线程安全问题

核心原则vector 不是线程安全的容器。

危险场景:

代码语言:javascript
复制
std::vector<int> shared_vec;

// 线程1
void write() {
    shared_vec.push_back(42);
}

// 线程2
void read() {
    if (!shared_vec.empty()) {
        int val = shared_vec[0];  // 可能读取到无效数据
    }
}

解决方案:使用互斥锁

代码语言:javascript
复制
std::mutex mtx;

void safe_write() {
    std::lock_guard<std::mutex> lock(mtx);
    shared_vec.push_back(42);
}

void safe_read() {
    std::lock_guard<std::mutex> lock(mtx);
    if (!shared_vec.empty()) {
        int val = shared_vec[0];
    }
}

六、与其他容器对比

特性

vector

deque

list

内存布局

连续

分段连续

非连续

随机访问

O(1)

O(1)

O(n)

头部插入删除

O(n)

O(1)

O(1)

迭代器失效频率

中等

缓存友好性

选择策略

  • 需要快速随机访问 → vector
  • 频繁头部操作 → deque
  • 大量中间插入删除 → list

七、最佳实践总结

  • 预分配原则:提前 reserve() 已知大小的空间
  • 优先使用 emplace:减少对象拷贝次数
  • 警惕迭代器失效:在修改操作后不要使用旧的迭代器
  • 善用移动语义:传递大 vector 时使用 std::move
  • 选择合适容器:根据操作特征选择最佳容器类型

通过深入理解 vector 的内部机制和正确使用模式,可以充分发挥其高性能特性,构建高效可靠的 C++ 应用程序。


八、参考资料

  • 《C++ Primer(第 5 版)》这本书是 C++ 领域的经典之作,对 C++ 的基础语法和高级特性都有深入讲解。
  • 《Effective C++(第 3 版)》书中包含了很多 C++ 编程的实用建议和最佳实践。
  • 《C++ Templates: The Complete Guide(第 2 版)》该书聚焦于 C++ 模板编程,而using声明在模板编程中有着重要应用,如定义模板类型别名等。
  • C++ 官方标准文档:C++ 标准文档是最权威的参考资料,可以查阅最新的 C++ 标准(如 C++11、C++14、C++17、C++20 等)文档。例如,ISO/IEC 14882:2020 是 C++20 标准的文档,可从相关渠道获取其详细内容。
  • cppreference.com:这是一个非常全面的 C++ 在线参考网站,提供了详细的 C++ 语言和标准库文档。
  • LearnCpp.com:该网站提供了系统的 C++ 教程,配有丰富的示例代码和清晰的解释,适合初学者学习和理解相关知识。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2026-01-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、vector 的本质与核心特性
  • 二、使用方法
    • 2.1. 定义和初始化
    • 2.2. 插入和删除元素
    • 2.3. 访问元素
    • 2.4. 迭代器与算法应用
    • 2.5. 容量和大小
  • 三、性能优化关键点
  • 四、实践应用
    • 4.1. 动态数组
    • 4.2. 存储自定义类型
    • 4.3. 实现栈结构
    • 4.4. 矩阵表示(二维 vector)
  • 五、注意事项与陷阱
    • 5.1. 迭代器失效问题
    • 5.2. 容量和内存分配问题
    • 5.3. 下标访问越界问题
    • 5.4. 拷贝和移动语义问题
    • 5.5. 异常安全性问题
    • 5.6. vector<bool> 的特殊陷阱
    • 5.7. 容量与大小的混淆
    • 5.8. 元素生命周期管理
    • 5.9. 多线程安全问题
  • 六、与其他容器对比
  • 七、最佳实践总结
  • 八、参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档