【C++初阶】第十站:vector 中通用函数的模拟实现

目录

vector中的三个重要迭代器

默认成员函数

构造函数(无参构造)

构造函数(函数模板)

构造函数(带有默认参数)

size_t

int

拷贝构造函数

赋值重载

析构函数

迭代器相关函数

begin和end

容量和大小相关函数

size

capacity

resize

修改容器内容相关函数

reserve

push_back

insert 

        情况一:pos迭代器失效 

        情况二:  insert之后迭代器失效

erase

        情况三:vs2019进行强制检查,erase以后认为it失效了,不能访问,访问就报错

访问容器相关函数

operator[ ]

const operator[ ]


前言:

🎯个人博客:Dream_Chaser

🎈博客专栏:C++

📚本篇内容:vector类通用函数的模拟实现

vector中的三个重要迭代器

在vector当中有三个成员变量_start、_finish、_endofstorage。

_start指向容器的头,_finish指向容器当中有效数据的尾,_endofstorage指向整个容器的尾。

默认成员函数

构造函数(无参构造)

vector():_start(nullptr),_finish(nullptr),_endofstorage(nullptr)
{}

我们可以在函数声明处给上缺省值,那么就不用在初始化列表中再初始化了:

class vector
{
private:iterator _start = nullptr;iterator _finish = nullptr;iterator _endofstorage = nullptr;
};

构造函数(函数模板)

  这是一个vector构造函数模板,它接收两个迭代器firstlast作为参数,用来创建一个新vector,并拷贝firstlast范围内的所有元素到这个新vector里。具体操作是遍历这个区间,逐个将元素添加到vector末尾。

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

构造函数(带有默认参数)

size_t

   定义了一个向量vector的构造函数,它接受两个参数:元素数量n和一个可选的默认值val。函数首先预留足够的容量来存储n个元素,然后通过一个循环,将val这个值添加到向量中 n次。

如果未提供val,则默认添加该类型默认值,如整数类型的0。这样实现了快速构造一个特定大小且元素具有相同值或默认值的向量对象。

vector(size_t n, const T& val = T())
{reserve(n);for (size_t i = 0; i < n; i++){push_back(val);}
}

int

    唯一不同在于第一个函数接受size_t n作为参数,而第二个函数接受int n,这种差异主要体现在参数类型的差别上,建议使用size_t更符合C++标准库容器的惯例,因为它是一种无符号类型,更适合表示容器的大小,能避免负数引发的错误。

vector(int n, const T& val = T())
{reserve(n);for (int i = 0; i <n ; i++){push_back(val);}
}

拷贝构造函数

       定义了一个C++向量(vector)类的拷贝构造函数,其功能是创建一个新的vector对象作为现有vector对象的完整副本。

       为新vector预分配与被拷贝vector相同的能力的内存空间,以优化存储管理。

       随后,通过遍历被拷贝vector的所有元素,并逐个将这些元素通过拷贝构造的方式添加到新vector中,实现了元素的深复制,确保了两个vector之间数据的独立性。

        简而言之,这是一个实现深度拷贝的拷贝构造函数,用于高效地创建vector对象的独立复制品。

// 定义一个拷贝构造函数,接受一个const引用类型的vector<T>参数v
vector(const vector<T>& v)
{// 首先,为新创建的vector预分配足够的内存来容纳v中的所有元素// 通过调用reserve函数设置容量至少为v.capacity()reserve(v.capacity());// 然后,遍历v中的每一个元素for(auto& e : v){// 对于v中的每一个元素e,使用push_back成员函数将其添加到当前vector对象中// 这里会自动调用T类型的拷贝构造函数来复制元素epush_back(e);}
}

赋值重载

      此 swap 函数通过C++标准库的 std::swap 高效地交换两个 vector 实例的内部指针,实现资源的快速互换。

void swap(vector<T>& v)
{std::swap(_start, v._start); // 交换起始指针std::swap(_finish, v._finish); // 交换结束指针std::swap(_endofstorage, v._endofstorage); // 交换容量指针
}

        简单来说就是重写了=操作符的行为,让它可以用于自定义类型(在这里是vector<T>)。这个函数首先创建了一个临时的vector<T>对象tmp,通过拷贝构造初始化为右边要赋值过来的vector<T>

        然后,它使用swap成员函数迅速将当前vector<T>的资源(包括内存指针等)与临时对象tmp交换,这一步骤隐含了原vector<T>资源的清理。最后,函数返回当前对象的引用,以便支持连续赋值(如a = b = c;)。这种方法提高了效率并确保了正确管理内存。

