(C++ STL) 详解vector模拟实现

news2024/11/19 4:21:33

目录

一.vector的介绍

1.vector的介绍

二.vector的定义模拟实现

三.vector各接口的模拟实现

1.vector迭代器的模拟实现

2.构造函数

   2.1无参构造

2.2 n个val构造

2.3迭代器区间构造

2.4通过对象初始化(拷贝构造)

3.析构函数

4.size

5.operator=

6.capacity

7.reserve

8.resize

9.operator[ ]

10.insert

11.push_back

12.erase

13.pop_back

14.empty


一.vector的介绍

1.vector的介绍

这是官方的文档介绍
cplusplus.com/reference/vector/vector/

1. vector是表示可变大小数组的序列容器。


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


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


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


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


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

二.vector的定义模拟实现

首先我们先 定义一个命名空间 来模拟实现咱们的vector类

类里面有三个私有 指针变量 分别指向数据块的开始,尾和存储容量的尾

namespace zyl
{
  

    template<class T>
    class vector
    {
     public:
      // Vector的迭代器是一个原生指针
      typedef T* iterator;
      typedef const T* const_iterator;

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

三.vector各接口的模拟实现

1.vector迭代器的模拟实现

vector的迭代器分为俩种

一种是普通迭代器 指向的内容可以被修改

一种是const迭代器 不可以修改只可读

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

        const_iterator cbegin() const
        {
            return _start;
        }
        const_iterator cend() const
        {
            return _finish;
        }

2.构造函数

vector 的四种构造函数

   2.1无参构造

主要是对各个指针初始化 赋值为空

 vector()
            :_endOfStorage(nullptr)
            ,_start(nullptr)
            ,_finish(nullptr)
        {

        }

2.2 n个val构造

直接向数组中尾插数据,用reserve提前扩容, 提高效率

然后需要注意的是  这里传参的第二个参数使用匿名对象,const T& val = T() 这种写法会调用默认构造(可以是任意类型),我们前面讲内置类型是没有默认构造函数的, 理论而言是没有的, 但是调用模板之后必须要支持默认构造

 vector(int n, const T& value = T())
            :_endOfStorage(nullptr)
            , _start(nullptr)
            , _finish(nullptr)
        {
            reserve(n);//提前开n个空间
            for (int i = 0;i < n;i++)
            {
                push_back(value);//缺省值默认为val;
            }
        }

2.3迭代器区间构造

这里又要使用模板实现, 要实现一个任意类型的迭代器允许任意类型的数据使用,直接用迭代器遍历数组, 尾插数据即可

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

2.4通过对象初始化(拷贝构造)

     通过传一个vector对象  然后进行交换 

        vector(const vector<T>& v)
        {
            vector<T> tmp(v.cbegin(), v.cend());
            swap(tmp);
        }

3.析构函数

  析构函数的主要功能 释放掉所有数据 然后三个指针指向空


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

4.size

返回当前vector长度

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

5.operator=

运算符重载=     实现深拷贝 把v对象赋给this

       vector<T>& operator= (vector<T> v)
        {
            if (this != &v)
            {
                delete[] _start;
                _start = new T[v.capacity()];
                for (size_t i = 0;i < v.size();i++)
                {
                    _start[i] = v[i];
                }
                _finish = _start + v.size();
                _endOfStorage = _start + v.capacity();
            }
            return *this;
        }

6.capacity

返回当前vector对象的容量是多少

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

7.reserve

在n>capacity时去进行扩容 是为了防止程序缩容

判段当前数据是为为空,需不需要旧数据的拷贝转移

遍历的时候,一定要使用深拷贝,不要使用memcpy去进行拷贝

 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 <size();i++)
                    {
                        tmp[i] = _start[i];
                    }
                    delete[] _start;
                }
                _start = tmp;
                _finish = _start + sz;
                _endOfStorage = _start + n;
            }
        }

8.resize

n < size() 就是删除数据,直接改变 _finish的指向即可

n > capacity()调用reserve函数扩容, 后遍历给数组赋值

 void resize(size_t n, const T& value = T())
        {
            //查看是否需要扩容
            if (n > capacity())
            {
                reserve(n);
            }

            if (n > size())
            {
                while (_finish > _start + n)
                {
                    *_finish = value;
                    ++_finish;
                }
            }
            else
            {
                _finish = _start + n;
            }
        }

9.operator[ ]

vector也支持下标访问

重载 [ ] 可以快速的对数据进行访问

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

10.insert

检查容量,观察是否需要扩容, 扩容前计算出pos与start之间,pos与start之间相对距离不变,扩容后更新pos位置(这里存在迭代器失效的问题)

