C++ vector 深度解析:属性、方法、底层实现与扩容机制

C++ vector 深度解析:属性、方法、底层实现与扩容机制

一、vector 核心属性与基本方法

1.1 容器特性

std::vector 是 C++ STL 中的动态数组实现,具有以下核心特性:

连续内存存储:元素在内存中连续存放动态扩容:自动管理内存,可动态增长高效随机访问:支持 O(1) 复杂度的随机访问尾部操作高效:在尾部插入/删除的时间复杂度为 O(1)

1.2 核心方法体系

方法类别典型方法时间复杂度典型应用场景插入操作push_back(), emplace_back(), insert()O(1) / O(n)尾部插入/指定位置插入删除操作pop_back(), erase(), clear()O(1) / O(n)尾部删除/指定位置删除访问操作operator[], at(), front(), back()O(1)随机访问、头尾元素访问容量操作size(), capacity(), empty(), resize()O(1)状态检查、容量调整遍历操作begin(), end(), 迭代器操作O(n)全量数据访问、算法处理排序操作sort(), stable_sort()O(n log n)数据排序

代码示例:基本操作

#include

#include

#include

using namespace std;

int main() {

// 定义并初始化vector

vector nums = {5, 2, 8, 1, 9};

// 插入元素

nums.push_back(10); // 尾部插入

nums.emplace_back(11); // 原地构造插入

nums.insert(nums.begin() + 3, 3); // 在第3个位置插入

// 访问元素

cout << "Front: " << nums.front() << endl; // 5

cout << "Back: " << nums.back() << endl; // 11

cout << "At index 2: " << nums.at(2) << endl; // 8

// 遍历输出

cout << "Original vector: ";

for (int num : nums) {

cout << num << " "; // 5 2 8 3 1 9 10 11

}

// 删除元素

nums.pop_back(); // 删除尾部

nums.erase(nums.begin() + 1); // 删除第1个元素

// 排序和调整大小

sort(nums.begin(), nums.end());

nums.resize(5); // 调整大小为5

cout << "\nAfter sort & resize: ";

for (int num : nums) {

cout << num << " "; // 1 2 3 5 8

}

// 使用算法

auto it = find(nums.begin(), nums.end(), 3);

if (it != nums.end()) {

cout << "\nFound 3 at position: " << distance(nums.begin(), it);

}

}

二、底层实现机制深度剖析

2.1 内存布局

vector 的内存布局包含三个关键指针:

起始指针:指向分配的内存块起始位置结束指针:指向最后一个有效元素的下一个位置容量结束指针:指向分配内存块的末尾

2.2 迭代器实现

vector 迭代器为随机访问迭代器,支持以下操作:

operator++ / operator--:前后移动operator+ / operator-:任意位置跳转operator*:访问当前元素operator->:访问元素成员比较操作:==, !=, <, > 等

迭代器实现代码示例:

template

class _Vector_iterator {

public:

typedef _Vector_iterator<_Tp, _Alloc> _Self;

typedef _Tp value_type;

typedef _Tp* pointer;

typedef _Tp& reference;

typedef ptrdiff_t difference_type;

typedef random_access_iterator_tag iterator_category;

pointer _M_current;

_Vector_iterator(pointer __x) : _M_current(__x) {}

// 迭代器移动

_Self& operator++() {

++_M_current;

return *this;

}

_Self operator++(int) {

_Self __tmp = *this;

++_M_current;

return __tmp;

}

// 随机访问

difference_type operator-(const _Self& __x) const {

return _M_current - __x._M_current;

}

// 访问元素

reference operator*() const {

return *_M_current;

}

pointer operator->() const {

return _M_current;

}

};

2.3 内存管理

vector 使用内存分配器管理内存:

内存分配:allocator_traits::allocate()内存释放:allocator_traits::deallocate()内存重分配:当容量不足时分配新内存块

三、扩容机制与性能优化

3.1 扩容触发条件

vector 的扩容发生在以下情况:

插入元素:当 size() == capacity() 时重新分配:调用 resize() 或 reserve() 指定更大容量时

3.2 扩容过程详解

分配新内存:通常按当前容量的固定倍数(如2倍)分配新内存移动元素:将旧内存中的元素逐个移动到新内存释放旧内存:释放原内存块更新指针:更新起始、结束和容量指针

扩容相关代码示例:

template

