C++ STL模板库9-容器2-vector
文章目录
- [C++ STL模板库9-容器2-vector](#C++ STL模板库9-容器2-vector)
-
- 一、基础概念
-
- [1. 类型成员(Type Members)](#1. 类型成员(Type Members))
- [2. 模板参数](#2. 模板参数)
- 二、构造函数
-
- [1. 语法](#1. 语法)
- [2. 示例](#2. 示例)
- 三、元素访问
-
- [1. 函数说明](#1. 函数说明)
- [2. 示例代码](#2. 示例代码)
- 四、容量操作
-
- [1. 函数说明](#1. 函数说明)
- [2. 关键点说明](#2. 关键点说明)
- [3. 关键操作解析](#3. 关键操作解析)
- [4. 操作示例](#4. 操作示例)
- 五、修改
-
- [1. 函数说明](#1. 函数说明)
- [2. 关键操作解析](#2. 关键操作解析)
- [3. 操作示例](#3. 操作示例)
- [4. 操作技巧](#4. 操作技巧)
- 六、迭代器操作
-
- [1. 函数说明](#1. 函数说明)
- [2. 关键操作解析](#2. 关键操作解析)
- [3. 操作示例](#3. 操作示例)
- [4. 注意事项](#4. 注意事项)
- 七、高效操作(C++11+)
-
- [1. 函数说明](#1. 函数说明)
- [2. 关键操作解析](#2. 关键操作解析)
- [3. 常见错误规避](#3. 常见错误规避)
- [4. 优先选择原则](#4. 优先选择原则)
- 八、非成员函数
-
- [1. 函数说明](#1. 函数说明)
- [2. 示例代码](#2. 示例代码)
- [3. 操作对比总结](#3. 操作对比总结)
- 九、完整应用示例
-
- [1. 类对象的向量](#1. 类对象的向量)
- [2. 通过数组初始化向量](#2. 通过数组初始化向量)
- 十、关键特性
- 十一、应用注意事项⚖️
-
- [1. 完美转发陷阱](#1. 完美转发陷阱)
- [2. 高性能尾部操作](#2. 高性能尾部操作)
- [3. 内存释放替代方案](#3. 内存释放替代方案)
- [4. 函数选择指南](#4. 函数选择指南)
- [5. 避免裸指针操作](#5. 避免裸指针操作)
vector
- 动态数组,也叫向量,内存连续,动态管理内存,下标访问,尾部增删效率高。是最常用的容器。
一、基础概念
1. 类型成员(Type Members)
类型 | 说明 |
---|---|
value_type |
元素类型(如 int ) |
allocator_type |
分配器类型(默认 std::allocator<T> ) |
size_type |
大小类型(通常 size_t ) |
reference |
元素引用(value_type& ) |
const_reference |
常量引用(const value_type& ) |
iterator |
随机访问迭代器 |
const_iterator |
常量迭代器 |
2. 模板参数
-
std::vector<T>(...) v; // T:元素类型, ...:参数
-
示例:
cpp#include <vector> vector<int> vn; vector<string> vs; // 元素的预分配和初始化 vector<int> vn (10); // 用0初始化10个元素 vector<int> vn (10, 5); // 用5初始化10个元素
二、构造函数
1. 语法
cpp
vector(); // 默认构造
vector(size_type count, T value); // 填充构造
vector(iterator first, iterator last); // 范围构造
vector(initializer_list<T> init); // 初始化列表构造
2. 示例
cpp
#include <iostream>
#include <vector>
#include <array>
int main() {
// ========== 1. 默认构造 ==========
std::vector<int> v1; // 创建空vector
std::cout << "v1大小: " << v1.size() << "\n"; // 输出 0
// ========== 2. 填充构造 ==========
std::vector<char> v2(5, 'A'); // 5个'A'
std::cout << "v2内容: ";
for (char c : v2) std::cout << c; // 输出 AAAAA
// ========== 3. 范围构造 ==========
std::array<int, 3> arr{10, 20, 30};
std::vector<int> v3(arr.begin(), arr.end()); // 复制数组元素
std::cout << "\nv3内容: ";
for (int n : v3) std::cout << n << " "; // 输出 10 20 30
// ========== 4. 初始化列表构造 ==========
std::vector<std::string> v4 = {"Apple", "Banana", "Cherry"};
std::cout << "\nv4内容: ";
for (auto& s : v4) std::cout << s << " "; // 输出 Apple Banana Cherry
// ========== 组合验证 ==========
std::vector<int> v5{
v3.begin(), // 复用v3的范围构造
v3.begin() + 2
};
std::cout << "\nv5内容: ";
for (int n : v5) std::cout << n << " "; // 输出 10 20
return 0;
}
-
初始化列表的等价操作
cpp// 以下两种写法等价 std::vector<int> a = {1,2,3}; std::vector<int> b({1,2,3}); // 显式构造函数
-
显式类型转换
cpp// 避免整数类型混淆 std::vector<int> v1(5, 2.5); // 输出 {2,2,2,2,2}(截断) std::vector<double> v2{5, 2.5}; // 输出 {5.0, 2.5}(保留精度)
三、元素访问
1. 函数说明
函数 | 说明 |
---|---|
at(size_type pos) |
边界检查访问(越界抛异常) |
operator[](size_type pos) |
无检查直接访问 |
front() |
首元素引用 |
back() |
末元素引用 |
data() |
底层数组指针 |
2. 示例代码
cpp
#include <iostream>
#include <vector>
#include <stdexcept> // 异常处理
int main() {
std::vector<int> nums{10, 20, 30, 40, 50};
// ===== 1. at()
std::cout << "at(2): " << nums.at(2) << '\n'; // ✅ 输出 30
// ===== 2. operator[] 无检查访问 =====
std::cout << "operator[3]: " << nums[3] << '\n'; // ✅ 输出 40
// nums[10] = 100; // ❌ 未定义行为(可能崩溃或数据污染)
// ===== 3. front() & back() 端点访问 =====
std::cout << "front(): " << nums.front() << '\n'; // ✅ 输出 10(首元素)
std::cout << "back(): " << nums.back() << '\n'; // ✅ 输出 50(尾元素)
// ===== 4. data() 底层指针访问 =====
int* ptr = nums.data();
std::cout << "data()[1]: " << ptr[1] << '\n'; // ✅ 输出 20
*ptr = 100; // 修改首元素
std::cout << "front()修改后: " << nums.front() << '\n'; // 输出 100
return 0;
}
- 优先选择原则:
- 用户输入索引必须用
at()
安全 - 内部循环索引访问用
operator[]
高效 - 和C兼容用
data()
兼容
- 用户输入索引必须用
四、容量操作
1. 函数说明
函数 | 作用描述 | 时间复杂度 | 内存影响 |
---|---|---|---|
empty() |
判断容器是否为空 | O(1) | 无 |
size() |
获取当前元素数量 | O(1) | 无 |
max_size() |
系统支持的最大元素数 | O(1) | 无 |
capacity() |
当前预分配内存容量 | O(1) | 无 |
reserve(new_cap) |
预扩容至指定容量 | O(n) | 可能重新分配内存 |
shrink_to_fit() |
释放多余内存(非强制) | O(n) | 可能缩小容量 |
2. 关键点说明
- 大小可增可减,引起大小变化的函数包括
resize()/push_back()/pop_back()/insert()/erase()
。 - 容量只增不减,直接引起容量变化的函数只有
reserve()
一个。 - 大小的增加可能导致容量的增加,容量的变化不会引起大小的变化。
- 通过resize()增加大小,新增部分会被初始化,但是通过reserve()增加容量,新增部分不做初始化。
- 位于容量范围内但在大小范围外的元素,可以通过下标或迭代器访问,但其值不确定。
- 无论是大小还是容量,它们的变化永远发生在向量容器的尾部。
3. 关键操作解析
-
基础状态检测(
empty()
/size()
)cppstd::vector<int> v; std::cout << "初始状态: " << "empty=" << v.empty() // 输出 true << ", size=" << v.size() // 输出 0 << "\n"; v.push_back(10); std::cout << "添加元素后: " << "empty=" << v.empty() // 输出 false << ", size=" << v.size() // 输出 1 << "\n";
-
容量动态增长(
capacity()
/reserve()
)cppstd::vector<std::string> words; // 初始容量(实现相关,通常0) std::cout << "初始容量: " << words.capacity() << "\n"; // 输出 0 words.reserve(3); // 预分配3元素空间 std::cout << "reserve(3)后容量: " << words.capacity() << "\n"; // 输出 3 words = {"A", "B", "C", "D"}; // 可能触发扩容(容量可能变6) std::cout << "超容添加后容量: " << words.capacity() << "\n"; // 输出 4
-
极限容量检测(
max_size()
)cppstd::vector<double> data; std::cout << "系统支持最大元素数: " << data.max_size() / 1e9 << " 亿" // 示例输出 2.30584e+09 亿 << "\n(实际受限于内存和系统架构)\n";
-
内存回收操作(
shrink_to_fit()
)cppstd::vector<char> buffer(1000); buffer.erase(buffer.begin()+100, buffer.end()); // 删除900元素 std::cout << "删除后: size=" << buffer.size() // 输出 100 << ", capacity=" << buffer.capacity() // 输出 1000 << "\n"; buffer.shrink_to_fit(); // 请求释放内存 std::cout << "shrink后: capacity=" << buffer.capacity() // 输出 100(可能) << "\n(实际效果依赖编译器实现)\n";
-
size()
vscapacity()
关系
-
扩容策略优化(以GCC为例)
cpp// GCC扩容规则:new_cap = max(2*old_cap, required_size) std::vector<int> v; v.reserve(1); // capacity=1 v.push_back(1); v.push_back(2); // 触发扩容 → capacity=2 v.push_back(3); // 再次扩容 → capacity=4
-
shrink_to_fit()
的底层实现cpp// 典型实现流程 if (size() < capacity()) { vector tmp(*this); // 拷贝构造新vector(按size分配) swap(tmp); // 交换数据指针 }
4. 操作示例
cpp
// 向量的大小和容量
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void print (vector<int>& vn) {
std::cout << "大小:" << vn.size () << " ";
std::cout << "容量:" << vn.capacity () << std::endl;
for (vector<int>::iterator it = vn.begin ();
it != vn.end (); it++)
cout << *it << ' ';
cout << endl;
}
int main (void) {
vector<int> v1 (10);
print (v1); //大小:10 容量:10 //0 0 0 0 0 0 0 0 0 0
v1.push_back (23);
print (v1); //大小:11 容量:20 //0 0 0 0 0 0 0 0 0 0 23
v1.resize (5);
print (v1); //大小:5 容量:20 //0 0 0 0 0
v1.resize (30);
print (v1); //大小:30 容量:30 //0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
for (int i = 0; i < 10; i++)//大小:20 容量:30
v1.pop_back ();
print (v1); //0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
v1.reserve (40); //大小:20 容量:40
print (v1); //0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
v1.reserve (10); //大小:20 容量:40
print (v1); //0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
v1.insert (v1.begin () + 4, 55);//大小:21 容量:40
print (v1); //0 0 0 0 55 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
v1.erase (v1.begin () + 4);//大小:20 容量:40
print (v1); //0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
v1.insert (v1.begin () + 4, 66);//大小:21 容量:40
print (v1); //0 0 0 0 66 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
vector<int>::iterator it =
find (v1.begin (), v1.end (), /*66*/88);
if (it == v1.end ())
cout << "没找到!" << endl;//没找到!
else
cout << *it << endl; //66
int arr[] = {12, 56, 77, 45, 98, 100, 2};
int *p = find (arr, arr + sizeof (arr) / sizeof (arr[0]), 45);
cout << *p << endl; // 45
return 0;
}
五、修改
1. 函数说明
函数 | 说明 | 内存影响 |
---|---|---|
clear() |
清空所有元素 | 元素销毁,容量保留 |
push_back(const T& value) |
尾部插入元素 | 可能触发扩容 |
pop_back() |
删除尾部元素 | 尾部元素销毁(容量不变) |
insert(iterator pos, const T& value) |
指定位置插入 | 插入节点后元素后移 |
erase(iterator pos) |
删除指定元素 | 删除节点后元素前移 |
resize(size_type count) |
调整元素数量 | 增删默认构造元素 |
swap(vector& other) |
交换两个容器内容 | 仅交换指针 |
assign() |
完全替换 vector 内容 |
原元素全销毁 |
2. 关键操作解析
-
clear()
vsresize(0)
cppstd::vector<int> v{1,2,3}; v.clear(); // 内存状态: [ ] 容量=3 v.resize(0); // 效果相同(但依赖实现)
-
插入删除的性能陷阱,头部插入和尾部插入都会使元素向后移动
cpp// 连续头部插入:O(n²) 性能灾难! for (int i = 0; i < 1000; ++i) v.insert(v.begin(), i); // 每次插入全体后移 // 优化方案:尾部插入后反转 for (int i = 0; i < 1000; ++i) v.push_back(i); std::reverse(v.begin(), v.end());
-
swap
的原子性优势cppcurrent_data.swap(new_data); // 原子指针交换
-
assign
的隐蔽风险cppstd::vector<std::string> names{"Alice", "Bob"}; std::vector<std::string> temp = names; // 深拷贝备份 names.assign(1000, "Unknown"); // 原元素"Alice","Bob"被销毁 // 若异常抛出:原数据丢失且无恢复!
3. 操作示例
cpp
#include <iostream>
#include <vector>
void print(const std::vector<int>& v, const char* msg) {
std::cout << msg << ": [";
for (int n : v) std::cout << n << " ";
std::cout << "] 容量: " << v.capacity() << "\n\n";
}
int main() {
// === 1. 初始状态 ===
std::vector<int> nums{10, 20, 30};
print(nums, "初始状态"); // [10 20 30] 容量:3
// === 2. push_back:尾部插入 ===
nums.push_back(40);
print(nums, "push_back(40)"); // [10 20 30 40] 容量:6(触发扩容)
// === 3. pop_back:尾部删除 ===
nums.pop_back();
print(nums, "pop_back()"); // [10 20 30] 容量:6
// === 4. insert:指定位置插入 ===
nums.insert(nums.begin() + 1, 99);
print(nums, "insert(pos1,99)"); // [10 99 20 30] 容量:6
// === 5. erase:指定位置删除 ===
nums.erase(nums.begin() + 2);
print(nums, "erase(pos2)"); // [10 99 30] 容量:6
// === 6. resize:调整元素数量 ===
nums.resize(5); // 新增2个默认0
print(nums, "resize(5)"); // [10 99 30 0 0] 容量:6
nums.resize(2); // 删除尾部3个
print(nums, "resize(2)"); // [10 99] 容量:6
// === 7. assign:完全替换内容 ===
nums.assign({7, 8, 9, 10});
print(nums, "assign({7,8,9,10})"); // [7 8 9 10] 容量:6
// === 8. swap:容器交换 ===
std::vector<int> other{100, 200};
nums.swap(other);
print(nums, "swap后nums"); // [100 200] 容量:2
print(other, "swap后other"); // [7 8 9 10] 容量:6
// === 9. clear:清空元素 ===
nums.clear();
print(nums, "clear()后"); // [] 容量:2(容量保留!)
return 0;
}
4. 操作技巧
-
容量预分配规则 ,提前把容量分配好,避免在循环里使用
push_back()
时扩容造成时间开销cppstd::vector<int> data; data.reserve(1000); // 预分配避免多次扩容 for (int i=0; i<1000; ++i) data.push_back(i); // 无扩容开销
-
安全删除模式
cpp// 删除特定条件元素(避免迭代器失效) auto it = data.begin(); while (it != data.end()) { if (*it % 2 == 0) it = data.erase(it); // erase返回下一有效迭代器 else ++it; }
-
移动语义优化
cpp// 在 words 尾部插入字符串变量 huge_str 使用移动语义而非拷贝 std::vector<std::string> words; std::string huge_str = get_large_string(); words.push_back(std::move(huge_str)); // 移动而非拷贝
六、迭代器操作
1. 函数说明
cpp
begin() / end(); // 正向迭代器
rbegin() / rend(); // 反向迭代器
cbegin() / cend(); // 常量正向迭代器
crbegin() / crend(); // 常量反向迭代器
2. 关键操作解析
-
随机访问:下标、迭代器
cppvn[1] = 40; *(vn.begin () + 1) = 40;
-
基础正向迭代(可读写)
cppstd::vector<int> nums{10, 20, 30}; // 修改第二个元素 auto it = nums.begin() + 1; *it = 200; // ✅ 允许修改 for (auto i = nums.begin(); i != nums.end(); ++i) std::cout << *i << " "; // 输出:10 200 30
-
反向迭代(镜像操作)
cppstd::vector<char> letters{'A','B','C'}; // 逆序输出并修改首元素 auto rit = letters.rbegin(); *rit = 'Z'; // ✅ 修改最后一个元素 for (auto r = letters.rbegin(); r != letters.rend(); ++r) std::cout << *r << " "; // 输出:Z B A
-
常量反向迭代(安全遍历)
cppconst std::string str = "HELLO"; // 逆序输出且无法修改 for (auto cr = str.crbegin(); cr != str.crend(); ++cr) std::cout << *cr; // 输出:OLLEH
-
迭代器类型转换关系
cppstd::vector<int>::iterator // begin()返回类型 std::vector<int>::const_iterator // cbegin()返回类型 std::vector<int>::reverse_iterator // rbegin()返回类型 std::vector<int>::const_reverse_iterator // crbegin()返回类型
-
与C风格数组的互操作
cppint arr[] = {100, 200, 300}; // 用标准库函数获取迭代器 auto arr_begin = std::begin(arr); // ✅ 等效 &arr[0] auto arr_end = std::end(arr); // ✅ 等效 &arr[3]
3. 操作示例
cpp
#include <iostream>
#include <vector>
using namespace std;
void print (vector<int>& vn)
{
vector<int>::size_type size = vn.size ();
cout << "元素个数:" << size << " -> ";
for (vector<int>::size_type i = 0; i < size; i++)
cout << vn[i] << ' ';
cout << endl;
}
void show (vector<int>& vn)
{
for (vector<int>::iterator it = vn.begin (); it != vn.end (); it++)
cout << *it << ' ';
cout << endl;
}
int main (void)
{
vector<int> vn;
vn.push_back (10);
vn.push_back (20);
vn.push_back (30);
print (vn); // 元素个数:3 -> 10 20 30
show (vn); // 10 20 30
vn.pop_back ();
print (vn); // 元素个数:2 -> 10 20
vn[1] = 40;
print (vn); // 元素个数:2 -> 10 40
vector<int>::const_iterator it = vn.begin ();
it++;
// (*it)++; // it的目标不可修改,如同常量指针
// const xxx *或xxx const *
// 而const vector<int>::iterator it 表示迭代器本身不可修改,如同指针常量
// xxx * const
vector<int> v1 (10);
print (v1); // 元素个数:10 -> 0 0 0 0 0 0 0 0 0 0
vector<int> v2 (10, 5);
print (v2); // 元素个数:10 -> 5 5 5 5 5 5 5 5 5 5
v1[0] = 100;
v1[9] = 200;
cout << v1.front () << ' ' << v1.back() << endl;//100 200
return 0;
}
4. 注意事项
-
类型推导陷阱
cppauto it1 = nums.begin(); // 推导为iterator(可修改) auto it2 = nums.cbegin(); // 推导为const_iterator(安全)
-
范围循环本质
cppfor (int n : nums) { /*...*/ } // 等价于: for (auto it = nums.begin(); it != nums.end(); ++it)
-
迭代器失效场景
cppstd::vector<int> data{1,2,3}; auto it = data.begin(); data.push_back(4); // 可能触发扩容 *it = 5; // ❌ 迭代器已失效(未定义行为)
七、高效操作(C++11+)
1. 函数说明
函数 | 说明 |
---|---|
emplace_back(Args&&... args) |
直接构造尾部元素 |
emplace(iterator pos, Args&&... args) |
直接构造指定位置元素 |
2. 关键操作解析
cpp
// 直接构造对象(避免拷贝)
struct Point { int x, y; };
std::vector<Point> points;
points.emplace_back(3, 4); // 直接调用构造函数
cpp
// ✅ 推荐使用emplace的情况
v.emplace_back("text", 100); // 构造参数简单
v.emplace(v.begin(), 1, 2, 3); // 插入位置敏感
// ⚠️ 不推荐使用的情况
v.emplace_back(existing_obj); // 已有对象直接push_back更清晰
cpp
// 简单类型(int/double等)
v.push_back(42); // ✅ 清晰简洁
// 多参数构造的复杂类型
v.emplace_back("ID007", 9.8, Vector3D()); // ✅ 避免临时对象
// 在迭代器中间插入
v.emplace(v.begin() + 2, config); // ✅ 减少元素移动次数
// 对象已存在时
auto obj = HeavyObject(...);
v.push_back(std::move(obj)); // ✅ 明确所有权转移
3. 常见错误规避
错误案例 | 后果 | 修正方案 |
---|---|---|
emplace_back({1,2,3}) |
编译错误(初始化列表歧义) | emplace_back(std::initializer_list{1,2,3}) |
emplace(pos, arg) 后使用旧迭代器 |
迭代器失效 | 接收返回的新迭代器 it = emplace(...) |
忽略移动语义类型 | 意外拷贝操作 | 对移动类型显式使用 std::move |
4. 优先选择原则
cpp
// 简单类型(int/double等)
v.push_back(42); // ✅ 清晰简洁
// 多参数构造的复杂类型
v.emplace_back("ID007", 9.8, Vector3D()); // ✅ 避免临时对象
// 在迭代器中间插入
v.emplace(v.begin() + 2, config); // ✅ 减少元素移动次数
// 对象已存在时
auto obj = HeavyObject(...);
v.push_back(std::move(obj)); // ✅ 明确所有权转移
八、非成员函数
1. 函数说明
函数 | 说明 |
---|---|
operator==, !=, <, > |
容器比较 |
std::swap(vector<T>& lhs, rhs) |
特化交换 |
erase(vector<T>& c, const T& value) |
删除匹配值(C++20) |
erase_if(vector<T>& c, Predicate pred) |
条件删除(C++20) |
2. 示例代码
-
容器比较运算符
cppstd::vector a{1,2,3}, b{1,2,3}, c{9}; // 等值比较(需元素数量和值相同) bool eq = (a == b); // ✅ true(深层次比较) // 字典序比较(常用于排序) bool lt = (c < a); // ✅ true(9<1)
-
特化交换函数
cppstd::vector v1{10,20}, v2{30}; // 高性能交换(无拷贝,仅交换指针) std::swap(v1, v2); // v1: [30]|v2: [10,20](容量同步交换)
-
值匹配删除
erase()
(C++20)cppstd::vector nums{5,2,5,8}; // 删除所有值为5的元素 std::erase(nums, 5); // 结果:[2,8](返回删除数量)
-
条件删除
erase_if()
(C++20)cppstd::vector data{-3,7,-1,0}; // 删除所有负数(λ表达式) std::erase_if(data, [](int x){return x<0;}); // 结果:[7,0](支持复杂逻辑)
3. 操作对比总结
函数 | 典型场景 | 优势 |
---|---|---|
operator== |
配置比对/结果验证 | 深度比较容器内容 |
std::swap(vector&, vector&) |
线程安全数据交换 | 零拷贝原子操作 |
erase(vector&, value) |
批量删除特定值 | 比循环+erase高效 |
erase_if(vector&, pred) |
复杂条件删除(如负/奇数) | 避免手写易出错的迭代器逻辑 |
九、完整应用示例
1. 类对象的向量
- 如果一个类类型的对象需要被存储在向量中,那么这个类至少应该支持无参构造,以确保为这个向量所分配的内存能够被正确的初始化。
- 该类可能还需要支持拷贝构造函数和拷贝赋值操作符重载函数。
- 如有必要可能还需要支持"=="和"<"操作符。
cpp
// 类对象的向量
#include <iostream>
#include <vector>
using namespace std;
class Integer {
public:
Integer (void) : m_data (0) {
cout << "无参构造:" << this << endl;
}
Integer (int data) : m_data (data) {
cout << "有参构造:" << this << endl;
}
~Integer (void) {
cout << "析构函数:" << this << endl;
}
Integer (const Integer& that) :
m_data (that.m_data) {
cout << "拷贝构造:" << &that << " -> " <<
this << endl;
}
Integer& operator= (const Integer& that) {
cout << "拷贝赋值:" << &that << " -> " <<
this << endl;
if (&that != this)
m_data = that.m_data;
return *this;
}
bool operator== (const Integer& that) const {
return m_data == that.m_data;
}
bool operator< (const Integer& that) const {
return m_data > that.m_data;
}
int m_data;
};
int main (void) {
cout << "-------- 1 --------" << endl;
vector<Integer> vi (5, Integer (100));//1次有参构造 5次拷贝构造 6次析构
cout << "-------- 2 --------" << endl;
vi.erase (vi.begin ()); //删除节点后元素前移 //4次拷贝赋值 5次析构
cout << "-------- 3 --------" << endl;
vi.insert (vi.begin (), Integer (200));//插入节点后元素后移 //1次有参构造 1次拷贝构造 4次拷贝赋值 6次析构
cout << "-------- 4 --------" << endl;
vi.resize (1); //调整容器大小为1 //5 次析构
cout << "-------- 5 --------" << endl;
vi.resize (5); //调整容器大小为5 //5 次析构
cout << vi.capacity() << endl; //5
cout << "-------- 6 --------" << endl;
vi.reserve (10); //调整容器大小为 10 //5次拷贝构造 添加5个初始化的0元素
cout << "-------- X --------" << endl;
vi[0] = 12; //分别调用5次 有参构造 拷贝赋值 析构函数:
vi[1] = 23;
vi[2] = 47;
vi[3] = 33;
vi[4] = 47;
return 0;
}
2. 通过数组初始化向量
-
核心代码:
vector<int> v1(&arr[0], &arr[5]);
cppint arr[5] = {1, 2, 3, 4, 5}; vector<int> v1 (&arr[0], &arr[5]);
-
迭代器范围原理:
-
&arr[0] 指向首元素(包含)
-
&arr[5] 是尾后指针(不包含)
-
实际构造范围:[arr[0], arr[5]) → 等价于 arr[0]到arr[4]
cpp// 用容器初始化容器 #include <iostream> #include <vector> using namespace std; int main (void) { int arr[5] = {1, 2, 3, 4, 5}; vector<int> v1 (&arr[0], &arr[5]);//实际构造范围:[arr[0], arr[5]) → 等价于 arr[0]到arr[4] for (int i = 0; i < v1.size (); i++) cout << v1[i] << ' '; cout << endl; // 1 2 3 4 5 vector<int> v2 (v1.begin () + 1, v1.end () - 1); for (int i = 0; i < v2.size (); i++) cout << v2[i] << ' '; cout << endl; // 2 3 4 for (vector<int>::reverse_iterator it = v1.rbegin (); it != v1.rend (); it++) cout << *it << ' '; // 5 4 3 2 1 cout << endl; return 0; }
-
十、关键特性
- 动态内存管理:自动扩容(通常按2倍扩容)
- 连续存储:支持指针算术操作
- 异常安全:强异常保证(部分操作)
- 时间复杂度:
- 随机访问:O(1)
- 尾部插入/删除:均摊O(1)
- 中间插入/删除:O(n)
十一、应用注意事项⚖️
1. 完美转发陷阱
cpp
std::vector<std::unique_ptr<Device>> devices;
// 错误:尝试复制 unique_ptr
// devices.emplace_back(new Device());
// 正确:显式移动
devices.emplace_back(std::make_unique<Device>());
2. 高性能尾部操作
cpp
vector<int> data;
data.reserve(1000); // 预分配避免多次扩容
for(int i=0; i<1000; ++i)
data.emplace_back(i);
// C++20 安全删除
erase_if(data, [](int x){ return x%2==0; });
3. 内存释放替代方案
cpp
// 强制释放内存的可靠方法
std::vector<int> huge_data = get_data();
std::vector<int>(huge_data).swap(huge_data); // 通过临时对象交换
4. 函数选择指南
场景 | 推荐操作 | 理论依据 |
---|---|---|
容器状态检测 | empty() 替代size()==0 |
语义更清晰 |
高性能批量插入 | reserve() 预分配 |
避免多次扩容开销 |
内存敏感型系统 | shrink_to_fit() |
减少内存占用(可能有效) |
跨平台开发 | 避免依赖max_size() |
不同系统差异过大 |
实时系统 | 慎用动态扩容 | 防止不可预测的内存分配延迟 |
5. 避免裸指针操作
cpp
// 改用data()获取首元素指针
vector<int> v{1,2,3};
int* p = v.data(); // 等效&v[0]但更安全
实际开发中,优先使用 emplace_back()
替代 push_back()
避免拷贝,结合 reserve()
可显著提升性能。