【c++】模拟实现vector

news2024/11/13 14:52:14

一.vector的成员变量与迭代器

vector里面可以存各种数据类型,所以必定会用到模板,假设vector的模板参数为T,那么T*这个指针封装后就是vector的迭代器

和string的迭代器很像,只不过string确定存的是字符,所以迭代器直接是char的指针,而vector存的数据类型不确定,就使用T的指针

而vector的3个成员变量_start,_finish,_endofstorage也就是3个迭代器,分别指向vector的开头,vector有效数据的结尾,vector容量的结尾

二.主干结构

namespace cyf
{
    
    template<class T>
    class vector
    {
        typedef T* iterator;
    public:
        //...
    private:
        //迭代器可看作一个指针,但也有区别
        iterator _start;//起始位置
        iterator _finish;//最后一个有效元素的下一位置
        iterator _endofstorage;//最大容量位置下一位
    };
}

三.构造函数和析构函数

1.构造函数

// 1
vector(size_t n, const T& val = T())
// 调用resize()接口需要计算size()和capacity(),所以要进行初始化
    :_start(nullptr)
    , _finish(nullptr)
    , _endofstorage(nullptr)
{
        resize(n,val);
}
// 用 n 个 val 值进行初始化,可以复用 resize 接口,但要注意调用resize()接口需要计算size()和capacity(),所以要将成员变量进行初始化

// 2
template<class InputIterator>
vector(InputIterator first, InputIterator last)
    : _start(nullptr)
    , _finish(nullptr)
    , _endofstorage(nullptr)
{
    while (first != last)
    {
        push_back(*first);
        ++first;
    }
}
//   用一段迭代器区间进行初始化,由于不同类型的容器迭代器类型可能不同,因此设计成函数模板,将区间内的内容尾插入vector即可,但注意调用push_back接口通过 _finish == _endofstorage 判断是否满,需要初始化


// 3
vector(size_t n, const T& val = T())
    :_start(nullptr)
    , _finish(nullptr)
    , _endofstorage(nullptr)
{
    reserve(n);
    for (size_t i = 0;i < n;++i)
    {
        push_back(val);
    }
}


// 4
vector(int n, const T& val = T())
    :_start(nullptr)
    , _finish(nullptr)
    , _endofstorage(nullptr)
{
    reserve(n);
    for (size_t i = 0;i < n;++i)
    {
        push_back(val);
    }
}

构造函数3和4的原因是当 vector<int> v(10,5)去初始化时 这是因为当使用两个参数进行构造时,编译器会优先匹配构造函数 (2),10和5都是int 类型但是1中对应的是size_t和int类型,所以编译器会优先匹配构造函数 2 ,此时对*first解引用会报错。所以需要再实现两个不同参数类型的构造函数重载:

2.析构函数

        ~vector()
        {
            if (_start)
            {
                delete[] _start;//释放空间
            }
            _start = _finish = _end_of_storage = nullptr;//置空
        }

四.reserve和resize

   void reserve(size_t n)
{
    if (n > capacity())
    {
        T* tmp = new T[n]; // 新开辟一块空间
        // 容器不为空
        if (_start)
        {
            size_t sizecp = size(); // 计算原来容器size个数
            for (size_t i = 0; i < size(); i++) // 拷贝数据
            {
                tmp[i] = _start[i];
            }
            delete[]_start; // 释放旧空间

            _start = tmp; // 更新_start
            _finish = _start + sizecp; // 更新_finish
            _endofstorage = _start + n; // 更新_endofstorage
        }
        // 容器为空,更新_start,_finish,_endofstorage的位置
        else
        {
            _start = _finish = tmp;
            _endofstorage = _start + n;
        }
    }
}

在reserve模拟实现时最容易犯的错误就是用memcpy进行拷贝数据 ,因为对于需要深拷贝的自定义类型,使用memcpy函数以后,新开辟空间里的元素和原空间里的元素所指向的内存空间是一样的,但是对于自定义类型来说扩容就需要深拷贝,memcpy拷贝完成后释放旧空间,当旧空间被释放时,会调用自定义类型的析构函数,从而使得新开辟空间里的元素指向的内存空间也被释放掉了,此时新空间中_start指向的就是被释放的空间,vector使用完后会自动调用析构函数,将_start指向的空间置为nullptr,将不属于自己维护的空间置为空释放就会报错。