void _Vector_base<_Tp, _Alloc>::_M_allocate_and_copy(

size_type __n, const _Tp* __first, const _Tp* __last) {

// 分配新内存

pointer __new_start = this->_M_allocate(__n);

pointer __new_finish = _Vector_impl<_Tp, _Alloc>::_S_copy(_Tp*,

__first, __last, __new_start);

// 释放旧内存

this->_M_deallocate(this->_M_start, this->_M_end_of_storage - this->_M_start);

// 更新指针

this->_M_start = __new_start;

this->_M_finish = __new_finish;

this->_M_end_of_storage = __new_start + __n;

}

template

void vector<_Tp, _Alloc>::_M_emplace_back_aux(const _Tp& __x) {

// 容量不足时扩容

if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage) {

// ... 其他情况处理

} else {

const size_type __old_size = size();

const size_type __len = __old_size != 0 ? 2 * __old_size : 1;

// 分配新内存并移动元素

pointer __new_start(this->_M_allocate(__len));

pointer __new_finish(_Uninitialized_copy_a(this->_M_impl._M_start,

this->_M_impl._M_finish, __new_start, _M_get_Tp_allocator()));

// 构造新元素

_M_get_Tp_allocator().construct(__new_finish, __x);

++__new_finish;

// 释放旧内存

_Destroy(this->_M_impl._M_start, this->_M_impl._M_finish);

_M_deallocate(this->_M_impl._M_start,

this->_M_impl._M_end_of_storage - this->_M_impl._M_start);

// 更新指针

this->_M_impl._M_start = __new_start;

this->_M_impl._M_finish = __new_finish;

this->_M_impl._M_end_of_storage = __new_start + __len;

}

}

3.3 性能优化策略

预分配内存:使用 reserve() 提前分配足够空间使用 emplace_back():避免不必要的拷贝构造避免频繁插入/删除:在非尾部位置操作效率低考虑 deque:当需要频繁在头尾插入时

四、深层考点与面试策略

4.1 核心考点解析

底层实现对比:

与 list 对比:vector 随机访问高效但插入/删除慢与 deque 对比:vector 是真正连续存储,deque 是分段连续

迭代器失效:

插入操作可能导致所有迭代器失效(如果触发扩容)删除操作仅使被删除元素的迭代器失效

内存管理:

内存连续分配扩容时的内存拷贝开销

4.2 面试题示例

题目1:vector 的 operator[] 和 at() 有什么区别?

答案要点:

operator[] 不检查边界,at() 会检查边界并抛出异常operator[] 通常比 at() 更快

题目2:vector 扩容时为什么通常选择 2 倍扩容策略?

答案要点:

平衡内存使用和扩容频率保证插入操作的平摊时间复杂度为 O(1)避免频繁的小幅度扩容

题目3:vector 的 data() 方法有什么作用?

答案要点:

返回指向底层数组的指针兼容 C 风格 API比 &vec[0] 更安全(允许空向量)

题目4:如何高效地在 vector 中间插入元素?

答案要点:

使用 insert() 但效率较低(O(n))考虑使用 deque 或 list 替代使用 emplace 减少拷贝开销

五、高级应用场景与扩展

5.1 自定义数据类型

#include

#include

#include

using namespace std;

class Employee {

public:

Employee(int id, string name) : id(id), name(name) {}

int getId() const { return id; }

string getName() const { return name; }

private:

int id;

string name;

};

int main() {

vector employees;

employees.emplace_back(1001, "Alice");

employees.emplace_back(1002, "Bob");

// 自定义比较函数

sort(employees.begin(), employees.end(),

[](const Employee& a, const Employee& b) {

return a.getId() < b.getId();

});

for (const auto& emp : employees) {

cout << emp.getId() << ": " << emp.getName() << endl;

}

}

5.2 对象池实现

#include

#include

#include

using namespace std;

class ObjectPool {

public:

ObjectPool(size_t initialSize = 10) {

pool.reserve(initialSize);

for (size_t i = 0; i < initialSize; ++i) {

pool.emplace_back(make_unique(i));

}

}

unique_ptr acquire() {

if (pool.empty()) {

return make_unique(nextId++);

}

auto obj = move(pool.back());

pool.pop_back();

return obj;

}

void release(unique_ptr obj) {

pool.push_back(move(obj));

}

private:

vector> pool;

int nextId = 0;

};

int main() {

ObjectPool pool(5);

auto obj1 = pool.acquire();

auto obj2 = pool.acquire();

cout << "Acquired: " << *obj1 << ", " << *obj2 << endl;

pool.release(move(obj1));

pool.release(move(obj2));

cout << "Pool size after release: " << pool.acquire().get() << endl;

}

