详解STL容器之——vector

部署运行你感兴趣的模型镜像

支持语音听播客

一、vector 基本概念

1.1 什么是 vector

vector 是 C++ 标准模板库(STL)中的一个动态数组容器,它能够自动调整大小以容纳元素。与传统数组相比,vector 提供了更丰富的功能和更高的安全性。

1.2 为什么使用 vector

  • 动态扩容:无需预先指定大小,可随时添加 / 删除元素
  • 内存管理:自动处理内存分配和释放,避免内存泄漏
  • 丰富接口:提供插入、删除、排序等便捷操作
  • 安全访问:支持边界检查(at () 方法),减少越界风险
  • 通用性:可存储任意类型的元素(通过模板实现)

 

1.3 vector 的内存结构

  • vector 在内存中以连续空间存储元素,类似数组

  • 当空间不足时,vector 会自动重新分配更大的内存块,并将原有元素复制过去

  • 扩容策略通常是倍增(如每次扩容为当前容量的 2 倍)

二、vector 的基本操作

2.1 头文件与命名空间

#include <vector>

using namespace std;

2.2 定义与初始化

// 定义空 vector
vector<int> vec1;  // 存储 int 类型的空 vector

// 定义并初始化大小
vector<double> vec2(5);  // 5 个元素,默认初始化为 0

// 定义并初始化元素值
vector<char> vec3(3, 'A');  // 3 个元素,值为 'A'

// 使用初始化列表(C++11+)
vector<int> vec4 = {1, 3, 5, 7, 9};
vector<string> vec5 {"apple", "banana", "cherry"};

// 从数组或其他容器复制
int arr[] = {2, 4, 6, 8};
vector<int> vec6(arr, arr + 4);  // 复制数组的前 4 个元素

2.3 元素访问

vector<int> nums = {10, 20, 30, 40, 50};
// 下标访问(不检查越界)
cout << nums[0] << endl;  // 输出:10
nums[2] = 35;             // 修改元素
// at() 方法(检查越界)
cout << nums.at(3) << endl;  // 输出:40
// nums.at(10) = 100;  // 越界会抛出 out_of_range 异常
// 访问首尾元素
cout << nums.front() << endl;  // 输出:10(首元素)
cout << nums.back() << endl;   // 输出:50(尾元素)
// 获取迭代器(指向元素的指针)
vector<int>::iterator it = nums.begin();  // 指向首元素
cout << *it << endl;  // 输出:10

2.4 添加元素

vector<int> vec;
// 尾部添加元素(常用)
vec.push_back(10);
vec.push_back(20);
vec.push_back(30);  // vec 现在包含 {10, 20, 30}
// 在指定位置插入元素
vec.insert(vec.begin() + 1, 15);  // 在第 2 个位置插入 15
// vec 现在包含 {10, 15, 20, 30}
// 批量插入
vector<int> newElements = {40, 50};
vec.insert(vec.end(), newElements.begin(), newElements.end());
// vec 现在包含 {10, 15, 20, 30, 40, 50}

2.5 删除元素

   

vector<int> vec = {1, 2, 3, 4, 5};

// 删除尾部元素
vec.pop_back();  // vec 变为 {1, 2, 3, 4}
// 删除指定位置元素
vec.erase(vec.begin() + 2);  // 删除第 3 个元素
// vec 变为 {1, 2, 4}
// 删除区间元素
vec.erase(vec.begin(), vec.begin() + 2);  // 删除前 2 个元素
// vec 变为 {4}
// 清空所有元素
vec.clear();  // vec 变为空,size = 0

2.6 容量与大小

vector<int> vec;
// 初始状态
cout << "容量:" << vec.capacity() << endl;  // 通常为 0
cout << "大小:" << vec.size() << endl;      // 0
// 添加元素后
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);
cout << "容量:" << vec.capacity() << endl;  // 通常为 4(自动扩容)
cout << "大小:" << vec.size() << endl;      // 3
// 调整大小
vec.resize(5);  // 增加到 5 个元素,新增元素默认初始化为 0
cout << "调整后大小:" << vec.size() << endl;  // 5
// 预分配容量(避免频繁扩容)
vec.reserve(10);  // 预分配 10 个元素的空间
cout << "预分配后容量:" << vec.capacity() << endl;  // 10

简单来说,capacity(容量)和 size(大小)是两个不同的概念:

  • size​:指的是当前 vector中实际持有的元素数量。

  • capacity​:指的是 vector当前已分配的内存空间最多能够容纳的元素数量,在下次需要重新分配内存之前,size可以增长到的上限

核心原因在于,vector为了减少频繁内存分配和数据拷贝的开销,在每次扩容时,​​并不是仅仅申请容纳一个新元素的空间​​,而是会申请一块更大的内存。下面这个表格清晰地展示了你提供的代码示例中容量和大小是如何变化的。

🔢 容量与大小变化示例

操作步骤

