C++之STL-vector+模拟实现

目录

一、vector的介绍和基本使用的方法

1.1 介绍

1.2 迭代器

1.3 vector的一些基本使用

1.3.1 构造函数

1.3.2 迭代器

1.3.3 有关容量的接口

1.3.4 增删查改

二、模拟实现vector

2.1 成员变量

 2.2 迭代器的实现

2.3 容量接口的实现

2.3.1 size函数实现

2.3.2 capacity函数实现

2.3.3 reserve函数实现       

2.3.4 resize函数的实现

2.4 增删查改接口的实现

2.4.1 operator[] 函数重载的实现

2.4.2 insert函数的实现      

2.4.3 erase函数的实现

2.4.4 swap函数的实现

2.4.5 pop_back函数实现

2.4.6 push_back函数的实现

2.5 构造函数和析构函数的实现 

2.5.1 有参构造 vector(int n, const T& value = T())

2.5.2 无参构造 vector()

2.5.3 拷贝构造 vector(const vector& v)

2.5.4 赋值重载

2.5.5 用迭代器区间进行构造

2.5.6 析构函数

 2.6 完整代码


一、vector的介绍和基本使用的方法

1.1 介绍


vector 是 C++ 标准库中的一个容器,提供了动态数组的功能。它可以根据需要动态增长或减少其大小,并且支持随机访问元素(类似我们初学的数组,但却有很多的不同),以下是其的几个特性:

  1. 动态大小: 允许在运行时动态增加或减少其大小,而无需在编译时指定数组大小。

  2. 随机访问: 可以使用索引随机访问元素。这意味着可以通过索引直接访问任何位置的元素,而不必像链表那样从头开始遍历。

  3. 连续存储: 元素在内存中是连续存储的,这样就可以利用 CPU 缓存的局部性原理,提高访问效率

  4. 自动管理内存: 自动管理动态数组的内存分配和释放,使得程序员无需手动处理内存管理问题。

  5. 模板类: vector 是一个模板类,可以存储任意类型的元素。这意味着可以创建包含整数、浮点数、自定义对象等任意类型的vector

1.2 迭代器

       简单介绍完了vector这个新朋友后,我们就不得不来好好聊聊六大组件之一的迭代器(接下来很多场景都会使用它),那么迭代器是什么呢?
       迭代器提供了一种统一的访问数据结构(如容器、数组、集合等)元素的方式,使得我们能够以统一的接口遍历和操作数据结构中的元素,而不必关心底层数据结构的实现细节。在 C++ 中,迭代器是一种类似于指针的对象,它允许我们遍历容器中的元素并访问它们。迭代器通常具有以下特性:

  1. 遍历元素: 通过迭代器,我们可以逐个遍历容器中的元素,访问它们并对其进行操作。

  2. 随机访问: 某些迭代器(如 std::vector 的迭代器)支持随机访问,允许我们以常数时间访问容器中的任意元素。

  3. 泛型性: 迭代器是泛型的,可以用于各种不同类型的数据结构,包括数组、链表、集合等。

  4. 迭代器范围: 迭代器通常表示一个范围,包括起始位置和终止位置。这样,我们可以通过两个迭代器来表示一个范围,例如在排序算法中指定待排序数组的范围。

1.3 vector的一些基本使用

1.3.1 构造函数

  1. 默认构造
    vector<int> s;//类似于这样的定义会调用默认构造,但是这是个空对象,没有内容
  2. 有参构造(介绍几个常用的)

    vector<int> arr(10,1);//构造并在对象中初始化10个1
    vector<int> arr(10,1);
    vector<int> brr(arr);//拷贝构造
    vector<int> arr(10,1);
    vector<int> brr(arr.begin(),arr.end());//用迭代器区间进行初始化

1.3.2 迭代器

  1. begin()  用对象显示调用这个函数会返回第一个数据位置的iterator/const iterator。
  2. end()  用对象显示调用这个函数会返回最后一位数据位置的iterator/const iterator。
  3. rbegin() 反向迭代器,显示调用这个函数会返回最后一位数据位置的iterator/const iterator。
  4. rbegin() 反向迭代器,显示调用这个函数会返回第一个数据位置的iterator/const iterator。

