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

news2024/11/16 13:31:50

目录

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 日 

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

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

相关文章

自动驾驶学习2-毫米波雷达

1、简介 1.1 频段 毫米波波长短、频段宽,比较容易实现窄波束,雷达分辨率高,不易受干扰。波长介于1~10mm的电磁波,频率大致范围是30GHz~300GHz 毫米波雷达是测量被测物体相对距离、相对速度、方位的高精度传感器。 车载毫米波雷达主要有24GHz、60GHz、77GHz、79GHz四个频段。 …

使用Postman进行接口测试---解析postman页面

一、Postman 是一款流行的 API 测试工具&#xff0c;它提供了丰富的功能来帮助开发者测试和调试 API。以下是 Postman 页面上的主要功能及其含义和作用&#xff1a; 1. 请求详情&#xff08;Request Details&#xff09; &#xff1a; - 方法&#xff08;Method&#xff0…

Disk Doctor for Mac 免激活版:数据安全守卫者

数据丢失是每个人都可能遇到的问题&#xff0c;但Disk Doctor for Mac能让这个问题迎刃而解。这款强大的数据恢复软件&#xff0c;能迅速找回因各种原因丢失的数据。 Disk Doctor采用先进的扫描技术&#xff0c;能深入剖析磁盘&#xff0c;找到并恢复被删除或损坏的文件。同时&…

JavaScript 进阶征途:解锁Function奥秘,深掘Object方法精髓

个人主页&#xff1a;学习前端的小z 个人专栏&#xff1a;JavaScript 精粹 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结&#xff0c;欢迎大家在评论区交流讨论&#xff01; 文章目录 &#x1f235;Function方法 与 函数式编程&#x1f49d;1 call &#x1f49d…

竞赛课第十周(巴什游戏,尼姆博弈)

目录 目的: 实验内容: 第一题 思路&#xff1a; 【参考代码】 【运行结果】 第二题 输入&#xff1a; 输出&#xff1a; 【参考代码】 【运行结果】 目的: 熟悉并掌握公平组合游戏 &#xff08;1&#xff09;巴什游戏、尼姆游戏 &#xff08;2&#xff09;图游戏…

rs485自动收发电路

R/RO&#xff1a;receive/receive out&#xff0c;接收&#xff0c;连接单片机的 rx D/DI&#xff1a;drive/drive in&#xff0c;驱动&#xff0c;连接单片机的 tx 自动控制电路的目的就是在 tx 空闲&#xff08;空闲为高并&#xff09;时拉低 RE 和 DE&#xff0c;工作&…

B/S模式的web通信

这里写目录标题 目标实现的目标 服务器代码&#xff08;采用epoll实现服务器&#xff09;整体框架main函数init_listen_fd函数&#xff08;负责对lfd初始化的那一系列操作&#xff09;epoll_run函数 一级目录二级目录二级目录二级目录 目标 实现的目标 我们要实现&#xff0c;…

Hive-URL解析函数

Hive-URL解析函数 1.实际工作需求 2.URL的基本组成 3.Hive中的Url解析函数 parse_url函数 parse_url_tuple函数

Nacos Docker 快速部署----解决nacos鉴权漏洞问题

Nacos Docker 快速部署 1. 说明 1.1 官方文档 官方地址 https://nacos.io/zh-cn/docs/v2/quickstart/quick-start.html docker启动文件的gitlhub地址 https://github.com/nacos-group/nacos-docker.git 问题&#xff1a; 缺少部分必要配置与说明 1.2 部署最新版本Nacos&…

RS2105XN功能和参数介绍及PDF资料

RS2105XN 品牌: RUNIC(润石) 封装: MSOP-10 开关电路: 单刀双掷(SPDT) 通道数: 2 工作电压: 1.8V~5.5V 导通时间(Tonmax): 50ns RS2105XN是一款模拟开关芯片。以下是RS2105XN的功能和参数介绍&#xff1a; 功能&#xff1a; 2通道单刀双掷&#xff08;SPDT&#xff09;模拟开关…