void resize(size_t n, const T& val = T())//泛型编程用匿名对象给缺省值
    {
        if (n <= size())
        {
            _finish = _start + n;
        }
        else
        {
            if (n > capacity())
            {
                reserve(n);
            }
            while (_finish < _start + n)
            {
                *_finish = val;
                _finish++;
            }
        }
    }

当n<size时直接将_finish移动到_start+n的位置但是容量是不变的

当n>size并且大于capacity时先扩容然后填充数据

五.拷贝构造与operator=

        vector(const vector<T>& v)
            :_start(nullptr)
            , _finish(nullptr)
            , _endofstorage(nullptr)
        {
            reserve(v.capacity()); //直接push_back的话capacity可能不一样大
            for (auto e : v)
            {
                push_back(e);
            }
            
        }
  • 进行拷贝构造时我们也容易使用memcpy进行拷贝,也会造成和reserve一样的问题

  • 所以我们可以用另一种巧妙的方式避免浅拷贝,那就是先扩容然后遍历要拷贝的vector,将其中每一个数据push_back进新的vector中

        void swap(vector<T>& v)
        {
            std::swap(_start, v._start);
            std::swap(_finish, v._finish);
            std::swap(_endofstorage, v._endofstorage);
        }
        vector<T>& operator=(vector<T> v)
        {
            swap(v);
            return *this;
        }

六.插入和删除

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

尾插首先检查容量,如果没有容量就扩容,在有效元素的下一个位置插入数据,并且挪动_finish的位置

void pop_back()
        {
            assert(!empty());
            _finish--;
        }

尾删要保证有元素才能进行删除,只需要挪动_finish 的位置即可

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

在pos位置插入数据x 首先检查pos位置的合理性,然后检查容量,从后往前挪动数据在pos 位置插入数据然后++_finish

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

覆盖性删除元素注意边界的问题

七.其他接口

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


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


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

八.迭代器失效问题

    • 扩容导致迭代器失效

#include <iostream>
#include <vector>
using namespace std;

void Test()
{
    vector<int> v{ 1,2,3,4,5,6 };
    auto it = v.begin();

    // 将有效元素个数增加到100个,多出的位置使用1填充,操作期间底层会扩容
    // v.resize(100, 1);
    
    // reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变
    // v.reserve(100);
    
    // 插入元素期间,可能会引起扩容,而导致原空间被释放
    // v.insert(v.begin(), 0); // v.push_back(8);
    
    // 给vector重新赋值,可能会引起底层容量改变
    v.assign(100, 8);

    while (it != v.end()) 
    {
        cout << *it << " ";
         ++it;
    }
    cout << endl;
}

int main()
{
    Test();
    return 0;
}

出错原因: 以上操作,都有可能会导致vector扩容,也就是说vector底层原来旧空间被释放掉, 而在打印时,it还使用的是释放之前的旧空间,在对it迭代器操作时,实际操作的是一块已经被释放的空间而引起代码运行时崩溃。

解决方式: 在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给it重新赋值即可

2.erase导致迭代器失效

#include <iostream>
#include <vector>
using namespace std;

void Test()
{
    int a[] = { 1, 2, 3, 4 };
    vector<int> v(a, a + sizeof(a) / sizeof(int));
    // 使用find查找3所在位置的iterator
    auto pos = find(v.begin(), v.end(), 3);
    // 删除pos位置的数据,导致pos迭代器失效。
    v.erase(pos);
    cout << *pos << endl; // 此处会导致非法访问
}

int main()
{
    Test();
    return 0;
}

erase删除pos位置元素后,pos位置之后的元素会往前移,没有导致底层空间的改变,理论上讲迭代器应该不会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是 没有元素的,那么pos就失效了。因此删除vector中任意位置上元素时,vs就认为该位置迭代器失效了。

