C++编程:vector容器的简单模拟实现

前言:

在C++标准库(STL)中,vector容器是最常见使用的动态数组。它结合了链表与数组的优点,提供了灵活的大小调整与高效的随机访问。本文将简单的对vector容器进行介绍并且对vector容器简单的模拟实现。

一、vector的文档介绍

1. vector 是表示可变大小数组的序列容器。
2. 就像数组一样, vector 也采用的连续存储空间来存储元素。也就是意味着可以采用下标对 vector 的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
3. 本质讲, vector 使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
4. vector 分配空间策略: vector 会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
5. 因此, vector 占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
6. 与其它动态序列容器相比( deque, list and forward_list ), vector 在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list forward_list统一的迭代器和引用更好。

 

二、模拟实现的简单准备工作

在模拟实现之前,我们需要明确我们想要实现的基本接口。

根据https://cplusplus.com/reference/vector/vector/cplusplus网站的信息我们可以知道,vector虽然相对于string简化很多,但仍然有许多的接口,既然是简单的模拟实现,我们只需要实现最基本,常用的接口就行了。

另外,由于stl库里自带系统的vector类模板,为了区分,我们可以在自己的命名空间内完成对vector模板代码的编写。

三、模拟实现

1、vector的类参数

根据vector文档可知,本质上vector功能的正常运行大多都离不开三个指针,分别是:_start,_finish,_endOfStorage。这三个指针分别指向vector的开头,实际存储数据的末尾后一位,以及开辟的空间大小末尾,通过类函数成员对这三个参数的使用,来实现了不同的功能。

于是我们首先要定义的就是vector类模板的成员参数,注意,应该在private:下定义。

namespace MyVector
{template<class T>class vector{private:T* _start;T* _finish;T* _endOfStorage;};
}

2、类的迭代器

我们通过对vector源代码的剖析,发现,vector迭代器实际上是一个指针,于是我们可以用typedef重命名来定义我们vector的迭代器,并且为了美观,我们顺便把类的参数类型也修改为迭代器iterator。

注意,迭代器有两种类型,一种是可以修改参数的,另外一种是不能修改参数的“常量”迭代器。定义我们应该重命名两个迭代器。

namespace MyVector
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterater;private:iterator _start;iterator _finish;iterator _endOfStorage;};
}

3、begin(),end()的实现

有了迭代器,自然会产生操控迭代器位置的一系列接口,begin,end应该是最常见的两个接口,分别返回容器的首位置,以及实际存储元素位置的下一位。

注意,这两个接口应该都有对应的返回常量迭代器的重载版本。

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

4、size,capacity的实现

我们用size返回此时存储元素的多少,capacity返回此时开辟的空间大小。

size_t size()
{return _finish - _start;
}size_t capacity()
{return _endOfStorage - _start;
}

5、reserve,push_back,pop_back的实现

根据算法的基本原理,push_back实际上本身就是检查空间是否满了,满了就先扩容,随后在确认空间足够的情况下 在尾部插入元素的接口。

避免频繁扩容的问题,我们使用 reserve 预分配足够的内存。

void reserve(size_t n)
{if (n > capacity()){size_t oldsize = size();iterator tmp = new T[n];if (_start){//这里不能使用memcpy,会引发浅拷贝的一些问题for (int i = 0; i < oldsize; ++i){tmp[i] = _start[i];}delete[]_start;}_strat = tmp;_finish = _start + oldsize;_endOfStorage = _start + n;}
}void push_back(const T& x)
{if (_endOfStorage == _finish){size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();reserve(newcapacity);}*finish = x;_finish++;
}void pop_back()
{assert(size() > 0);_finish--;
}

6、构造函数,赋值重载,拷贝构造函数,析构函数,swap的实现

在大部分情况下,编译器默认生成的构造函数就足够我们使用,所以我们只需要实现拷贝构造与赋值重载函数就行了。

但是我们注意到,如果我们手动写的拷贝构造与赋值重载,编译器就不会自动生成默认的拷贝函数,为了防止这个问题,我们需要用default来强制让编译器生成。

在写赋值重载函数的时候,我们面临这一个抉择,我们可以实现现代写法swap,高效的实现赋值重载,所以,我们就需要先实现一个swap函数:

vector = default;//强制生成构造函数vector(const vector<T>& v)//拷贝构造
{reserve(v.size());for (auto it : v){push_back(it);}
}vector<T>& operator= (vector<T> v)
{swap(v);return *this;
}~vector()
{delete[]_start;_start = _finish = _endOfStorage = nullptr;
}void swap(vector<T>& v)
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endOfStorage, v._endOfStorage);
}

7、各种自定义类型迭代器的构造函数实现

我们发现,vector也支持其他自定义类型的迭代器传参,来构造。

为了接受不同的类型,所以我们又要使用模板来实现一个函数模板:
 

