c++STL-vector的模拟实现
c++STL-vector的模拟实现
- vector的模拟实现
- 基本信息
- 构造函数
- 析构函数
- 返回容量(capacity)
- 返回元素个数(size)
- 扩容(reserve和resize)
- 访问([])
- 迭代器(**iterator**)
- 尾插(push_back)
- 插入(insert)
- 删除(erase)
- 拷贝构造
- 交换(swap)
- 赋值重载(operator=)
- 参考程序
vector的模拟实现
建议先看c++STL-string的模拟实现-CSDN博客。
string
的模拟实现用的是顺序表的模板。
这里的模拟实现模仿某个STL版本的vector
。
基本信息
库中的vector
用的是定位new
和内存池。这里的模拟实现不涉及内存池的概念,所有信息采用1个数组表示,用原始的堆模拟内存池。
template<class T>
class vector {
public:typedef T* iterator;//typedef会受到访问限定符影响typedef const T* const_iterator;
private:iterator _start;iterator _finish;iterator _endofstorage;
};
vector
的很多函数和string
类似。
模板类在类中可以不用写全类型。比如这里:
template<class T>
class vector {...vector f(vector& x){}
};
原本应该是vector<T>
,但这里不推荐去掉。
构造函数
vector
的拷贝构造需要用户实现,否则默认拷贝构造只会给浅拷贝。
这里选择三个构造函数接口进行模拟实现:
//无参构造函数
explicit vector (const allocator_type& alloc = allocator_type());//用n个val去初始化
vector (size_type n, const value_type& val,const allocator_type& alloc = allocator_type());//迭代器初始化
template <class InputIterator>vector (InputIterator first, InputIterator last,const allocator_type& alloc = allocator_type());
首先将全体指针初始化为nullptr
,然后根据构造对象用的形参填充数据。也就是说这里给出至少2个构造函数,只给指针初始化为nullptr
的,和用指定数据填充的。
其他构造函数根据vector - C++ Reference自行实现即可。
在类里面能写模板函数。构造函数用函数模板是为了能用不同类型的迭代器初始化。但如果两个的参数类型相同,可能会自动选择最匹配的
比如:
template<T>
class vector{
public:template<InputIterator>vector(InputIterator first,InputIterator second){...}vector(size_t n, T value=T()){...}
}void f(){vector<int>a(10,0);
}
我们肯定希望vector<int>a(10,0)
走的是vector(size_t n, T value=T())
,但编译器却为它匹配了vector(InputIterator first,InputIterator second)
。
所以针对这种情况,只能加一个vector(int n, const T& value=T())
的函数重载。
析构函数
直接delete[] _start;
即可。不用free
是因为T
可能是类。
返回容量(capacity)
返回_endofstorage-_start
即可。
返回元素个数(size)
返回_finish-_start
即可。
扩容(reserve和resize)
扩容时用new
,不用realloc
,原因是T
可能是类,需要调用构造函数。扩容时如果初始容量为0,需要给个非0值作为初始容量。
既然不用realloc
,那等于是每次扩容都是异地扩容,需要特别注意三个迭代器指向的空间,防止旧空间的迭代器和新空间的迭代器进行计算。
而且原来的数据可能不为空,所以需要将原来的数据深拷贝给新开的空间。不要用memcpy
拷贝数据,如果自己选的数据类型是自定义类型,会导致深层次的浅拷贝问题导致错误初始化。
即扩容时因为是异地扩容,所以需要将之前的数据拷贝到新数组,用
memcpy
的话只会将原数组存储的地址拷贝到新数组而不会调用构造函数,实际它们管理的还是原来的空间,当释放就空间时,新数组存储的地址就无效。所以需要用循环来深拷贝。
除非使用计数变量,或知道数据的位置。
reserve
:
判断是否有扩容的必要,即请求的容量大于初始容量时才进行扩容。
- 对象刚创建,经过构造函数初始化列表后指针全体为
nullptr
。这里给1个初始空间,实际上给不给都无所谓。 - 对象并非刚创建。则
_start
非空,需要将原来的数据拷贝给现在的数据,同时容量按2倍增速扩大。
resize
:
resize
是在reserve
的基础上将数据初始化为指定内容,在初始化时允许改变_finish
的值。
访问([])
返回具体下标即可。
需要加两个,一个可读可写,一个只读。
迭代器(iterator)
初学时用指针实现即可。
begin()
和end()
要给两个版本:只读(加const
修饰)和可读可写。
尾插(push_back)
- 插入前先检查是否需要扩容。扩容后尾指针赋值并自增1即可。
- 或先实现插入(
insert
),调用insert
间接实现即可。
和string
不同,因为用的是迭代器而不是size_t
,指针几乎不可能是0,所以头插可以不用特别注意。
但vector
的形参用的迭代器,一旦发生扩容,形参表示的迭代器会失效。所以需要记录偏移量,方便调整。
迭代器实参不能用引用,因为形参可能是函数。
插入(insert)
单个元素的插入:
- 插入前先判断插入的位置是否在两个指针之间,在的话再尝试扩容,否则越界访问。
- 之后挪动数据,将数据插入后,尾指针自增1。
多个元素的插入:
- 插入前先判断插入的位置是否在两个指针之间,在的话再尝试扩容,否则越界访问。
- 之后挪动合适数量的数据,将数据插入后,调整尾指针。
但无论是哪个,都要返回迭代器,防止迭代器失效的问题。
原容器没有指定下标插入,这里为了方便使用了下标标记。
删除(erase)
//删除指定位置的迭代器表示的
iterator erase (iterator position);
iterator erase (iterator first, iterator last);
需要先检查数组中是否还有元素。
后通过挪动数据覆盖即可。每次挪动完成后将尾指针自减。
迭代器实参不能用引用,因为形参可能是函数。
而且erase
同样有可能使迭代器失效。
拷贝构造
形参为引用。
当调用拷贝构造时,编译器生成一个新的临时对象,用形参的引用对象的数据赋值给新的临时对象。
因此拷贝构造函数需要进行深拷贝。
交换(swap)
因为是一个用三个指针指向空间的不同位置来实现,所以交换两个对象,交换它们的三个指针即可。
赋值重载(operator=)
//c++98自带的用vector对象进行拷贝构造
vector& operator= (const vector& x);
赋值重载的形参不用引用,使传参过程中会调用拷贝构造来生成形参的临时对象。
之后形参的临时对象和*this
,两个对象交换(swap
)成员指针即可,临时对象变成了*this
,并以*this
的面目活下去,而原*this
变成了临时对象调用析构函数。
参考程序
#pragma once
#include<cassert>namespace mystd {template<class T>void swap(T& a, T& b) {T tmp = a;a = b;b = tmp;}template<class T>class vector {public:typedef T* iterator;typedef const T* const_iterator;//构造函数vector<T>():_start(nullptr), _finish(nullptr), _endofstorage(nullptr){}vector<T>(size_t n, const T& val = T()): _start(nullptr), _finish(nullptr), _endofstorage(nullptr) {reserve(n);_finish = _start + n;for (size_t i = 0; i < n; i++) {_start[i] = val;}}//析构函数~vector<T>() {delete[]_start;_start = _finish = _endofstorage = nullptr;}//获取容量信息size_t capacity() const {return _endofstorage - _start;}//获取数量size_t size() const {return _finish - _start;}//扩容void reserve(size_t n) {while (n > capacity()) {size_t sz = size();size_t new_c = capacity();new_c = new_c == 0 ? n : new_c * 2;T* tmp = new T[new_c]{ T() };if (_start != nullptr) {for (size_t i = 0; i < sz; i++) {tmp[i] = _start[i];}}_start = tmp;_finish = tmp + sz;_endofstorage = tmp + new_c;}}void resize(size_t n, const T& val) {reserve(n);for (size_t i = size(); i < n; i++)_start[i] = val;_finish = _start + n;}//访问T& operator[](size_t pos) {assert(pos < size());return _start[pos];}const T& operator[](size_t pos) const {assert(pos < size());return _start[pos];}//迭代器iterator begin() {return _start;}iterator end() {return _finish;}const_iterator begin() const {return _start;}const_iterator end() const {return _finish;}//插入//单个元素iterator insert(iterator pos, const T& val) {assert(pos >= begin());assert(pos <= end());size_t sz = pos - begin();//计算偏移量reserve(size() + 1);pos = begin() + sz;auto End = end();while (End > pos) {*End = *(End - 1);--End;}*End = val;_finish++;return pos;}//尾插void push_back(const T& val) {insert(end(), val);}//删除//删除指定迭代器iterator erase(iterator pos) {assert(pos >= begin());assert(pos < end());auto tmp = pos;while (tmp < _finish) {*tmp = *(tmp + 1);++tmp;}--_finish;return pos;}//删除区域的迭代器//左闭右开iterator erase(iterator Begin, iterator End) {assert(Begin >= begin());assert(End <= end());assert(Begin <= End);size_t sz = size_t(End - Begin);iterator tmp = Begin;while (tmp + sz < end()) {*tmp = *(tmp + sz);++tmp;}_finish = tmp;return Begin;}//尾删void pop_back() {erase(end() - 1);}//拷贝构造vector<T>(const vector<T>& a):_start(nullptr), _finish(nullptr), _endofstorage(nullptr) {reserve(capacity());for (auto& x : a)push_back(x);}//交换void swap(vector<T>& b) {vector<T>& a = *this;mystd::swap(a._start, b._start);mystd::swap(a._finish, b._finish);mystd::swap(a._endofstorage, b._endofstorage);}//赋值重载vector<T>& operator=(vector<T> tmp) {swap(tmp);return *this;}private:iterator _start;iterator _finish;iterator _endofstorage;};
}
相关文章:

c++STL-vector的模拟实现
cSTL-vector的模拟实现 vector的模拟实现基本信息构造函数析构函数返回容量(capacity)返回元素个数(size)扩容(reserve和resize)访问([])迭代器(**iterator**)…...
C++核心编程解析:模板、容器与异常处理全指南
文章目录 一、模板1.1 定义1.2 作用1.3 函数模版1.3.1 格式 1.4 类模版1.4.1 格式1.4.2 代码示例1.4.3 特性 二、容器2.1 概念2.2 容器特性2.3 分类2.4 向量vector2.4.1 特性2.4.2 初始化与操作2.4.3 插入删除 2.5 迭代器2.6 列表(list)2.6.1 遍历方式2.…...

在 Elasticsearch 中连接两个索引
作者:来自 Elastic Kofi Bartlett 解释如何使用 terms query 和 enrich processor 来连接 Elasticsearch 中的两个索引。 更多有关连接两个索引的查询,请参阅文章 “Elastic:开发者上手指南” 中的 “丰富数据及 lookup” 章节。 Elasticsea…...
Linux常用命令详解(下):打包压缩、文本编辑与查找命令
一、打包压缩命令 在Linux系统中,打包与压缩是文件管理的核心操作之一。不同的工具适用于不同场景,以下是最常用的命令详解: 1. tar命令 作用:对文件进行打包、解包、压缩、解压。 语法: tar [选项] [压缩包名] […...

使用 Watt toolkit 加速 git clone
一、前言 Watt toolkit 工具是我经常用于加速 GitHub 网页和 Steam 游戏商店访问的工具,最近想加速 git clone,发现可以使用 Watt toolkit 工具的代理实现。 二、查看端口 我这里以 Ubuntu 为例,首先是需要将加速模式设置为 System࿱…...

应急响应靶机——WhereIS?
用户名及密码:zgsf/zgsf 下载资源还有个解题.exe: 1、攻击者的两个ip地址 2、flag1和flag2 3、后门程序进程名称 4、攻击者的提权方式(输入程序名称即可) 之前的命令: 1、攻击者的两个ip地址 先获得root权限,查看一下历史命令记录&#x…...

Docke容器下JAVA系统时间与Linux服务器时间不一致问题解决办法
本篇文章主要讲解,通过docker部署jar包运行环境后出现java系统内时间与服务器、个人电脑真实时间不一致的问题原因及解决办法。 作者:任聪聪 日期:2025年5月12日 问题现象: 说明:与实际时间不符,同时与服务…...

【MCP】其他MCP服务((GitHub)
【MCP】其他MCP服务((GitHub) 1、其他MCP服务(GitHub) MCP广场:https://www.modelscope.cn/mcp 1、其他MCP服务(GitHub) 打开MCP广场 找到github服务 访问github生成令牌 先…...
SQL:MySQL函数:日期函数(Date Functions)
目录 时间是数据的一种类型 🧰 MySQL 常用时间函数大全 🟦 1. 获取当前时间/日期 🟦 2. 日期运算(加减) 🟦 3. 时间差计算 🟦 4. 格式化日期 🟦 5. 提取时间部分 Ƿ…...

内存 -- Linux内核内存分配机制
内存可以怎么用? kmalloc:内核最常用,用于频繁使用的小内存申请 alloc_pages:以页框为单位申请,物理内存连续 vmalloc:虚拟地址连续的内存块,物理地址不连线 dma_alloc_coherent:常…...

关于读写锁的一些理解
同一线程的两种情况: 读读: public static void main(String[] args) throws InterruptedException {ReentrantReadWriteLock lock new ReentrantReadWriteLock();Lock readLock lock.readLock();Lock writeLock lock.writeLock();readLock.lock();S…...

C++修炼:模板进阶
Hello大家好!很高兴我们又见面啦!给生活添点passion,开始今天的编程之路! 我的博客:<但凡. 我的专栏:《编程之路》、《数据结构与算法之美》、《题海拾贝》、《C修炼之路》 欢迎点赞,关注&am…...

android-ndk开发(10): use of undeclared identifier ‘pthread_getname_np‘
1. 报错描述 使用 pthread 获取线程名字, 用到 pthread_getname_np 函数。 交叉编译到 Android NDK 时链接报错 test_pthread.cpp:19:5: error: use of undeclared identifier pthread_getname_np19 | pthread_getname_np(thread_id, thread_name, sizeof(thr…...

UI自动化测试框架:PO 模式+数据驱动
🍅 点击文末小卡片,免费获取软件测试全套资料,资料在手,涨薪更快 1. PO 设计模式简介 什么是 PO 模式? PO(PageObject)设计模式将某个页面的所有元素对象定位和对元素对象的操作封装成…...
大小端的判断方法
大小端(Endianness) 是计算机存储多字节数据(如整数、浮点数)时的两种不同方式,决定了字节在内存中的排列顺序。 1. 大端(Big-Endian) 高位字节存储在低地址,低位字节存储在高地址。…...

Java笔记4
第一章 static关键字 2.1 概述 以前我们定义过如下类: public class Student {// 成员变量public String name;public char sex; // 男 女public int age;// 无参数构造方法public Student() {}// 有参数构造方法public Student(String a) {} }我们已经知道面向…...
DAY22kaggle泰坦尼克号
参考了机器学习实战进阶:泰坦尼克号乘客获救预测_天池notebook-阿里云天池 数据处理省略 直接上模型 5.12.1 一些数据的正则化 这里我们将Age和fare进行正则化: from sklearn import preprocessing scale_age_fare preprocessing.StandardScaler().…...

2025年渗透测试面试题总结-渗透测试红队面试八(题目+回答)
网络安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 渗透测试红队面试八 二百一十一、常见中间件解析漏洞利用方式 二百一十二、MySQL用户密码存储与加密 …...

MiniMind:3块钱成本 + 2小时!训练自己的0.02B的大模型。minimind源码解读、MOE架构
大家好,我是此林。 目录 1. 前言 2. minimind模型源码解读 1. MiniMind Config部分 1.1. 基础参数 1.2. MOE配置 2. MiniMind Model 部分 2.1. MiniMindForCausalLM: 用于语言建模任务 2.2. 主干模型 MiniMindModel 2.3. MiniMindBlock: 模型的基本构建块…...

如何进行前端性能测试?--性能标准
如何进行前端性能测试?–性能标准 前端性能测试指标: 首次加载阶段 场景:用户首次访问网页,在页面还未完全呈现各种内容和功能时的体验。重要指标及原因 首次内容绘制(FCP - First Contentful Paint)…...

通信网络编程——JAVA
1.计算机网络 IP 定义与作用 :IP 地址是在网络中用于标识设备的数字标签,它允许网络中的设备之间相互定位和通信。每一个设备在特定网络环境下都有一个唯一的 IP 地址,以此来确定其在网络中的位置。 分类 :常见的 IP 地址分为 I…...

Off-Policy策略演员评论家算法SAC详解:python从零实现
引言 软演员评论家(SAC)是一种最先进的Off-Policy策略演员评论家算法,专为连续动作空间设计。它在 DDPG、TD3 的基础上进行了显著改进,并引入了最大熵强化学习的原则。其目标是学习一种策略,不仅最大化预期累积奖励&a…...

热门CPS联盟小程序聚合平台与CPA推广系统开发搭建:助力流量变现与用户增长
一、行业趋势:CPS与CPA模式成流量变现核心 在移动互联网流量红利见顶的背景下,CPS(按销售付费)和CPA(按行为付费)模式因其精准的投放效果和可控的成本,成为企业拉新与用户增长的核心工具。 CPS…...
(自用)Java学习-5.9(Thymeleaf,自动装配,自定义启动器 )
一、Thymeleaf 模板技术 片段定义与复用 <!-- 声明片段 --> <div th:fragment"b1">...</div> <!-- 插入片段 --> <div th:insert"~{bottom :: b1}"></div> <!-- 追加内容 --> <div th:replace"~{botto…...

Linux系统管理与编程15:vscode与Linux连接进行shell开发
兰生幽谷,不为莫服而不芳; 君子行义,不为莫知而止休。 【1】打开vscode 【2】点击左下角连接图标 【3】输入远程连接 选择合适的操作系统 输入密码,就进入Linux环境的shell编程了。 在vscode下面粘贴拷贝更方便。比如 然后在v…...

RabbitMQ概念详解
什么是消息队列? 消息队列是一种在应用程序之间传递消息的技术。它提供了一种异步通信模式,允许应用程序在不同的时间处理消 息。消息队列通常用于解耦应用程序,以便它们可以独立地扩展和修改。在消息队列中,消息发送者将消息发送…...
Excel-to-JSON插件专业版功能详解:让Excel数据转换更灵活
前言 在数据处理和系统集成过程中,Excel和JSON格式的转换是一个常见需求。Excel-to-JSON插件提供了一套强大的专业版功能,能够满足各种复杂的数据转换场景。本文将详细介绍这些专业版功能的应用场景和使用方法。 订阅说明 在介绍具体功能之前…...

linux基础操作5------(shell)
一.前言 本文来介绍一下linux的shell,除了最后的那个快捷键,其他的还是做一个了解就行了。 Shell: 蛋壳的意思,是linux中比较重要的一个概念,所有的命令其实都称之为shell命令。 看图解:shell就是内核的一…...

BUUCTF 大流量分析(三) 1
BUUCTF:https://buuoj.cn/challenges 文章目录 题目描述:密文:解题思路:flag: 相关阅读 CTF Wiki BUUCTF | 大流量分析 (一)(二)(三) 题目描述: …...
feign.RequestInterceptor 简介-笔记
1. feign.RequestInterceptor 简介 Feign 是一个声明式 Web 服务客户端,用于简化 HTTP 请求的编写与管理。feign.RequestInterceptor 是 Feign 提供的一个接口,用于在请求发出之前对其进行拦截和修改。这在微服务架构中非常有用,比如在请求中…...