Android studio 新版本 NewUI toolbar显示快捷按钮

新版本的Android studio 启用新的界面&#xff0c;以前许多快捷按键位置有变化 文章目录 设置始终显示主菜单设置ToolBar快捷按钮显示设置右下角显示分支 设置始终显示主菜单 原本要点击左上角几个横向才显示的菜单 设置始终显示&#xff0c;View -> Appearance -> Mai…

五一超级课堂---Llama3-Tutorial(Llama 3 超级课堂)---第三节llama 3图片理解能力微调(xtuner+llava版)

课程文档&#xff1a; https://github.com/SmartFlowAI/Llama3-Tutorial 课程视频&#xff1a; https://space.bilibili.com/3546636263360696/channel/collectiondetail?sid2892740&spm_id_from333.788.0.0 操作平台&#xff1a; https://studio.intern-ai.org.cn/consol…

信号槽机制

目录 信号槽机制 Qt 中的信号 槽函数 槽函数定义 通过代码创建槽函数 通过ui文件创建槽函数 自定义信号 带参数的信号与槽 信号槽断开绑定 信号槽机制 信号和槽机制是 Qt 中一个非常重要的一个机制, 因为有信号和槽机制, 就可以通过某些条件的触发来调用这些槽函数, …

百度地图API 快速入门

一、创建一个应用 创建成功可以在应用程序中查看到自己的ak密钥 二、基本使用 2.1 显示地图 在static下创建demo1.html &#xff08;将密钥换成自己的就可以显示地图了&#xff09; 示例&#xff1a; <!DOCTYPE html> <html> <head><meta name"…

HTML4(四)

1. 框架标签 <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><title>框架标签</title></head><body><!-- 利用iframe嵌入一个普通网页 --><iframe src"https://www.toutia…

FPGA+MCU+SDRAM方案,用于服装厂生产过程中以及设计过程中制作样板,剪裁布料

FPGAMCUSDRAM方案&#xff0c;用于服装厂生产过程中以及设计过程中制作样板&#xff0c;剪裁布料 客户应用&#xff1a;服装厂制衣 主要功能&#xff1a; 1.支持步进电机、直流电机 2.支持同时3轴电机协调工作 3.支持以太网/USB联机控制 4.支持LCD 屏显示状态 5.支持HP11/…

AI大模型探索之路-训练篇18:大语言模型预训练-微调技术之Prompt Tuning

系列篇章&#x1f4a5; AI大模型探索之路-训练篇1&#xff1a;大语言模型微调基础认知 AI大模型探索之路-训练篇2&#xff1a;大语言模型预训练基础认知 AI大模型探索之路-训练篇3&#xff1a;大语言模型全景解读 AI大模型探索之路-训练篇4&#xff1a;大语言模型训练数据集概…

mysql安装及基础设置

关系型数据库 MySQL是一种关系型数据库管理系统&#xff0c;采用了关系模型来组织数据的数据库&#xff0c;关系数据库将数据保存在不同的表中&#xff0c;用户通过查询 sql 来检索数据库中的数据。 yum 方式安装 mysql # yum -y install mysql-server # systemctl start my…

2024 全自动ai生成视频MoneyPrinterTurbo源码

只需提供一个视频 主题 或 关键词 &#xff0c;就可以全自动生成视频文案、视频素材、视频字幕、视频背景音乐&#xff0c;然后合成一个高清的短视频。 源码下载&#xff1a;https://download.csdn.net/download/m0_66047725/89208288 更多资源下载&#xff1a;关注我。

OSI网络7层的功能介绍

目录 1.OSI功能介绍 2.SNA 3.X.25 1.OSI功能介绍 2.SNA SNA: IBM Systems Network Architecture)SNA是IBM公司开发的网络体系结构&#xff0c;在IBM公司的主机环境中得到广泛的应用。一般来说&#xff0c;SNA主要是IBM公司的大型机(ES/9000、S/390等)和中型机(AS/400)的主要…