size()(元素数量)

capacity()(总容量)

说明

vector<int> vec;

0

​0​​ (或由实现定义)

初始状态,空容器

vec.push_back(1);

1

​1​

插入第一个元素,分配刚好容纳1个元素的空间。

vec.push_back(2);

2

​2​

插入第二个元素,容量不足,触发第一次扩容。

vec.push_back(3);

3

​4​

插入第三个元素,容量再次不足,触发第二次扩容。

💡 深入理解扩容机制

  • ​扩容的触发条件​:当执行 push_backinsert或 resize等操作导致 size即将超过当前的 capacity时,vector就会自动进行扩容
  • ​扩容的策略:倍增​​(例如,扩大到当前容量的 2 倍)

在上面的例子中,从容纳2个元素扩容到容纳4个元素,就是采用的2倍策略。这种策略的优点是使得连续插入n个元素的​​均摊时间复杂度为 O(1)​​,虽然单次扩容可能开销大,但总体来看非常高效。

  • ​扩容的具体步骤​

​申请新内存​​:在另一块空闲内存中申请一个更大的连续空间(例如,原容量的2倍)。•

​迁移数据​​:将原有内存中的所有元素拷贝或移动到新内存中。

​释放旧内存​​:析构并释放原来的内存块。

更新指针​​:内部指针指向新的内存空间,并更新 capacity值。

​重要提示​​:这个重新分配的过程会导致之前获取的所有​​迭代器、指针和引用失效​

  • 不同实现的差异​

需要注意的是,​​C++ 标准并未规定具体的扩容因子​​,这取决于不同的编译器和标准库实现。虽然 2 倍增长很常见(如 GCC),但其他实现(如 MSVC)可能会采用 1.5 倍等不同的增长因子,以在内存利用率和分配频率之间取得更好的平衡

🚀 性能优化建议

如果你在编码时能预知 vector最终需要容纳的大致元素数量,强烈建议使用 reserve()函数提前分配足够的内存。

vector<int> vec;
vec.reserve(100); // 预先分配至少能容纳100个元素的内存空间

for (int i = 0; i < 100; ++i) {
    vec.push_back(i); // 在添加前100个元素时,将不会触发任何扩容操作
}

这样做可以​​完全避免在添加元素过程中因多次扩容带来的性能损耗​​,对于处理大量数据时性能提升非常显著。

三、vector 的遍历方式

3.1 下标遍历

vector<int> nums = {1, 3, 5, 7, 9};
for (int i = 0; i < nums.size(); i++) {
    cout << nums[i] << " ";
}
cout << endl;

3.2 迭代器遍历

vector<int> nums = {1, 3, 5, 7, 9};
// 正向迭代器
for (vector<int>::iterator it = nums.begin(); it != nums.end(); it++) {
    cout << *it << " ";
}
cout << endl;
// 反向迭代器
for (vector<int>::reverse_iterator rit = nums.rbegin(); rit != nums.rend(); rit++) {
    cout << *rit << " ";  // 反向输出:9 7 5 3 1
}
cout << endl;
// C++11 自动类型推导(更简洁)
for (auto it = nums.begin(); it != nums.end(); it++) {
    cout << *it << " ";
}
cout << endl;

🚀迭代器的类型必须明确声明

注意:第二个for循环中,不可以直接省略 vector<int>::,因为iterator是依赖于特定容器的类型,这是C++语法规则的要求。
不过,从C++11标准开始,可以使用 auto关键字来让编译器自动推导迭代器的类型,从而简化代码书写。
下面的表格对比了三种声明迭代器的方式:


💡反向迭代器

反向迭代器理解为对普通迭代器(正向迭代器)的一种​​适配器​​。它的核心设计是​​反转方向​​:

  • 对反向迭代器进行 ++操作,相当于对其内部封装的正向迭代器进行 --操作,从而向容器头部移动。
  • 对反向迭代器进行 --操作,则相当于对正向迭代器进行 ++操作,向容器尾部移动

这种设计使得 rbegin()和 rend()与 begin()和 end()形成了一种​​镜像对称​​关系

  • rbegin():返回一个反向迭代器,它指向容器的最后一个元素。在实现上,它通常由 end()适配而来
  • rend():返回一个反向迭代器,它指向容器第一个元素的前一个位置(一个"起始之前"的虚拟位置)。在实现上,它通常由 begin()适配而来

3.3 foreach 循环(C++11+)

vector<int> nums = {1, 3, 5, 7, 9};

// 只读遍历
for (int num : nums) {
    cout << num << " ";
}
cout << endl;
// 修改元素(使用引用)
for (int &num : nums) {
    num *= 2;  // 每个元素乘以 2
}
// 输出修改后的结果
for (int num : nums) {
    cout << num << " ";  // 输出:2 6 10 14 18
}
cout << endl;

四、vector 的常用算法

4.1 排序

