C++:STL中vector的使用和模拟实现

在上一篇中讲到了string类,string并不属于STL中因为string出现的比STL早,但是在使用方法上两者有相似之处,学习完string后再来看vector会容易的多,接着往下阅读,一定会有收获滴!

目录

vector的介绍

vector的使用

 vector的构造函数

 vector的拷贝构造

 vector iterator 的使用

 vector的空间

vector增删改 


vector的介绍

1.string是存储字符串的类,vector是表示可变大小数组的序列容器。

2. 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素 进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自 动处理。

3. 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小 为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是 一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大 小。

4. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存 储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是 对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。

5. 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增 长。

6. 与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末 尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list和forward_list 统一的迭代器和引用更好。

vector的使用

对于vector的接口有很多,想要更仔细的看这里:https://legacy.cplusplus.com/reference/vector/vector/?kw=vector

 vector的构造函数

vector的构造函数重载了好几种,所以初始化的方法也有多种。

vector() //无参构造vector(size_t n,const value_type& val = value_type()) //构造并初始化n个valvector(InputIterator first, InputIterator last) //使用迭代器来初始化

 对于第二种的构造函数中value_type()会去调用该类型的构造函数,第三种使用迭代器来构造,对于vector的迭代器就是原生指针,区间是左闭右开。

示例:

int a[5] = { 1,2,3,4,5 };vector<int> v(a, a + 4);for (auto e : v){cout << e << " ";}cout << endl;vector<int>::iterator it = v.begin();vector<int> v1(v.begin(), v.end());for (auto e : v1){cout << e << " ";}cout << endl;

运行结果:

模拟实现:
成员变量:

typedef T* iterator;
typedef const T* const_iterator;iterator _start = nullptr;// 指向数据块的开始
iterator _finish = nullptr;// 指向有效数据的尾
iterator _end_Of_Storage = nullptr;// 指向存储容量的尾
        vector(){}vector(size_t n, const T& value = T()){resize(n,value);}vector(int n, const T& value = T()){resize(n, value);}template<class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);first++;}}

 vector的拷贝构造

vector的拷贝构造是指创建一个新的vector对象,其内容与原vector对象完全相同。

vector (const vector& x);

 模拟实现:

vector(const vector<T>& v){_start = new T[v.capacity()];for (size_t i = 0; i < v.size(); i++){_start[i] = v. _start[i];}_finish = _start + v.size();_end_Of_Storage = _start + v.capacity();}

在模拟实现时需要注意,拷贝分为浅拷贝和深拷贝,如果在vector的实现中使用浅拷贝就会出问题,会出现析构两次以及野指针的问题,所以需要深拷贝。 

 vector iterator 的使用

查看vector文档可以看到vector的迭代器

 begin()和end():获取第一个数据位置的iterator/const_iterator,获取最后一个数据的下一个位置的iterator/const_iterator。

模拟实现:

iterator begin(){return _start;}iterator end() {return _finish;}const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}

 vector的空间

size() 返回 vector 当前存储的元素数量,是实际已使用的空间大小。
示例:

std::vector<int> vec = {1, 2, 3};
std::cout << vec.size(); // 输出 3

capacity() 返回 vector 当前分配的存储空间大小(以元素数量计),可能大于或等于 size()
示例:

std::vector<int> vec;
vec.reserve(10);
std::cout << vec.capacity(); // 输出 10(size 仍为 0)

resize(n) 调整 vectorsizen

  • n < size(),多余元素被删除。
  • n > size(),新增元素默认初始化(或通过第二个参数指定值)。
    示例:
std::vector<int> vec = {1, 2, 3};
vec.resize(5);      // 变为 {1, 2, 3, 0, 0}
vec.resize(2);       // 变为 {1, 2}

reserve(n) 预分配至少容纳 n 个元素的内存空间,避免多次动态扩容(仅影响 capacity,不改变 size)。
示例:

std::vector<int> vec;
vec.reserve(100);    // 提前分配空间
vec.push_back(1);    // 不会触发扩容