遍历挪动数据

将val插入pos位置

注意: 检查pos位置的合法性

iterator insert(iterator pos, const T& x)
        {
            //pos范围必须在_start和_finish之间
            assert(pos>=_start);
            assert(pos <= _finish);
            //内存满了 进行扩容
            if (_finish == _endOfStorage)
            {
                size_t len = pos - _start;
                reseve(capacity() > 0 ? 4 : capacity * 2);
                pos = _start + len;
            }
            iterator end = _finish;
            //移动数据 进行插入
            while (end >= pos)
            {
                *end = *(end - 1);
                end--;
            }
            *pos = x;
            ++_finish;
            return pos;
        }

11.push_back

尾插 直接调用insert 在_finish位置去插入

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

12.erase

erase函数可以删除所给迭代器pos位置的数据

在删除数据前需要判断容器释放为空

若为空则需做断言处理,删除数据时直接将pos位置之后的数据统一向前挪动一位,将pos位置的数据覆盖即可。 

iterator erase(iterator pos)
        {
            //判断pos是否合法
            assert(pos > _finish);
            assert(pos < _start);
            assert(!empty());

            iterator begin = pos + 1;
            while (begin < _finish)
            {
                *(begin - 1) = *begin;
                ++begin;
            }
            --_finish;
            return pos;
        }

13.pop_back

pop_back直接调用erase去_finish位置进行删除

 void pop_back()
        {
            erase(_finish);
        }

14.empty

进行判空 直接看_finish == _start是否相同就可以了

bool empty() const
        {
            return _finish == _start;
        }

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

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

相关文章

PCB沉金包边工艺流程与主要作用经验总结

🏡《总目录》 目录 1,什么是PCB沉积包边2,PCB沉金包边作用2.1,射频屏蔽2.2,EMC认证2.3,防氧化2.4,强电屏蔽2.5,美观3,PCB沉金包边的工艺流程4,总结1,什么是PCB沉积包边 PCB沉金包边是指,在PCB的侧边也包裹上铜皮,并在铜皮的表面进行沉金工艺。在高频电路板中经常…

探索数字时代的核心:服务器如何塑造未来并助你成就大业

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

【Java学习之道】Java网络编程API介绍

引言 在Java中&#xff0c;进行网络编程的主要方式是通过Java网络编程API。这些API提供了一组类和接口&#xff0c;用于创建网络应用&#xff0c;如TCP和UDP通信、URL访问等。在这一节中&#xff0c;我们将带你领略Java网络编程API的魅力。 一、InetAddress InetAddress类是表…

基于区块链与联邦学习技术的数据交易平台

目录 基于区块链与联邦学习技术的数据交易平台 基于区块链与联邦学习技术的数据交易平台 联邦学习与区块链的集成的优势在于能够确认参与各方的身份并实现学习过程追溯。 首先&#xff0c;通过的身份认证系统与定制化的联邦学习协议来解决交易各方身份确认的问题。 如图1所示…

【网络编程】序列化与反序列化

文章目录 一、网络协议二、序列化和反序列化1. 结构化数据2. 序列化和反序列化 三、网络版计算器1. 协议定制2. 客户端处理收到的数据3. 整体代码 一、网络协议 网络协议 是通信计算机双方必须共同遵从的一组约定&#xff0c;为了使数据在网络上能够从源地址到目的地址&#x…

CCF CSP认证 历年题目自练Day33

题目一 试题编号&#xff1a; 202212-1 试题名称&#xff1a; 现值计算 时间限制&#xff1a; 1.0s 内存限制&#xff1a; 512.0MB 问题描述&#xff1a; 问题描述 评估一个长期项目的投资收益&#xff0c;资金的时间价值是一个必须要考虑到的因素。简单来说&#xff0c;假设…

华为DHCP配置实例

条件&#xff1a; R1为DHCP配置网关&#xff0c;S1为二层交换机 要求&#xff1a; PC1获取到vlan10地址&#xff0c;PC2获取vlan10地址&#xff0c;PC3获取vlan20地址 方法1&#xff1a; S1正常配置vlan10&#xff0c;20&#xff0c;配置与R1相连的1口为trunk口 R1的1口&a…

python中使用xml.dom.minidom模块读取解析xml文件

python中可以使用xml.dom.minidom模块读取解析xml文件 xml.dom.minidom模块应该是内置模块不用下载安装 对于一个xml文件来说比如这个xml文件的内容为如下 <excel version"1.0" author"huangzhihui"><table id"1"><colum id&qu…

ROS OpenCV库 示例

