从零到一学习c++(基础篇--筑基期七-vector与迭代器)

  从零到一学习C++(基础篇) 作者:羡鱼肘子

温馨提示1:本篇是记录我的学习经历,会有不少片面的认知,万分期待您的指正。

 温馨提示2:本篇会尽量用更加通俗的语言介绍c++的基础,用通俗的语言去解释术语。

 温馨提示3:看本篇前可以先了解前篇的内容,知识体系会更加完整哦。

从零到一学习c++(基础篇--筑基期六-string)-CSDN博客

标准库类型vector 

1. 什么是vector?

vector 的基本概念

定义
  • vector 是一个 动态数组(dynamic array),属于C++标准库中的 顺序容器(sequential container)。

  • 头文件#include <vector>

  • 命名空间std::vector<T>

核心特性
  1. 动态大小:元素数量可以动态增长或缩减。

  2. 连续存储:元素在内存中连续存放,支持快速随机访问(通过下标)。

  3. 类型安全:所有元素必须是相同类型 T

  4. 自动内存管理:内存由 vector 自动分配和释放。

2. 基本用法

声明与初始化
#include <vector>
using namespace std;

// 初始化方式
vector<int> v1;             // 默认初始化,空vector
vector<int> v2(v1);         // 拷贝v1的元素(v1和v2类型必须相同)
vector<int> v3 = v1;        // 等价于v2(v1)
vector<int> v4(5, 10);      // 5个元素,每个初始化为10
vector<int> v5(5);          // 5个元素,默认值初始化(int为0)
vector<int> v6{1, 2, 3};    // 列表初始化(C++11)
vector<int> v7 = {1, 2, 3}; // 等价于v6
添加元素
  • push_back():在尾部插入元素。

  • emplace_back()(C++11):直接在容器尾部构造元素(更高效)。

  • insert():在指定位置插入元素(效率较低,需移动后续元素)。

vector<int> v;
v.push_back(42);          // 插入42
v.emplace_back(42);       // 直接构造42(避免复制)
v.insert(v.begin(), 100); // 在头部插入100
温馨小贴士:

为什么emplace_back()更高效?

1. push_back 的工作原理

假设你有一个类 Person:

class Person {
public:
    Person(string name, int age) {
        cout << "构造函数被调用" << endl;
    }
    Person(const Person& other) {
        cout << "拷贝构造函数被调用" << endl;
    }
};

当你使用 push_back 时:

vector<Person> people;
people.push_back(Person("Alice", 25));

发生了什么呐?

  1. 构造临时对象:先调用构造函数 Person("Alice", 25),创建一个临时对象。

  2. 拷贝到容器:再调用拷贝构造函数,将这个临时对象复制到 vector 的内存中。

  3. 销毁临时对象:临时对象被销毁。

多了一次额外的拷贝操作呢!

2. emplace_back 的工作原理

改用 emplace_back

people.emplace_back("Alice", 25);

发生了什么?

  1. 直接构造:直接在 vector 的内存空间中调用构造函数 Person("Alice", 25)

  2. 没有临时对象不创建临时对象,因此没有拷贝操作

3. 为什么更高效?

  • push_back:需要先构造对象,再拷贝到容器(如果对象很大,拷贝成本高)。

  • emplace_back:直接在容器内存中构造对象,跳过了临时对象和拷贝步骤

  • 性能提升:尤其对复杂对象(如包含大量数据的类)效果显著。

4. 技术原理

  • 可变参数模板emplace_back 使用 C++11 的可变参数模板,可以接受任意数量和类型的参数。

  • 完美转发:将这些参数直接传递给对象的构造函数,实现“原地构造”(in-place construction)。

5. 对比示例

