【C++ 】-vector:新时代动态数组的革新与未来

news2024/11/15 11:40:21

目录

1. vector的介绍及使用

1.1 vector的介绍

1.1.1 vector是什么

1.1.2 vector的存储机制

1.2 vector的使用

1.2.1 定义和构造函数

1.2.2 迭代器

1.2.3 容量相关操作

1.2.4 元素访问和修改

1.3 迭代器失效问题

2. vector深度剖析及模拟实现

2.1 std::vector的模拟实现

2.2 memcpy的使用问题

2.3 动态二维数组

总结


 

专栏:C++学习笔记 

上一卷:C++—STL

在C++标准模板库(STL)中,vector是一个非常重要的容器,它提供了一个动态数组,可以根据需要自动调整其大小。

1. vector的介绍及使用

1.1 vector的介绍

1.1.1 vector是什么

vector是一个序列容器,表示可变大小的数组。与数组相似,vector使用连续的存储空间来存储元素,因此可以通过下标访问元素,且效率与数组相当。不同的是,vector的大小是动态的,会随着元素的增加自动调整。

小李的理解vector就像是一个能自动扩展的数组,当需要放更多的东西时,它会自动找更大的地方,把原来的东西搬过去。

1.1.2 vector的存储机制

vector的实现基于动态分配数组。当新元素插入时,如果当前数组容量不足,vector会重新分配一个更大的数组,并将现有元素复制到新数组中。这种重新分配是一个耗时操作,但vector通常会预留额外的空间以减少重新分配的频率,从而优化性能。

小李的理解vector的存储方式就像是搬家,当家里东西太多放不下时,它会找到一个更大的房子,把所有东西搬过去,这样下次再放东西就不用总是搬家了。

1.2 vector的使用

使用vector时,必须熟悉其常用接口。以下是一些重要的接口:

1.2.1 定义和构造函数

  • 无参构造函数:vector()
  • 指定大小和默认值的构造函数:vector(size_type n, const value_type& val = value_type())
  • 拷贝构造函数:vector(const vector& x)
  • 使用迭代器范围的构造函数:vector(InputIterator first, InputIterator last)
#include <vector>
#include <iostream>

int main() {
    std::vector<int> v1; // 默认构造函数
    std::vector<int> v2(10, 1); // 构造一个包含10个1的vector
    std::vector<int> v3(v2); // 拷贝构造
    std::vector<int> v4(v2.begin(), v2.end()); // 迭代器范围构造

    return 0;
}

小李的理解创建vector的方法就像是买不同规格的箱子,有的箱子是空的(无参构造),有的箱子里已经装满了指定数量的物品(指定大小和默认值),有的箱子是完全照搬另一个箱子的东西(拷贝构造),还有的是根据一个范围内的物品来装箱(迭代器范围构造)。

1.2.2 迭代器

  • begin()end():获取首元素和末尾后一个位置的迭代器
  • rbegin()rend():获取末元素和首元素前一个位置的反向迭代器
#include <vector>
#include <iostream>

