【C++初阶】vector的模拟实现
大家好我是沐曦希💕
文章目录
- 一、前言
- 二、无参构造&析构
- 三、基础接口
- 1.empty和clear
- 2.size和capacity
- 3.[]和iterator
- 四、reserve和resize
- 五、尾插尾删
- 六、其他构造
- 1.迭代器区间构造
- 2.拷贝构造
- 七、memcpy问题
- 八、完整代码
一、前言
在模拟实现容器时候,我们需要的不是造一个更好的轮子,而是在了解原理更好理解和运用容器。
那么通过查看源码,抽出vector容器主题框架:
template <class T, class Alloc = alloc>
class vector {typedef T value_type;typedef value_type* iterator;typedef const value_type* const_iterator;
protected:iterator start;iterator finish;iterator end_of_storage;
}
本质上与T*a,size_t size,size_t capacity是类似的:

对于size = _finish - _start

对于capacity = _endofstorage-_start
二、无参构造&析构
- 无参构造
初始化值为空。
vector():_start(nullptr),_finish(nullptr),_endofstarge(nullptr)
{}
- 析构
~vector()
{delete[] _start;_start = _finish = _endofstarge = nullptr;
}
三、基础接口
1.empty和clear
- empty
bool empty()
{return _finish == _start;
}
- clear
void clear()
{_finish = _start; //这里可不能置为nullptr
}
2.size和capacity
- size
int size() const
{return _finish - _start;
}
-capacity
int capacity() const
{return _endofstarge - _start;
}
加const是让const类对象和非const类对象都能调用
3.[]和iterator
这里提供const和非const两个版本,加const是只可读,不能更改,不加const是可读可写。
- []
T& operator[](const size_t pos)
{assert(pos < size());return _start[pos];
}
const T& operator[](const size_t pos)
{assert(pos < size())return _start[pos];
}
- iterator
同理普通迭代器和const迭代器版本,同理,范围for循环此时也是可以实现的:
typedef T* iterator;
typedef const T* const_iterator;
iterator begin()
{return _start;
}
iterator end()
{return _finish;
}
const_iterator beign()
{return _start;
}
const_iterator end()
{return _finish;
}
四、reserve和resize
- reserve
reserve最大问题是深拷贝,开辟新空间进行赋值的时候如果直接使用memcpy是浅拷贝。
void reserve(size_t n)
{if (n > capacity()){int Oldsize = size();//size需要保存,方便更改_finsihT* tmp = new T[n];//为空不需要拷贝if (_start){for (size_t i = 0; i < Oldsize; ++i){tmp[i] = _start[i];}}_start = tmp;_finish = _start + Oldsize;_endofstarge = _start + n;tmp = nullptr;}
}
- size()要保存
因为size = _finish - _start,_start已经改变了。而_finish没有改变,那么size就是一个任意数了,不再是原来的size了,就需要在开始时候用Oldsize进行存储size
- 使用memcpy问题
memcpy拷贝数据是按字节来拷贝的属于浅拷贝,对于需要在堆开辟空间的自定义类型存在问题,多次释放同一块空间将导致程序崩溃
vector<vector<int>> vv; vector<int> v(4, 1); //复用push_back尾插 vv.push_back(v); vv.push_back(v); vv.push_back(v); vv.push_back(v); //需要扩容成2倍 vv.push_back(v); for (size_t i = 0; i < vv.size(); i++) {for (size_t j = 0; j < vv[i].size(); j++){cout << vv[i][j] << " ";}cout << endl; }
- resize
用n个数据初始化vector,那么就要用n和size进行比对,分情况讨论:
1、当n大于当前的size时,将size扩大到n,扩大的数据为val,若val未给出,则默认为容器所存储类型的默认构造函数所构造出来的值。
2、当n小于当前的size时,将size缩小到n。
根据resize函数的规则,进入函数我们可以先判断所给n是否小于容器当前的size,若小于,则通过改变_finish的指向,直接将容器的size缩小到n即可,否则先判断该容器是否需要增容,然后再将扩大的数据赋值为val即可。
void resize(size_t n, const T& value = T())
{if (n < size()){_finish = _start + n;}reserve(n); // reserve对于n<capacity是不会做任何事情的for (size_t i = 0; i < n - size(); ++i){_finish[i] = value;}_finish = _start + n;
}
resize的参数初始化值为T类型的构造,这里可不能直接初始化为0,要是T是自定义类型呢?是vector呢?所以这里如果T是vector的化调用的就是vector的构造函数。另外,这里还需要注意的一点是:构造vector的时候是匿名对象,匿名对象具有常性,不可修改所以要加上const修饰。对于内置类型比如int,都是有构造函数的
五、尾插尾删
void push_back(const T& value)
{if (_finish == _endofstarge){size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newCapacity);}*_finish = value;++_finish;
}
void pop_back()
{assert(_start < _finish);if (_finish > _start){--_finish;}
}
六、其他构造
1.迭代器区间构造
vector(InputIterator first, InputIterator last):_start(nullptr),_finish(nullptr),_endofstarge(nullptr)
{while (first != last){push_back(*first);//int不能解引用++first;}
}
类模板的成员函数可以是函数模板,使之可以是任意类型的迭代器区间,包括了自身的迭代器区间构造
另外,初始化列表全部初始化为nullptr,没有初始化就是随机值,出现野指针
2.拷贝构造
初始化列表全都要初始化为nullptr,否则就是随机值
//写法一
vector(const vector<T>& v):_start(nullptr), _finish(nullptr), _endofstarge(nullptr)
{reserve(v.capacity());for (const auto& e : v){push_back(e);}
}
//写法二
void swap(vector<T>& v)
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstarge, v._endofstarge);
}
vector(const vector<T>& v):_start(nullptr), _finish(nullptr), _endofstarge(nullptr)
{vector<T> tmp(v.begin(), v._end());swap(tmp);
}
- 赋值重载
可以复用拷贝构造
//缺陷是不能自己拷贝自己
vector& operator=(vector<T> v)
{swap(v);return *this;
}
这种写法就是有一个小问题:如果是自己拷贝自己呢?加个判断?没用,因为此时已经传值传参过来了,加个判断没啥意义了。但是这个问题不大,我们允许存在,平时自己也很少自己赋值自己。
另外,这里是传值调用,有人会说了:传引用也可以啊,此时如果是引用的话,v2赋值给v1,v1不是v2的拷贝,直接把v2换成了v1,v1换给了v2,v2本身已经发生变化了,这不是赋值了。
七、memcpy问题
如果拷贝的是内置类型的元素,memcpy即高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝,指向同一块空间,假设我们仍然在reserve接口中使用memcpy进行拷贝:
int main()
{bite::vector<bite::string> v;v.push_back("1111");v.push_back("2222");v.push_back("3333");return 0;
}
问题分析:
- memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中
- 如果拷贝的是内置类型的元素,memcpy既高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。
结论:如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。
八、完整代码
- vector.h
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
namespace lj
{template<class T>class vector{public:// Vector的迭代器是一个原生指针typedef T* iterator;typedef const T* const_iterator;iterator begin(){return _start;}iterator end(){return _finish;}const_iterator cbegin() const{return _start;}const_iterator cend() const{return _finish;}iterator rbegin(){return _finish;}iterator rend(){return _start;}const_iterator rbegin() const{return _finish;}const_iterator rend() const{return _start;}// construct and destroyvector():_start(nullptr), _finish(nullptr), _endofstorage(nullptr){}vector(int n, const T& value = T()):_start(nullptr), _finish(nullptr), _endofstorage(nullptr){reserve(n);for (size_t i = 0; i < n; ++i){push_back(value);}}template<class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);first++;}}vector(const vector<T>& v){int* tmp = new T[v.capacity()];for (size_t i = 0; i < v.size(); ++i){tmp[i] = v[i];}_start = tmp;_finish = _start + v.size();_endofstorage = _start + v.capacity;}vector<T>& operator=(vector<T> v){vector tmp(v);_start = _finish = _endofstorage = nullptr;swap(_start, tmp._start);swap(_finish, tmp._finish);swap(_endofstorage, tmp._endofstorage);}~vector(){delete[] _start;_start = _finish = _endofstorage = nullptr;}// capacitysize_t capacity() const{return _endofstorage - _start;}size_t size() const{return _finish - _start;}void reserve(size_t n){if (n > capacity()){int Oldsize = size();T* tmp = new T[n];if (_start){for (size_t i = 0; i < size(); ++i){tmp[i] = _start[i];}}_start = tmp;_finish = _start + Oldsize;_endofstorage = _start + n;tmp = nullptr;}}void resize(size_t n, const T& value = T()){if (n <= size()){_finish = _start + n;}reserve(n);for (size_t i = 0; i < n - size(); ++i){_finish[i] = value;}_finish = _start + n;}///access///T& operator[](size_t pos){assert(pos < size());return _start[pos];}const T& operator[](size_t pos)const{assert(pos < size());return _start[pos];}///modify/void push_back(const T& x){if (_finish == _endofstorage){int newCapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newCapacity);}*_finish = x;++_finish;}void pop_back(){assert(_start < _finish);if (_finish > _start){--_finish;}}void swap(vector<T>& v){vector tmp;tmp._start = _start;_start = v._start;v._start = tmp._start;tmp._finish = _finish;_finish = v._finish;v._finish = tmp._finish;tmp._endofstorage = _endofstorage;_endofstorage = v._endofstorage;v._endofstorage = tmp._endofstorage;tmp._start = tmp._finish = tmp._endofstorage = nullptr;}iterator insert(iterator pos, const T& x){assert(pos >= _start);assert(pos <= _finish);size_t Olddel = pos - _start;if (_finish == _endofstorage){size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newCapacity);}pos = _start + Olddel;auto it = _finish;while (it > pos){*it = *(it - 1);it--;}*pos = x;++_finish;return pos;}iterator erase(iterator pos){assert(pos >= _start);assert(pos < _finish);auto it = pos;while (it < _finish){(*it) = *(it + 1);it++;}--_finish;return pos;}private:iterator _start; // 指向数据块的开始iterator _finish; // 指向有效数据的尾iterator _endofstorage; // 指向存储容量的尾};void test_vector1(){vector<int> v1(10, 2);for (auto e : v1){cout << e << " ";}cout << endl;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);for (auto e : v1){cout << e << " ";}cout << endl;v1.reserve(20);cout << v1.size() << endl;cout << v1.capacity() << endl;vector<int> v2;for (auto e : v2){cout << e << " ";}cout << endl;cout << v2.size() << endl;cout << v2.capacity() << endl;}void test_vector2(){vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);for (auto e : v1){cout << e << " ";}cout << endl;vector<int> v2(v1.rend(), v1.rbegin());for (auto e : v2){cout << e << " ";}cout << endl;cout << v2.size() << endl;cout << v2.capacity() << endl;v2.resize(10, 1);for (auto e : v2){cout << e << " ";}cout << endl;cout << v2.size() << endl;cout << v2.capacity() << endl;v2.resize(5);for (auto e : v2){cout << e << " ";}cout << endl;cout << v2.size() << endl;cout << v2.capacity() << endl;v2.resize(15, 2);vector<int>::iterator it = v2.begin();while (it < v2.end()){cout << (*it) << " ";++it;}cout << endl;for (size_t i = 0; i < v2.size(); ++i){cout << v2[i] << " ";}cout << endl;v2.pop_back();for (auto e : v2){cout << e << " ";}cout << endl;cout << v2.size() << endl;cout << v2.capacity() << endl;v2.pop_back();v2.pop_back();v2.pop_back();v2.pop_back();v2.pop_back();v2.pop_back();v2.pop_back();v2.pop_back();v2.pop_back();for (auto e : v2){cout << e << " ";}cout << endl;cout << v2.size() << endl;cout << v2.capacity() << endl;v2.swap(v1);cout << "------------------ v1 --------------------" << endl;for (auto e : v1){cout << e << " ";}cout << endl;cout << v1.size() << endl;cout << v1.capacity() << endl;cout << "------------------ v2 --------------------" << endl;for (auto e : v2){cout << e << " ";}cout << endl;cout << v2.size() << endl;cout << v2.capacity() << endl;v1.resize(15);cout << v1.size() << endl;cout << v1.capacity() << endl;v1.insert(v1.begin(), 1);v1.insert(v1.end(), 1);for (auto e : v1){cout << e << " ";}cout << endl;cout << v1.size() << endl;cout << v1.capacity() << endl;}
}
相关文章:
【C++初阶】vector的模拟实现
大家好我是沐曦希💕 文章目录一、前言二、无参构造&析构三、基础接口1.empty和clear2.size和capacity3.[]和iterator四、reserve和resize五、尾插尾删六、其他构造1.迭代器区间构造2.拷贝构造七、memcpy问题八、完整代码一、前言 在模拟实现容器时候࿰…...
微信小程序、小游戏的流量主一般可以赚多少钱?
本篇文章主要科普小程序、小游戏流量主一般赚钱的实际情况,通过在下长期运营的经验汇总而成。 日期:2023年2月26日 作者:任聪聪 小程序、小程序满1000用户后即可开通流量主,但实际上很多人并没有传说中的那种日赚几千的流量收入的…...
jni-Demo-基于linux(c++ java)
跑一个jni 的最简单的Demo需要提前准备 VsCode 编译器、win10下,vscode中集成linux操作系统、c编译器(gcc、g),java编译器(jdk1.8)参考:https://mangocool.com/1653030123842.htmlJniDemo类&…...
指针的进阶——(1)
本次讲解重点: 1、字符指针 2、数组指针 3、指针数组 4、数组传参和指针传参 5、函数指针 关于指针这个知识点的主题,我们在前面已经初级阶段已经对指针有了大致的理解和应用了。我们知道了指针的概念: 1、指针就是地址,但口…...
电商平台的促销活动如何抵御大流量的ddos攻击
每一次活动大促带来的迅猛流量,对技术人而言都是一次严峻考验。如果在活动期间遭受黑产恶意 DDoS 攻击,无疑是雪上加霜。电商的特性是业务常态下通常不会遭受大流量 DDoS 攻击,且对延迟敏感,因此只需要在活动期间按需使用 DDoS 防…...
代码随想录-48-104. 二叉树的最大深度
目录前言题目1.层序迭代思路2. 本题思路分析:3. 算法实现4. pop函数的算法复杂度5. 算法坑点前言 在本科毕设结束后,我开始刷卡哥的“代码随想录”,每天一节。自己的总结笔记均会放在“算法刷题-代码随想录”该专栏下。 代码随想录此题链接 …...
【Vue3源码】第六章 computed的实现
【Vue3源码】第六章 computed的实现 上一章节我们实现了 ref 及其它配套的isRef、unRef 和 proxyRefs API。这一章开始实现computed计算属性。 认识computed 接受一个 getter 函数,返回一个只读的响应式 ref 对象。该 ref 通过 .value 暴露 getter 函数的返回值。…...
Java基础之注解
3.注解 3.1概述【理解】 概述 对我们的程序进行标注和解释 注解和注释的区别 注释: 给程序员看的注解: 给编译器看的 使用注解进行配置配置的优势 代码更加简洁,方便 3.2自定义注解【理解】 格式 public interface 注解名称 { public 属性类型 属性名() default 默认值…...
三、线性表
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 前言 提示:这里可以添加本文要记录的大概内容: 自学JAVA数据结构笔记,跟学视频为:黑马程序员Java数据结构与java算法全套教程…...
C++统计方形
统计方形 内存限制:256 MB 时间限制:1 S 题目描述 有一个n*m方格的棋盘,求其方格包含多少正方形、长方形(此处长方形不包含正方形) 输入格式 输入存在多组测试数据。每组测试数据输入两个整数n,m,数字不超…...
Tina_Linux配网开发指南
OpenRemoved_Tina_Linux_配网_开发指南 1 概述 1.1 编写目的 介绍Allwinner 平台上基于wifimanager-v2.0 的WiFi 配网方式,包括softap(WiFi ap 模式热点配网),soundwave(声波配网),BLE(蓝牙低功耗配网)。 1.2 适用范围 • allwinner 软件平台tina v5.0 版本及以…...
高频面试题|RabbitMQ如何防止消息的重复消费?
一. 前言最近有很多小伙伴开始找工作,在面试时,面试官经常会问我们这样一个题目:RabbitMQ如何防止重复消费?有很多小伙伴这个时候都在想,消息怎么还会重复消费呢???.......所以他们在面试后就跑来问壹哥,针对这个比…...
黑盒测试用例设计方法-边界值分析法
目录 一、边界值定义 二、边界值的考虑 三、边界值的优化 四、边界值的设计用例的步骤 五、案例 六、边界值的类型 一、边界值定义 边界值分析法就是对输入或输出的边界值进行测试的一种黑盒测试方法。通常边界值分析法是作为对等价类划分法的补充,这种情况下…...
项目风险管理中不可忽视的5个关键点
1、风险意识非常重要 项目经理必须要有风险意识,并不是项目计划做好就万事大吉,而是需要对项目风险进行预判,时刻保持风险意识,及时发现和处理项目风险。 项目风险管理关键:风险意识 2、建立组织风险资产库 寻…...
Linux->进程地址空间
目录 前言: 1. 程序地址空间回顾 2. 进程空间是什么 3. 进程地址空间与内存 4. 进程地址空间和内存的关联 5. 为什么要有进程地址空间 前言: 我们在平时学习的过程当中总是听到栈、堆、代码段等等储存空间,但是这些东西到底是什么&…...
【奶奶看了也不会】AI绘画 Mac安装stable-diffusion-webui绘制AI妹子保姆级教程
1.作品图 2.准备工作 目前网上能搜到的stable-diffusion-webui的安装教程都是Window和Mac M1芯片的,而对于因特尔芯片的文章少之又少,这就导致我们还在用老Intel 芯片的Mac本,看着别人生成美女图片只能眼馋。所以小卷这周末折腾了一天&#…...
基于stm32电梯管理系统设计
基于stm32电梯管理系统设计这里记录一下以前自己做的嵌入式课程设计,报告中的图片和文字太多了,全部一个一个把搬过来太麻烦了,需要完整文本和代码自行q我963160156,也可在微信公众号 *高级嵌入式软件* 里回复 *电梯* 查看完整版文章摘要关键…...
Spring中的FactoryBean 和 BeanFactory、BeanPostProcessor 和BeanFactoryPostProcessor解析
文章目录FactoryBean 和 BeanFactory后置处理器BeanPostProcessor 和 BeanFactoryPostProcessorBeanPostProcessorBeanFactoryPostProcessorFactoryBean 和 BeanFactory BeanFactory接⼝是容器的顶级接⼝,定义了容器的⼀些基础⾏为,负责⽣产和管理Bean的…...
【C++从入门到放弃】类和对象(上)
🧑💻作者: 情话0.0 📝专栏:《C从入门到放弃》 👦个人简介:一名双非编程菜鸟,在这里分享自己的编程学习笔记,欢迎大家的指正与点赞,谢谢! 类和对…...
什么牌子的蓝牙耳机便宜好用?四款高品质蓝牙耳机推荐
随着时代的发展,蓝牙耳机的使用频率越来越高,不少人外出时除了带手机外,蓝牙耳机也成为了外出必备的数码产品之一。现在的蓝牙耳机品牌众多,什么牌子的蓝牙耳机便宜好用?下面,我来给大家推荐四款高品质的蓝…...
HTML 列表、表格、表单
1 列表标签 作用:布局内容排列整齐的区域 列表分类:无序列表、有序列表、定义列表。 例如: 1.1 无序列表 标签:ul 嵌套 li,ul是无序列表,li是列表条目。 注意事项: ul 标签里面只能包裹 li…...
家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...
【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...
基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...
C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...
【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...
初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...
【分享】推荐一些办公小工具
1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由:大部分的转换软件需要收费,要么功能不齐全,而开会员又用不了几次浪费钱,借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...