/*

std::move 本身并不执行任何实际的移动操作,它只是将一个左值(lvalue)转换为右值引用(rvalue reference),这样编译器可以选择使用移动构造函数或移动赋值运算符来提高性能。

1.避免不必要的拷贝

2.转移资源所有权

3.可以在适当的时候提高程序的性能,尤其是在处理大型对象或资源密集型对象时。

*/

5.3 多线程环境扩展

Java 中的 CopyOnWriteArrayList 实现了线程安全的动态数组:

import java.util.concurrent.CopyOnWriteArrayList;

public class ConcurrentVectorExample {

public static void main(String[] args) {

CopyOnWriteArrayList list = new CopyOnWriteArrayList<>();

// 生产者线程

new Thread(() -> {

for (int i = 0; i < 10; i++) {

list.add("Item-" + i);

}

}).start();

// 消费者线程

new Thread(() -> {

for (String item : list) {

System.out.println("Read: " + item);

}

}).start();

}

}

六、总结与最佳实践

6.1 关键特性总结

连续内存:缓存友好,访问高效动态扩容:自动管理内存尾部操作高效:适合作为栈使用中间操作低效:插入/删除需要移动元素

6.2 最佳实践建议

优先使用 emplace_back():避免不必要的拷贝预分配内存:使用 reserve() 避免多次扩容避免频繁中间插入:考虑 deque 或 list 替代注意迭代器失效:扩容会使所有迭代器失效

6.3 面试应对策略

底层实现:清晰阐述动态数组的结构性能分析:对比 list 和 deque 的操作复杂度应用场景:说明何时选择 vector 而非其他容器高级特性:了解 emplace 系列方法和移动语义

七、补充一:resize和reverse

在 C++ 中,resize 和 reverse 是 std::vector 提供的两个不同功能的成员函数。它们用于不同的目的,分别用于调整容器的大小和反转容器中的元素顺序。

7.1 resize()

resize() 用于调整 std::vector 的大小。它可以增加或减少向量的大小,并且可以选择性地提供一个默认值来初始化新增的元素。

用法

调整大小并使用默认值初始化新增元素:

如果新大小大于当前大小,向量将扩展,新增的元素将被初始化为 T()(即默认构造的值)。如果新大小小于当前大小,向量将缩小,多余的元素将被删除。

void resize(size_type new_size);

调整大小并提供一个特定值初始化新增元素:

如果新大小大于当前大小,向量将扩展,新增的元素将被初始化为提供的值。如果新大小小于当前大小,向量将缩小,多余的元素将被删除。

void resize(size_type new_size, const T& value);

示例

#include

#include

int main() {

std::vector vec = {1, 2, 3, 4, 5};

// 扩展向量并使用默认值初始化新增元素

vec.resize(8);

// vec: {1, 2, 3, 4, 5, 0, 0, 0}

// 扩展向量并提供特定值初始化新增元素

vec.resize(10, 10);

// vec: {1, 2, 3, 4, 5, 0, 0, 0, 10, 10}

// 缩小向量

vec.resize(3);

// vec: {1, 2, 3}

return 0;

}

7.1 reverse()

reverse() 用于反转 std::vector 中的元素顺序。它接受两个迭代器参数,表示要反转的范围。

用法

void reverse(iterator first, iterator last);

first:指向要反转范围的第一个元素的迭代器。last:指向要反转范围的后一个位置的迭代器(不包含该位置的元素)。

示例

#include

#include

#include

int main() {

std::vector vec = {1, 2, 3, 4, 5};

// 反转整个向量

std::reverse(vec.begin(), vec.end());

// vec: {5, 4, 3, 2, 1}

// 反转部分向量

std::reverse(vec.begin(), std::next(vec.begin(), 3));

// vec: {3, 4, 5, 2, 1}

// 打印向量

for (int n : vec) {

std::cout << n << " ";

}

std::cout << std::endl;

return 0;

}

总结

resize():用于调整向量的大小,可以扩展或缩小向量,新增元素可以选择性地初始化。reverse():用于反转向量中元素的顺序,可以反转整个向量或向量的一部分。

相关推荐

电脑亮度调到最大还是很暗怎么回事?(已解决)
突尼斯男足世界排名
365bet现场走地盘

突尼斯男足世界排名

📅 08-10 👁️ 7204
街头篮球冠军手游 街头篮球手游SG哪个厉害
365bet现场走地盘

街头篮球冠军手游 街头篮球手游SG哪个厉害

📅 07-08 👁️ 3557