OpenCV库&#xff08;Open Source Computer Vision Library&#xff09;是一个基于BSD许可发行的跨平台开源计算机视觉库&#xff0c;可以运行在Linux、Windows和mac OS等操作系统上。OpenCV由一系列C函数和少量C类构成&#xff0c;同时提供C、Python、Ruby、MATLAB等语言的接口…

【Linux】shell运行原理及权限

主页点击直达&#xff1a;个人主页 我的小仓库&#xff1a;代码仓库 C语言偷着笑&#xff1a;C语言专栏 数据结构挨打小记&#xff1a;初阶数据结构专栏 Linux被操作记&#xff1a;Linux专栏 LeetCode刷题掉发记&#xff1a;LeetCode刷题 算法&#xff1a;算法专栏 C头疼…

【密码学】第二章 密码学的基本概念

1、密码学定义 密码编制学和密码分析学共同组成密码学 密码编制学&#xff1a;研究密码编制密码分析学&#xff1a;研究密码破译 2、密码体制的五个组成部分 明文空间M&#xff0c;全体明文的集合密文空间C&#xff0c;全体密文的集合密钥空间K&#xff0c;全体密钥的集合。…

Unity之ShaderGraph如何实现上下溶解

前言 我们经常在电影中见到的一个物体或者人物&#xff0c;从头上到脚下&#xff0c;慢慢消失的效果&#xff0c;我么今天就来体验一下这个上下溶解。 主要节点 Position节点&#xff1a;提供对网格顶点或片段的Position 的访问 Step节点&#xff1a;如果输入In的值大于或等…

openGauss学习笔记-101 openGauss 数据库管理-管理数据库安全-客户端接入之用SSH隧道进行安全的TCP/IP连接

文章目录 openGauss学习笔记-101 openGauss 数据库管理-管理数据库安全-客户端接入之用SSH隧道进行安全的TCP/IP连接101.1 背景信息101.2 前提条件101.3 操作步骤 openGauss学习笔记-101 openGauss 数据库管理-管理数据库安全-客户端接入之用SSH隧道进行安全的TCP/IP连接 101.…

【C++】手撕STL系列——stack,queue篇

前言 前面实现了string和vector&#xff0c;理所应当就该轮到stack和queue啦&#xff0c;本篇还会涉及到一个比较重要且听起来很厉害的概念——适配器模式 适配器模式 在之前数据结构初阶的学习过程中&#xff0c;我们学习的栈是由数组加上一些限制组成的容器&#xff0c;底…

SpringBoot面试题6:Spring Boot 2.X 有什么新特性?与 1.X 有什么区别?

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:Spring Boot 2.X 有什么新特性?与 1.X 有什么区别? Spring Boot是一种用于简化Spring应用程序开发的框架,它提供了自动配置、起步依赖和快速开…

7.继承与多态 对象村的优质生活

7.1 民法亲属篇&#xff1a;继承&#xff08;inheritance&#xff09; 了解继承 在设计继承时&#xff0c;你会把共同的程序代码放在某个类中&#xff0c;然后告诉其他的类说此类是它们的父类。当某个类继承另一个类的时候&#xff0c;也就是子类继承自父类。以Java的方式说&…

微信小程序框架---视图层逻辑层API事件

目录 前言 一、小程序框架介绍 1.响应的数据绑定 2.页面管理 3.基础组件 4.丰富的 API 二、视图层 View 1.WXML 数据绑定 列表渲染 条件渲染 模板 2.WXSS 尺寸单位 样式导入 内联样式 选择器 全局样式与局部样式 3.WXS 示例 注意事项 页面渲染 数据处理 …

VMtools安装Euler系统

前言 本文章针对刚安装好系统&#xff0c;未进行任何配置的&#xff0c;建议安装好系统先安装vmtools不然很多功能不能使用&#xff0c;也不能传文件 开机先等待开机自检 在下面这个地方输入用户名和密码&#xff08;在安装过程中设置的&#xff09; 成功登录 点击安装vm…

Go实现CORS(跨域)

引言 很多时候&#xff0c;需要允许Web应用程序在不同域之间&#xff08;跨域&#xff09;实现共享资源。本文将简介跨域、CORS的概念&#xff0c;以及如何在Golang中如何实现CORS。 什么是跨域 如果两个 URL 的协议、端口&#xff08;如果有指定的话&#xff09;和主机都相…

Maven打包添加本地工程jar包

前言 先吐槽几句,公司有一小组专门来做各个项目的测试环境以及打包上线的工作&#xff0c;我们称之为XX,这个XX并不是什么业务领导&#xff0c;也只是一个螺丝钉。这群人每天对上跪舔&#xff0c;对其他人爱搭不理&#xff0c;给人一种高高在上的感觉&#xff0c;之前的一个老…