include <algorithm>  // 需要包含 algorithm 头文件
vector<int> nums = {5, 2, 8, 1, 9};
// 升序排序
sort(nums.begin(), nums.end());
// nums 变为 {1, 2, 5, 8, 9}
// 降序排序(使用 greater 比较器)
sort(nums.begin(), nums.end(), greater<int>());
// nums 变为 {9, 8, 5, 2, 1}
// 自定义排序(按偶数优先)
sort(nums.begin(), nums.end(), [](int a, int b) {
    return a % 2 < b % 2;  // 偶数排在前面
});
// nums 变为 {2, 8, 5, 1, 9}

🚀greater<int>()的作用

  • ​​greater<>是一个​​函数对象​​(仿函数),定义在 <functional>头文件它重载了函数调用运算符 operator(),其逻辑是判断第一个参数是否​​大于​​第二个参数。当 a > b时返回 true,这意味着 a应该排在 b的前面,从而实现​​降序​​排序。使用后,数组变为 {9, 8, 5, 2, 1}。

  • 注意事项​​:greater<int>是模板,greater<int>()是创建的一个临时对象。确保包含了 <functional>头文件。另外,如果类型支持 operator >,也可以用于其他类型(如 doublestring等)。

💡Lambda 表达式​分析

sort(nums.begin(), nums.end(), [](int a, int b) {
    return a % 2 < b % 2;  // 偶数排在前面
});
// nums 变为 {2, 8, 5, 1, 9}
  • [](int a, int b)是Lambda的引入部分,[]是捕获列表(这里为空),(int a, int b)是参数列表。

  • return a % 2 < b % 2;是函数体。a % 2和 b % 2的结果是0(偶数)或1(奇数)。

  • ​规则解读​​:当 a % 2 < b % 2为真时,a排在 b前面。这意味着:

    • 如果 a是偶数(0),b是奇数(1),则 0 < 1为真,a(偶数)排在前面。

    • 如果 a是奇数(1),b是偶数(0),则 1 < 0为假,b(偶数)排在前面。

    • 如果两者奇偶性相同,它们的相对顺序在排序后是​​未定义的​​,因为 sort是​​不稳定排序​​。结果中,偶数 {2, 8}都排在了奇数 {5, 1, 9}的前面。

4.2 查找

vector<int> nums = {10, 20, 30, 40, 50};
// 查找元素是否存在
auto it = find(nums.begin(), nums.end(), 30);
if (it != nums.end()) {
    cout << "找到元素,位置:" << (it - nums.begin()) << endl;  // 输出:2
} else {
    cout << "未找到元素" << endl;
}
// 查找第一个大于 35 的元素
auto it2 = find_if(nums.begin(), nums.end(), [](int x) {
    return x > 35;
});
if (it2 != nums.end()) {
    cout << "第一个大于 35 的元素:" << *it2 << endl;  // 输出:40
}

这段代码展示了在 C++ vector中查找元素的两种核心方法:精确值查找和条件查找。下面我们来详细分析其工作原理和关键要点。

🔍 代码概览

代码主要演示了两种查找方式:

  • 精确值查找​​:使用 std::find查找值为 30的元素。
  • 条件查找​​:使用 std::find_if查找第一个大于 35的元素。

这两种算法都定义在 <algorithm>头文件中,通过迭代器操作,是 C++ 标准库中处理序列的通用方式

⚙️ 工作原理与输出解析

   精确值查找 (std::find)

auto it = find(nums.begin(), nums.end(), 30);
if (it != nums.end()) {
    cout << "找到元素,位置:" << (it - nums.begin()) << endl;  // 输出:2
} else {
    cout << "未找到元素" << endl;
}
  • ​​执行过程​​:std::find从 nums.begin()开始,依次将每个元素与目标值 30进行比较,直到找到匹配项或遍历完整个容器(到达 nums.end())。

  • ​​返回值​​:如果找到,返回指向该元素的​​迭代器​​;否则返回 nums.end()。代码通过 it != nums.end()判断查找是否成功。

  • ​​位置计算​​:it - nums.begin()计算的是目标元素与容器起始位置的偏移量,即索引(从0开始)。因为 30是 vector中的第三个元素(在 10, 20之后),所以输出索引为 2

   条件查找 (std::find_if)

auto it2 = find_if(nums.begin(), nums.end(), [](int x) {
    return x > 35;
});
if (it2 != nums.end()) {
    cout << "第一个大于 35 的元素:" << *it2 << endl;  // 输出:40
}
  • ​​执行过程​​:std::find_if同样线性遍历容器,但对每个元素调用作为第三个参数提供的 ​​Lambda 表达式​​ [](int x) { return x > 35; }。这个 Lambda 是查找条件的核心。

  • ​​Lambda 表达式的作用​​:它是一个匿名函数,接受一个 int型参数 x(即容器中的当前元素),并返回一个布尔值。当 x > 35为真时,表示当前元素满足条件。

  • ​​查找结果​​:std::find_if返回第一个使 Lambda 表达式返回 true的元素的迭代器。在 nums中,40是第一个大于 35的元素,因此 it2指向 40,*it2解引用得到其值 40。