template<class InputIterator>
vector(InputIterator first, InputIterator last)//类模板中的函数模板,支持各种自定义类型的迭代器的构造
{while (first != last){push_back(*first);++first;}
}

8、[],resize的实现

为了向数组一样支持随机访问,我们需要对[]进行重载。

由于reserve()只修改capacity大小,不修改size大小,所以我们需要实现一个resize函数,既修改capacity大小,也修改size大小。

T& operator[](size_t pos)
{assert(pos < size());return _start[pos];
}const T& operator[](size_t pos)
{assert(pos < size());return _start[pos];
}void resize(size_t n, const T& value = T())//调用T类型的默认默认构造函数
{if (n < size()){_finish = _start + n;}if (n > size()){reserve(n);while (_finish != _start + n){push_back(value);}}
}

9、erase与insert的实现

erase与insert是vector容器接口中比较难实现的部分,不仅要检查是否越界,我们还要格外注意迭代器的失效问题,当然,为了解决迭代器的失效,我们需要返回新的迭代器,并在原来的使用中让原迭代器接受返回的迭代器:

 iterator insert(iterator pos, const T& x){assert(pos <= _finish);assert(pos >= _start);if (_finish == _endOfStorage){size_t oldsize = pos - _start;size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();reserve(newcapacity);pos = _start + oldsize;}iterator i = _finish - 1;while (i >= pos){*(i + 1) = *i;i--;}*pos = x;++_finish;return  pos;}iterator erase(iterator pos){assert(pos < _finish);assert(pos >= _start);iterator tmp = pos + 1;while (tmp < _finish){*(tmp - 1) = *tmp;++tmp;}--_finish;return pos;}

四、结语

Vector 容器在 C++ 编程中扮演着重要角色,其灵活性和高效性使其成为处理动态数组的首选。通过深入理解 vector 的机制和最佳实践,可以更高效地进行编程,充分发挥 C++ 的强大功能。

希望本文对你深入了解 C++ 中的 vector 容器有所帮助!

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

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

相关文章

uniapp实现路由拦截——实战案例(二)

uniapp如何实现登录路由拦截&#xff1f; 今天再次介绍一下 uni-simple-router 插件&#xff0c;记得最初使用时&#xff0c;是在三年以前了&#xff0c;这里简单介绍通过自动读取 pages.json 作为路由表的方式&#xff0c;欢迎指教~ 文章目录 uniapp如何实现登录路由拦截&…

LangChain入门学习笔记(二)——LangChain表达式语言(LCEL)

基于LangChain框架编写大模型应用的过程就像垒积木&#xff0c;其中的积木就是Prompts&#xff0c;LLMs和各种OutputParser等。如何将这些积木组织起来&#xff0c;除了使用基本Python语法调用对应类的方法&#xff0c;一种更灵活的方法就是使用位于LangChain-Core层中的LCEL&a…

SwiftUI 6.0(Xcode 16)全新 @Entry 和 @Previewable 宏让开发妙趣横生

概览 如火如荼的 WWDC 2024 已进入第五天&#xff0c;苹果开发平台中众多海量新功能都争先恐后的喷薄欲出。 在这里就让我们从中挑两个轻松有趣的新功能展示给小伙伴们吧&#xff1a;它们分别是 全新的 Entry 和 Previewable 宏。 在本篇博文中&#xff0c;您将学到如下内容&a…

【C++ 11 新特性】lambda 表达式详解

文章目录 1. 常见 lambda 面试题&#x1f58a; 1. 常见 lambda 面试题&#x1f58a; &#x1f34e;① 如果⼀个 lambda 表达式作为参数传递给⼀个函数&#xff0c;那这个函数可以使⽤这个 lambda 表达式捕获的变量吗 ? &#x1f427; 函数本身无法直接访问到 lambda表达式捕获…

vue3实现表格的分页以及确认消息弹窗

表格的分页实例展示 效果1:表格按照每行10条数据分页,且编号也会随之分页自增 实现按照页码分页效果 第二页 展示编号根据分页自动增长 固定表格高度 这边设置了滚动条,同时表格高度实现自适应滚动条高度 template部分 表格代码 编号是按照页码条数进行循环并根据索引自增…

力扣191. 位1的个数

Problem: 191. 位1的个数 文章目录 题目描述思路复杂度Code 题目描述 思路 题目规定数值的范围不会超过32位整形数 1.定义统计个数的变量oneCount&#xff1b;由于每次与给定数字求与的变量mask初始化为1 2.for循环从0~32&#xff0c;每一次拿mask与给定数字求与运算&#xff…

【Linux应用】Linux系统的设备管理——Udev

1.udev概述 udev是 Linux2.6内核里的一个功能&#xff0c;它替代了原来的 devfs&#xff0c;成为当前 Linux 默认的设备管理工具&#xff0c;能够根据系统中的硬件设备的状态动态更新设备文件&#xff0c;包括设备文件的创建&#xff0c;删除等。 udev以守护进程的形式运行&am…