1.3.3 有关容量的接口

  1. size 获取数据个数
  2. capacity 获取容量大小
  3. empty 判断是否为空
  4. resize 更改当前对象的size
  5. reserve 更改当前对象容量

1.3.4 增删查改

  1. push_back 尾插
  2. pop_back 尾删
  3. insert 在pos位置之前插入数据
  4. erase 删除pos位置的数据
  5. swap 交换两个vector的数据域空间
  6. operator[] 函数重载,使得我们可以像访问数组一样的访问vector。

二、模拟实现vector

终于来到我们的重头戏了,话不多说,直接开始。(注意:vector是需要使用模板的哦)。

2.1 成员变量

       主要的i成员就三个,_start指向数据块的开始,_finish指向有效数据的尾,_endOfStorage指向存储容量的尾。c++11支持声名时带缺省相当于会进行默认初始化。
 

private:iterator _start = nullptr; // 指向数据块的开始iterator _finish = nullptr; // 指向有效数据的尾iterator _endOfStorage = nullptr; // 指向存储容量的尾

 2.2 迭代器的实现

因为vector的迭代器是原生指针所以实现起来很方便
 

            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;}

记住在vector中是有两个迭代器的,一个普通的,一个const的。

2.3 容量接口的实现

2.3.1 size函数实现

个函数比较容易只需要用_finish-_start就可以了

            size_t size() const{return _finish - _start;}

2.3.2 capacity函数实现

            size_t capacity() const{return _endOfStorage - _start;}

2.3.3 reserve函数实现       

       该函数有一个参数 n,根据要求当n>capacity时该函数才会发挥作用,所以我们要做一个判断。根据描述,我需要开辟一个新空间,那很明显就会涉及原空间数据拷贝的问题,这里我们不能使用memcpy进行拷贝,当我们模板参数T为stirng等类型的话,这将会是一个浅拷贝,具体参考下图。

       那么我们该如何去处理这个问题呢,其实我们只需遍历一边直接用赋值语句就可以,内置类型不必说,自定义类型会去调用其拷贝构造,帮助我们完成深拷贝。但是扩容拷贝之后又会产生新问题,因为扩容之后_start指向的是新空间的地址,而_finish和_endOfStorage都未更新,所以我们要先对这些数据进行更新,先看下面的更新代码。

_start = tmp;//tmp为新空间的地址
_finish = _start + size();
_endOfStorage = _start + size();

       当你自己试过你就会发现_finish出问题了,他会是个野指针,这是为什么呢,明明逻辑是对的呀。其实问题就出在size()上,size = _finish - _start 。用数学代数的方法,不难发现_finishi确实没变,那怎么办呢,总不能修改size吧,其实只要把未变化的size先记录下来就好了。
 

            void reserve(size_t n){if (n > capacity()){size_t sz = size();T* tmp = new T[n];if (_start){for (size_t i = 0; i < sz; i++){tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = _start + sz;_endOfStorage = _start + n;}}

2.3.4 resize函数的实现

       根据描述,当传入参数 n 小于capacity时,就直接缩小size的值就好,当n大于capacity时,就需要扩容了,但这里我们可以复用我们reserve函数进行扩容,然后使用传入参数对扩容的size进行初始化,是不是很方便。

            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;}}}

2.4 增删查改接口的实现

2.4.1 operator[] 函数重载的实现

先来个简单的开开胃。(注意别忘了实现const迭代器的接口哦)

            T& operator[](size_t pos){assert(pos < size());return _start[pos];}const T& operator[](size_t pos)const{assert(pos < size());return _start[pos];}

2.4.2 insert函数的实现      

       插入数据首先要考虑的问题就是要不要扩容,所以这里我们需要做一个判断若_finish==_endOfStorage则调用reserve进行扩容,但是扩容之后会产生问题,pos指向的是旧空间插入位置的地址,但是新空间的插入地址我们不知道啊,所以这里就需要我们提前记录pos相对于_start位置的偏移量。

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

这里为什么insert有返回值我们放在erase说明。
 