💡 关键要点与扩展知识

特性

std::find

std::find_if

​查找依据​

与特定值​​完全相等​

满足​​自定义条件​

​常用场景​

查找确切值

查找满足特定属性(如范围、特定字段)的元素

​复杂度​

​O(n)​​,线性时间复杂度​O(n)​​,线性时间复杂度
  • 迭代器有效性​​:查找返回的迭代器(如 itit2)与容器当前的状态绑定。如果之后修改了容器(如添加、删除元素),这些迭代器可能会​​失效​,再次使用可能导致未定义行为

  • 查找所有匹配项​​:std::find和 std::find_if只返回​​第一个​​匹配的元素。如果需要找到所有满足条件的元素,通常需要在循环中多次调用,每次从上次找到的位置之后开始新的查找

  • 性能考量​​:这两种算法都是线性查找,适用于数据量不大或无需频繁查找的场景。如果需要对​​大型数据集进行频繁查找​​,可以考虑先将向量排序,然后使用 std::binary_search(时间复杂度 O(log n)),或使用 std::unordered_set(平均时间复杂度 O(1))这样的关联容器

  • ​用于自定义类型​​:如果 vector中存储的是自定义的结构体或类,使用 std::find需要该类型重载了 ==运算符。而 std::find_if则更加灵活,可以通过 Lambda 表达式指定任何判断条件,例如查找某个特定成员变量符合要求的对象

4.3 统计

vector<int> nums = {1, 2, 2, 3, 2, 4};
// 统计元素出现次数
int count = count(nums.begin(), nums.end(), 2);
cout << "元素 2 出现次数:" << count << endl;  // 输出:3
// 统计满足条件的元素个数
int evenCount = count_if(nums.begin(), nums.end(), [](int x) {
    return x % 2 == 0;  // 偶数
});
cout << "偶数个数:" << evenCount << endl;  // 输出:3

4.4 反转

​​​​​​​vector<int> nums = {1, 2, 3, 4, 5};
// 反转元素顺序
reverse(nums.begin(), nums.end());
// nums 变为 {5, 4, 3, 2, 1}

4.5 去重

​​​​​​​

vector<int> nums = {1, 2, 2, 3, 3, 3, 4};
// 先排序,再去重
sort(nums.begin(), nums.end());
//std::unique只能识别并处理​​相邻的重复元素​​。
auto newEnd = unique(nums.begin(), nums.end());
nums.erase(newEnd, nums.end());  // 删除重复元素
// nums 变为 {1, 2, 3, 4}

unique()函数

  • 遍历排序后的范围,将​​相邻的重复元素​​“移除”。这里的“移除”并非真正从内存中删除,而是将每个重复元素组的​​第一个唯一元素​​向前覆盖,覆盖到这些重复元素的位置。重复的元素被移动到范围的末尾,其值变为​​未定义状态​​。

  • 返回值​​:返回一个指向​​新逻辑结尾​​的迭代器(newEnd)。这个新逻辑结尾是指向去重后不重复序列的最后一个元素的下一个位置

  • ​执行后 nums的可能状态​​:{1, 2, 3, 4, ?, ?, ?}?代表未定义的重复值)。容器的 size()此时并未改变。必须配合 erase才能物理删除。

这套“排序 → unique→ erase”的组合拳是 C++ 中处理 vector去重的经典且高效的方法。它的核心在于通过排序使重复元素相邻,再利用 unique进行逻辑去重,最后通过 erase完成物理删除。

五、vector 的高级用法

5.1 存储自定义类型

// 定义学生结构体
struct Student {
    string name;
    int age;
    double score;

    // 构造函数
    Student(string n, int a, double s) : name(n), age(a), score(s) {}
};

// 创建学生 vector
vector<Student> students;
// 添加元素
students.push_back(Student("Alice", 20, 95.5));
students.push_back(Student("Bob", 19, 88.0));
students.push_back(Student("Charlie", 21, 92.5));
// 遍历学生信息
for (const auto &stu : students) {
    cout << stu.name << " " << stu.age << " " << stu.score << endl;
}
// 按分数排序
sort(students.begin(), students.end(), [](const Student &a, const Student &b) {
    return a.score > b.score;  // 降序
});

🔢成员初始化列表

在 C++ 中,​​成员初始化列表(Member Initializer List)主要用于构造函数​​,用来在对象创建时直接初始化成员变量,而不是先默认初始化再赋值。这不仅是风格问题,更关系到初始化的正确性和效率。

✨ 语法规则解析

Student(string n, int a, double s) : name(n), age(a), score(s) {}

这行代码 可以分解为以下几个部分:

📝 常规写法对比

除了上面这种高效的初始化列表方式,构造函数还有另一种常见的写法:在函数体内赋值。

