STL讲解——模拟实现vector
STL讲解——模拟实现vector
vector深度剖析
在STL源码中,发现vector定义的并不是 start、size、capacity,而是start、finish、end_of_storage.
这样就可以得到size()和capacity()。
size=finish-start
capacity=end_of_storage-start
扩容可能是本地扩容也可能是异地扩容,但大约是扩容1.5倍-2倍(vs是扩容1.5倍,g++是2倍)
设计一个框架
namespace tom
{template<class T>class vector{public:vector():_start(nullptr), _finish(nullptr), _endofstorage(nullptr){}//一个类模板的成员函数,又可以是一个函数模板(套娃,可以在模板类里面再上增加模板类)~vector(){if (_start){delete[] _start;_start = _finish = _endofstorage = nullptr;}}private:iterator _start;iterator _finish;iterator _endofstorage;};
}
这里设计的构造函数和拷贝构造一定要初始化列表(把所有的成员都初始称nullptr),不初始化的话就是随机地址这样没法正常赋值和析构。
析构函数一定要用delete[ ] start;不要忘记‘[ ]’
查一下std::vector的构造函数有哪些。发现有迭代器实现的,可以初始化的,还有拷贝构造。
我们也添加一下
namespace tom
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;vector():_start(nullptr), _finish(NULL), _endofstorage(NULL){}vector(size_t n, const T& val = T()){reserve(n);for (size_t i = 0; i < n; ++i){push_back(val);}}vector(int n, const T& val = T()){reserve(n);for (int i = 0; i < n; ++i){push_back(val);}}//一个类模板的成员函数,又可以是一个函数模板(套娃,可以在模板类里面再上增加模板类)template <class InputIterator>vector<T>(InputIterator first, InputIterator last):_start(nullptr), _finish(nullptr), _endofstorage(nullptr){while (first != last){push_back(*first);++first;}}void swap(vector& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);}//vector(const vector& v) 这样写也可以的vector<T>(const vector<T>& v)//回顾一下,拷贝构造和复制构造不用加<T>.:_start(nullptr)//一定要初始化,不然会给随机值,析构随机地址会报错的, _finish(nullptr), _endofstorage(nullptr){_start = new T[v.capacity()];//经典写法_finish = _start + v.size();_endofstorage = _start + v.capacity();memcpy(_start, v._start, v.size() * sizeof(T));}//vector& operator=(vector v)vector<T>& operator=(vector<T> v)//推荐写这个,好理解{swap(v);return *this;}size_t size() const{return _finish - _start;}size_t capacity() const{return _endofstorage - _start;}~vector(){if (_start){delete[] _start;_start = _finish = _endofstorage = nullptr;}}private:iterator _start;iterator _finish;iterator _endofstorage;};
}
还设计了size()与capacity()。
注意:
1、构造函数重载是如果只有一个vector(size_t n, const T& val = T()) 和 vector(InputIterator first, InputIterator last)是不行的,比如vector v1(16,5);时这该怎么办?调用哪一个呢?编译器会直接调用迭代器调用的,因为构造函数的两个参数的类型完全一致。这个时候我们要再重载一个vector(int n, const T& val = T())的就不会出现这种情况了。
2、可以在模板类里面再上增加模板类,一个类模板的成员函数,又可以是一个函数模板。
3、我们要写一个赋值构造就需要运算符重载一下 ‘=’ 不然会出现浅拷贝(自定义类型涉及到资源开辟就会析构报错)。
4、还可以用拷贝构造,复制构造可以用现代写法,因此我们也要重载一个内部 swap( )函数,可以减少三次深拷贝。
增、删、查、改
迭代器
iterator begin(){return _start;}const_iterator end() const{return _finish;}const_iterator begin() const{return _start;}iterator end(){return _finish;}
有了迭代器就方便后面的循环与增删查改了
void reserve(size_t n){if (n > capacity()){T* tmp = new T[n];int old_size=size();if (_start){memcpy(tmp, _start, sizeof(T) * size());delete[] _start;}_start = tmp;_finish = _start + old_size;_endofstorage = _start + n;}}void resize(size_t n, const T& val = T()){if (n < size()){_finish = _start + n;}else{if (n > capacity()){reserve(n);}while (_finish != _start + n){*_finish = val;++_finish;}}}void push_back(const T& x){if (_finish == _endofstorage){size_t new_capacity = capacity() == 0 ? 4 : capacity() * 2;reserve(new_capacity);}*_finish = x;_finish++;}void pop_back(){if (_start < _finish){--_finish;}}iterator insert(iterator pos, const T& x){assert(pos >= _start);assert(pos <= _finish);if (_finish==_endofstorage)//必须这么写,不然会pos迭代器失效{size_t len = pos - _start;reserve(capacity()==0?4:capacity()*2);pos = _start + len;}iterator end = _finish;//必须写在扩容后面,因为另开辟空间,会更改finishwhile (end > pos){*(end) = *(end-1); --end;}*pos = x;++_finish;return pos;}iterator erase(iterator pos)//会引发迭代器失效{assert(pos >= _start);assert(pos <= _finish);iterator start = pos+1;iterator end = _finish;while (start < end){*(start - 1) = *start;++start;}--_finish;return pos;}
增
第一个问题就是扩容问题:
设计一个resize(),在设计一个reserve()函数进行扩容,第一个可以扩容+初始化,第二个只是单纯扩容。
在这里用的是memcpy进行拷贝。扩容的时候要记得记录一下原来的size大小,如果原地扩容还好,异地扩容就会有天然之别,换了换了所有的地址,这时候start、finish、endofstorage都要更改.
resize可以复用一下reserve函数,然后再初始化赋值。
接下来就是设计push_back()、insert(pos,x)函数,先判断是否超出capacity,如果超出就进行扩容(1.5-2倍)。
void reserve(size_t n){if (n > capacity()){T* tmp = new T[n];int old_size=size();if (_start){memcpy(tmp, _start, sizeof(T) * size());delete[] _start;}_start = tmp;_finish = _start + old_size;_endofstorage = _start + n;}}
insert()函数先检查容量,容量不够扩容,再移动数据,如果空间足够直接移动数据。但是要记得一件事,有可能会出现迭代器失效的。
插入时时迭代器失效
int a[] = { 1, 2, 3, 4 };
tom::vector<int> v(a, a + sizeof(a) / sizeof(a[0]));// 删除pos位置的数据,导致pos迭代器失效auto pos = find(v.begin(), v.end(), 3);v.erase(pos);cout << *pos << endl; // 此处会导致非法访问pos = find(v.begin(), v.end(), 3);v.insert(pos, 30);cout << *pos << endl; // 此处会导致非法访问
在pos位置插入数据,导致pos迭代器失效。
insert会导致迭代器失效,是因为insert可
能会导致增容,增容后pos还指向原来的空间,而原来的空间已经释放了。
于是乎pos变成了野指针,无法在pos处插入数据。
要随时更新pos的位置,先设置一个变量len记录pos到start的距离,最后再将pos更新,返回值设为pos就好。
删
删除也会出现迭代器失效问题
int a[] = { 1, 2, 3, 4 };
tom::vector<int> v(a, a + sizeof(a) / sizeof(a[0]))
// 实现删除v中的所有偶数
// 下面的程序会崩溃掉,如果是偶数,erase导致it失效
// 对失效的迭代器进行++it,会导致程序崩溃auto it = v.begin();while (it != v.end()){if (*it % 2 == 0)v.erase(it);++it;}
以上的函数就会出现问题甚至崩溃:有两个连续的偶数时就会出现漏删,当有偶数结尾时就会出现崩溃。
it(pos每次都会++),是偶数时finish每次–,这样最后pos超过了finish就会失效,一直跑下去知道崩溃,那可不可以用pos<finish呢?还是不行,这是为了以后和list的迭代器用法统一,以为listNode没法比较地址大小。
要怎么修改呢,第一要实时更新pos,第二要控制pos++(遇到偶数不加,奇数就++);
it = v.begin();while (it != v.end()){if (*it % 2 == 0)it = v.erase(it);else++it;}
memcpy拷贝的局限
假设模拟实现的vector中的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是 浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。
修改代码
因为讲了memcpy只能进行浅拷贝,我用vector时是没什么问题的,可是vector就会出现很严重的问题,会引发析构报错。
这时我们要运算符重载一下‘=’
赋值构造就用现代写法
vector<T>& operator=(vector<T> v)//推荐写这个,好理解{swap(v);return *this;}
reserve()需要修改,扩容就一步一步拷贝。
void reserve(size_t n){if (n > capacity()){T* tmp = new T[n];if (_start){for (int i = 0; i < size(); i++){*(tmp + i) = _start[i];}delete[] _start;}_finish = tmp + size();//先定义finish,不要换顺序_start = tmp;_endofstorage = _start + n;}}
resize()复用reserve的不用改,修改代码也不能用memcpy,直接一个一个赋值拷过去。
void resize(size_t n, const T& val = T()){if (n < size()){_finish = _start + n;}else{if (n > capacity()){reserve(n);}while (_finish != _start + n){*_finish = val;++_finish;}}}
就解决了浅拷贝的问题了。不管是插入还是删除都不能用memcpy()都要用赋值拷贝一个一个传。
拷贝构造也要改造一下:
vector<T>(const vector<T>& v)//回顾一下,拷贝构造和复制构造不用加<T>.:_start(nullptr)//一定要初始化,不然会给随机值,析构随机地址会报错的, _finish(nullptr), _endofstorage(nullptr){/*_start = new T[v.capacity()];//经典写法_finish = _start + v.size();_endofstorage = _start + v.capacity();memcpy(_start, v._start, v.size() * sizeof(T));*/ //memcpy是一种浅拷贝(vector<string> 就死定了),不能用/*for (size_t i = 0; i < v.size(); i++)//用这个就可以深拷贝了{_start[i] = v._start[i];}*/vector<T> tmp(v.begin(), v.end());//现代写法swap(tmp);}
增加细节
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];}
还要设计一个const类型的。
反向迭代器就到list我们再细讲
相关文章:
STL讲解——模拟实现vector
STL讲解——模拟实现vector vector深度剖析 在STL源码中,发现vector定义的并不是 start、size、capacity,而是start、finish、end_of_storage. 这样就可以得到size()和capacity()。 sizefinish-start capacityend_of_storage-start 扩容可能是本地扩容也…...
各种经典排序算法介绍及实现源码
一,冒泡排序(Bubble Sort) 排序算法是程序员必须了解和熟悉的一类算法,排序算法有很多种,基础的如:冒泡、插入、选择、快速、归并、计数、基数和桶排序等。 冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求,如果不…...
历史大讲堂:这是真·图形化 苹果系统历史回顾(上)
众所周知,米国有个非常牛掰的公司叫苹果,想必大家对这个logo不陌生吧。 目前已发布的苹果产品有iPhone、iPad、iPod等等,简直花样繁多,而且各种功能很好用,我的手机就是一部苹果iPhone X。 等一下,似乎扯远…...
今天女神节,用python画个贺卡送给母亲吧
今天女神节,你给女神妈妈准备了什么祝福呢?如果还没有,那么画个贺卡送给她吧,在你眼里,她是一个什么样的人呢? 是"可爱",“温柔”,“美丽”,“漂亮”…...
【编程基础之Python】11、Python中的表达式
【编程基础之Python】11、Python中的表达式Python中的表达式表达式与运算符算术表达式赋值表达式比较表达式逻辑表达式位运算表达式总结Python中的表达式 在Python中,表达式是由操作数、运算符和函数调用等组成的语法结构,可以进行各种数学运算、逻辑判…...
华为OD机试真题Python实现【乱序整数序列两数之和绝对值最小】真题+解题思路+代码(20222023)
乱序整数序列两数之和绝对值最小 题目 给定一个随机的整数数组(可能存在正整数和负整数)nums, 请你在该数组中找出两个数,其和的绝对值(|nums[x]+nums[y]|)为最小值 并返回这两个数(按从小到大返回)以及绝对值。 每种输入只会对应一个答案。但是,数组中同一个元素不能使用两…...
字符串转换整数 (atoi)(python)
链接: https://leetcode.cn/problems/string-to-integer-atoi 题目描述: 请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C 中的 atoi 函数)。 函数 myAtoi(string s) 的算法…...
洛谷 P1115 最大子段和
题目链接:P1115 最大子段和 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题目描述 给出一个长度为 n 的序列 a,选出其中连续且非空的一段使得这段和最大。 输入格式 第一行是一个整数,表示序列的长度 n。 第二行有 n 个整数ÿ…...
【Linux】-- 权限和Shell运行原理
目录 Shell的运行原理 用户切换 su - / su sudo 权限 chmod chown chgrp 八进制方法修改文件属性 目录权限 粘滞位 umask 自定义默认权限 Shell的运行原理 广义上,Linux发行版 Linux内核 外壳程序 Linux 从广义上来理解它是一个操作系统 而从狭义上…...
C++各类设计模式及实现详解
软件领域中的设计模式为开发人员提供了一种使用专家设计经验的有效途径。设计模式中运用了面向对象编程语言的重要特性:封装、继承、多态,真正领悟设计模式的精髓是可能一个漫长的过程,需要大量实践经验的积累。最近看设计模式的书࿰…...
【Linux】进程理解与学习(Ⅰ)
环境:centos7.6,腾讯云服务器Linux文章都放在了专栏:【Linux】欢迎支持订阅🌹相关文章推荐:【Linux】冯.诺依曼体系结构与操作系统进程概念什么是进程?进程是什么?我们打开任务管理器可以看到有…...
认识代码之前,请先认识你自己 |《编程人生》
这是我的湛庐课程《给技术人的职场突围课》 (链接) 的一部分。 这篇文章也是 IT 女神征文活动 的一部分。 《编程人生》是一本优秀程序员的采访集,里面记录了15位世界级编程大师的故事。 我在 发刊词 里面说过,在这个书单课里&am…...
react学习笔记-5:react路由
react旧版本路由 旧版本的路由是按照组件的方式来写的 编写router/index.tsx文件 import App from "../App" import Home from "../views/Home" import About from "../views/About" import { BrowserRouter,Routes,Route } from "react…...
[Python图像处理] 使用高通滤波器实现同态滤波
使用高通滤波器实现同态滤波同态滤波基础实现同态滤波相关链接同态滤波基础 同态滤波是一种去除图像中乘性噪声的技术,常用于校正图像中的不均匀照明。根据图像形成的光照反射模型,图像 f(x,y)f(x,y)f(x,y) 可以由以下两个分量表征: 入射到…...
PyTorch深度学习:60分钟入门
PyTorch深度学习:60分钟入门 本教程的目的: 更高层级地理解PyTorch的Tensor库以及神经网络。训练一个小的神经网络来对图像进行分类。 本教程以您拥有一定的numpy基础的前提下展开 Note: 务必确认您已经安装了 torch 和 torchvision 两个包。 这是一个基于Pytho…...
C语言指针常见问题汇总
我们在学C语言时,指针是我们最头疼的问题之一,针对C语言指针,博主根据自己的实际学到的知识以及开发经验,总结了以下使用C语言指针时常见问题。 1、指针做函数参数 学习函数的时候,讲了函数的参数都是值拷贝…...
Coremail邮件系统全新上线存档邮箱功能
邮箱积累邮件太多,搜索起来又慢又麻烦! 我的重要邮件忘记下载丢失了!14天自动删除太难了! 有没有可能重要邮件自动存档,解救一下“遗忘星”人? 在我们日常工作中,邮件是最经常使用的办公工具之一…...
Python绘图
1.二维绘图 a. 一维数据集 用 Numpy ndarray 作为数据传入 ply 1. import numpy as np import matplotlib as mpl import matplotlib.pyplot as pltnp.random.seed(1000) y np.random.standard_normal(10) print "y %s"% y x range(len(y)) print "x%s&q…...
【独家】华为OD机试 - 第K个最小码值的字母(C 语言解题)
最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试…...
整数反转(python)
题目链接: https://leetcode.cn/problems/reverse-integer/ 题目描述: 给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。 如果反转后整数超过 32 位的有符号整数的范围 [−231,231−1][−2^{31}, 2^{31} − 1][−231,231…...
【数据结构】二叉树与堆
文章目录1.树概念及结构1.1树的相关概念1.2树的结构2.二叉树概念及结构2.1相关概念2.2特殊的二叉树2.3二叉树的性质2.4二叉树的存储结构3.二叉树的顺序结构及实现3.1二叉树的顺序结构3.2堆的概念3.3堆的实现Heap.hHeap.c3.4堆的应用3.4.1 堆排序3.4.2 TOP-KOJ题最小K个数4.二叉…...
Git图解-常用命令操作-可视化
目录 一、前言 二、初始化仓库 2.1 设置用户名与邮箱 2.2 初始化仓库 三、添加文件 四、查看文件状态 五、查看提交日志 六、查看差异 七、版本回退 八、删除文件 九、分支管理 9.1 创建分支 9.2 切换分支 9.3 查看分支 9.4 合并分支 十、文件冲突 十一、转视…...
C语言-基础了解-20-typedef
typedef 一、typedef C 语言提供了 typedef 关键字,您可以使用它来为类型取一个新的名字。下面的实例为单字节数字定义了一个术语 BYTE: typedef unsigned char BYTE; 在这个类型定义之后,标识符 BYTE 可作为类型 unsigned char 的缩写&…...
Ubuntu系统升级16.04升级18.04
一、需求说明 作为Linux发行版中的后起之秀,Ubuntu 在短短几年时间里便迅速成长为从Linux初学者到实验室用计算机/服务器都适合使用的发行版,目前官网最新版本是22.04。Ubuntu16.04是2016年4月发行的版本,于2019年4月停止更新维护。很多软件支…...
CM6.3.2启用Kerberos(附问题解决)
基础准备支持JCE的jdk重新安装JCE的jdk(已正确配置跳过)删除/usr/java/下面的jdk,然后通过CM->管理->安全->安装Java无限制...重新安装后,配置Java(可选)主机->主机配置->搜java->Java主目录 配置路径主机->所有主机->设置->高级:Java配置Kerberos安…...
QML 动画(组合动画)
在QML中,可以把多个动画组合成一个单一的动画。 组合动画的类型: ParallelAnimation 动画同时进行(并行)SequentialAnimation 动画按照顺序执行(顺序执行)注意:将动画分组为“顺序动画”或“…...
【PHP代码注入】PHP代码注入漏洞
漏洞原理RCE为两种漏洞的缩写,分别为Remote Command/Code Execute,远程命令/代码执行PHP代码注入也叫PHP代码执行(Code Execute)(Web方面),是指应用程序过滤不严,用户可以通过HTTP请求将代码注入到应用中执行。代码注入(代码执行)…...
Python 常用语句同C/C++、Java的不同
文章目录前言1. 数字 int2. 字符 string3. 列表 List4. 元组 tuple5. 字典 dictionary6. 集合 set7. 值类型变量与引用类型变量8. if elif else9. >、<、>、<、、!10. while11. for前言 本篇为本人前段时间的一个简单汇总,这里可能并不齐全,…...
一把火烧掉了苹果摆脱中国制造的幻想,印度制造难担重任
这几年苹果不断推动印度制造,希望摆脱对中国制造的依赖,然而近期苹果在印度的一家代工厂发生大火却证明了苹果的这一计划遭受重大打击,印度制造根本就无法中国制造。一、印度制造屡屡发生幺蛾子苹果推动印度制造已有多年了,然而印…...
常用的 JavaScript 数组 API
以下是一些常用的 JavaScript 数组 API 的代码示例: 1、push() push(): 在数组末尾添加一个或多个元素,返回新的数组长度 const arr [1, 2, 3]; const newLength arr.push(4, 5); console.log(arr); // [1, 2, 3, 4, 5] console.log(newLength); //…...
要加强县门户网站的建设管理办法/黑帽seo排名技术
程序员在为某个应用系统编写接入其它应用系统的程序代码的时候,常常为了用户认证大伤脑筋:1) 让最终用户频繁登录? 似乎是一个让用户很难接受的解决方案。2) 在代码中内置用户名和密码? 代码需要随用户和密码的变化经常维护,同时在很多场合…...
北京seo优化费用/宁波seo关键词排名优化
驾驭共享变量 问题:如何实现一个对象实例化完成后立即运行后台线程do work? 不能这样: 构造函数中启动线程,并start()因为 breaks the class invariant. 不能call method 在初始化完成之前,A…...
黄骅市网站建设/广州seo推广服务
对于常见的音频播放,使用XAudio2足够了。 时间是把杀猪刀,滑稽的是我成了猪 早在Windows Vista中,M$推出了新的音频架构UAA,其中的CoreAudio接替了DSound、WaveXxx、MediaFundation,通过Core Audio APIs,Windows的音频…...
做预算查价格的网站是哪个/深圳百度首页优化
终于决定买空间了,不过鉴于国内建站太过于和谐了,所以只能考虑在国外购买。godaddy(中文是"去老爹"?囧~)算是国内用户用得最多的,而且做得确实也挺不错的,就买了一个域名和一个共享空间。买好后发现域名可用…...
自考都到哪个网站找题做/如何优化搜索引擎的搜索功能
在判断电脑系统前,我们先要知道版本号,通过函数调用返回的信息,就可以知道是什么系统。下面这图是官方提供的关于Windows版本对应的号码我们可以通过系统Windows的API中GetVersionEx这个函数获取win8.1下的版本。(这里我只稍微解释下…...
网站怎么做流程/网络seo软件
初始时数123显示在计算机屏幕上。每一分钟计算机都给屏幕上的数加上102,计算机专家Bill可任意改变计算机屏幕上所显示数的各位数字的前后顺序,试问Bill能否永远不让屏幕上出现有4位数字的数? ----------------------------------------------…...