std::vector是C++标准库提供的动态数组,自动管理内存;频繁push_back未reserve会导致多次重分配;手动实现需用new/delete[]并正确调用构造/析构函数,不可用realloc。

std::vector 是最直接的自增长数组实现
不用自己造轮子,std::vector 就是 C++ 标准库提供的动态数组。它在底层自动管理内存:插入新元素时容量不足,就分配更大内存块、拷贝旧数据、释放旧内存。
常见误用是反复调用 push_back() 却没预留空间,导致多次重分配——比如往空 vector 中插入 1000 个元素,可能触发 10+ 次内存重分配(取决于增长策略,通常是 1.5× 或 2×)。
实操建议:
- 已知大致规模时,先调用
reserve(n)预留容量,避免频繁重分配 - 若需频繁在尾部增删,
vector合适;若需中间插入/删除多,考虑std::list或std::deque -
size()返回当前元素个数,capacity()返回已分配内存能容纳的元素数,二者不等时说明有冗余空间
手动实现简易自增长数组要注意 realloc 语义
如果出于学习或特殊约束必须手写,核心是用 new/delete[] 管理原始内存,不能依赖 realloc(C 风格),因为 C++ 对象有构造/析构语义。
立即学习“C++免费学习笔记(深入)”;
错误做法:直接用 realloc 复制内存块——这会跳过新对象的构造函数,对含非平凡构造函数的类型(如 std::string)导致未定义行为。
正确路径:
- 分配新内存块,用
new (ptr) T(args...)定位 new 逐个构造新对象 - 对旧数组中每个有效对象调用析构函数,再
delete[]释放原内存 - 更新指针和大小字段
示例关键片段:
T* new_data = new T[new_capacity];<br>for (size_t i = 0; i < size_; ++i) {<br> new (&new_data[i]) T(std::move(data_[i])); // 移动构造<br> data_[i].~T(); // 显式析构<br>}<br>delete[] data_;<br>data_ = new_data;
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
增长因子选 1.5 还是 2?影响内存碎片和性能
增长因子决定每次扩容时新容量是旧容量的多少倍。选 2 简单但浪费内存(比如从 1MB 扩到 2MB,旧 1MB 可能很快被系统回收,但新 2MB 占用更久);选 1.5 减少峰值内存占用,但计算稍复杂(需向上取整)。
标准库实现通常用 1.5(如 GCC libstdc++)或黄金比例近似值(1.618),避免 2 的幂次导致内存分配器反复申请相邻大块内存,加剧碎片。
实操注意点:
- 避免用
capacity_ *= 2粗暴翻倍,改用capacity_ = capacity_ + capacity_ / 2或类似方式逼近 1.5 倍 - 首次分配别太小(如 1),否则早期频繁扩容;建议起步至少 4 或 8
- 增长后记得检查是否溢出:
if (new_capacity → 触发异常或 abort
移动语义能显著降低扩容开销
当数组里存的是大对象(如 std::vector<:string></:string>),拷贝整个对象代价高。C++11 后,应优先使用移动而非拷贝来转移旧数据。
关键不是“能不能移动”,而是“有没有正确声明移动操作”。若类没定义移动构造函数,std::move 也会退化为拷贝。
验证方式:
- 对自定义类型,确保有
T(T&&)和T& operator=(T&&),且标记为noexcept - 扩容循环中用
new (&new_data[i]) T(std::move(data_[i]))而非T(data_[i]) - 编译时加
-fno-exceptions时,noexcept更重要,否则移动可能被禁用
真正难的不是写扩容逻辑,是让所有参与类型的移动语义可靠生效——漏掉一个 noexcept,整个链路就回退到深拷贝。

