// 在函数体内赋值的写法
Student(string n, int a, double s) {
    name = n;
    age = a;
    score = s;
}

关键区别​​:对于像 intdoublestring这样的类型,两种方式的效果最终看起来是一样的。但对于一些复杂的类类型成员,使用初始化列表是​​直接调用拷贝构造函数​​进行初始化;而函数体内赋值则是先调用默认构造函数初始化,再调用赋值运算符进行赋值。这就是效率差异的来源。

💡 进阶用法与技巧

带默认参数的构造函数​

可以为构造函数的参数提供默认值,这样在创建对象时就可以更加灵活。

struct Student {
    string name;
    int age;
    double score;
    // 参数带有默认值
    Student(string n = "Unknown", int a = 0, double s = 0.0) : name(n), age(a), score(s) {}
};

int main() {
    Student s1; // 使用所有默认值:name="Unknown", age=0, score=0.0
    Student s2("Alice"); // 部分使用默认值:name="Alice", age=0, score=0.0
    Student s3("Bob", 20); // name="Bob", age=20, score=0.0
    Student s4("Charlie", 21, 90.5); // 使用所有传入的值
}

处理成员变量与参数同名的情况​

当构造函数的参数名与成员变量名相同时,可以使用 this指针来明确区分

struct Student {
    string name;
    int age;
    // 参数名与成员变量名相同
    Student(string name, int age) {
        this->name = name; // 使用 this-> 来指代成员变量
        this->age = age;//等价于(*this).age=age;
    }
};

不过,更优雅的做法仍然是使用初始化列表,并通过微调参数名(如在参数前加下划线 _或缩写)来避免歧义,从而保持高效率。

🔍 C和C++语言对结构体处理的差异

  • 在C语言中,当你定义了一个结构体 struct StudentStudent本身并不能直接作为一个类型名使用。你必须在使用时每次都加上 struct关键字,即 struct Student。因此,在C语言中,为了简化,人们通常会使用 typedef来创建一个别名:

typedef struct Student {
    // ... 成员
} Student;
// 现在才可以这样使用
Student s1;
  • 然而,在C++中,​​这个限制被取消了​​。一旦你定义了 struct StudentStudent这个名称就直接进入了当前作用域,可以像 intdouble等内置类型一样,直接作为类型名使用。所以 Student本身就是一个完整的类型,前面的 struct关键字就不再需要了。那么 vector<Student>就是完全正确的语法

🚀使用常量引用​​

for (const auto &stu : students) {
    cout << stu.name << " " << stu.age << " " << stu.score << endl;
}

const auto &stu表示 stu是容器中元素的常量引用。使用引用可以​​避免在每次迭代时复制整个 Student对象​​,提升效率;加上 const确保遍历过程中不会意外修改元素内容。这是遍历容器时推荐的方式。

💡 核心要点与扩展建议

  • ​​动态数组的优势​​:vector作为动态数组,其大小可以在运行时改变,无需预先指定,非常灵活。

  • ​​排序的稳定性​​:std::sort是一种不稳定的排序算法。如果存在分数相同的学生,排序后他们的相对顺序可能改变。若需保持相同分数学生的原始顺序,可使用 std::stable_sort。

  • ​​Lambda 表达式的灵活性​​:Lambda 表达式让自定义排序规则变得非常方便。例如,如果想按年龄升序排序,规则可改为 return a.age < b.age;。还可以实现多条件排序,比如先按分数降序,分数相同再按年龄升序:

sort(students.begin(), students.end(), [](const Student &a, const Student &b) {
    if (a.score != b.score) return a.score > b.score;
    return a.age < b.age;
});
  • ​内存管理考虑​​:如果预先知道需要存储的学生数量较多,可以在添加元素前使用 reserve()方法为 vector预分配足够的内存空间,这可以减少多次重新分配和拷贝的开销,提升性能。

5.2 二维 vector​​​​​​​

// 创建 3x4 的二维 vector,初始值为 0
vector<vector<int>> matrix(3, vector<int>(4, 0));
// 赋值
matrix[0][0] = 1;
matrix[1][2] = 5;
matrix[2][3] = 10;
// 遍历
for (int i = 0; i < matrix.size(); i++) {
    for (int j = 0; j < matrix[i].size(); j++) {
        cout << matrix[i][j] << "\t";
    }
    cout << endl;
}
// 动态调整大小
matrix.resize(5);  // 增加到 5 行
for (auto &row : matrix) {
    row.resize(6, 0);  // 每行增加到 6 列,初始值为 0
}

5.3 性能优化技巧​​​​​​​

