STL---vector详解(从使用到底层)

news2026/3/28 2:43:33
前言在我的C专栏里有一篇讲解string的文章里边的各种接口讲解的比较详细大家对使用有疑惑的可以去我的专栏里看重复的接口相似的使用我就不再过多介绍了本文主要讲vector的底层。vector简介vector就是一个会自动扩容的顺序表。在cplusplus里边它是一个模板类。template class T, class Alloc allocatorT class vector; // generic templatevector的使用构造函数关于第一个构造函数的解释explicit vector (const allocator_type alloc allocator_type());//声明也许会有人有疑惑明明已经有第二个模板参数了就算不想用它默认的那个空间配置器类型即allocatorT那模板显示实例化的时候显示传第二个模板参数就行了为什么构造函数里还要给参数答(首先要知道allocator_type()是typedef过的而且模板参数是类型函数参数是对象不是一个概念模板决定的是类型但决定不了模板里函数用这个类型的哪个对象。)模板第二个参数Alloc决定了「允许使用的分配器类型」构造函数参数只能传「该类型的对象」不能传其他类型不传构造函数参数时默认用「模板参数类型的默认对象」传构造函数参数时用的是「该类型下你指定的具体对象」但类型必须和模板参数一致vectorint v1;//第一个构造函数当它是无参构造就行vectorint v2(10, 1);vectorint v2(v2.begin(), v2.end());遍历for (size_t i 0; i 10; i) { cout v1[i] ; } cout endl; for (auto e : v1) { cout e ; } cout endl; vectorint::iterator it1 v1.begin(); while (it1 ! v1.end()) { cout (*it1) ; it1; } cout endl;Capacity模块这里唯一需要注意的一个点就是在vs底层是1.5倍扩容构造的空vector的容量就是0不像string有一个_Buf数组。Modifiers模块vector里并没有去实现头插头删原因就是由于效率太低但可以使用insert去实现头插的效果。void test_03() { vectorint v1(5, 2);// 2 2 2 2 2 //在某个位置前插入 v1.insert(v1.begin(), 5);// 5 2 2 2 2 2 v1.insert(v1.begin() 3, 6);// 5 2 2 6 2 2 2 //在某个值前插入---vector里边没有find函数因为算法库里的find就够使用了 //在v1的6这个值前边插入一个7 auto it1 find(v1.begin(), v1.end(), 6); v1.insert(it1, 7); for (auto e : v1) { cout e ; } cout endl; v1.erase(v1.begin()); v1.erase(v1.begin() 1, v1.begin() 3);//删除的区间是左闭右开的 for (auto e : v1) { cout e ; } cout endl; }vector的一些特殊使用string s;vectorchar v1;这两个的区别在哪?从底层的角度来说都是一个字符数组但是vectorchar仅仅只是说数组里边存着char没有\0的标识并且没有string里边针对字符串的各种处理接口。////void push_back (const value_type val);vectorstring v2;v2.push_back(xxc);上边的代码走的是隐式类型转换xxc作为const char*的字符串会先去构造一个string类型的临时对象而val引用这个临时对象将这个临时对象拷贝构造给vector内部此时的value_type就是string。关于范围for便利自定义类型的小注意点void test_01() { vectorstring v1; v1.push_back(xxc); v1.push_back(lqm); /* * 范围for的底层会转化成*iterator*iterator的结果是一个string类型的对象 * 不加引用就是调用的赋值运算符重载是深拷贝效率不高了 * 所以建议如果是自定义类型就加上引用去便利如果不需修改一同加上const * 对于内置类型由于引用底层也是转化成指针去开空间的所以加不加引用效果都不大 */ for (const auto e : v1) { cout e ; } }initializer_listinitializer_list是C11出的新语法它是一个模板类底层其实是一个数组并且有两个指针一个指向头一个指向尾通俗理解就是两个迭代器。vector支持initializer_list去构造。vector (initializer_listvalue_type il, const allocator_type alloc allocator_type());//initializer_List auto il1 { 1,2,3 };//相当于是initializer_listint il auto il2 { 1,2,3,4,5,6,7 }; /* * v1v2是直接调用vector的拷贝构造 * {12345}会直接被编译器识别为initializer_listint类型传参传给拷贝构造的第一个参数 * 构造函数底层用相当于用范围for将数据一个个push_back()到vector里边 * * v3v4相当于是隐式类型转换 * 先用initializer_list去构造一个vector临时对象再将这个临时对象拷贝构造给v3, v4 * 但是编译器底层会优化成直接构造 */ vectorint v1({1,2,3,4,5}); vectorint v2({1,2,3,4,5,6,7,8,9,10}); //vectorint v3{1,2,3,4,5};//另一种写法效果是跟下边的v3一样的 vectorint v3 { 1,2,3,4,5 }; vectorint v4 { 1,2,3,4,5,6,7,8,9,10 };vector底层实现vector.h#pragma once #includeassert.h #includeiostream #includeinitializer_list using namespace std; namespace xxc { templateclass T class vector { public: typedef T* iterator; typedef const T* const_iterator; iterator begin() { return _start; } iterator end() { return _finish; } const_iterator begin() const { return _start; } const_iterator end() const { return _finish; } //这个默认构造无法省略不写虽然它什么都没干但是由于写了拷贝构造编译器不会再自动生成构造函数 //这就导致我们要vectorint v;就无构造可用 //也可以vector() default;强制编译器生成默认构造不管有没有显示写构造 vector() {} //注意这里都是在声明的地方给缺省值的不给的话由于C标准没有规定给不给成员变量初始化 //不同平台对于初不初始化成员变量都是不确定的所以给缺省值比较安全 //不初始化如果成员变量里边是随机值那么reserve的时候就会有问题 vector(const vectorT v) { reserve(v.capacity()); for (auto e : v) { push_back(e); } } //initializer_list是支持迭代器的 vector(initializer_listT il) { reserve(il.size()); for (auto e : il) { push_back(e); } } //注下边两个函数在调用的时候会产生问题因为函数调用会去匹配最合适的 //xxc::vectorint v7(10, 1);此时会匹配到迭代器区间构造因为10和1都是int类型匹配到模板最合适 //n个val构造的参数一个是size_t一个是T参数不如迭代器区间构造匹配 //但迭代器区间构造里涉及到解引用就运行出错了 //解决方法就是再写一个最匹配的实例函数 //迭代器区间构造---为什么要用函数模板是因为可能要用到其他容器的迭代器区间来构造vector template class InputIterator vector(InputIterator first, InputIterator last) { while (first ! last) { push_back(*first); first; } } vector(int n, T val T()) { resize(n, val); } vector(size_t n, T val T()) { resize(n, val); } //如果vector为空再析构 ~vector() { if (_start) { delete[] _start; _start _finish _end_of_shorage nullptr; } } void swap(vectorT v) { std::swap(_start, v._start); std::swap(_finish, v._finish); std::swap(_end_of_shorage, v._end_of_shorage); } //现代写法---v1 v7v7先拷贝构造给vv跟v1再交换一下数据相当于就v7赋值给了v1最后函数结束v出作用域销毁 vectorT operator(vectorT v) { swap(v); return *this; } size_t size() const { return _finish - _start; } size_t capacity() const { return _end_of_shorage - _start; } void reserve(size_t n) { /* * 下边的_finish _start size();有问题 * 因为size()是去调用size()函数此时的_start已经不是原来的0了指向了新空间 * 但_finish还是原来的0两个相减计算出来的size()就不对了应该加上原来的size()才对 * 所以要先保存原来的size() */ //if (n capacity()) //{ // //异地扩容 // T* tmp new T[n]; // if (_start)//如果_start不为nullptr就拷贝旧数据过来 // { // memcpy(tmp, _start, size()); // } // _start tmp; // _finish _start size(); // _end_of_shorage _start n; //} if (n capacity()) { //异地扩容 T* tmp new T[n]; size_t old_size size(); if (_start)//如果_start不为nullptr就拷贝旧数据过来 { //memcpy不是深拷贝 //memcpy(tmp, _start, sizeof(T) * old_size); for (size_t i 0; i old_size; i) { tmp[i] _start[i];//内置类型浅拷贝自定义类型调用赋值运算符重载(深拷贝) } delete[] _start; } _start tmp; _finish _start old_size; _end_of_shorage _start n; } } T operator[](size_t i) { assert(i size()); return _start[i]; } const T operator[](size_t i) const { assert(i size()); return _start[i]; } iterator insert(iterator pos, const T x) { assert(pos _finish pos _start); //扩容 if (_finish _end_of_shorage) { //这里如果不更新pos迭代器扩容后会存在一个迭代器失效的问题 size_t len pos - _start; reserve(capacity() 0 ? 4 : 2 * capacity()); pos _start len; } //这里的挪动数据没有string的那么麻烦要去考虑头插的无限循环问题 //因为pos是迭代器往后再怎么减都不可能是0的循环的条件也不可能恒成立 iterator end _finish - 1; while (end pos) { *(end 1) *end; end--; } *pos x; _finish; return pos; } iterator erase(iterator pos) { assert(pos _start); assert(pos _finish); iterator it pos 1; while (it ! _finish) { *(it - 1) *it; it;//尽量用前置因为后置在底层是会有拷贝的Date类里边就有 } _finish--; return pos; } void push_back(const T x) { //扩容 if (_finish _end_of_shorage) { reserve(capacity() 0 ? 4 : 2 * capacity()); } *_finish x; _finish; } void pop_back() { //assert(_finish _start); assert(!empty()); _finish--; } bool empty() const { return _start _finish; } void clear() { _finish _start; } //T()是匿名对象也叫临时对象 void resize(size_t n, T val T()) { if (n size()) { reserve(n); while (_finish ! _start n) { *(_finish) val; _finish; } } else { _finish _start n; } } private: iterator _start nullptr; iterator _finish nullptr; iterator _end_of_shorage nullptr; }; }vector的一些小细节迭代器失效//迭代器失效 void test_03() { xxc::vectorint v1; v1.push_back(1); v1.push_back(2); v1.push_back(3); v1.push_back(4); //v1.push_back(5); v1.insert(v1.begin(), 0); v1.insert(v1.begin() 2, 0); Print(v1); int x; cin x; auto it find(v1.begin(), v1.end(), x); v1.insert(it, 100); *it 1000;//这里的it也是迭代器失效 /* * 扩容会让it失效 * 由于不知道什么时候扩容所以insert过后就认为其失效 * * 明明insert函数内部有对迭代器失效进行了处理为什么it还是失效了 * 因为insert函数的iterator是传值传值是拷贝改变不会影响it本身 * * 能不能改成传引用 * 不能因为像v1.insert(v1.begin(), 0);这种场景的v1.begin()是传值返回 * 返回的是临时对象具有常性那begin()传引用返回呢不行因为无法应对 * v1.insert(v1.begin() 2, 0);这种场景v1.begin() 2计算结果也是临时对象具有常性 * * 那insert函数参数的iterator如果改成const引用呢 * 不行加上constinsert函数内部的pos迭代器无法修改了 * * 综上失效的迭代器无法使用 * * 同理使用erase函数依旧认为是迭代器失效了vs下对迭代器会有严格的检查 * * 为了解决迭代器失效库里边的inserterase....会有一个返回值使用迭代器之后更新一下就可以使用了 */ //erase中迭代器失效的简单场景 // 要求删除所有偶数 auto it v1.begin(); while (it ! v1.end()) { if (*it % 2 0) { // erase也会迭代器失效失效的迭代器就不能再使用了 // 要重新赋值更新这个迭代器才能使用 it v1.erase(it); } else { it; } } }insert函数里的迭代器失效关于T()C里边对于内置类型也有构造函数目的就是为了在给像resize这种函数的缺省值的时候能用模板给缺省值。void test_04() { int a int();//0 int b(1);//1 int c int(10);//10 //其他类型也一样不给他初始化默认就初始化为00.0ASCII码为0的值即\0 }由浅拷贝引发的问题以下这是reserve没修改前的代码void reserve(size_t n) { if (n capacity()) { //异地扩容 T* tmp new T[n]; size_t old_size size(); if (_start)//如果_start不为nullptr就拷贝旧数据过来 { //memcpy不是深拷贝 memcpy(tmp, _start, sizeof(T) * old_size); delete[] _start; } _start tmp; _finish _start old_size; _end_of_shorage _start n; } }问题拷贝构造自定义类型譬如string的时候会有深拷贝的问题因为string内部也会指向空间由于拷贝构造是复用真正的问题出在reservememcpy没有深拷贝导致拷贝给tmp的数据不包括string内部指向的资源使得tmp和_start指向同一块资源在delete[] _start的时候不仅释放掉了vector内部的string并且string也会调用自己的析构函数去释放掉自己额外开辟的空间这样一来tmp指向的数据就变成随机值了紧接着_start tmp后续打印出来的就变成随机值了使用函数模板打印不同vector我们自己写的vector在xxc这个命名空间里边而库里边的vector在std这个命名空间里边如果我们在既想打印库里边的又想打印自己手动实现的vector同一个Print就无法实现功能此时要设计成模板。templateclass Container void Print(const Container v) { for (auto e : v) { cout e ; } cout endl; }

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

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

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…