Cpp::STL—vector类的模拟实现(11)

news2024/11/28 18:35:21

文章目录

  • 前言
  • 一、各函数接口总览
  • 二、默认成员函数
    • vector();
    • vector(size_t n, const T& val = T( ));
    • template< class InputIterator> vector(InputIterator first, InputIterator last);
    • vector(const vector<T>& v);
    • vector<T>& operator=(const vector<T>& v);
    • vector<T>& operator=(const vector<T> v);
    • ~vector();
  • 三、迭代器相关函数
  • 四、容量和大小有关函数
    • size & capacity
    • reserve
      • 野指针问题
      • 浅拷贝问题
    • resize
    • empty
  • 五、增删查改有关函数
    • push_back
    • pop_back
    • insert
    • erase
    • swap
  • 六、访问容器相关函数
    • operator[ ]
    • front & back
  • 总结


前言

  我们来实现一下vector吧!
  这会很有意思的!


一、各函数接口总览

  不如先来看看我们要实现的接口,请注意!实际实现并未声明和定义分离,因为这涉及到模板的一些特性,我们先按下不表,后面再做解释,且为了避免与库里的vector相冲突,我们要用命名空间包起来!

namespace HQ
{
	//模拟实现vector
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		//默认成员函数
		vector();                                           //构造函数
		vector(size_t n, const T& val);                     //构造函数
		template<class InputIterator>                      
		vector(InputIterator first, InputIterator last);    //构造函数
		vector(const vector<T>& v);                         //拷贝构造函数
		vector<T>& operator=(const vector<T>& v);           //赋值运算符重载函数
		~vector();                                          //析构函数

		//迭代器相关函数
		iterator begin();
		iterator end();
		const_iterator begin() const;
		const_iterator end() const;

		//容量和大小相关函数
		size_t size() const;
		size_t capacity() const;
		void clear()void reserve(size_t n);
		void resize(size_t n, const T& val = T());
		bool empty() const;

		//修改容器内容相关函数
		void push_back(const T& x);
		void pop_back();
		iterator insert(iterator pos, const T& x);
		iterator erase(iterator pos);
		void swap(vector<T>& v);

		//访问容器相关函数
		T& front();
		const T& front(); const
		T& back();
		const T& back(); const
		T& operator[](size_t i);
		const T& operator[](size_t i)const;

	private:
		iterator _start;        // 指向容器的头
		iterator _finish;       // 指向有效数据的尾
		iterator _endofstorage; // 指向容器的尾
	};
	
	template<class T>
	void print_vector(const vector<T>& v); 
}

二、默认成员函数

vector();

很显然的无参构造,这时候只需要将三个成员变量初始化为空指针即可:

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

vector(size_t n, const T& val = T( ));

我们可以先使用reserve函数将容器容量先设置为n,然后使用push_back函数尾插n个值为val的数据到容器当中即可,并且正如我们上篇说的一样,最好再重载一个vector(int n, const T& val = T( )); 版本:

vector(size_t n, const T& val)
	:_start(nullptr)
	,_finish(nullptr)
	,_end_of_storage(nullptr)
{
	reserve(n); // 调用reserve函数将容器容量设置为n
	for (size_t i = 0; i < n; i++) // 尾插n个值为val的数据到容器当中
	{
		push_back(val);
	}
}

template< class InputIterator> vector(InputIterator first, InputIterator last);

因为该迭代器区间可以是其他容器的迭代器区间,也就是说该函数接收到的迭代器的类型是不确定的,所以我们这里需要将该构造函数设计为一个函数模板:

// 请注意,接下来的接口将不再给出初始化列表
// 事实上,运用缺省值这里会更方便一些
template<class InputIterator> // 模板函数
vector(InputIterator first, InputIterator last)
{
	// 将迭代器区间在[first,last)的数据一个个尾插到容器当中
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

vector(const vector& v);

vector(const vector<T>& v)
{
	reserve(v.capacity());
	for (const auto& e : v)
	{
		push_back(e);
	}
}

vector& operator=(const vector& v);

//传统写法
vector<T>& operator=(const vector<T>& v)
{
	if (this != &v) // 防止自己给自己赋值
	{
		delete[] _start; // 释放原来的空间
		_start = new T[v.capacity()]; // 开辟一块和容器v大小相同的空间
		
		// 注意这里不能使用memcpy函数进行拷贝!
		// 原因是memcpy是值拷贝,浅拷贝!
		for (size_t i = 0; i < v.size(); i++) // 将容器v当中的数据一个个拷贝过来
		{
			_start[i] = v[i];
		}
		
		_finish = _start + v.size(); // 容器有效数据的尾
		_end_of_storage = _start + v.capacity(); // 整个容器的尾
	}
	return *this; //支持连续赋值
}

vector& operator=(const vector v);

事实上,赋值运算符重载还有一个非常狂野的写法,我们看这种方式舍弃了引用传值,先来个拷贝构造,接着再跟this交换数值空间,又因为v是临时变量,出作用域后立马销毁,非常巧妙,有种借力打击的感觉!:

vector<T>& operator=(vector<T> v)
{
	if (this != &v) // 防止自己给自己赋值
	{
		swap(v);
	}
	return *this;
}

~vector();

对容器进行析构时,首先判断该容器是否为空容器,若为空容器,则无需进行析构操作,若不为空,则先释放容器存储数据的空间,然后将容器的各个成员变量设置为空指针即可:

~vector()
{
	if (_start) //避免对空指针进行释放
	{
		delete[] _start; 
		_start = _finish = _end_of_storage = nullptr;
	}
}

三、迭代器相关函数

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

typedef T* iterator;
typedef const T* const_iterator;

begin函数返回容器的首地址,end函数返回容器当中有效数据的下一个数据的地址,而我们还需要重载一对适用于const对象的begin和end函数,使得const对象调用begin和end函数时所得到的迭代器只能对数据进行读操作,而不能进行修改:

iterator begin()
{
	return _start; // 返回容器的首地址
}

iterator end()
{
	return _finish; // 返回容器当中有效数据的下一个数据的地址
}

const_iterator begin() const
{
	return _start; // 返回容器的首地址
}

const_iterator end() const
{
	return _finish; // 返回容器当中有效数据的下一个数据的地址
}

四、容量和大小有关函数

size & capacity

由于两个指针相减的结果,就是这两个指针之间对应类型的数据个数,所以利用三个原生指针,我们可以很显然的得出 size 和 capacity

size_t size() const
{
	return _finish - _start; // 返回容器当中有效数据的个数
}

size_t capacity() const
{
	return _endofstorage - _start; // 返回当前容器的最大容量
}

reserve

void reserve(size_t n)
{
	if (n > capacity())
	{
		size_t old_size = size();
		T* tmp = new T[n];
		//memcpy(tmp, _start, old_size * sizeof(T)); // err
		for (size_t i = 0; i < old_size; i++)
		{
			tmp[i] = _start[i];
		}
		delete[] _start;

		_start = tmp;
		_finish = tmp + old_size;
		_end_of_storage = tmp + n;
	}
}

我们要注意两个问题:

野指针问题

迭代器失效的问题,出现野指针_finish指向错误。在delete[ ] _start的时候,_finish还在指向旧空间,导致调用size()得到数据错误,我们的解决办法就是在开辟新空间之前,保存一下旧size()的值,存在临时变量old_size里面
在这里插入图片描述

浅拷贝问题

memcpy是逐字节拷贝,属于浅拷贝。当T为自定义类型,使用memcpy进行浅拷贝操作,会指向同一块空间,我们的解决办法是利用赋值运算符,就算是自定义类型,也有自己的赋值构造函数
在这里插入图片描述

resize

根据resize函数的规则,进入函数我们可以先判断所给n是否小于容器当前的size,若小于,则通过改变_finish的指向,直接将容器的size缩小到n即可,否则先判断该容器是否需要增容,然后再将扩大的数据赋值为val即可。

那等于呢?等于怎么办?
那就不动呗~直接跳出

// 在C++当中内置类型也可以看作是一个类,它们也有自己的默认构造函数
// 所以在给resize函数的参数val设置缺省值时,设置为T()即可
void resize(size_t n, const T& val = T())
{
	if (n < size()) // 当n小于当前的size时
	{
		_finish = _start + n; // 将size缩小到n
	}
	else if (n > capacity())
	{
		reserve(n);
		while (_finish < _start + n) // 将size扩大到n
		{
			*_finish = val;
			_finish++;
		}
	}
}

empty

empty函数可以直接通过比较容器当中的_start和_finish指针的指向来判断容器是否为空,若所指位置相同,则该容器为空

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

五、增删查改有关函数

push_back

要尾插数据首先得判断容器是否已满,若已满则需要先进行增容,然后将数据尾插到_finish指向的位置,再将_finish++即可

void push_back(const T& x)
{
	// 扩容
	if (_finish == _end_of_storage)
	{
		reserve(capacity() == 0 ? 4 : capacity() * 2);
	}

	*_finish = x;
	++_finish;
}

pop_back

尾删数据之前也得先判断容器是否为空,若为空则做断言处理,若不为空则将_finish- -即可

//尾删数据
void pop_back()
{
	assert(!empty()); // 容器为空则断言
	--_finish; // _finish指针前移
}

insert

insert函数可以在所给迭代器pos位置插入数据,在插入数据前先判断是否需要增容,然后将pos位置及其之后的数据统一向后挪动一位,以留出pos位置进行插入,最后将数据插入到pos位置即可

iterator insert(iterator pos, const T& x)
{
	assert(pos >= _start);
	assert(pos <= _finish);

	// 扩容
	if (_finish == _end_of_storage)
	{
		// 若需要增容,则需要在增容前记录pos与_start之间的间
		size_t len = pos - _start;
		reserve(capacity() == 0 ? 4 : capacity() * 2);
		pos = _start + len;
	}

	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		--end;
	}
	*pos = x;
	++_finish;

	return pos;
}