1. 预分配容量避免频繁扩容
vector<int> vec;
vec.reserve(1000);  // 预分配 1000 个元素空间
for (int i = 0; i < 1000; i++) {
    vec.push_back(i);  // 避免多次扩容
}
// 2. 使用 emplace_back() 替代 push_back()(C++11+)
vector<Student> students;
// push_back 需要先创建临时对象,再复制
students.push_back(Student("Alice", 20, 95.5));
// emplace_back 直接在容器内部构造对象,效率更高
students.emplace_back("Bob", 19, 88.0);
// 3. 交换技巧释放多余内存
vector<int> temp;
vec.swap(temp);  // vec 变为空,temp 接管原 vec 的内存
// 或使用:vec.shrink_to_fit();  // C++11 释放多余容量

六、实战案例:图书管理系统

6.1 案例需求

  • 使用 vector 管理图书信息(书名、作者、ISBN、价格)

  • 实现图书添加、删除、查找、排序和显示功能

6.2 代码实现​​​​​​​

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
// 图书结构体
struct Book {
    string title;
    string author;
    string isbn;
    double price;

    Book(string t, string a, string i, double p) 
        : title(t), author(a), isbn(i), price(p) {}
};
// 图书管理系统类
class Library {
private:
    vector<Book> books;
public:
    // 添加图书
    void addBook(const Book &book) {
        books.push_back(book);
        cout << "图书《" << book.title << "》添加成功!" << endl;
    }

    // 按 ISBN 删除图书
    bool removeBook(const string &isbn) {
        auto it = find_if(books.begin(), books.end(), [&](const Book &b) {
            return b.isbn == isbn;
        });

        if (it != books.end()) {
            cout << "删除图书:《" << it->title << "》" << endl;
            books.erase(it);
            return true;
        }

        cout << "未找到 ISBN 为 " << isbn << " 的图书!" << endl;
        return false;
    }

    // 按书名查找图书
    vector<Book> findBooksByTitle(const string &title) {
        vector<Book> result;
        for (const auto &book : books) {
            if (book.title.find(title) != string::npos) {
                result.push_back(book);
            }
        }
        return result;
    }

    // 按价格排序(升序)
    void sortByPrice() {
        sort(books.begin(), books.end(), [](const Book &a, const Book &b) {
            return a.price < b.price;
        });
        cout << "图书已按价格升序排序!" << endl;
    }

    // 显示所有图书
    void displayAllBooks() {
        if (books.empty()) {
            cout << "图书馆暂无图书!" << endl;
            return;
        }

        cout << "\n===== 图书馆藏书 =====" << endl;
        cout << "序号\t书名\t\t作者\t\tISBN\t\t价格" << endl;
        for (int i = 0; i < books.size(); i++) {
            cout << (i + 1) << "\t" << books[i].title << "\t\t" 
                 << books[i].author << "\t\t" << books[i].isbn << "\t\t" 
                 << books[i].price << endl;
        }
    }
};
int main() {
    Library library;
    int choice;

    do {
        cout << "\n===== 图书管理系统 =====" << endl;
        cout << "1. 添加图书" << endl;
        cout << "2. 删除图书" << endl;
        cout << "3. 查找图书" << endl;
        cout << "4. 按价格排序" << endl;
        cout << "5. 显示所有图书" << endl;
        cout << "0. 退出系统" << endl;
        cout << "请选择操作:";
        cin >> choice;

        switch (choice) {
            case 1: {
                string title, author, isbn;
                double price;

                cout << "书名:";
                cin.ignore();  // 清除输入缓冲区
                getline(cin, title);

                cout << "作者:";
                getline(cin, author);

                cout << "ISBN:";
                cin >> isbn;

                cout << "价格:";
                cin >> price;

                library.addBook(Book(title, author, isbn, price));
                break;
            }
            case 2: {
                string isbn;
                cout << "请输入要删除的图书 ISBN:";
                cin >> isbn;
                library.removeBook(isbn);
                break;
            }
            case 3: {
                string title;
                cout << "请输入要查找的书名关键词:";
                cin.ignore();
                getline(cin, title);

                vector<Book> result = library.findBooksByTitle(title);
                if (result.empty()) {
                    cout << "未找到相关图书!" << endl;
                } else {
                    cout << "\n找到 " << result.size() << " 本相关图书:" << endl;
                    for (int i = 0; i < result.size(); i++) {
                        cout << (i + 1) << ". 《" << result[i].title << "》 - " 
                             << result[i].author << " (" << result[i].price << "元)" << endl;
                    }
                }
                break;
            }
            case 4:
                library.sortByPrice();
                break;
            case 5:
                library.displayAllBooks();
                break;
            case 0:
                cout << "感谢使用,再见!" << endl;
                break;
            default:
                cout << "无效选择,请重新输入!" << endl;
        }
    } while (choice != 0);

    return 0;
}

七、vector 使用注意事项

7.1 性能考虑

  • 扩容开销:频繁调用 push_back() 可能导致多次内存重分配,影响性能
  • 随机访问:vector 支持 O(1)随机访问,但插入 / 删除中间元素效率较低((O(n)))
  • 内存碎片:多次扩容可能导致内存碎片,影响系统整体性能