情况1:简单类型(如 int

vector<int> v;
v.push_back(20);      // 构造临时int(20),然后复制(或移动)
v.emplace_back(20);   // 直接构造,没有区别(但对int来说,优化不明显)
  • 对简单类型,性能差异不大,但 emplace_back 仍更优。

情况2:复杂对象

class BigData {
public:
    BigData(int size) { /* 分配大量内存 */ }
    BigData(const BigData& other) { /* 深拷贝,成本高! */ }
};

vector<BigData> vec;
vec.push_back(BigData(1000));    // 1次构造 + 1次拷贝(代价高)
vec.emplace_back(1000);          // 仅1次构造(无拷贝)
  • 对复杂对象,emplace_back 节省了深拷贝的时间。

6. 什么时候用 emplace_back

  • 当对象的构造函数需要参数时,优先用 emplace_back

  • 需要插入临时对象或直接传递参数时,用它更高效。

  • 简单类型(如 int)可以用,但性能提升不明显。

总结:emplace_back 通过直接在容器内存中构造对象,避免了临时对象的创建和拷贝操作,是 C++11 后更高效的插入方式! 😊

访问元素
  • v[n]:通过下标访问,不检查越界(类似数组)。

  • v.at(n):通过下标访问,越界抛出 std::out_of_range 异常。

  • v.front():返回第一个元素。

  • v.back():返回最后一个元素。

cout << v[0];       // 访问第0个元素(不检查越界)
cout << v.at(1);    // 访问第1个元素(越界会抛出异常)(推荐)
cout << v.front();  // 第一个元素(即v[0])
cout << v.back();   // 最后一个元素(即v[v.size()-1])
删除元素

  • pop_back():删除尾部元素。

  • erase():删除指定位置的元素(返回指向下一个元素的迭代器)。

  • clear():清空所有元素。

v.pop_back();      // 删除最后一个元素
v.clear();         // 清空所有元素
v.erase(v.begin() + 1); // 删除第2个元素(迭代器位置)
大小与容量
vector 的内存管理
  • size():当前元素个数。

  • capacity():当前分配的存储空间能容纳的元素数量。

  • resize(n):调整 size 为 n,多出的元素默认初始化。

  • reserve(n):预分配至少能容纳 n 个元素的内存(避免频繁扩容)。 

动态扩容机制
  • 当插入元素超过当前 capacity 时,vector 会重新分配更大的内存(通常是翻倍)。

  • 扩容成本需要拷贝所有元素到新内存,并释放旧内存。

  • 优化策略:如果预先知道元素数量,使用 reserve() 减少扩容次数。

int size = v.size();      // 当前元素个数
bool isEmpty = v.empty(); // 是否为空
v.resize(10);             // 调整大小为10(多出的元素默认初始化)
int cap = v.capacity();   // 当前分配的内存能容纳的元素数量
v.reserve(100);           // 预分配内存(避免频繁扩容)

vector<int> v;
v.reserve(100); // 预分配100个元素的内存
for (int i = 0; i < 100; ++i) {
    v.push_back(i); // 不会触发扩容
}

3. 遍历vector

方法1:下标遍历
for (int i = 0; i < v.size(); i++) {
    cout << v[i] << " ";
}
方法2:迭代器遍历

vector 的迭代器
  • 迭代器提供了统一的访问容器元素的方式。

  • begin() 和 end() 返回指向首元素和尾后元素的迭代器。

  • 范围for循环(C++11)底层依赖迭代器。

for (auto it = v.begin(); it != v.end(); it++) {
    cout << *it << " ";
}
温馨小贴士:
  • vector 的迭代器是 随机访问迭代器(支持 it + n 操作)。

  • 修改 vector(如 push_back)可能导致迭代器失效。

以下操作会导致 vector 的迭代器、指针或引用失效:
  • 插入元素如果引发扩容,所有迭代器失效。

  • 删除元素:被删除元素之后的迭代器失效

  • 性能建议
  • 避免在中间位置频繁插入或删除(时间复杂度 O(n))。

  • 优先使用 emplace_back 代替 push_back(减少拷贝开销)。

  • vector<int> v = {1, 2, 3};
    auto it = v.begin();
    v.push_back(4); // 可能导致扩容,it失效!
    cout << *it;    // 未定义行为!

方法3:范围循环(C++11+)
for (auto num : v) {
    cout << num << " ";
}

 对比其他容器

容器特点
vector动态数组,尾部操作高效,支持随机访问,内存连续
list双向链表,任意位置插入/删除高效,不支持随机访问
deque双端队列,头尾插入/删除高效,支持随机访问
array固定大小数组,不支持动态扩容

4. 重要特性小结

动态扩容
  • 当插入元素超过当前容量时,vector会自动分配更大的内存(通常是当前容量的2倍)。

  • 频繁扩容会影响性能,可以用 reserve() 预先分配足够空间。

元素连续存储
  • 所有元素在内存中是连续存放的,类似数组。

  • 支持指针算术操作(例如 &v[0] 是首元素地址)。

5. 注意事项

  1. 越界访问v[i]不会检查越界,但v.at(i)会抛出std::out_of_range异常。

  2. 迭代器失效

    • 在修改vector(如插入、删除)后,旧的迭代器可能失效。

    • 例如,push_back可能导致内存重新分配,之前的迭代器指向无效地址。

  3. 性能

    • 尾部插入/删除(push_back/pop_back)高效(O(1)时间)。

    • 中间插入/删除需要移动元素,效率较低(O(n)时间)。

6. 示例代码

#include <vector>
#include <iostream>
using namespace std;

int main() {
    vector<int> v;          // 空vector
    v.reserve(10);          // 预分配内存
    for (int i = 0; i < 10; ++i) {
        v.emplace_back(i);  // 插入0~9
    }

    // 修改元素
    v[0] = 100;

    // 删除偶数
    for (auto it = v.begin(); it != v.end(); ) {
        if (*it % 2 == 0) {
            it = v.erase(it); // erase返回下一个有效迭代器
        } else {
            ++it;
        }
    }

    // 输出结果
    for (int num : v) {
        cout << num << " ";
    }
    // 输出:100 1 3 5 7 9
    return 0;
}

7. 什么时候用vector?

  • 需要频繁在尾部添加/删除元素。

  • 需要随机访问元素(通过下标)。

  • 不确定元素数量,需要动态调整大小。

通过以上的学习,我们接触到了一个新的词:迭代器 ,那么什么是迭代器呢?

迭代器 (强化概念)

1. 迭代器的基本概念

什么是迭代器?
  • 迭代器是访问和操作容器(如 vectorlistmap 等)元素的通用机制。

  • 行为类似指针:可以解引用(*it)访问元素,用箭头操作符(->)访问成员,支持移动(++it--it)。

  • 迭代器是容器和算法之间的桥梁,使得算法可以独立于容器类型工作。

为什么需要迭代器?
  • 提供统一的元素访问方式,避免直接操作容器内部结构。

  • 支持泛型编程(例如 sort() 函数可以对所有支持随机访问迭代器的容器排序)。

2. 迭代器的基本操作

获取迭代器
vector<int> v = {1, 2, 3};
auto it_begin = v.begin(); // 指向第一个元素的迭代器
auto it_end = v.end();     // 指向尾后元素(最后一个元素的下一个位置)
常用操作
操作说明
*it解引用,获取迭代器指向的元素
it->mem访问元素的成员(等价于 (*it).mem
++it / it++移动到下一个元素
--it / it--移动到上一个元素(仅双向或随机访问迭代器支持)
it1 == it2判断两个迭代器是否指向同一位置
it + n / it - n随机访问迭代器支持跳跃(如 vector

3. 迭代器类型

迭代器类别

C++ 标准库定义了 5 类迭代器(从功能弱到强排序):

  1. 输入迭代器(Input Iterator):只读,单遍扫描(如 istream_iterator)。

  2. 输出迭代器(Output Iterator):只写,单遍扫描(如 ostream_iterator)。

  3. 前向迭代器(Forward Iterator):可读写,多遍扫描(如 forward_list 的迭代器)。

  4. 双向迭代器(Bidirectional Iterator):可前向和后向移动(如 list 的迭代器)。

  5. 随机访问迭代器(Random Access Iterator):支持所有指针算术操作(如 vectordeque 的迭代器)。

不同容器的迭代器类型
容器迭代器类型
vector随机访问迭代器
deque随机访问迭代器
list双向迭代器
forward_list前向迭代器
map/set双向迭代器

4. 迭代器的使用

遍历容器
vector<int> v = {1, 2, 3};
// 使用迭代器遍历
for (auto it = v.begin(); it != v.end(); ++it) {
    cout << *it << " ";
}
// 使用范围for循环(底层依赖迭代器)
for (int num : v) {
    cout << num << " ";
}
修改元素
vector<int> v = {1, 2, 3};
auto it = v.begin();
*it = 100; // 将第一个元素改为100

5. 迭代器失效问题

何时失效?
  • 向容器中添加或删除元素可能导致迭代器失效(尤其是 vector 和 string)。

  • 具体场景

    1. 添加元素

      • 如果引发扩容(push_back 导致 size > capacity),所有迭代器失效。

    2. 删除元素

      • 被删除元素之后的迭代器失效。

示例
vector<int> v = {1, 2, 3};
auto it = v.begin();
v.push_back(4);      // 可能导致扩容,it 失效!
cout << *it << endl; // 未定义行为!
如何避免?
  • 在修改容器后,重新获取迭代器。

  • 使用返回值更新迭代器(例如 erase() 返回删除后的下一个有效迭代器)。

vector<int> v = {1, 2, 3, 4};
auto it = v.begin();
while (it != v.end()) {
    if (*it % 2 == 0) {
        it = v.erase(it); // erase 返回下一个有效迭代器
    } else {
        ++it;
    }
}

6. 特殊迭代器

1. const_iterator
  • 用于只读访问容器元素,不能修改元素。

  • 通过 cbegin() 和 cend() 获取。

vector<int> v = {1, 2, 3};
vector<int>::const_iterator cit = v.cbegin();
// *cit = 10; // 错误:不能修改元素
2. 反向迭代器(reverse_iterator)
  • 从后向前遍历容器,通过 rbegin() 和 rend() 获取。

  • 仅双向或随机访问迭代器支持。

vector<int> v = {1, 2, 3};
for (auto rit = v.rbegin(); rit != v.rend(); ++rit) {
    cout << *rit << " "; // 输出 3 2 1
}
3. 插入迭代器(insert_iterator)
  • 用于向容器中插入元素(如 back_inserterfront_inserter)。

  • 示例:

vector<int> v = {1, 2, 3};
auto back_it = back_inserter(v); // 插入到尾部
*back_it = 4; // v变为 [1, 2, 3, 4]

7. 迭代器与算法

标准库算法(如 sortfind)通过迭代器操作容器:

vector<int> v = {3, 1, 4, 2};
sort(v.begin(), v.end()); // 排序 [1, 2, 3, 4]
auto it = find(v.begin(), v.end(), 3); // 查找元素3的位置

8. 迭代器辅助函数

advance 和 distance
  • advance(it, n):将迭代器移动 n 步。

  • distance(it1, it2):计算两个迭代器之间的距离。

  • 注意:distance 的时间复杂度取决于迭代器类型(随机访问迭代器为 O(1),其他为 O(n))。

vector<int> v = {1, 2, 3, 4, 5};
auto it = v.begin();
advance(it, 3);       // it 指向4
cout << *it << endl;  // 输出4
cout << distance(v.begin(), it) << endl; // 输出3

9. 注意事项

  1. 不要解引用 end() 迭代器:它指向尾后元素,不可解引用。

  2. 迭代器类型必须匹配:不同容器的迭代器类型不同,不能混用。

  3. 谨慎处理迭代器失效:修改容器后,迭代器可能失效。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/967911.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ASP.NET Core WebSocket、SignalR

目录 WebSocket SignalR SignalR的基本使用 WebSocket WebSocket基于TCP协议&#xff0c;支持二进制通信&#xff0c;双工通信。性能和并发能力更强。WebSocket独立于HTTP协议&#xff0c;不过我们一般仍然把WebSocket服务器端部署到Web服务器上&#xff0c;因为可以借助HT…

【蓝桥杯嵌入式】4_key:单击+长按+双击

全部代码网盘自取 链接&#xff1a;https://pan.baidu.com/s/1PX2NCQxnADxYBQx5CsOgPA?pwd3ii2 提取码&#xff1a;3ii2 1、电路图 将4个按键的引脚设置为input&#xff0c;并将初始状态设置为Pull-up&#xff08;上拉输入&#xff09; 为解决按键抖动的问题&#xff0c;我们…

五、AIGC大模型_01大模型基础知识

1、基本概念 1.1 定义 目前&#xff0c;谈到大模型&#xff0c;通常都指的是大语言模型&#xff08;LLMs&#xff0c;即&#xff1a;Large Language Models) 大语言模型是具有大规模参数和复杂计算结构的深度学习模型&#xff0c;通常由深度神经网络构建而成&#xff0c;参数…

微服务与网关

什么是网关 背景 单体项目中&#xff0c;前端只用访问指定的一个端口8080&#xff0c;就可以得到任何想要的数据 微服务项目中&#xff0c;ip是不断变化的&#xff0c;端口是多个的 解决方案&#xff1a;网关 网关&#xff1a;就是网络的关口&#xff0c;负责请求的路由、转发…

Spring Cloud工程完善

目录 完善订单服务 启动类 配置文件 实体类 Controller Service Mapper 测试运行 完成商品服务 启动类 配置文件 实体类 Controller Service Mapper 测试运行 远程调用 需求 实现 1.定义RestTemplate 2.修改order-service中的OrderService 测试运行 Rest…

网络安全网格架构(CSMA) 网络安全框架csf

CSRF:Cross Site Request Forgy&#xff08;跨站请求伪造&#xff09; 用户打开另外一个网站&#xff0c;可以对本网站进行操作或攻击。容易产生传播蠕虫。 CSRF攻击原理&#xff1a; 1、用户先登录A网站 2、A网站确认身份返回用户信息 3、B网站冒充用户信息而不是直接获取用…

数据库系统课设——教务管理系统

目录 前言 一、总体设计 1、知识背景 2、模块介绍&#xff08;需求分析&#xff09; 3、设计步骤 3.1 页面原型设计 3.2 前端页面开发 3.3 后端接口开发 3.4 数据库设计 二、详细设计 1、 系统功能模块划分 2、 数据流程图 3、数据库概念结构设计 4、 数据库逻辑…

论文概览 |《Cities》2024.12 Vol.155(上)

本次给大家整理的是《Cities》杂志2024年12月第152期的论文的题目和摘要&#xff0c;一共包括73篇SCI论文&#xff01;由于论文过多&#xff0c;我们将通过两篇文章进行介绍&#xff0c;本篇文章介绍第1--第30篇论文! 论文1 Digital economy and risk response: How the digita…

FANUC机器人示教器中如何显示或关闭寄存器或IO的注释信息?

FANUC机器人示教器中如何显示或关闭寄存器或IO的注释信息? 如下图所示,我们打开一个子程序,可以看到程序中的寄存器和IO是显示注释信息的, 如果想关闭注释显示的话,怎么设置? 如下图所示,按下下一页的箭头(NEXT键), 如下图所示,点击“编辑”,在弹出的窗口中,选择“…

[QMT量化交易小白入门]-二十二、deepseek+cline+vscode,让小白使用miniQMT量化交易成为可能

本专栏主要是介绍QMT的基础用法&#xff0c;常见函数&#xff0c;写策略的方法&#xff0c;也会分享一些量化交易的思路&#xff0c;大概会写100篇左右。 QMT的相关资料较少&#xff0c;在使用过程中不断的摸索&#xff0c;遇到了一些问题&#xff0c;记录下来和大家一起沟通&a…

快速集成DeepSeek到项目

DeepSeek API-KEY 获取 登录DeekSeek 官网&#xff0c;进入API 开放平台 2. 创建API-KEY 复制API-KEY进行保存&#xff0c;后期API调用使用 项目中集成DeepSeek 这里只展示部分核心代码&#xff0c;具体请查看源码orange-ai-deepseek-biz-starter Slf4j AllArgsConstructo…

关于浏览器缓存的思考

问题情境 开发中要实现一个非原生pdf预览功能&#xff0c;pdf链接放在一个固定的后台地址&#xff0c;当重新上传pdf后&#xff0c;预览pdf仍然是上一次的pdf内容&#xff0c;没有更新为最新的内容。 查看接口返回状态码为 200 OK(from disk cache)&#xff0c; 表示此次pdf返回…

MAAS | Ollama 搭建本地 AI 大模型 deepseekWeb 界面调用

目录 一、环境准备二、安装 Ollama三、下载并部署 DeepSeek 模型四、简单交互五、通过 Web 界面调用大模型 在当今人工智能快速发展的时代&#xff0c;本地部署大语言模型赋予了用户更高的灵活性和个性化服务体验。本文介绍了如何准备环境、安装Ollama框架、下载并部署DeepSeek…

C++ ——从C到C++

1、C的学习方法 &#xff08;1&#xff09;C知识点概念内容比较多&#xff0c;需要反复复习 &#xff08;2&#xff09;偏理论&#xff0c;有的内容不理解&#xff0c;可以先背下来&#xff0c;后续可能会理解更深 &#xff08;3&#xff09;学好编程要多练习&#xff0c;简…

Baklib助力内容中台实施最佳实践的关键要素与成功案例

内容概要 内容中台的实施对于现代企业在数字化转型过程中具有重要的战略意义。内容中台不仅提升内容管理的效率&#xff0c;还能为企业提供更灵活的内容运营能力。在实施过程中&#xff0c;关键在于了解如何构建有效的架构设计、选择适合的技术、以及促进团队协作。尤其是像Ba…

选择 JxBrowser 还是 SWT Browser

当您需要在 SWT 应用程序中显示网页内容时&#xff0c;通常有两种选择&#xff1a;内置的 Browser 小部件或像 JxBrowser 这样的商业选项。 本文将详细剖析两者之间的差异&#xff0c;帮助您根据自身需求做出正确选择。 简而言之 内置的 Browser 是一个简单但功能可靠的小部…

RoboGrasp:一种用于稳健机器人控制的通用抓取策略

25年1月来自北京大学和哈佛大学的论文“RoboGrasp: A Universal Grasping Policy for Robust Robotic Control”。 模仿学习和世界模型在推进通用机器人学习方面显示出巨大的潜力&#xff0c;而机器人抓取仍然是实现精确操控的关键挑战。现有方法通常严重依赖机械臂状态数据和…

金仓数据库-KingbaseES-学习-01-单机部署(非图形化安装)

目录 一、环境信息 二、介绍 三、下载地址 四、安装步骤 1、配置内核参数 &#xff08;1&#xff09;文件系统相关 &#xff08;2&#xff09;共享内存与信号量&#xff08;IPC&#xff09; &#xff08;3&#xff09;网络与端口配置 &#xff08;4&#xff09;关键场…

双周报Vol.65:新增is表达式、字符串构造和数组模式匹配增强、IDE模式匹配补全增强...多项技术更新!

MoonBit更新 新增 is 表达式 这个表达式的语法形式为 expr is pat&#xff0c;这个表达式为 Bool 类型&#xff0c;当 expr 符合 pat 这个模式的时候返回 true&#xff0c;比如&#xff1a; fn use_is_expr(x: Int?) -> Unit {if x is Some(i) && i > 10 { .…

【Apache Paimon】-- 作为一名小白,如何系统地学习 Apache paimon?

目录 一、整体规划 1. 了解基本概念与背景 2. 学习资料的选择 3. 学习路径与规划 4. 学习建议 5. 注意事项 6. 参考学习资料 二、详细计划 阶段 1&#xff1a;了解基础&#xff08;1-2 周&#xff09; 阶段 2&#xff1a;深入掌握核心功能&#xff08;3-4 周&#xf…