总之,使用迭代器应注意迭代器失效问题,解决办法是:在使用前,对迭代器重新赋值即可

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

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

相关文章

ubuntu20.04网络配置

安装net-toolssudo apt-get install net-tools2、ifconfig查看网卡设备其中flags表中&#xff1a;running表示正在使用中。查看设备核心网络路由表&#xff1a;route -nDestination目标网段或者主机Gateway网关地址&#xff0c;”*” 表示目标是本主机所属的网络&#xff0c;不…

【Vue2+Element ui通用后台】用户列表

文章目录新增用户用户列表新增用户 首先增加一个 ‘新增’ 按钮&#xff0c;点击弹出对话框来新增用户。弹出框可以使用 Element UI 的 Dialog对话框&#xff0c;其中 visible 表示是否显示 Dialog&#xff0c;支持 .sync 修饰符。我们点击新增按钮把这个标识置为 true&#x…

金山系不惧微软,前有WPS力扛Office,后有eversheet接力再战

金山软件作为国产互联网元老企业&#xff0c;这里出来的IT大佬不计其数&#xff0c;小米的雷军、逸趣网络的吴裔敏、甜瓜在线的朱勇......金山软件不屈不挠的企业文化&#xff0c;沉淀已久&#xff0c;造就一大批人才&#xff0c;甚至追溯到40年前。 1981年&#xff0c;张旋龙在…

IntelliJ IDEA 闪退的解决办法

场景 最近这idea闪退频率又多了不少 以前 几天一闪退 现在 一天N多次闪退 如下图 看这崩溃日志 这怎么顶 解决办法 查看崩溃日志 日志 1 日志2 日志3 可以看出现在生效的参数 Command Line: -Xms128m -Xmx750m -XX:ReservedCodeCacheSize512m -XX:IgnoreUnrecognized…

SCA技术进阶系列(一):SBOM应用实践初探

现代软件都是组装的而非纯自研。随着开源组件在数字化应用中的使用比例越来越高&#xff0c;混源开发已成为当前业内主流开发方式。开源组件的引入虽然加快了软件开发效率&#xff0c;但同时将开源安全问题引入了整个软件供应链。软件组成成分的透明性成为软件供应链安全保障的…

Flink检查点详解

说白了就是等你要处理的这个或这波数据被所有任务&#xff08;执行完所有算子&#xff09;处理完了 再做检查点保存&#xff08;下图就是三个数据都被map、sum处理完 就做检查点保存 source是读取数据的&#xff09; 下图只是一个检查点的保存过程&#xff08;拆解&#xff09…

Express框架中JWT基础 - 对称|非对称加密

在上一篇内容当中已经使用过了JWT(JSONWebToken)做验证登录&#xff0c;采用的是对称加密的方式&#xff0c;那么在本篇当中来进一步的讲解关于JWT的基础使用对称以及非对称加密&#xff1b;先来简单的回顾上一篇内容当中使用到的对称加密&#xff1a; 对称加密 首先是通过expr…

Kafka架构组成及相关内容

0. 主要参考&#xff1a;1. Kafka基础架构组成&#xff1a;2. Kafka的一些操作命令&#xff1a;3. Kafka 生产者消息发送流程&#xff1a;4. Kafka 的ack机制&#xff1a;5. Kafka 生产者消息发送模式&#xff08;同步/异步&#xff09;&#xff1a;6. Kafka发送消息的分区策略…

元宇宙之声:nspace

nspace 行政总裁为我们介绍他在元宇宙中的最新创作以及对 2023 年的愿景。 本期节目我们邀请了 nspace 行政总裁 Ethan Liu 分享他的 The Sandbox 之旅以及他们的最新创作。 可以告诉我们更多关于 nspace 的信息吗&#xff1f; nspace 是一家专注于开发新的元宇宙商业模式的初创…

html 拖拽事件详解

