【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类型的拷贝构造函数来复制元素e
        push_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的距离,用于扩容后重新定位pos
        size_t len = pos - _start;
        // 扩容逻辑:初始为空时至少分配4个单位空间,否则加倍当前容量
        reserve(capacity() == 0 ? 4 : capacity() * 2);
        // 扩容后,根据之前计算的偏移量重新定位pos
        pos = _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   5
		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)
			{
				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 日 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Django项目之电商购物商城 -- 创建收货地址

Django项目之电商购物商城 – 创建收货地址 一. 在users中创建新的视图与路由用于创建收货地址 # 设置收货地址 class AddressView(View):def get(self , request):return render(request , "user_center_site.html")# 设置收货地址path(user_center_site/, views.…

web前端学习笔记9

9. HTML5新增元素及属性 9.1 HTML5新增结构元素 HTML5引入了几个新的结构元素,极大地改善了网页的组织和结构方式。以下是HTML5中的一些关键新结构元素: 标签说明<header>页面或页面中某一个区块的页眉,通常是一些引导和导航信息<nav>可以作为页面导航的链接组&…

【c++】线程池的原理及实现

&#x1f4bb;文章目录 &#x1f4c4;前言线程池的原理概念工作原理 线程池的实现线程池的基础结构任务队列的实现工作线程的实现 线程池的应用与拓展线程池的拓展 &#x1f4d3;总结 &#x1f4c4;前言 不知道各位是否有试过点进限时抽奖网站、抢票网站呢&#xff1f;你是否好…

第19讲:Ceph集群CrushMap规则定制与调优:从基础到高级应用

文章目录 1.CrushMap规则拓扑结构1.1.集群默认的CrushMap规则拓补图1.2.自定义的CrushMap规则拓补图 2.定制CrushMap规则的方法以及注意事项3.通过二进制文件编写一套CrushMap规则3.1.将系统默认的CrushMap规则导出3.2.根据需求编写CrushMap规则3.3.将编写好的规则导入到集群中…

Fastapi+docker+tortoise-orm+celery

因为项目是后期引入celery,所以导致构建docker的时候只有fastapi的项目&#xff0c;celery的重启比较麻烦 1.docker安装celery pip install celery安装celery的时候注意python版本与celery版本的适配&#xff0c;有些celery的版本不支持python的版本&#xff0c;具体的版本请看…

计算机毕业设计 | vue+springboot汽车销售管理系统(附源码)

1&#xff0c;项目介绍 本项目基于spring boot以及Vue开发&#xff0c;前端实现基于PanJiaChen所提供的开源后台项目vue-element-admin改造。 针对汽车销售提供客户信息、车辆信息、订单信息、销售人员管理、财务报表等功能&#xff0c;提供经理和销售两种角色进行管理。 2&…

记录如何查询域名txt解析是否生效

要查询域名的TXT记录&#xff0c;可以使用nslookup命令。具体步骤如下&#xff1a;12 打开命令行终端。输入命令 nslookup -qttxt 域名&#xff0c;将"域名"替换为你要查询的实际域名。执行命令后&#xff0c;nslookup会返回域名的TXT记录值。 如何查询域名txt解析是…

面试题库-项目

1.项目主要实现了哪些功能&#xff1f; 本项目是专门为校园食堂窗口定制的一款软件产品&#xff0c;包括系统管理后台和客户端两部分。其中系统管理后台主要提供给食堂内部员工使用&#xff0c;可以对餐厅的菜品、套餐、订单、员工等进行管理维护。客户端主要提供给学生及校职…

什么是分库分表?代表性框架有哪些?

在互联网系统开发过程中&#xff0c;所谓的分库分表并不是一个新概念。或者说&#xff0c;对于很多开发人员而言&#xff0c;说起分库分表&#xff0c;大家都或多或少有所了解&#xff0c;也都知道数据量大了就需要进行分库分表。但是究竟如何实现分库分表呢&#xff1f; 要想…

创建Spring Boot项目及配置

目录 一、创建项目所需要的插件 1、安装插件 二、创建项目 三、创建项目所面临的常见问题。 1、IDEA不能识别 2、无效的发行版本 3、确认jar包是否下载成功 一、创建项目所需要的插件 1、安装插件 首先需要在IDEA插件里面搜索Spring&#xff0c;选择Spring Boot Helper…

什么是短信群发上行和下行

短信群发是一种广泛应用于商业和个人通信的技术&#xff0c;通过一次多条的方式&#xff0c;可以快速高效地传递信息。在实际的群发过程中&#xff0c;会涉及到上行和下行的概念。本文将详细介绍什么是短信群发上行和下行&#xff0c;并解释它们的应用。 什么是短信群发上行 群…

Dbeaver连接一段时间不操作后断开的问题

右键数据库连接点击编辑连接点击初始化将连接保持改成60s

BW4HANA混合建模 用ADSO的哪个视图?

写日志的ADSO除了1,2,3表之外。还会有6,7,8view。8view是上了BW4HANA2.0之后激活ADSO就会生成的。如果旧版本没有8&#xff0c;那就RSDG_ADSO_ACTIVATE激活一下。 如果勾了外部HANA视图&#xff0c;那就等于说还有一个HANA view。 首先咱知道ADSO是BW里面用来物理存储&#xf…

做一个属于自己的软件-pyside6快速上手教程

首先环境需要安装python3和pip&#xff0c;软件使用pycharm&#xff0c;安装也都很简单 首先需要安装pyside6,在终端执行&#xff1a; pip install pyside6 然后进入可视化编辑界面 pyside6-designer 进入后创建即可 可以从左侧点击鼠标拉组件进入到中间的工作区&#xff…

BLIP和BLIP2 论文讲解

文章目录 BLIPIntroductionMethod模型架构预训练目标字幕和过滤&#xff08;Capfilt&#xff09; BLIP2IntroductionMethod模型结构Q-Former预训练第一阶段Q-Former预训练第二阶段 BLIP 论文&#xff1a; 《BLIP: Bootstrapping Language-Image Pre-training for Unified Visio…

详解BOM编程

华子目录 BOM编程window对象常见的window对象的属性常见的window对象的方法注意 history对象history对象的属性history对象的方法 screen 对象navigator 对象属性方法 location对象属性方法示例 BOM编程 JavaScript本质是在浏览器中运行&#xff0c;所以JavaScript提供了BOM&a…

一文详解FDA邮件认证证书的重要性及其应用

随着全球化和电子商务的飞速发展&#xff0c;跨国贸易和沟通变得越来越频繁。在这个过程中&#xff0c;邮件作为重要的沟通工具&#xff0c;其安全性和可信度成为了各方关注的焦点。FDA&#xff08;美国食品药品监督管理局&#xff09;邮件认证证书就是在这一背景下应运而生的一…

1W 3KVDC 隔离 稳压单输出 DC/DC 电源模块——TPV-SAR 系列

TPV-SAR系列产品是专门针对PCB上分布式电源系统中需要与输入电源隔离且输出精度要求较高的电源应用场合而设计。该产品适用于&#xff1b;1&#xff09;输入电源的电压变化≤5%&#xff1b;2&#xff09;输入输出之前要求隔离电压≥3000VDC&#xff1b;3&#xff09;对输出电压…

mac电脑如何安装java

1、检查当前系统的 Java 版本 打开终端,输入以下命令查看当前 Java 版本 /usr/bin/java -version 2、前往 Java 官网下载 Java JDK 打开 Java 官网 (https://www.java.com/zh-CN/download/) 并下载最新版本的 Java JDK。 3、安装 Java JDK 双击下载的 .dmg 文件启动安装程序…

【全开源】Java共享台信息共享系统源码

特色功能 信息整合与共享&#xff1a;该平台提供一站式信息整合服务&#xff0c;将各种类型的信息资源进行汇聚&#xff0c;方便用户快速查找和获取所需资源。多种共享功能&#xff1a;支持信息共享、共享车位、共享会议室、共享电动车等多种共享功能&#xff0c;提高资源利用…