int main() {
    std::vector<int> v{1, 2, 3, 4, 5};
    for (auto it = v.begin(); it != v.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;
    return 0;
}

小李的理解迭代器就像是一个指针,可以帮我们一个一个地访问箱子里的东西,从头到尾,也可以从尾到头。

1.2.3 容量相关操作

  • size():获取当前元素个数
  • capacity():获取当前容量
  • empty():判断是否为空
  • resize():调整大小
  • reserve():预留存储空间
#include <vector>
#include <iostream>

int main() {
    std::vector<int> v;
    v.reserve(10); // 预留空间
    v.resize(5); // 调整大小为5
    std::cout << "Size: " << v.size() << ", Capacity: " << v.capacity() << std::endl;
    return 0;
}

 

小李的理解size告诉我们箱子里有多少东西,capacity告诉我们箱子能装多少东西,empty告诉我们箱子是不是空的,resize可以调整箱子里的东西数量,reserve可以提前预留空间,避免频繁换箱子。

1.2.4 元素访问和修改

  • operator[]:下标访问
  • at():带边界检查的访问
  • push_back():尾部插入
  • pop_back():尾部删除
  • insert():在指定位置插入
  • erase():删除指定位置的元素
  • swap():交换两个vector的内容
#include <vector>
#include <iostream>

int main() {
    std::vector<int> v{1, 2, 3, 4, 5};
    v.push_back(6); // 尾部插入
    std::cout << "After push_back(6): ";
    for (const auto& elem : v) {
        std::cout << elem << " ";
    }
    std::cout << std::endl; // After push_back(6): 1 2 3 4 5 6
    
    v.pop_back(); // 尾部删除
    std::cout << "After pop_back: ";
    for (const auto& elem : v) {
        std::cout << elem << " ";
    }
    std::cout << std::endl; // After pop_back: 1 2 3 4 5
    
    v.insert(v.begin() + 2, 10); // 在第三个位置插入10
    std::cout << "After insert at position 2: ";
    for (const auto& elem : v) {
        std::cout << elem << " ";
    }
    std::cout << std::endl; // After insert at position 2: 1 2 10 3 4 5
    
    v.erase(v.begin() + 2); // 删除第三个位置的元素
    std::cout << "After erase at position 2: ";
    for (const auto& elem : v) {
        std::cout << elem << " ";
    }
    std::cout << std::endl; // After erase at position 2: 1 2 3 4 5
    
    std::vector<int> v2{7, 8, 9};
    std::swap(v, v2); // 交换内容
    std::cout << "After swap: v: ";
    for (const auto& elem : v) {
        std::cout << elem << " ";
    }
    std::cout << std::endl; // After swap: v: 7 8 9
    std::cout << "v2: ";
    for (const auto& elem : v2) {
        std::cout << elem << " ";
    }
    std::cout << std::endl; // v2: 1 2 3 4 5

    return 0;
}

解释

  • v.push_back(6)vector末尾插入6,结果为1 2 3 4 5 6
  • v.pop_back()删除vector末尾元素,结果为1 2 3 4 5
  • v.insert(v.begin() + 2, 10)在第三个位置插入10,结果为1 2 10 3 4 5
  • v.erase(v.begin() + 2)删除第三个位置的元素,结果为1 2 3 4 5
  • std::swap(v, v2)交换vv2的内容,v变为7 8 9v2变为1 2 3 4 5

 

 

小李的理解vector就像是一个万能的箱子,不仅可以用下标访问里面的东西,还可以在尾部添加或删除东西,在指定位置插入或删除东西,还能和另一个箱子的东西互换。

1.3 迭代器失效问题

vector的操作中,有些操作可能会导致迭代器失效,如resizereserveinsertassignpush_back等。当这些操作引起底层数组重新分配时,原来的迭代器会指向无效的内存区域,继续使用这些迭代器会导致程序崩溃。因此,在这些操作后,必须重新获取迭代器。

#include <vector>
#include <iostream>

int main() {
    std::vector<int> v{1, 2, 3, 4, 5, 6};
    auto it = v.begin();
    
    // 操作可能导致迭代器失效
    v.assign(100, 8);
    
    // 必须重新获取迭代器
    it = v.begin();
    while (it != v.end()) {
        std::cout << *it << " ";
        ++it;
    }
    std::cout << std::endl;
    return 0;
}

小李的理解迭代器就像是箱子里的一个标记,告诉我们从哪里开始访问东西。但是如果箱子重新调整过,原来的标记就会失效,所以每次调整后需要重新放置标记。

2. vector深度剖析及模拟实现

 

2.1 std::vector的模拟实现

要深入理解vector,可以尝试实现一个简单的模拟版本。下面是一个简化的vector实现示例:

#include <iostream>  // Include this header for std::cout and std::endl

template <typename T>
class Vector {
private:
    T* data;
    size_t sz;
    size_t cap;

    void reallocate(size_t new_cap) {
        T* new_data = new T[new_cap];
        for (size_t i = 0; i < sz; ++i) {
            new_data[i] = std::move(data[i]);
        }
        delete[] data;
        data = new_data;
        cap = new_cap;
    }

public:
    Vector() : data(nullptr), sz(0), cap(0) {}

    void push_back(const T& value) {
        if (sz == cap) {
            reallocate(cap == 0 ? 1 : cap * 2);
        }
        data[sz++] = value;
    }

    size_t size() const { return sz; }
    size_t capacity() const { return cap; }
    T& operator[](size_t index) { return data[index]; }

    ~Vector() { delete[] data; }
};

int main() {
    Vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);

    std::cout << "v size: " << v.size() << ", capacity: " << v.capacity() << std::endl; // v size: 3, capacity: 4
    for (size_t i = 0; i < v.size(); ++i) {
        std::cout << v[i] << " ";
    }
    std::cout << std::endl; // 1 2 3

    return 0;
}