erase

erase函数可以删除所给迭代器pos位置的数据,在删除数据前需要判断容器释放为空,若为空则需做断言处理,删除数据时直接将pos位置之后的数据统一向前挪动一位,将pos位置的数据覆盖即可

iterator erase(iterator pos)
{
	assert(pos >= _start);
	assert(pos < _finish);

	iterator it = pos + 1;
	while (it != end())
	{
		*(it - 1) = *it;
		++it;
	}
	--_finish;
	
	return pos;
}

swap

直接调用库当中的swap函数将两个容器当中的各个成员变量进行交换即可

void swap(vector<T>& v)
{
	// 根据就近原则,所以你需要加上个std::
	std::swap(_start, v._start);
	std::swap(_finish, v._finish);
	std::swap(_end_of_storage, v._end_of_storage);
}

六、访问容器相关函数

operator[ ]

vector也支持我们使用“下标+[ ]”的方式对容器当中的数据进行访问,实现时直接返回对应位置的数据即可

T& operator[](size_t i)
{
	assert(i < size()); // 检测下标的合法性
	return _start[i]; // 返回对应数据
}

const T& operator[](size_t i) const
{
	assert(i < size()); // 检测下标的合法性
	return _start[i]; // 返回对应数据
}

front & back

很简单,也是直接利用原生指针来返回即可

T& front()
{
	return *_start;
}
 
const T& front() const
{
	return *_start;
}

T& back()
{
	return *(_finish - 1);
}
 
const T& back() const
{
	return *(_finish - 1);
}

总结

  怎么样,接下来我们就要迎来不太一样的list了哦!

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

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

相关文章

腾讯云SDK基本概念

本文旨在介绍您在使用音视频终端 SDK&#xff08;腾讯云视立方&#xff09;产品过程中可能会涉及到的基本概念。 音视频终端 SDK&#xff08;腾讯云视立方&#xff09; 应用 音视频终端 SDK&#xff08;腾讯云视立方&#xff09;通过应用的形式来管理您的项目&#xff08;Ap…

C/C++进阶(一)--内存管理

更多精彩内容..... &#x1f389;❤️播主の主页✨&#x1f618; Stark、-CSDN博客 本文所在专栏&#xff1a; 学习专栏C语言_Stark、的博客-CSDN博客 其它专栏&#xff1a; 数据结构与算法_Stark、的博客-CSDN博客 ​​​​​​项目实战C系列_Stark、的博客-CSDN博客 座右铭&a…

免费录屏软件工具:助力高效屏幕录制

录屏已经成为了一项非常实用且广泛应用的技术。无论是制作教学视频、记录游戏精彩瞬间&#xff0c;还是进行软件操作演示等&#xff0c;我们都常常需要一款可靠的录屏软件。今天&#xff0c;就让我们一起来探索那些功能强大录屏软件免费版&#xff0c;看看它们是如何满足我们多…

ARTS Week 42

Algorithm 本周的算法题为 2283. 判断一个数的数字计数是否等于数位的值 给你一个下标从 0 开始长度为 n 的字符串 num &#xff0c;它只包含数字。 如果对于 每个 0 < i < n 的下标 i &#xff0c;都满足数位 i 在 num 中出现了 num[i]次&#xff0c;那么请你返回 true …

【数据结构强化】应用题打卡

应用题打卡 数组的应用 对称矩阵的压缩存储 注意&#xff1a; 1. 2.上三角的行优先存储及下三角的列优先存储与数组的下表对应 上/下三角矩阵的压缩存储 注意&#xff1a; 上/下三角压缩存储是将0元素统一压缩存储&#xff0c;而不是将对角线元素统一压缩存储 三对角矩阵的…

接口隔离原则在前端的应用

什么是接口隔离 接口隔离原则&#xff08;ISP&#xff09;是面向对象编程中的SOLID原则之一&#xff0c;它专注于设计接口。强调在设计接口时&#xff0c;应该确保一个类不必实现它不需要的方法。换句话说&#xff0c;接口应该尽可能地小&#xff0c;只包含一个类需要的方法&am…

SKD4(note上)

微软提供了图形的界面API&#xff0c;叫GDI 如果你想画某个窗口&#xff0c;你必须拿到此窗口的HDC #include <windows.h> #include<tchar.h> #include <stdio.h> #include <strsafe.h> #include <string>/*鼠标消息 * 键盘消息 * Onkeydown * …