YOLOv10的使用总结

目录 YOLOv10介绍 部署和使用示例 微调训练 YOLO模型因其在计算成本和检测性能之间的平衡而在实时目标检测中很受欢迎。前几天YOLOv10也刚刚发布了。我们这篇文章就来看看YOLOv10有哪些改进&#xff0c;如何部署&#xff0c;以及微调。 概述 实时物体检测旨在以较低的延迟准…

CSS 实现电影信息卡片

CSS 实现电影信息卡片 效果展示 CSS 知识点 CSS 综合知识运用 页面整体布局 <div class"card"><div class"poster"><img src"./poster.jpg" /></div><div class"details"><img src"./avtar…

这 10 种架构师,不合格!

大家好&#xff0c;我是君哥。 架构师这个岗位是好多程序员努力的方向&#xff0c;尤其是刚毕业的时候&#xff0c;对架构师有一种崇拜感。毕竟从初级到架构要经历好几次级别飞跃。 工作时间久了&#xff0c;发现架构师这个岗位&#xff0c;其实定义非常广泛&#xff0c;根据工…

Element-ui中Table表格无法显示

Element-ui中Table表格无法显示 在使用过程中发现样式正常显示但是table就是不显示&#xff0c;研究了一段时间后&#xff0c;发现问题是项目结构的问题 当你创建vue和安装el的时候&#xff0c;一定要注意进入到正确的项目文件夹&#xff0c;如果在外面也出现一个package.jso…

【iOS】UI学习——cell的复用及自定义cell

目录 前言cell的复用手动&#xff08;非注册&#xff09;自动&#xff08;注册&#xff09; 自定义cell总结 前言 Cell复用和自定义Cell是在开发iOS应用时常见的一种优化技巧和定制需求。   Cell复用是UITableView或UICollectionView的一个重要优化机制。当用户滚动这些视图时…

图解 Twitter 架构图

写在前面 两年前&#xff0c;马老板收购了twitter&#xff0c;并且做了一系列的大动作。那么今天我们来看一下这个全球最火的软件之一的架构。 Twitter解析 开始之前&#xff0c;我先提前说明一下&#xff0c;我之前不是做搜推广的&#xff0c;所以对这些了解不是很深&…

实战项目: 负载均衡

0. 前言 这个项目使用了前后端,实现一个丐版的LeetCode刷题网站,并根据每台主机的实际情况,选择对应的主机,负载均衡的调度 0.1 所用技术与开发环境 所用技术: C STL 标准库 Boost 准标准库 ( 字符串切割 ) cpp- httplib 第三方开源网络库 ctemplate 第三方开源前端网…

【Java】已解决:java.lang.OutOfMemoryError: Java heap space

文章目录 一、问题分析背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决Java&#xff1a;java.lang.OutOfMemoryError: Java heap space 一、问题分析背景 在Java开发过程中&#xff0c;有时我们会遇到java.lang.OutOfMemoryError: Java heap spa…

容器镜像外网同步方案

目录 一、目的 二、安装nexus 1、购买香港云主机​编辑 2、安装nexus 3、启动nexus 服务 4、放行安全组 三、配置nexus 1、登录nexus管理页面 2、修改nexus密码 3、创建 Blob 存储空间(可选) 4、创建 镜像代理仓库 5、Realms配置 四、拉取镜像 1、配置docker 2、…

Floyd-Warshall

应用场景 要求出每两点之间的最短路。或判断两点之间的连通性&#xff08;两点之间是否有路径&#xff09;。 板子 代码&#xff08;必背!!!&#xff09; for(int k 1; k < n; k)for(int i 1; i < n; i)for(int j 1; j < n; j)d[i][j] min(d[i][j], d[i][k] …

C数据结构:排序

目录 冒泡排序 选择排序 堆排序 插入排序 希尔排序 快速排序 hoare版本 挖坑法 前后指针法 快速排序优化 三数取中法 小区间优化 快速排序非递归 栈版本 队列版本 归并排序 归并排序非递归 ​编辑 计数排序 各排序时间、空间、稳定汇总 冒泡排序 void Bub…

内存-VSS、RSS、PSS、USS

一、 VSS 虚拟耗用内存大小&#xff0c;是进程可以访问的所有虚拟内存的总量&#xff0c;包括进程独自占用的物理内存、和其他进程共享的内存、分配但未使用的内存。 RSS 驻留内存大小&#xff0c;是进程当前实际占用的物理内存大小&#xff0c;包括进程独自占用的物理内存、…

【Java】图的初识

文章目录 【Java】图的初识图是什么图的基本组成部分图的类型图的表示方法图的常见操作 Java中图的表示方法邻接矩阵邻接表 常见操作图的遍历深度优先搜索&#xff08;DFS&#xff09;广度优先搜索&#xff08;BFS) 结论 【Java】图的初识 图是什么 图是一种数学概念&#xf…