解释

  • v.push_back(1), v.push_back(2), v.push_back(3)将三个元素插入vector
  • 初始容量为0,每次容量不足时容量翻倍,最终容量为4,大小为3。
  • 输出vector大小和容量,结果为v size: 3, capacity: 4
  • 输出所有元素,结果为1 2 3

 

小李的理解这个模拟实现就像是自己动手做一个可扩展的箱子,当箱子满了,我们自己找个更大的地方搬过去,这样就可以不断地增加箱子里的东西。

2.2 memcpy的使用问题

vector的实现中,有时会使用memcpy来复制内存。但是,如果元素类型是自定义类型,且涉及资源管理,memcpy的浅拷贝会导致问题,如内存泄漏或程序崩溃。因此,对于自定义类型,应避免使用memcpy,而应使用元素的拷贝构造函数。

#include <iostream>
#include <cstring>

class MyClass {
public:
    int* data;
    MyClass(int value) {
        data = new int(value);
    }
    ~MyClass() {
        delete data;
    }
};

int main() {
    MyClass obj1(10);
    MyClass obj2(20);
    
    // 浅拷贝导致问题
    std::memcpy(&obj2, &obj1, sizeof(MyClass));
    
    std::cout << *obj2.data << std::endl; // 可能导致程序崩溃
    
    return 0;
}

 

小李的理解memcpy就像是复制一个箱子里的东西,但如果箱子里的东西需要特别处理(比如需要手动管理的资源),直接复制可能会出问题,应该用正确的方法来复制这些东西。、

 

 结论:如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。

2.3 动态二维数组

使用vector可以方便地构建动态二维数组,例如,生成杨辉三角:

#include <vector>
#include <iostream>

void generate_pascals_triangle(size_t n) {
    std::vector<std::vector<int>> vv(n);
    for (size_t i = 0; i < n; ++i) {
        vv[i].resize(i + 1, 1);
        for (size_t j = 1; j < i; ++j) {
            vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
        }
    }
    for (const auto& row : vv) {
        for (int val : row) {
            std::cout << val << " ";
        }
        std::cout << std::endl;
    }
}

int main() {
    size_t n = 5; // You can change this value to generate more rows of Pascal's Triangle
    generate_pascals_triangle(n);
    return 0;
}

小李的理解动态二维数组就像是很多小箱子放在一个大箱子里,每个小箱子可以根据需要调整大小,用来存放不同数量的东西。比如杨辉三角,每一行的小箱子里放的东西都不一样多。

总结

C++中的vector是一个动态数组,可以根据需要自动调整大小。它使用连续的内存空间,像普通数组一样可以通过下标快速访问元素,但与普通数组不同的是,vector可以动态增加或减少元素。创建vector有多种方式,包括默认构造、指定大小和默认值、拷贝构造等。常用的操作有插入、删除、访问和遍历元素,vector还提供了容量管理的方法,如reserveresize,以优化性能。在使用过程中需要注意迭代器失效的问题,当vector重新分配内存时,原来的迭代器会失效,必须重新获取。通过这些特性,vector在处理动态数据时非常方便和高效。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【笔记】finalshell中使用nano编辑器GNU