实验 3 存储器实验

实验 3 存储器实验 1、实验目的 掌握静态随机存储器 RAM 的工作特性。掌握静态随机存储器 RAM 的读写方法。 2、实验要求 (1)做好实验预习&#xff0c;熟悉MEMORY6116 芯片各引脚的功能和连接方式&#xff0c;熟悉其他实验元器件的功能特性和使用方法&#xff0c;看懂电路图…

CSS | 响应式布局之媒体查询(media-query)详解

media type(媒体类型)是CSS 2中的一个非常有用的属性&#xff0c;通过media type我们可以对不同的设备指定特定的样式&#xff0c;从而实现更丰富的界面。media query(媒体查询)是对media type的一种增强&#xff0c;是CSS 3的重要内容之一。随着移动互联网的发展&#xff0c;m…

中国靠谱热门交友软件app排行榜前十名测评

在信息飞速发展的时代&#xff0c;交友软件层出不穷。究竟哪些才是靠谱又热门的呢&#xff1f;这份交友软件 app 排行榜将为你揭晓&#xff0c;带你走进不同的社交天地&#xff0c;开启精彩交友之旅。 咕哇找搭子小程序&#xff1a;这是一个实名制的找搭子交友平台&#xff0c;…

基于ssm的学生社团管理系统 社团分配系统 社团活动调度平台 学生社团管理 信息化社团管理开发项目 社团活动管理 社团预约系统(源码+文档+定制)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

时尚科技融合:Spring Boot下的“衣依”服装销售平台

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常适…

虚拟机三种网络模式详解

在电脑里开一台虚拟机&#xff0c;是再常见不过的操作了。无论是用虚拟机玩只有旧版本系统能运行的游戏&#xff0c;还是用来学习Linux、跑跑应用程序都是很好的。而这其中&#xff0c;虚拟机网络是绝对绕不过去的。本篇文章通俗易懂的介绍了常见的虚拟网络提供的三种网络链接模…

小红书AI配音神器:3秒变声百种风格

小红书AI配音神器&#xff1a;3秒变声百种风格 小红书推出黑科技FireRedTTS&#x1f3a4;&#xff0c;3秒克隆你的声音✨&#xff0c;支持搞怪、温柔等多种风格&#x1f389;&#xff01;只需几秒参考音频&#xff0c;轻松生成个性化语音&#xff0c;短视频配音神器&#x1f3…

把白底照片变蓝色用什么软件免费 批量更换证件照底色怎么弄

作为专业的修图师&#xff0c;有时候也会接手证件照修图和换底色工作&#xff0c;这种情况下&#xff0c;需要换底色的照片也许达到上百张。为了提高工作效率&#xff0c;一般需要批量快速修图&#xff0c;那么使用什么软件工具能够给各式不同的照片批量更换背景色呢&#xff1…

Python并发编程(1)——Python并发编程的几种实现方式

更多精彩内容&#xff0c;请关注同名公众&#xff1a;一点sir&#xff08;alittle-sir&#xff09; Python 并发编程是指在 Python 中编写能够同时执行多个任务的程序。并发编程在任何一门语言当中都是比较难的&#xff0c;因为会涉及各种各样的问题&#xff0c;在Python当中也…

【Unity AI】基于 WebSocket 和 讯飞星火大模型

文章目录 整体AIManagerDialogueManagerUIManagerModelManagerAudioManagerSaveManager 详细部分AIUI动画音频 整体 AIManager 负责配置讯飞的appId&#xff0c;生成鉴权URL&#xff0c;通过WebSocket向服务器请求并返回数据&#xff08;分为最终返回和流式返回&#xff09; …

C++基础(6)——模板初阶

目录 1.泛型编程 2.函数模板 2.1函数模板的概念 2.2 函数模板格式 2.3 函数模板的原理 2.4 函数模板的实例化 2.4.1隐式实例化&#xff1a;让编译器根据实参推演模板参数的实际类型 2.4.2显式实例化&#xff1a;在函数名后的<>中指定模板参数的实际类型 2.5 模板…

C++ WebDriver扩展

概述 WebDriver协议基于HTTP&#xff0c;使用JSON进行数据传输&#xff0c;定义了client与driver之间的通信标准。无论client的实现语言&#xff08;如Java或C#&#xff09;&#xff0c;都能通过协议中的endpoints准确指示driver执行各种操作&#xff0c;覆盖了Selenium的所有功…

Redis入门第五步:Redis持久化

欢迎继续跟随《Redis新手指南&#xff1a;从入门到精通》专栏的步伐&#xff01;在本文中&#xff0c;我们将深入探讨Redis的持久化机制&#xff0c;这是确保数据在服务器重启后不会丢失的关键功能。了解如何配置和使用不同的持久化方法&#xff0c;对于构建可靠的应用程序至关…