2.4.3 erase函数的实现

       根据描述erase函数是要删除pos位置的值,删除倒还好说,挪动覆盖数据就可以了,但是这里会涉及一个叫做迭代器失效的问题,先简单介绍一下什么是迭代器失效。
       迭代器失效是指在进行某些操作后,原本有效的迭代器不再指向预期的元素位置,或者完全失去了指向元素的能力。这里失效很明显,你删除了数据,那原本的迭代器就失效了,
具体可以跑一下下面删除对象中所有偶数的代码:

//删除对象中的所有偶数
std::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);for (auto e : v){cout << e << " ";}cout << endl;auto it = v.begin();while (it != v.end()){vs2019进行强制检查,erase以后认为it失效了,不能访问,访问就报错if (*it % 2 == 0){v.erase(it);}++it;}for (auto e : v){cout << e << " ";}cout << endl;
}

       迭代器失效是无法避免的,但是我们可以去避免让其对我们代码产生影响,所以erase有一个返回值,其会返回删除位置下一位的迭代器,insert则是返回新插入位置的迭代器。函数还需要进行一下断言,pos要大于_start且要小于_finish。

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

2.4.4 swap函数的实现

这个比较容易,我们可以复用库中的swap函数。

           void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endOfStorage, v._endOfStorage);}

2.4.5 pop_back函数实现

直接复用erase就ok。

            void pop_back(){erase(end()-1);}

2.4.6 push_back函数的实现

这个也是直接复用就行了(是不是很爽)。

            void push_back(const T& x){insert(end(), x);}

2.5 构造函数和析构函数的实现 

2.5.1 有参构造 vector(int n, const T& value = T())

            vector(int n, const T& value = T()){reserve(n);while (_finish < _start + n){*_finish = value;++_finish;}}

这里需要调用reserve对整体进行一个初始化。

2.5.2 无参构造 vector()

 vector()
{}

2.5.3 拷贝构造 vector(const vector<T>& v)

            vector(const vector<T>& v){reserve(v.capacity());for (auto& e : v){push_back(e);}}

2.5.4 赋值重载

            vector<T>& operator= (vector<T> v){swap(v);return *this;}

2.5.5 用迭代器区间进行构造

            template<class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);first++;}}

template<class InputIterator> 表示这个函数模板接受一个类型为 InputIterator 的参数。这个模板可以用来生成各种接受不同类型迭代器的函数。

2.5.6 析构函数

            ~vector(){delete[] _start;_start = _endOfStorage = _finish = nullptr;}

 2.6 完整代码