ctrl O 保存 enter 确定 ctrl X 退出 nano编辑 能不用就不用吧 因为我真用不习惯 nano编辑的文件也可以用vim编辑的

网页提示“非私密连接”怎么办?

当网页提示“非私密连接”或“您与该网站的连接不是私密连接”&#xff0c;这通常意味着浏览器无法建立一个安全的HTTPS连接。HTTPS协议是HTTP协议的安全版本&#xff0c;通过SSL协议加密数据传输&#xff0c;以保护用户的数据免受中间人攻击或监听。主要有下面几个原因&#x…

In Search of Lost Online Test-time Adaptation: A Survey--论文笔记

论文笔记 资料 1.代码地址 https://github.com/jo-wang/otta_vit_survey 2.论文地址 https://arxiv.org/abs/2310.20199 3.数据集地址 1论文摘要的翻译 本文介绍了在线测试时间适应(online test-time adaptation,OTTA)的全面调查&#xff0c;OTTA是一种专注于使机器学习…

Apache配置与应用(优化apache)

Apache配置解析&#xff08;配置优化&#xff09; Apache链接保持 KeepAlive&#xff1a;决定是否打开连接保持功能&#xff0c;后面接 OFF 表示关闭&#xff0c;接 ON 表示打开 KeepAliveTimeout&#xff1a;表示一次连接多次请求之间的最大间隔时间&#xff0c;即两次请求之间…

一.5 高速缓存至关重要

这个简单的示例揭示了一个重要的问题&#xff0c;即系统花费了大量的时间把信息从一个地方挪到另一个地方。hello程序的机器指令最初是存放在硬盘上&#xff0c;当程序加载时&#xff0c;它们被复制到主存&#xff1b;当处理器运行程序时&#xff0c;指令又从主存复制到处理器。…

C++报警:warning: zero as null pointer constantstddef.h

源码和警告内容 解决办法&#xff1a; select(0,nullptr,nullptr,nullptr,&delay); 关于NULL和nullptr的区别&#xff1a; 在C中&#xff0c;nullptr和null&#xff08;通常指的是NULL宏&#xff0c;因为C标准中并没有直接定义null关键字&#xff09;都用于表示空指针&am…

基于eBPF的procstat软件追踪等待锁和持有锁的时间

在并发编程中&#xff0c;锁的使用是保证线程安全的重要手段。然而&#xff0c;过度使用锁或者锁竞争可能导致性能瓶颈。为了分析程序中锁的使用情况&#xff0c;我们可以借助procstat软件来追踪程序加锁时间和等待锁的时间。procstat是一个基于eBPF&#xff08;extended Berke…

uniapp安卓端实现语音合成播报

最初尝试使用讯飞语音合成方式,能获取到语音数据,但是数据是base64格式的,在安卓端无法播放,网上有说通过转成blob格式的url可以播放,但是uniapp不支持转换的api;于是后面又想其他办法,使用安卓插件播报原生安卓语音播报插件 - DCloud 插件市场 方案一(讯飞语音合成) 1.在讯飞…

AGE Cypher 查询格式

使用 ag_catalog 中的名为 cypher 的函数构建 Cypher 查询&#xff0c;该函数返回 Postgres 的记录集合。 Cypher() Cypher() 函数执行作为参数传递的 Cypher 查询。 语法&#xff1a;cypher(graph_name, query_string, parameters) 返回&#xff1a; A SETOF records 参…

[240709] X-CMD 发布 v0.3.15:新增 uname、coin、df 和 uptime 模块;优化非 Posix Shell