vector<T>& operator=(vector<T> tmp)
{swap(tmp); // 使用swap成员函数交换临时对象tmp与当前对象的状态return *this; // 返回当前对象的引用,支持链式赋值
}

析构函数

     核心任务是清理并释放由vector实例所占用的资源。具体而言,它首先通过delete[] _start;释放了存储元素的数组内存,防止内存泄漏。

        将与该vector实例相关的三个关键指针——_start_finish_endofstorage——全部赋予nullptr值,不仅提高了程序的安全性,避免了悬挂指针的潜在风险,还便于开发人员在调试过程中识别出这些资源已被妥善清理,且对象处于待销毁的最终状态。

// 定义vector类的析构函数
~vector()
{// 释放_start指针指向的动态分配内存,这里是存储vector元素的数组delete[] _start;// 将_start、_finish和_endofstorage指针全部置为nullptr// 目的是:// 1. 避免野指针,提高程序运行时的安全性// 2. 方便调试, nullptr表明这些资源已被释放// 3. 有助于指示该vector对象已完全清理,准备安全销毁_start = _finish = _endofstorage = nullptr;
}

迭代器相关函数

vector当中的迭代器实际上就是容器当中所存储数据类型的指针。

// 定义一个迭代器类型,其中T代表某种数据类型,iterator是一个指向该类型T的指针。
typedef T* iterator;// 定义一个常量迭代器类型,它是一个指向常量的指针,用于遍历不可修改的数据。
// 这意味着通过const_iterator访问的数据不能被修改,保证了数据的安全性。
typedef const T* const_iterator;

begin和end

定义begin()函数,返回指向容器起始元素的迭代器

定义end()函数,返回指向容器最后一个元素之后位置的迭代器

iterator begin()
{return _start; // 返回_start指针,它是容器的第一个元素的地址
}iterator end()
{return _finish; // 返回_finish指针,它是容器结束位置的下一位置
}

定义const版本的begin()函数和end()函数,用于不可修改的迭代访问(只读不可写)

const_iterator begin() const
{return _start; // 返回指向容器起始的常量迭代器,保证元素不可被修改
}const_iterator end() const
{return _finish; // 返回指向容器结束位置之后的常量迭代器,// 保证迭代访问过程中元素不可被修改
}

容量和大小相关函数

size

返回vector中元素的个数, 这是vector中保存的实际对象的数量,不一定等于它的存储容量。

size_t size()
{return _finish - _start;
}

capacity

返回当前为vector分配的存储空间大小,以元素表示

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

resize

     n<size,容器的内容会被缩减至前n个元素,移除超出的部分(并销毁这些元素)。

    size<n<capacity,容器的内容会通过在末尾插入足够多的元素来扩展,以达到n的大小。如果指定了val值,新插入的元素将初始化为val的副本;如果没有指定val,默认情况下,它们将被赋予默认值初始化(空值)。

    n>capacity,那么会自动重新分配已分配的存储空间。

// 该函数用于重新调整容器的大小,并可选地用指定值填充新增的元素
void resize(size_t n, const T& val = T())
{// 如果请求的新大小小于等于当前容器的大小,则只需调整_finish指针即可,无需增删元素if (n <= size()){_finish = _start + n; // 将_finish指针前移,使容器表现为缩小}else{// 如果请求的新大小大于当前容器的容量,首先确保容量足够大reserve(n); // 调整容器的容量至少为n// 然后,使用指定的值val填充从当前位置到新大小之间的所有元素while (_finish < _start + n) // 循环直到到达新的结束位置{*_finish = val; // 将val赋值给当前位置的元素++_finish; // 移动到下一个位置}}
}

修改容器内容相关函数

reserve

规则:

要求vector容器容量至少足以容纳n个元素。

如果n大于当前的vector容量,则该函数使容器重新分配其存储空间,将其容量增加到n(或更大)。

在所有其他情况下,函数调用不会导致重新分配,vector容量也不会受到影响。

这个函数对vector的大小没有影响,也不能改变vector的元素:

  1. 不影响元素数量
  2. 不改变元素内容

