C++_vector简单源码剖析:vector模拟实现

文章目录

    • 🚀1.迭代器
    • 🚀2.构造函数与析构函数
      • ⚡️2.1 默认构造函数vector()
      • ⚡️2.2 vector(int n, const T& value = T())
        • ⚡️内置类型也有构造函数
      • ⚡️2.3 赋值重载operator=
      • ⚡️2.4 通用迭代器拷贝
      • ⚡️2.5 vector(initializer_list<T> il)
      • ⚡️2.6 拷贝构造vector(const vector<T>& v)
      • ⚡️2.6 析构函数~vector()
    • 🚀3.内存相关
    • 🚀4.获取
    • 🚀5.修改
      • ⚡️5.1 insert插入
      • ⚡️5.2 erase删除
      • ⚡️5.2 push_back尾插
      • ⚡️5.3 pop_back尾删

大家好!本文会模拟一个基本的vector类,帮助我们更好的理解vector的内置函数的实现与规则。

先在.h文件声明每个需要实现的函数,需要实现的成员:

namespace bit
{template<class T>class vector{public://1.迭代器// Vector的迭代器是一个原生指针typedef T* iterator;typedef const T* const_iterator;iterator begin();iterator end();const_iterator begin() const ;const_iterator end() const;// 2.构造函数与析构函数vector();vector(int n, const T& value = T());vector<T>& operator= (vector<T> v);template<class InputIterator>vector(InputIterator first, InputIterator last);vector(initializer_list<T> il);vector(const vector<T>& v);~vector();// 3.内存相关size_t size() const;size_t capacity() const;void reserve(size_t n);void resize(size_t n, const T& value = T());//4.获取T& operator[](size_t pos);const T& operator[](size_t pos)const;//5.修改void push_back(const T& x);void pop_back();void swap(vector<T>& v);iterator insert(iterator pos, const T& x);iterator erase(Iterator pos);private:iterator _start; // 指向数据块的开始iterator _finish; // 指向有效数据的尾iterator _endOfStorage; // 指向存储容量的尾};
}

备注:private有三个成员变量,都是迭代器,_start 指向数据块的开始 ,_finish指向有效数据的尾 ,_endOfStorage指向存储容量的尾

接下来一步一步的剖析实现:

🚀1.迭代器

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

备注: begin()返回首元素的指针,end()返回尾元素下一个位置的指针,当然也要多实现一个const的版本,以适应const string类型

🚀2.构造函数与析构函数

// 2.构造函数与析构函数vector();vector(int n, const T& value = T());vector<T>& operator= (vector<T> v);template<class InputIterator>vector(InputIterator first, InputIterator last);vector(initializer_list<T> il);vector(const vector<T>& v);~vector();

⚡️2.1 默认构造函数vector()

vector() =default;

备注:vector 不需要特别的默认构造,用编译器生成的就行,我们知道,编译器在我们写了其他的构造函数时是不会生成默认构造的,所以该代码的意思是使编译器强制生成默认构造

⚡️2.2 vector(int n, const T& value = T())

vector(int num, const T& temp = T())
{reserve(num);for (int i = 0; i < num; i++){push_back(temp);}
};

备注: reserve是扩容函数,push_back是尾插函数,后面会实现。

⚡️内置类型也有构造函数

⚡️关于(重要)const T& temp = T()

在C++中,为了满足模板的需要,为内置类型也添加了默认构造函数

什么意思呢? 就是关于内置类型也可以这样初始化:

int i = 0;
int j(1);
int k = int();
int x = int(2);

是不是很像类的初始化的形式 ?没错,在C++中,内置类型可以像类一样传参初始化,当然就如原本的内置类型一样,不传参就是随机值,传了的那个形参就是参数的值。

这样做有什么好处呢?我们回到本函数实现的代码,如果T = int , 则:

vector(int num, const int& temp = int())
{reserve(num);for (int i = 0; i < num; i++){push_back(temp);}
};

const int& temp = int()由于int也可以用类的方式给缺省值,被赋予了一个int类型的匿名临时对象,cosnt又为这个临时对象赋予常性,就可以起别名,所以这样的语法就可以通过了。

最后,const T& temp = T()的参数形式可以满足T为自定义类型,也可以满足内置类型

⚡️2.3 赋值重载operator=

vector<T>& operator= (vector<T> v){swap(v);return (*this);
};

备注:swap是一个交换private内三个成员的函数,后面会实现。

⚡️2.4 通用迭代器拷贝

template<class InputIterator>
vector(InputIterator first, InputIterator last){reserve(last- first);while(first != last){push_back(*first);first++;}
}

备注:

  1. 这里使用的是函数模板,由编译器推断迭代器类型,生成对应的函数。
  2. 该函数的意义是支持通过其他类型的迭代器来拷贝内容,例子如下:
int main()
{string s1("123456");vector<int> test1(s1.begin(), s1.end());for (auto e : test1){cout << e<<" ";}
}

输出:49 50 51 52 53 54

这里就做到通过string的迭代器拷贝整个s1到test1

⚡️2.5 vector(initializer_list il)

vector(initializer_list<T> il){reserve(il.size());for (auto e : il){push_back(e);}
}

备注:

  1. 先简单介绍一下 initializer_list 是什么, initializer_list是一种特殊的标准库类型,用于在函数参数或对象构造函数中初始化列表初始化的一种方式。它允许你以简洁的方式向函数传递一组值,或者在对象的构造函数中初始化一组值,可以让函数接受不定数量的参数,而在对象构造函数中使用它可以方便地初始化成员变量。
auto test = {1,2,3,4,5};
//这里编译器推断的类型是 initializer_list
  1. 借助 initializer_list 我们就可以传入{1,2,3,4}这种形式的数组进行初始化
int main()
{vector<int> test1 = {1,2,3,4};for (auto e : test1){cout << e<<" ";}
}

输出:1 2 3 4

⚡️2.6 拷贝构造vector(const vector& v)

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

备注:无。

⚡️2.6 析构函数~vector()

~vector(){delete[] _start;_start = nullptr;_end_of_storage = nullptr;_finish = nullptr;
}

备注:只用释放 头迭代器_start 就行了。

🚀3.内存相关

// 3.内存相关size_t size() const{_finish - _start;}size_t capacity() const{_end_of_storage - _start;}void reserve(size_t n){if( n  > capacity()){size_t len = size();iterator tmp = new iterator[n+1];if(_start){for(int i = 0 ; i < len ; i++){tmp[i] = (*this)[i];}delete[] _start;}_start = tmp;_finish = tmp+len;	_endOfStorage = tmp + n ;		}}void resize(size_t n, const T& value = T()){if(n <= size()){_finish = _start +n;return;}if(n > capacity()){reserve(n);}iterator it = _finish;_finish = _start +n;while(it !=_finish ){*it = value;it++;}}

备注:

  1. size() 返回 vector的数据个数, capacity() 返回 vector的数据个数的容量,迭代器相减(本质是指针相减)是迭代器指向位置的距离
  2. reserve()修改内存,本质上是new了一段新空间,将内容拷贝到新空间,再释放旧空间。
  3. 关于const T& value = T()的意思上文有讲,在2.2。

🚀4.获取

//4.获取T& operator[](size_t pos){return *(_start + x);}const T& operator[](size_t pos)const{return *(_start + x);}

备注:该函数使vector模板可以像数组一样访问元素,当然也要重载一个const版本。

🚀5.修改

//5.修改iterator insert(iterator pos, const T& x);iterator erase(Iterator pos);void push_back(const T& x);void pop_back();void swap(vector<T>& v);

⚡️5.1 insert插入

iterator insert(iterator pos, T x)
{int len = pos - _start;//记录pos的下标位置if (size() == capcitity())//判断扩容{size_t new_capcitity = capcitity() == 0 ? 4 : capcitity() * 2;reserve(new_capcitity);}iterator end = _finish - 1;//记录最后一个元素pos = _start + len;//重置pos,因为扩容后pos可能会失效while (end >= pos)//从最后一个数据开始,一个一个往后搬{*(end + 1) = *end;end--;}*pos = x;_finish++;return pos; //返回pos位置的指针
};

备注:

  1. 关于重置pos,因为从上文的扩容函数可知,扩容的本质是开辟新空间,所以原来的pos可能不再指向新空间的pos位置了,则导致迭代器失效(迭代器指向错误的位置), 则需要重置。
  2. 同时在使用过insert函数的迭代器也是存在迭代器失效的问题,所以,建议失效后迭代器不要访问。除非赋值更新一下这个失效的迭代器,严格一点的编译器会直接报错。
  3. 为了解决迭代器失效的问题,insert以返回值的形式返回重新生效的迭代器。

例子:

vector<int> test1 = {1,2,3,4};
int cnt = 2;
vector<int>::iterator pos = test1.begin()+1;
//错误写法,pos会失效
while (cnt--)
{test1.insert(pos, 0);
}
//实在要用的正确写法
while (cnt--)
{pos= test1.insert(pos, 0);
}

⚡️5.2 erase删除

iterator erase(iterator pos)
{if (_start){iterator head = pos;while (head < _finish){*(head) = *(head + 1);head++;}_finish--;}return pos;
};

备注:传入erase的迭代器也不推荐再使用,不同的平台的情况可能不同,可能会出现迭代器失效的问题。

⚡️5.2 push_back尾插

void push_back(const T& x)
{insert(_finish, x);
}

备注:复用insert。

⚡️5.3 pop_back尾删

void pop_back()
{erase(_finish - 1);
}

备注:复用erase。






本文就到这里,感谢你看到这里!
我知道一些人看文章喜欢静静看,不评论,但是他会点赞,这样的人,帅气低调有内涵,美丽大方很优雅,明人不说暗话,要你手上的一个点赞!

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

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

相关文章

软件测试面试题(三)

一&#xff1a;软件测试的步骤是什么&#xff1f; 测试过程按4个步骤进行&#xff0c;即单元测试&#xff0c;集成测试&#xff0c;确认测试和系统测试及发版测试。 开始是单元测试&#xff0c;集中对用源代码实现的每一个程序单元进行测试&#xff0c;检查各个程序模块是否正…

vue3-hooks

命名规则&#xff1a;use--->谁相关.js/ts 举例&#xff1a; import {reactive} from vue import axios from axiosexport default function(){let dogList reactive({https://images.dog.ceo/breeds/pembroke/n02113023_4373.jpg })//方法 async function getDog(){try…

计算机网络基础 - 计算机网络和因特网(1)

计算机网络基础 计算机网络和因特网什么是 Internet?具体构造的的角度服务角度网络结构 网络边缘网络核心电路交换分组交换概述排队时延和分组丢失转发表和路由选择协议按照有无网络层的连接 分组交换 VS 电路交换 接入网DSL 因特网接入电缆因特网接入光纤到户 FTTH无线接入网…

MySQL数据源不停机迁移到AWS RDS MySQL

就在近期&#xff0c;谷歌云捅了个大篓子&#xff0c;误删除了一家投资公司&#xff08;Unisuper&#xff0c;管理着800亿美元基金&#xff09;在谷歌云所有地域的所有数据&#xff0c;删得相当彻底&#xff0c;连备份数据都没给人家留一个。 Unisuper 是一家澳大利亚退休金基…

C++-逻辑语句

if语句 基本格式&#xff1a; 只有判断结果为true&#xff0c;才会执行后续{}内的代码 if (要执行的判断&#xff0c;结果需是bool型) {判断结果true&#xff0c;才会执行的代码; }if (条件判断) { 如果判断结果为true&#xff0c;会执行的代码; }else{如果判断结果为false…

OS多核多线程锁记录笔记

自旋锁作用 自旋锁的是为了保护两个核上的公共资源&#xff0c;也就是全局变量&#xff0c;只有在一方也就是一个核抢到了自选锁&#xff0c;才能对公共资源进行操作修改&#xff0c;当然还有其他形似的锁如互斥锁&#xff0c;这里不比较两者的区别&#xff0c;以前没有深入的去…

常用shell命令总结(Linux命令)

当前目录 . 上一级目录 … 根目录&#xff0c;或者是目录拼接符 / 管道符&#xff08;左侧输出作为右侧输入&#xff09; | 上一个命令的返回码 $? 或 || 且 && cat 查看文档 cat XX.txt 加权限 chmod x 文件 chmod 777 文件 改变文件的所有者 chown newowne…

外卖小程序开发指南:从源码开始构建高效的外卖平台

今天&#xff0c;笔者将为您详细讲解如何从源码开始构建一个高效的外卖小程序&#xff0c;帮助您快速进入这一蓬勃发展的市场。 一、需求分析与设计 需求分析包括&#xff1a; 1.用户需求 2.市场需求 3.技术需求 二、前端开发 以下是开发步骤&#xff1a; -使用微信开发…

封装了一个iOS中间放大的collectionView layout

效果图如下所示 原理&#xff1a;就是首先确定一个放大和缩小系数和原大小对应的基准位置&#xff0c;然后根据距离每个布局属性到视图中心的距离和基准点到中心的距离的差距/基准点到中心的距离&#xff0c; 计算出每个布局属性的缩放系数 下面是代码 // // LBHorizontalCe…

英语学习笔记22——Give me/him/her/us/them a .... Which one?

Give me/him/her/us/them a … Which one? 给我/他/她/我们/他们一个…… 哪一个&#xff1f; 词汇 Vocabulary empty a. 空的&#xff0c;啥也没有的    v. 倒空 例句&#xff1a;这个盒子是空的。    This box is empty.    这是个空盒子。    This is an emp…

学习笔记——STM32F103V3版本——HC-05模块控制数码管

一.硬件 1.HC-05模块 2.数码管 3.连接硬件 二.在keil5中的代码 main.c代码&#xff1a; #include "stm32f10x.h" #include "buletooth.h" #include "led.h" #include "sys.h" #include "usart.h" #include "delay.…

HTTP content-type MIME 类型(IANA 媒体类型)

Content-Type(MediaType)&#xff0c;即是Internet Media Type&#xff0c;互联网媒体类型&#xff0c;也叫做MIME类型。在互联网中有成百上千中不同的数据类型&#xff0c;HTTP在传输数据对象时会为他们打上称为MIME的数据格式标签&#xff0c;用于区分数据类型。最初MIME是用…

数据与结构--AVL树

目录 AVL树的概念 AVL树的性质 AVL树结点的定义 AVL树的插入 AVL树的旋转 左单旋 右单旋 左右双旋 右左单旋 AVL树的验证 AVL树的查找 AVL树的修改 AVL树的删除 AVL树的概念 二叉搜索树虽然可以提高我们查找数据的效率&#xff0c;但如果插入二叉搜索树的数据是…

ubuntu 安装 kvm 启动虚拟机

1. 基础环境设置 #更新环境 apt update apt upgrade#配置网卡 cat >/etc/netplan/br.yml<<EOF network:ethernets:eth2: {}bridges:br0:interfaces:- eth2addresses:- 192.192.1.213/24gateway4: 192.192.1.1nameservers:addresses:- 8.8.8.8- 8.8.4.4 EOF #安装组件…

LFSR线性反馈移位寄存器及Verilog实现

一、LFSR LFSR线性反馈移位寄存器&#xff0c;通常由移位寄存器和异或门组成&#xff0c;主要用于产生伪随机序列等。 线性反馈的含义是各个寄存器的输出通过一个反馈函数连接到第一级触发器的输入&#xff1b;LFSR中的寄存器的个数被称为LFSR的级数。 LFSR分为两类&#xff…

开源的在线JSON数据可视化编辑器jsoncrack本地部署与远程访问

文章目录 1. 在Linux上使用Docker安装JSONCrack2. 安装Cpolar内网穿透工具3. 配置JSON Crack界面公网地址4. 远程访问 JSONCrack 界面5. 固定 JSONCrack公网地址 JSON Crack 是一款免费的开源数据可视化应用程序&#xff0c;能够将 JSON、YAML、XML、CSV 等数据格式可视化为交互…

Iphone自动化指令每隔固定天数打开闹钟关闭闹钟

1.业务需求&#xff1a;小z每隔五天有一个夜班&#xff0c;然后下午会有三个小时的休息时间&#xff0c;如果闹钟不响就会错过交班日期&#xff0c;但是如果设置闹钟&#xff0c;iPhone的闹钟只能设定固定循环日期闹钟&#xff0c;或者一次的闹钟&#xff0c;导致要么忘记设闹钟…

每日一题22:Pandas:字符串函数之患某种疾病的患者

一、每日一题 患者信息表&#xff1a; Patients ----------------------- | Column Name | Type | ----------------------- | patient_id | int | | patient_name | varchar | | conditions | varchar | ----------------------- 在 SQL 中&#xff0c;patient…

【C语言】指针运算

前言 前面在“走进指针世界”中我已经讲解过指针相关的很多前置知识&#xff0c;其实还有一个很重要的部分就是指针的运算。这篇博客&#xff0c;就让我们一起了解一下指针的运算吧&#xff01; 指针作为变量&#xff0c;是可以进行算术运算的&#xff0c;只不过情况会和整型…

LLM-Llama在 MAC M1上体验Llama.cpp和通义千问Qwen 1.5-7B

Llama.cpp的主要目标是在各种硬件上&#xff08;本地和云端&#xff09;实现LLM推断&#xff0c;同时保持最小的设置和最先进的性能。 纯C/C实现&#xff0c;没有任何依赖关系Apple芯片是一级的支持对象 - 通过ARM NEON、Accelerate和Metal框架进行优化对x86架构的AVX、AVX2和…