为了使元素可拖拽&#xff0c;需要在标签上设置draggabletrue属性。 文本、图片和链接是默认可以拖放的&#xff0c;它们的draggable属性自动被设置成了true。 图片和链接按住鼠标左键选中&#xff0c;就可以拖放。 文本只有在被选中的情况下才能拖放。如果显示设置文本的dr…

LinkedIn工具-领英精灵参数怎么设置?

前言&#xff1a; 领英精灵是高端技术人员针对领英平台研发的工具。具有好友分组、备注&#xff0c;一键批量加-好友&#xff0c;批量撤-回邀请&#xff0c;批量群-发消息&#xff0c;批量导出好友资料&#xff0c;批量点-赞、Groups管理七大功能。通过领英精灵可提高领英开发…

Java集合进阶 | Collection接口

本专栏主要是记录学习完本专栏主要是记录学习Java中的知识点&#xff0c;如果刚开始学习Java的小伙伴可以点击下方连接查看专栏 JavaWeb&#xff1a;&#x1f525;JavaWeb Java入门篇&#xff1a; &#x1f525;Java基础学习篇 Java进阶学习篇&#xff08;持续更新中&#xff0…

Three.js入门以及案例(全方位解析)

下载three.js 压缩包 github链接查看所有版本 threejs&#xff1a;https://github.com/mrdoob/three.js/releases 下载即可 常用的文件目录 three.js-文件包 └───build——three.js相关库&#xff0c;可以引入你的.html文件中。│ └───docs——Three.js API文档文件│…

【机器学习算法】模型评估 “神经网络,聚类,向量机,关联规则”算法模型的评估。

模型评估* 数据集的切割 训练-测试数据的方式、交叉验证的方式 我们通常会把数据集切割为训练数据集或者测试数据集&#xff0c;训练数据集用来训练模型用&#xff0c;测试数据集我们一般用来测试模式的实际效能怎么样。 我们在将数据分为训练和测试数据集的时候我们会使用…

go-zero使用consul作为注册中心

目录 在rpc服务中添加配置 导入包&#xff1a; 在rpc服务中添加配置&#xff1a; 引入 Consul config 配置项 user.yml 文件 修改 user.go,将 rpc注册到consul rpc的发现 在api服务中添加配置&#xff1a; 修改api/etc/user.yam 文件 修改 user.yml 修改api/user.go …

@Import的用法

官方定义: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#spring-core Using the ImportAnnotation Much as the <import/> element is used within Spring XML files to aid in modularizing configurations, the Import annotat…

一文详解ARP报文格式及工作原理

ARP&#xff08;地址解析协议&#xff09;作用&#xff1a;将目的IP解析为目的MAC&#xff0c;用于二层帧结构的目标MAC封装&#xff0c;数据必须封装为帧才能够被网卡发送出去&#xff0c;帧中必须包含MAC。报文格式&#xff1a;ARP报文不能穿越路由器&#xff0c;不能被转发到…

基于飞桨实现钢铁企业废钢判级迈入智能化道路

目前&#xff0c;国家“双碳”战略与“数据智能”环境正驱动着钢铁企业废钢判级迈入智能化道路。针对生产过程中带来高能耗和高污染问题&#xff0c;企业通过使用飞桨用友废钢智能判级系统&#xff0c;助力提升自身经济效益和安全生产水平。 建设背景 目前&#xff0c;国家“双…

如何在IDEA中创建Web项目

&#x1f44c; 棒棒有言&#xff1a;也许我一直照着别人的方向飞&#xff0c;可是这次&#xff0c;我想要用我的方式飞翔一次&#xff01;人生&#xff0c;既要淡&#xff0c;又要有味。凡事不必太在意&#xff0c;一切随缘&#xff0c;缘深多聚聚&#xff0c;缘浅随它去。凡事…

QT入门Buttons之QToolButton

目录 一、界面布局介绍 1、布局器中的位置及使用 2、控件的界面属性 2.1对象名称和大小设置 2.2对象文本设置和鼠标箭头更改 2.3、扁平化样式 二、属性功能介绍 1、显示箭头属性 2、按钮风格 3、添加默认action属性 三、Demo展示 此文为作者原创&#xff0c;转载请标…