拷贝的部分为什么不能使用memcpy:

    vector容器存储的是 string 对象,每个 string 对象内部管理着一块动态分配的字符数组。如果直接使用 memcpy 来复制 string 对象,只是浅拷贝了指针和一些成员变量,源对象和复制后的对象会共享同一块字符数组。当源 vector 销毁时,这块字符数组会被释放,导致新 vector 中的字符串变为悬挂指针,访问时会引发未定义行为。

那为什么要用这个编译器默认的赋值重载?

这里强调一个事情就是说:

        对于内置类型,由于其直接存储值的特性,复制总是“深拷贝”(直接存储其值,没有指针或间接管理的资源)。而对于自定义类型,尤其是包含动态分配资源的类型,需要明确区分深拷贝和浅拷贝,并根据需要实现深拷贝以保证数据的独立性和正确性。

// 该函数用于重新分配容器的内存,确保至少能容纳n个元素而不必重新分配
void reserve(size_t n)
{// 检查请求的容量是否大于当前容量if (n > capacity()){// 分配一个新的内存块,大小为n个T类型的元素T* tmp = new T[n];// 记录当前容器中元素的数量size_t sz = size();// 如果原容器已有数据if (_start){// 将原容器的数据复制到新分配的内存中for (size_t i = 0; i < sz; i++){tmp[i] = _start[i];}// 释放原容器占用的内存delete[] _start;}// 更新指针,//使得_start指向新内存的开始,//_finish指向已有的元素末尾,//_endofstorage指向新内存的末尾_start = tmp;_finish = _start + sz;_endofstorage = _start + n;}
}

push_back

在vector当前最后一个元素的末尾添加一个新元素。val的内容被复制(或移动)到新元素中。

void push_back(const T& x)
{//方法一/*if (_finish == _endofstorage){reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x;++_finish;*///直接复用:insert(end(),x);
}

insert 

当 _finish == _endofstorage  说明该容器需要扩容,在此过程中会引发一些bug。

调试过后,发现pos指向的内容变为随机值:

从上述示例中,引出我们迭代器失效的例子:

                                                情况一:pos迭代器失效 

        扩容后pos失效了,失效原因:扩容导致,原来的迭代器还指向旧的空间

解决办法:更新pos

// 在指定迭代器位置pos前插入一个值为x的元素
void insert(iterator pos, const T& x)
{// 断言检查,确保pos位于_start和_finish之间assert(pos >= _start);assert(pos <= _finish);// 如果 Finish 指针已达到存储空间末端,进行扩容if (_finish == _endofstorage){// 计算当前位置距离_start的距离,用于扩容后重新定位possize_t len = pos - _start;// 扩容逻辑:初始为空时至少分配4个单位空间,否则加倍当前容量reserve(capacity() == 0 ? 4 : capacity() * 2);// 扩容后,根据之前计算的偏移量重新定位pospos = _start + len;}// 从_finish指针所指位置开始(容器尾部),向后移动所有元素,为新元素腾出空间iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end; // 每个元素向后移动一位--end;}// 在腾出的位置插入新元素x*pos = x;// 更新_finish指针,表示容器元素数量增加++_finish;
}

        

        情况二:  insert之后迭代器失效

怎么避免呢:

erase

        情况三:vs2019进行强制检查,erase以后认为it失效了,不能访问,访问就报错

验证erase迭代器失效:

用示例 1 2 3 4 5 验证的时候,发现没有出现问题,但是用 1 2 3 4 5 6 验证则会出现错误:

而且发现用这个验证的时候,2 2 3 4 5,并没有把所有的偶数都删去。

验证 1 2 3 4 5 和 2 2 3 4 5:

当验证 1 2 3 4  5  6时:

当循环中删除元素后直接使用++it,如果该元素正好是最后一个满足删除条件的元素,那么在删除后,it会指向容器的end(),此时再执行++it就会越界,尤其是在开启了迭代器调试检查的编译器环境下(如VS2019的迭代器检查功能),会直接报告错误。

        不能直接++it,如果没有删除元素,才进行迭代器的自增

	void test_vector5(){//1  2   3   4   5  //1  2   3  4    5  6//2  2   3   4   5std::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){it = v.erase(it);}else{++it;}}for (auto e : v){cout << e << " ";}cout << endl;}

访问容器相关函数

operator[ ]

非 const版本 的 operator[ ] 允许对容器内的元素进行读写操作。

这个函数被调用时,它会直接返回 _start[pos] ,即容器起始地址偏移pos位置的元素的引用。由于返回的是引用,所以可以通过这个返回值来修改容器内的元素。

// 非const版本的下标访问运算符
T& operator[](size_t pos)
{// 断言检查传入的索引pos是否小于当前容器的大小,确保索引合法assert(pos < size());// 返回指定位置pos的元素的引用,允许用户修改该元素return _start[pos];
}

 

const operator[ ]

     const 版本的 operator[ ] 主要用于 const对象 或者通过 const引用访问容器时。它返回元素的常量引用,意味着通过这种方式访问到的元素是只读的,不能被修改,从而保证了容器在声明为const时的数据完整性。

// const版本的下标访问运算符
const T& operator[](size_t pos) const
{// 同样进行索引合法性检查assert(pos < size());// 返回指定位置pos的元素的常量引用,保证该元素不可被修改return _start[pos];
}

🔧本文修改次数:0

🧭更新时间:2024年 5 月 10 日 

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

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

相关文章

不想让Win系统更新,那就让它暂停一万年

按照下图所示进行操作 winR 输入 regedit&#xff0c;进入注册表编辑器 随后依次点击 HKEY_LOCAL_MACHINE ⬇ SOFTWARE ⬇ Microsoft ⬇ WindowsUpdate ⬇ UX ⬇ Settings 最后在右侧空白处 文件类型 新建DWORD&#xff08;32位&#xff09;值&#xff08;D&#xff09; 命名…

PyQt5的布局管理

文章目录 1.垂直布局和水平布局垂直布局&#xff08;QVBoxLayout&#xff09;&#xff1a;水平布局&#xff08;QHBoxLayout&#xff09;&#xff1a; 2. 布局中的addStrech2.1 我们首先看只有一个Strech的情况&#xff0c;比较容易理解2.2 两个Strech2.3 多个Strech 3.栅格布局…

FPGA HDMI Sensor无线航模摄像头

FPGA方案&#xff0c;接收摄像头sensor 图像数据后&#xff0c;通过HDMI输出到后端 客户应用&#xff1a;无线航模摄像头 主要特性&#xff1a; 1.支持2K以下任意分辨率格式 2.支持多种型号sensor 3.支持自适应摄像头配置&#xff0c;并补齐输出时序 4.可定制功能&#xff…

OpenHarmony 实战开发(南向)-Docker编译环境搭建

Docker环境介绍 OpenHarmony为开发者提供了两种Docker环境&#xff0c;以帮助开发者快速完成复杂的开发环境准备工作。两种Docker环境及适用场景如下&#xff1a; 独立Docker环境&#xff1a;适用于直接基于Ubuntu、Windows操作系统平台进行版本编译的场景。 基于HPM的Docker…

【ArcGIS Pro微课1000例】0058:玩转NetCDF多维数据集

一、NetCDF介绍 NetCDF(network Common Data Form)网络通用数据格式是由美国大学大气研究协会(University Corporation for Atmospheric Research,UCAR)的Unidata项目科学家针对科学数据的特点开发的,是一种面向数组型并适于网络共享的数据的描述和编码标准。NetCDF广泛应…

【Java】Java中栈溢出的常见情况及解决方法

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

Linux实验二:文件IO操作

目录 一、实验目的二、实验内容三、实验环境四、参考代码五、实验步骤步骤1. 编辑程序源代码test2.c步骤2. 编译源代码test2.c步骤3. 编辑源文件alice.txt步骤4. 运行程序test2 六、实验结果七、实验总结 一、实验目的 1、掌握Linux中系统调用、文件描述符的基本概念&#xff…

docker-compose管理jenkins

1.安装docker和compose 1.docker 更新系统&#xff1a;yum update 安装依赖项&#xff1a;yum install -y yum-utils device-mapper-persistent-data lvm2 配置镜像源&#xff1a;yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce…

5.12母亲节营销攻略:TikTok助力出海品牌赢得用户心

母亲节&#xff0c;作为一个全球性的节日&#xff0c;不仅是表达对母亲的感激之情的时刻&#xff0c;也是品牌们展示创意、赢得用户心的黄金机会。2024母亲节将至&#xff0c;如何利用TikTok在母亲节这一特殊时刻进行营销&#xff0c;赢得用户的心&#xff0c;成为出海品牌必须…

AWS-TGW同区域多vpc打通

同区域vpc打通方案配置 跨区域参考另一个博客 点我跳转 vpc110.30.0.0/16实例110.30.4.178 vpc2172.31.0.0/16实例2172.31.43.180 1. 新建tgw 默认配置即可 2.创建挂载&#xff0c;两个vpc打通就需要创建2个挂载 3. 观察tgw的路由规则 等待挂载完全创建完成后&#xff0c;会…

校友录系统的设计与开发

**中文摘要&#xff1a;**随着互联网技术的不断发展和普及&#xff0c;人们对于信息化、数字化的需求也越来越高。在此背景下&#xff0c;校友录系统的设计与开发显得尤为重要。本文旨在设计和开发一款方便实用的校友录系统&#xff0c;为学校提供一个联系和管理校友的平台&…

超分辨率专题 | 3 种方法、4 个教程、10 个数据集,一文 Get 核心知识点

2010 年 12 月&#xff0c;清华大学电子工程系教授苏光大接到一通不寻常的电话&#xff0c;内蒙古自治区准格尔刑警队的警员拿着一张模糊不清的犯罪嫌疑人人脸图像&#xff0c;向苏光大寻求帮助。 「这张图像是由路边的监控摄像头拍摄的&#xff0c;像素非常低&#xff0c;肉眼…

2024数维杯C题24页完整解题思路+1-4问代码解题+运行高清结果图

C题天然水合物资源量评价 点击链接加入群聊【2024数维杯数学建模ABC题资料汇总】&#xff1a; 2024数维杯C题完整思路24页配套代码1-4问后续参考论文https://www.jdmm.cc/file/2710638 下面内容是持续更新的 根据勘探数据确定天然气水合物资源的分布范围。 假设勘探区域内的…

Mura CMS processAsyncObject SQL注入漏洞复现(CVE-2024-32640)

0x01 产品简介 Mura CMS(Content Management System)是一款用于创建和管理网站内容的开源内容管理系统。它具有许多功能和灵活性,使其成为许多网站开发者和内容创作者的首选工具。是一个强大的企业网站解决方案,可用于创建和管理公司网站、产品目录、新闻发布、客户支持和…

异步时序电路的分析方法

异步时序电路的分析方法 在异步时序电路中&#xff0c;只有部分触发器由时钟脉冲 CP触发&#xff0c;其它触发器由电路内部信号触发。分析异步时序电路时需写出时钟方程&#xff0c;并特别注意各触发器的时钟条件在何时满足&#xff0c;其状态方程才能使用 Tips&#xff1a;在…

Sass语法介绍-变量介绍

02 【Sass语法介绍-变量】 sass有两种语法格式Sass(早期的缩进格式&#xff1a;Indented Sass)和SCSS(Sassy CSS) 目前最常用的是SCSS&#xff0c;任何css文件将后缀改为scss&#xff0c;都可以直接使用Sassy CSS语法编写。 所有有效的 CSS 也同样都是有效的 SCSS。 Sass语…

window golang 升级版本

执行go tidy&#xff0c;发现执行不了&#xff0c;得升级一下版本了 进入官网&#xff0c;并选择合适的系统以及版本。https://go.dev/dl/ 这台电脑是windows&#xff0c;我本人比较喜欢下载zip自己解压。 解压&#xff0c;这里我选择直接覆盖原文件&#xff0c;需要保留原版…

前端技术交流群

欢迎来到前端筱园用户交流&#xff01;这是一个专注于前端编程技术、学习资源和行业动态的讨论平台。在这里&#xff0c;你可以分享经验、提问、回答问题&#xff0c;与其他前端开发者一起学习和成长。 &#x1f31f;亲爱的朋友们&#x1f31f; 大家好&#xff01;感谢你们一直…

TCP的特性(4)

TCP特性 拥塞控制(可靠性机制)延迟应答(效率机制)捎带应答(效率机制)面向字节流(粘包问题)TCP异常机制(心跳包)小结 拥塞控制(可靠性机制) 虽然TCP引入了滑动窗口,能够高效可靠的传输大量数据,但是在开始阶段就发送大量数据,可能引起一系列问题. TCP引入了慢启动机制,先发少量的…

私域流量优化:如何利用 AIPL 模型洞察客户生命周期价值

在当今这个数字化时代&#xff0c;商业战场的硝烟从未如此浓烈。随着互联网红利的逐渐消退&#xff0c;公域流量的成本水涨船高&#xff0c;企业间对于有限用户资源的争夺已进入白热化阶段。每一次点击、每一个曝光背后&#xff0c;都是企业不得不承担的高昂代价。在此背景下&a…