#pragma once
#include  <assert.h>
#include  <string.h>
namespace bit{template<class T>class vector{public:// Vector的迭代器是一个原生指针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;}// construct and destroyvector(){}vector(int n, const T& value = T()){reserve(n);while (_finish < _start + n){*_finish = value;++_finish;}}template<class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);first++;}}vector(const vector<T>& v){reserve(v.capacity());for (auto& e : v){push_back(e);}}vector<T>& operator= (vector<T> v){swap(v);return *this;}~vector(){delete[] _start;_start = _endOfStorage = _finish = nullptr;}// capacitysize_t size() const{return _finish - _start;}size_t capacity() const{return _endOfStorage - _start;}void reserve(size_t n){if (n > capacity()){size_t sz = size();T* tmp = new T[n];if (_start){for (size_t i = 0; i < sz; i++){tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = _start + sz;_endOfStorage = _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;}}}///access///T& operator[](size_t pos){assert(pos < size());return _start[pos];}const T& operator[](size_t pos)const{assert(pos < size());return _start[pos];}///modify/void push_back(const T& x){insert(end(), x);}void pop_back(){erase(end()-1);}void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endOfStorage, v._endOfStorage);}iterator insert(iterator pos, const T& x){assert(pos >= _start && pos <= _finish);if (_finish == _endOfStorage){int len = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end+1) = *end;end--;}*pos = x;_finish++;return pos;}iterator erase(iterator pos){assert(pos >= _start && pos < _finish);iterator tmp = pos + 1;while (tmp < _finish){*(tmp - 1) = *tmp;tmp++;}_finish--;return pos;}private:iterator _start = nullptr; // 指向数据块的开始iterator _finish = nullptr; // 指向有效数据的尾iterator _endOfStorage = nullptr; // 指向存储容量的尾};}

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

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

相关文章

阿斯达年代记三强争霸新手开荒注意事项 搬砖攻略和注意问题分享

阿斯达年代记三强争霸新手开荒注意事项 搬砖攻略和注意问题分享 阿斯达年代三强争霸这款游戏刚开始公测就获得了玩家们的集体关注&#xff0c;这是一款根据影视剧改编的MMORPG游戏&#xff0c;玩家将置身于名为阿斯大陆的奇幻世界&#xff0c;加入阿斯达、亚高、不法者三大势力…

Prompt之美:如何设计提示词让大模型变“聪明”

目录 一. Prompt关键要素 二. Prompt技巧 三. 实战中的Prompt优化 四. 参考文献 一. Prompt关键要素 Prompt是一个简短的文本输入&#xff0c;用于引导AI模型生成特定的回答或执行特定任务。换句话说&#xff0c;Prompt是你与AI模型沟通的方式。一个好的Prompt可以让AI更准…

从现在开始:让AI写代码,你只负责敲tab键

如果你是一名程序员&#xff0c;你一定有过这样的经历&#xff1a;在编写代码的时候&#xff0c;突然遇到了一个棘手的问题&#xff0c;需要花费大量的时间去查找资料、尝试不同的解决方案&#xff0c;甚至有时候还需要去问同事或者在网上寻求帮助。这样的情况不仅会浪费你的时…

用立方样条联合SHAP分析在危险因素鉴定中的作用

用立方样条联合SHAP分析在危险因素鉴定中的作用 1. SHAP分析告诉我们变量之间的关系 SHAP分析计算的SHAP值代表了某变量对于结局指标的贡献&#xff0c;代表了相关性的趋势&#xff0c;SHAP分析中的散点图是对以上关系的可视化&#xff0c;从中我们可以直观看到随着变量值的变…

百度 | 如何白嫖文心一言4.0,偷偷的用!

文心一言4.0 官方价一个月 59.9&#xff0c;贵不贵&#xff0c;很贵啊 现在有个白嫖文心一言4.0的方法 分享给大家 效果比3.0好用 如何使用 这里用到文心智能体平台&#xff0c;也是百度出的&#xff0c;和字节跳动的coze很像 这里打开文心智能体平台&#xff0c;自行百度…

diskMirror docker 使用容器部署 diskMirror 服务器!!!

Welcome to diskMirror-docker 获取项目 这个项目是 diskMirror-spring-boot 镜像版本的项目&#xff0c;您可以使用下面的命令将此项目编译为一个镜像&#xff01; # 进入到您下载的源码包目录 cd diskMirror-docker# 点击脚本来进行版本的设置以及对应版本的下载 设置 和 编…

JavaEE:File类查询一个文件的路径(举例+源码 )

一、File类概述 Java 中通过 java.io.File 类来对一个文件&#xff08;包括目录&#xff09;进行抽象的描述。File 类中的方法可以对文件路径以及文件名等信息进行查询&#xff0c;也可以对文件进行各项增删改操作&#xff0c;本文主要介绍 File 类的查询方法。 二、代码示例 …

Python入门第10篇(编码)

目录 一、编码是什么&#xff1f; 二、Python中编码 1.读取文件引发的问题 2.其实是Windows的问题 3.试着改改问题 4.各种骚操作 5.终极解决 6.推荐方案 总结 Python系列文章目录 前言 编码存在于所有文件&#xff0c;比较常见的ASCII、utf8、gbk等。最常用的还是ut…

大模型 AI 框架昇思 MindSpore 2.3.RC1 发布,训练、推理性能大幅提升,JIT 编译强化

经过社区开发者们几个月的开发与贡献&#xff0c;现正式发布昇思 MindSpore2.3.RC1 版本&#xff0c;通过多维混合并行以及确定性 CKPT 来实现超大集群的高性能训练&#xff0c;支持大模型训推一体架构&#xff0c;大模型开发训练推理更简、更稳、更高效&#xff0c;并在训推一…

【产品设计】B端产品权限设计~功能权限设计篇

对于B端设计而言&#xff0c;良好的权限设计架构是支持其复杂业务的基础和关键。 一、什么是权限管理 权限管理&#xff0c;一般指根据系统设置的安全规则或者安全策略&#xff0c;用户可以访问而且只能访问自己被授权的资源。 简而言之&#xff0c;用户登录系统后&#xff0…

使用 Redux 管理全局状态

Redux 是个状态集中管理框架&#xff0c;状态可以跨组件共享&#xff0c;状态更新后&#xff0c;调用监听器。其实状态可以认为就是个全局对象&#xff0c;为什么要做一个框架来管理呢&#xff1f;如果我们自己使用一个全解字典来管理状态是不是也行&#xff1f;如果不做任何控…

想在游泳健身的同时畅听音乐,随时哈氪漫游这款IP68防水的骨传导耳机

平时健身的过程中&#xff0c;音乐是许多健身爱好者的忠实伴侣。无论是在室内的健身房&#xff0c;还是户外的自然风光中&#xff0c;一副优质的耳机可以极大地提升我们的锻炼体验。现在市面上专为运动设计的耳机选择非常多&#xff0c;我喜欢用骨传导耳机&#xff0c;目前在用…

深入理解多线程编程

title: 深入理解多线程编程 date: 2024/4/25 17:32:02 updated: 2024/4/25 17:32:02 categories: 后端开发 tags:线程同步互斥锁死锁避免竞态条件线程池异步编程性能优化 第一章&#xff1a;多线程基础 1.1 线程概念与原理 线程&#xff1a;在操作系统中&#xff0c;一个程序…

ADB常用命令

大家好&#xff0c;今天给大家分享一些ADB的常用命令&#xff0c;我们作为测试了解ADB命令能给我们带来哪些好处呢&#xff1f; 1、ADB 命令可以帮助测试人员与Android设备进行交互&#xff0c;如安装、卸载应用&#xff0c;获取设备信息等&#xff0c;便于更深入地测试设备功能…

世强硬创获昕感科技授权代理,SiC MOSFET实现超低导通电阻

近日&#xff0c;世强先进&#xff08;深圳&#xff09;科技股份有限公司&#xff08;下称“世强先进”&#xff09;获北京昕感科技有限责任公司&#xff08;下称“昕感科技”&#xff0c;英文名&#xff1a;NEXIC&#xff09;授权代理&#xff0c;为光伏、储能、电网、新能源汽…

C# 给图片添加文字水印

目录 应用场景 开发运行环境 方法说明 方法代码 调用示例 小结 应用场景 在某些应用项目&#xff08;如电子档案信息管理&#xff09;中&#xff0c;查看电子图片信息是经常使用到的功能&#xff0c;此时我们就需要给显示在浏览器中的图片添加文字水印版权或提示信息。…

Golang对接Ldap(保姆级教程:概念搭建实战)

Golang对接Ldap&#xff08;保姆级教程&#xff1a;概念&搭建&实战&#xff09; 最近项目需要对接客户的LDAP服务&#xff0c;于是趁机好好了解了一下。LDAP实际是一个协议&#xff0c;对应的实现&#xff0c;大家可以理解为一个轻量级数据库。用户查询。比如&#xff…

力扣HOT100 - 114. 二叉树展开为链表

解题思路&#xff1a; class Solution {List<TreeNode> list new ArrayList<>();public void flatten(TreeNode root) {recur(root);for (int i 1; i < list.size(); i) {TreeNode pre list.get(i - 1);TreeNode cur list.get(i);pre.left null;pre.right…

使用Shell终端访问Linux

一、实验目的 1、熟悉Linux文件系统访问命令&#xff1b; 2、熟悉常用 Linux Shell的命令&#xff1b; 3、熟悉在Linux文件系统中vi编辑器的使用&#xff1b; 4、进一步熟悉虚拟机网络连接模式与参数配置&#xff01; 二、实验内容 1、使用root帐号登陆到Linux的X-windows…

【Qt 学习笔记】Qt常用控件 | 输入类控件 | Combo Box的使用及说明

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt常用控件 | 输入类控件 | Combo Box的使用及说明 文章编号&#xff…