7.2 迭代器失效问题

  • 插入操作:若触发扩容,所有迭代器都会失效
  • 删除操作:被删除元素及之后的迭代器会失效
  • 解决方法:插入 / 删除后重新获取迭代器

7.3 常见错误

  1. 越界访问:使用 [] 访问元素时不检查边界
  2. 空容器操作:对空 vector 调用 front()back() 或 pop_back()
  3. 误用容量和大小:混淆 capacity() 和 size() 的区别
  4. 内存泄漏:存储指针时忘记释放内存
  5. 低效操作:在 vector 中间频繁插入 / 删除元素

7.4 替代容器选择

  • list:适合频繁插入 / 删除操作
  • deque:支持两端高效插入 / 删除,且随机访问较快
  • array:固定大小的数组,性能优于 vector
  • map/set:需要有序或键值对存储时

八、总结与拓展

 核心知识点回顾

  • vector 是动态数组容器,支持自动扩容和高效随机访问

  • 基本操作:初始化、访问、添加、删除、遍历

  • 常用算法:排序、查找、统计、反转、去重

  • 高级用法:存储自定义类型、二维 vector、性能优化

  • 注意事项:迭代器失效、性能考虑、替代容器选择

附录:测试代码

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
 
//1.vector的构造函数 
void test_vector1()
{
	cout<<"===1.vector的构造函数测试===" ;
	vector<int> v1; //默认构造 
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
 
    cout<<"v1:" ;
	for (size_t i = 0; i < v1.size(); ++i)
	{
		cout << v1[i] << " ";
	}
	cout << endl;
 
    //拷贝构造 
	vector<int> v2(v1);
	cout<<"v2(拷贝构造)" ;
	for (size_t i = 0; i < v2.size(); ++i)
	{
		cout << v2[i] << " ";
	}
	cout << endl;
 
	vector<int> v3;
	v3.push_back(10);
	v3.push_back(20);
	v3.push_back(30);
	v3.push_back(40);
	
	//赋值操作 
	v1 = v3;
    cout<<"v1(赋值后):" ;
	for (size_t i = 0; i < v1.size(); ++i)
	{
		cout << v1[i] << " ";
	}
	cout << endl;
}
 
//2.vector的遍历 
void test_vector2()
{
	cout<<"===2.vector的遍历测试===" ;
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
 
	//(1)下标访问 
	cout<<"下标访问:" ;
	for (size_t i = 0; i < v.size(); ++i)
	{
		v[i] += 1;
		cout << v[i] << " ";
	}
	cout << endl;
 
	//(2)迭代器遍历:
	cout<<"迭代器遍历:" ;
	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		*it -= 1;
		cout << *it << " ";
		++it;
	}
	cout << endl;
 
	//(3)范围for遍历
//	cout<<"范围for遍历:" ;
//	for (auto ch : v)
//	{
//		cout << ch << " ";
//	}
//	cout << endl;
}
 
//3.其他迭代器 
void test_vector3()
{
	cout<<"===3.其他迭代器测试:==="<<endl ;
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	
	//普通正向迭代器,可读可写
	cout<<"正向迭代器";
	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		*it *= 2;
		cout << *it << " ";
		++it;
	}
	cout << endl;
 
	//普通反向迭代器,可读可写
	cout<<"反向迭代器:";
	vector<int>::reverse_iterator rit = v.rbegin();
	while (rit != v.rend())
	{
		*rit /= 2;
		cout << *rit << " ";
		++rit;
	}
	cout << endl;
 
	//const迭代器,只读
	cout<<"const迭代器"; 
	vector<int>::const_iterator cit = v.begin();
	while (cit != v.end())
	{
		//*cit += 1;错误,不能给常量赋值
		cout << *cit << " ";
		++cit;
	}
	cout << endl;
}

//4.vector的容量 
void test_vector4()
{
	cout<<"===4.vector的容量测试===" <<endl;
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	cout <<"初始大小:" <<v.size() << endl;
	cout << "初始容量:"<<v.capacity() << endl << endl;//vs下1.5倍增容,Linux下2倍增容
 
	v.reserve(10);
	cout <<"reserve(10)后大小"<< v.size() << endl;
	cout <<"reserve(10)后容量"<< v.capacity() << endl << endl;
 
	v.resize(20);
	cout<<"resize(20)后内容:";
	for (size_t i = 0; i < v.size(); ++i)
	{
		cout << v[i] << " ";
	}
	cout << endl;
	cout << "resize(20)后大小:"<<v.size() << endl;
	cout <<"resize(20)后容量:"<< v.capacity() << endl;
}

//5.vector中at和[]的区别 
void test_vector5()
{
	cout<<"===5.at和[]的区别测试===" <<endl;
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
  
    cout<<"正常访问:v[0]="<<v[0]<<",v.at(0)="<<v.at(0)<<endl;
	
	//注意:一下代码会导致程序崩溃,已注释 
	//v[4] = 5;//断言错误
	//v.at(4) = 5;//抛异常
	cout<<"[]越界访问会导致断言错误,at越界访问会抛出异常"<<endl<<endl;
	 
}