区别: 

  • size 是实际元素数量,capacity 是当前分配的内存容量。
  • resize 修改 size 并可能初始化/删除元素,reserve 仅调整 capacity 不改变 size
  • 频繁插入数据时,reserve 可减少重新分配开销。

capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。 这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义 的。vs是PJ版本STL,g++是SGI版本STL。 reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问 题。 resize在开空间的同时还会进行初始化,影响size。

 模拟实现:

          size_t size() const{return _finish - _start;}size_t capacity() const{return _end_Of_Storage - _start;}void reserve(size_t n){if (n > capacity()){size_t oldsz = size();T* tmp = new T[n];/*memcpy(tmp, _start, n * sizeof(T));*/for (size_t i = 0; i < oldsz; i++){tmp[i] = _start[i];}delete[] _start;_start = tmp;_finish = tmp + oldsz;_end_Of_Storage = _start + n;}}void resize(size_t n, const T& value = T()){if (n < size()){_finish = _start + n;}else{reserve(n);while (_finish != _start + n){*_finish = value;_finish++;}}}

vector增删改 

尾插push_back() 在 vector 末尾添加元素:

vector<int> vec = {1, 2, 3};
vec.push_back(4); // vec: {1, 2, 3, 4}

模拟实现:

void push_back(const T& x){/*if (size() == capacity()){size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();reserve(newcapacity);}*_finish = x;_finish++;*/insert(end(), x);}

如果不用insert函数就使用注释那一段代码来实现尾插,效果是一样的,只不过先实现insert函数的话写尾插会更方便一点。

insert()在指定位置插入元素:

vec.insert(vec.begin() + 1, 5); // vec: {1, 5, 2, 3, 4}

模拟实现:

iterator insert(iterator pos, const T& x){assert(pos >= _start && pos <= _finish);if (size() == capacity()){size_t len = pos - _start;size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();reserve(newcapacity);pos = _start + len;}iterator end = _finish - 1;while (pos <= end){*(end+1) = *end;end--;}*pos = x;++_finish;return pos;}

erase()删除指定位置的元素: 

vec.erase(vec.begin() + 2); // 删除第3个元素,vec: {1, 5, 3, 4}
vec.erase(vec.begin(), vec.begin() + 2); // 删除前2个元素,vec: {3, 4}

在使用erase需要注意迭代器失效的问题, 直接删除当前迭代器指向的元素会导致该迭代器失效。

例如:

std::vector<int> vec = {1, 2, 3, 4};
auto it = vec.begin() + 1;
vec.erase(it); // it失效
// 此时访问*it是未定义行为

以及循环删除多个元素:

std::vector<int> vec = {1, 2, 3, 4};
for (auto it = vec.begin(); it != vec.end(); ++it) {if (*it % 2 == 0) {vec.erase(it); // it失效后,++it行为未定义}
}

对于这些我们需要重新来定义迭代器it,erase会返回指向被删除下一位置的迭代器,所以我们需要重新接收更新迭代器,正确处理方法

std::vector<int> vec = {1, 2, 3, 4};
for (auto it = vec.begin(); it != vec.end(); ) {if (*it % 2 == 0) {it = vec.erase(it); // 更新迭代器} else {++it;}
}

模拟实现:

iterator erase(iterator pos){assert(pos >= _start && pos < _finish);auto it = pos + 1;while (it != end()){*(it - 1) = *it;it++;}_finish--;return pos;}

对于erase删除数据在vector中就是需要挪动数据,直接将需要删除的数据覆盖,将后面的数据往前挪,最后返回被删除元素的位置。在进行删除动作之前需要进行断言,不可越界访问。

尾删:pop_back():删除最后一个元素

vec.pop_back(); // vec: {1, 5, 2, 3, 4}

模拟实现:

如果先实现好erase函数,pop_back函数就很好写了。

void pop_back(){/*if(_finish != _start)--_finish;*/erase(end());}

vector的使用就介绍到这里啦,这也是常用的函数,需要了解更多可以查看vector文档来学习,如果你已经了解string类,想必vector也手到擒来,string类和STL的用法相似,学会其中一种,再去学习其它STL就比较容易。 

 

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

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

相关文章

仓库管理的流程、绩效和解决方案?

什么是仓库管理&#xff1f; 仓库管理涉及对所有仓库运营的日常监督。一个全面、集成的仓库管理解决方案采用行业最佳实践&#xff0c;并涵盖使高效运营得以实现的所有基本要素。这些要素包括分销和库存管理、仓库劳动力管理以及业务支持服务。此外&#xff0c;由内部提供或与服…

TIM 实现定时中断【STM32L4】【实操】

使用定时器实现定时中断的功能&#xff1a;比如每1ms进入中断处理函数使用STM32CubeMX配置TIM初始化先了解每个参数的含义&#xff0c;在进行配置Counter Settings: 计数器基本设置Prescaler(PSC): 预分频器&#xff0c;设置预分频器系数Counter Mode: 技术模式&#xff0c;…

Elasticsearch 的聚合(Aggregations)操作详解

目录 1. 概述 2. 聚合类型分类详解 2.1 桶聚合&#xff08;Bucket Aggregations&#xff09; 2.1.1 基础桶聚合 2.1.2 特殊桶聚合 2.1.3 高级桶聚合 2.2 指标聚合&#xff08;Metric Aggregations&#xff09; 2.2.1 单值指标聚合&#xff08;Single-value Metrics&am…

电子电气架构 --- 高阶智能驾驶对E/E架构的新要求

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 做到欲望极简,了解自己的真实欲望,不受外在潮流的影响,不盲从,不跟风。把自己的精力全部用在自己。一是去掉多余,凡事找规律,基础是诚信;二是…

0.深度学习环境配置步骤

0.深度学习环境配置步骤 这里介绍深度学习环境配置详细步骤&#xff0c;包括安装软件&#xff0c;每一步都有安装时的截图&#xff08;后续持续更新&#xff0c;敬请关注&#xff09; 目录如下&#xff1a; 1.安装anaconda 2.安装CUDA 3.安装CU_DNN 4.安装pytorch

在 Azure 中配置 SMS 与 OTP

1. Azure Active Directory B2C (AAD B2C) 中的 SMS/OTP 身份验证 1.1. 现状与原理&#xff1a;电话注册与登录 Azure Active Directory B2C (AAD B2C) 提供了将电话号码作为用户身份标识进行注册和登录的功能&#xff0c;旨在为用户提供一种便捷的替代传统电子邮件或用户名登录…

简单实现支付密码的页面及输入效果

干我们这行&#xff0c;风吹日晒不到&#xff0c;就怕甲方突发奇想。 今天客户要做一个安全密码前置校验&#xff0c;还要做成支付宝那种效果。ps:android端 心理吐槽了一万遍以后&#xff0c;还是得面对现实。 先用通义问一遍&#xff0c;给了两个方案&#xff0c;要么自己写&…

proxmox 解决docker容器MongoDB创建报错MongoDB 5.0+ requires a CPU with AVX support

目录 最简单直接的方式 测试MongoDB docker compose的安装shell脚本 验证访问 最简单直接的方式 让虚拟机直接使用宿主机的物理 CPU 功能标志。 打开 Proxmox Web UI。 选择你的 VM → 硬件 (Hardware) → CPU → 点击 编辑 (Edit)。 将 CPU 类型改为 host。 确认并重启…

向前滚动累加SQL 实现思路

一、业务背景在经营分析场景里&#xff0c;我们经常需要回答&#xff1a;“截至今天&#xff0c;过去 N 天/月/周累计发生了多少&#xff1f;”“把维度切到省、市、房型、项目经理、代理商等&#xff0c;结果又是什么&#xff1f;”本文用两个真实需求做演示&#xff1a;以天为…

Spring AI(14)——文本分块优化

RAG时&#xff0c;检索效果的优劣&#xff0c;和文本的分块的情况有很大关系。SpringAI中通过TokenTextSplitter对文本分块。本文对SpringAI提供的TokenTextSplitter源码进行了分析&#xff0c;并给出一些自己的想法&#xff0c;欢迎大家互相探讨。查看了TokenTextSplitter的源…

Python----大模型(RAG 的智能评估-LangSmith)

一、LangSmith LangSmith是LangChain的一个子产品&#xff0c;是一个大模型应用开发平台。它提供了从原 型到生产的全流程工具和服务&#xff0c;帮助开发者构建、测试、评估和监控基于LangChain 或其他 LLM 框架的应用程序。 安装 LangSmith pip install langsmith0.1.137 官网…

磁悬浮轴承转子不平衡质量控制策略设计:原理、分析与智能实现

磁悬浮轴承(Active Magnetic Bearing, AMB)以其无接触、无摩擦、高转速、无需润滑等革命性优势,在高端旋转机械领域(如高速电机、离心压缩机、飞轮储能、航空航天动力系统)展现出巨大潜力。然而,转子固有的质量不平衡是AMB系统面临的核心挑战之一,它诱发强同步振动,威胁…

C++查询mysql数据

文章目录 文章目录 1.前言 2. 代码 &#xff08;1&#xff09;执行查询SQL &#xff08;2&#xff09;获取结果集 &#xff08;3&#xff09;遍历结果集&#xff08;获取字段数、行数&#xff09; &#xff08;4&#xff09;释放资源 3.完整代码 1.前言 我们成功连接数…

【论文阅读】-《GenAttack: Practical Black-box Attacks with Gradient-Free Optimization》

GenAttack&#xff1a;利用无梯度优化的实用黑盒攻击 Moustafa Alzantot UCLA Los Angeles, U.S.A malzantotucla.edu Yash Sharma Cooper Union New York, U.S.A sharma2cooper.edu Supriyo Chakraborty IBM Research New York, U.S.A supriyous.ibm.com Huan Zhang UCLA Los…

CT、IT、ICT 和 DICT区别

这四个术语&#xff1a;CT、IT、ICT 和 DICT&#xff0c;是信息通信行业中常见的核心概念&#xff0c;它们既有演进关系&#xff0c;又有各自的技术重点。&#x1f539; 一、CT&#xff08;Communication Technology&#xff09;通信技术**定义&#xff1a;**以语音通信为核心的…

Effective C++ 条款4:确定对象被使用前已先被初始化

Effective C 条款4&#xff1a;确定对象被使用前已先被初始化核心思想&#xff1a;永远在使用对象前将其初始化。未初始化对象是未定义行为的常见来源&#xff0c;尤其对于内置类型。 1. 内置类型手动初始化 int x 0; // 手动初始化 const char* text &quo…

LangSmith的配置介绍

文章目录注册及登录生成API KeyLangSmith的配置方式一&#xff1a;放运行环境里方式二&#xff1a;写代码里执行代码查看LangSmith上是否看到本次运行的项目记录LangSmith的其他注意注册及登录 首先使用邮箱注册一个账号及设置密码&#xff0c;等收到收到邮件后&#xff0c;进…

Linux的生态与软件安装

坚持用 清晰易懂的图解 代码语言&#xff0c;让每个知识点变得简单&#xff01; &#x1f680;呆头个人主页详情 &#x1f331; 呆头个人Gitee代码仓库 &#x1f4cc; 呆头详细专栏系列 座右铭&#xff1a; “不患无位&#xff0c;患所以立。” Linux的生态与软件安装前言目录…

3.4 安全-分布式-数据库-挖掘

一、数据库的安全数据库里面的安全措施&#xff1a;用户标识和鉴定&#xff1a;用户的账户口令等存取控制&#xff1a;对用户操作进行控权&#xff0c;有对应权限码才能操作。密码存储和传输&#xff1a;加密存储。视图的保护&#xff1a;视图需要授权审计&#xff1a;专门的文…

多线程 Reactor 模式

目录 多线程 Reactor 模式的核心动机 多线程演进方向 多线程 Reactor 模型结构 多线程 EchoServer 实现核心部分 Handler 的多线程化 多线程 Reactor 的三个核心点 本篇文章内容的前置知识为 单线程 Reactor 模式&#xff0c;如果不了解&#xff0c;可点击链接学习 单线程…