目录 X-CMD 发布 v0.3.15✨ uname✨ coin✨ df✨ uptime✨ fish | onsh | nu | elv✨ go✨ env X-CMD 发布 v0.3.15 ✨ uname 新增了 uname 模块&#xff0c;用于增强 uname 命令的功能。 ✨ coin 新增了 coin 模块&#xff0c;作为 CoinCap 平台信息查看器。 ✨ df 新增了…

Prometheus+Grafana监控Linux主机

1、安装Prometheus 1.1 、下载Prometheus 下载网址 https://github.com/prometheus/prometheus/releases选择需要的版本 wget https://github.com/prometheus/prometheus/releases/download/v2.53.0/prometheus-2.53.0.linux-amd64.tar.gz1.2、安装Prometheus软件 1.2.1、…

命名空间namespace--c++入门基础等

个人主页点这里~ 1.命名空间-namespace 简介 &#xff1a;在C/C中&#xff0c;变量、函数和后面要学到的类都是大量存在的&#xff0c;这些变量、函数和类的名称将都存在于全局作用域中&#xff0c;可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化&#xf…

递归、搜索与回溯算法 2024.7.4-24.7.9

专题介绍&#xff1a; 一、递归 1、汉诺塔问题 class Solution {public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {int n A.size();move(n,A,B,C);// 将A柱上的n个盘子通过借助B盘子全部挪到C柱子上}void move(int m,List<Integ…

UI组件库---vantList组件接口多次调用大坑问题

问题描述&#xff1a;当使用refesh下拉操作时&#xff0c;vanlist组件会多次调用&#xff08;大概三次&#xff09;&#xff01; 解决方案&#xff1a; 1、接口错误的时候&#xff0c;大量重复请求。 可能接口错误时vant3内部某些变量没重置&#xff0c;导致一直重复请求&am…

08.C2W3.Auto-complete and Language Models

往期文章请点这里 目录 N-Grams: OverviewN-grams and ProbabilitiesN-gramsSequence notationUnigram probabilityBigram probabilityTrigram ProbabilityN -gram probabilityQuiz Sequence ProbabilitiesProbability of a sequenceSequence probability shortcomingsApproxi…

tauri如何实现窗口拖动,自定义标题栏

文章目录 一、tauri是什么&#xff1f;二、封装好的标题栏&#xff0c;引用修改即可使用三 相关配置实现细节实现窗口拖动 一、tauri是什么&#xff1f; Tauri是一个开源框架&#xff0c;用于创建跨平台的桌面应用程序。它使用Rust编程语言&#xff0c;并结合了现有的Web技术&…

javascript DOM BOM 笔记

Web API API的概念 API&#xff08;Application Programming Interface,应用程序编程接口&#xff09;是一些预先定义的函数&#xff0c;目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力&#xff0c;而又无需访问源码&#xff0c;或理解内部工作机制的细…

PLM系统供应商:PLM系统供应商哪家好

PLM系统供应商&#xff1a;PLM系统供应商哪家好 在智能制造时代&#xff0c;产品生命周期管理&#xff08;PLM&#xff09;系统已成为企业提升产品创新能力、优化生产流程、加速产品上市的关键工具。作为这一领域的核心力量&#xff0c;PLM系统供应商正以前所未有的速度和深度&…

vue3实现无缝滚动 列表滚动 vue3-seamlessscroll

vue3框架内使用无缝滚动&#xff0c;使用一个插件比较合适&#xff08;gitee地址&#xff09;&#xff1a; vue3-seamless-scroll: Vue3.0 无缝滚动组件 具体更多配置请看&#xff1a; 组件配置 | vue3-scroll-seamless 1. 安装&#xff1a; npm install vue3-seamless-sc…

红酒与电影经典:那些银幕上的醉人瞬间

在光影交织的银幕世界里&#xff0c;红酒不仅是品味生活的象征&#xff0c;更是情感与故事的催化剂。每当夜幕降临&#xff0c;一杯色泽深邃的红酒&#xff0c;便能带我们走进那些令人陶醉的影片瞬间&#xff0c;感受不同的人生百态。今天&#xff0c;就让我们一起回味那些银幕…