//6.vector的插入和删除 
void test_vector6()
{
	cout<<"===6.vector的插入和删除测试===" <<endl;
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
 
    cout<<"原始内容:";
//    for(auto ch:v){
//    	cout<<ch<<" ";
//	}
     for(int i=0;i<v.size();i++){
     	cout<<v[i];
	 }
	cout<<endl;
	
	v.insert(v.begin(), 0);
	v.insert(v.begin(), -1);
	cout<<"插入后:"; 
//	for (auto ch : v)
//	{
//		cout << ch << " ";
//	}
    for(int i=0;i<v.size();i++){
     	cout<<v[i];
	 }
	cout << endl;
 
	v.erase(v.begin());
	cout<<"删除后:" ;
//	for (auto ch : v)
//	{
//		cout << ch << " ";
//	}
    for(int i=0;i<v.size();i++){
     	cout<<v[i];
	 }
	cout << endl<<endl;
}
 
//7.find和sort 
void test_vector7()
{
	cout<<"===7.find和sort测试==="<<endl; 
	vector<int> v;
	v.push_back(1);
	v.push_back(3);
	v.push_back(2);
	v.push_back(8);
	v.push_back(7);
	v.push_back(3);
 
    cout<<"原始内容:";
//	for (auto ch : v)
//	{
//		cout << ch << " ";
//	}
    for(int i=0;i<v.size();i++){
     	cout<<v[i];
	 }
	cout << endl;
 
	//使用算法中的find删除第一个3
	vector<int>::iterator pos = find(v.begin(), v.end(), 3);
	if (pos != v.end())
	{
		v.erase(pos);
	}
	cout<<"删除第一个3后:"; 
//	for (auto ch : v)
//	{
//		cout << ch << " ";
//	}
    for(int i=0;i<v.size();i++){
     	cout<<v[i];
	 }
	cout << endl;
 
	//使用算法中的sort排序 
	sort(v.begin(), v.end());
	cout<<"排序后:"; 
//	for (auto ch : v)
//	{
//		cout << ch << " ";
//	}
    for(int i=0;i<v.size();i++){
     	cout<<v[i];
	 }
	cout << endl;
}
 
//8.迭代器失效--插入造成的失效 
void test_vector8()
{
	cout<<"===8.迭代器失效测试-插入==="<<endl; 
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
 
    cout<<"原始内容:";
//	for(auto ch:v){
//		cout<<ch<<" ";
//	} 
    for(int i=0;i<v.size();i++){
     	cout<<v[i];
	 }
	cout<<endl;
	
	//注意:以下代码演示迭代器失效问题
	//在实际使用中,插入元素后应该重新获取迭代器 
	vector<int>::iterator it = v.begin();
	v.push_back(6);
	v.push_back(7);//it迭代器失效了,增容
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}
 
//9.迭代器失效-erase造成的失效 
void test_vector9()
{
	cout<<"===9.迭代器失效测试-erase==="<<endl; 
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	v.push_back(6);
 
	cout<<"原始内容:";
//	for(auto ch:v){
//		cout<<ch<<" ";
//	} 
    for(int i=0;i<v.size();i++){
     	cout<<v[i];
	 }
	cout<<endl;
	
	//要求删除容器中所有的偶数
	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		//if (*it % 2 == 0)
		//{
		//	v.erase(it);//删除it后迭代器就失效了,因为it的位置不对了
		//}
		//++it;
		if (*it % 2 == 0)
		{
			it = v.erase(it);//erase会返回删除的下一个位置的迭代器
		}
		else
		{
			++it;
		}
	}
 
    cout<<"删除偶数后:";
//	for (auto ch : v)
//	{
//		cout << ch << " ";
//	}
    for(int i=0;i<v.size();i++){
     	cout<<v[i];
	 }
	cout << endl<<endl;
}
 
int main()
{
	cout<<"C++ STL vector容器详解演示"<<endl;
	cout<<"================"<<endl<<endl;
	
	test_vector1();
	test_vector2();
	test_vector3();
	test_vector4();
	test_vector5();
	test_vector6();
	test_vector7();
	test_vector8();
	test_vector9();
	
	cout<<"演示完成!"<<endl;
	return 0;
}

 

您可能感兴趣的与本文相关的镜像

ACE-Step

ACE-Step

音乐合成
ACE-Step

ACE-Step是由中国团队阶跃星辰(StepFun)与ACE Studio联手打造的开源音乐生成模型。 它拥有3.5B参数量,支持快速高质量生成、强可控性和易于拓展的特点。 最厉害的是,它可以生成多种语言的歌曲,包括但不限于中文、英文、日文等19种语言

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

智码行者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值