C++动态数组的简易实现
C++动态数组的简易实现
在啃过 STL源码剖析的vector这一章后,我准备自己写一个动态数组。因为在STL中的vector为了防止频繁的发生,添加元素->配置空间->移动元素->释放原空间,于是采用类似缓冲池的技术,减少空间配置的次数。这个技术就是 size + capacity 。下面举个例子,假如我们正常使用动态数组怎么改变
//使用动态数组的步骤int* iptr = new int[10];//插入元素 1
//1、创建新空间
int* tmp = new int[11];
//2、将旧元素移到新空间
for(int i=0;i<10;++i)tmp[i] = iptr[i];
//3、插入元素赋值
tmp[10]=1;//4、释放旧元素
delete[] iptr;//5、改变指向
iptr = tmp;
可以看到我们繁琐的步骤,导致每插入一个元素就要进行一次内存配置操作,非常的麻烦,且进行一次malloc代价很大。这个效果在数组越大的时候体现的越明显。这个时候STL就引入了容量(预备空间)来作为缓冲区,其实也就是一个预备空间。只有当我们预备空间满了才会进行元素拷贝和移动。
ps:STL中 vector 有空间配置器,底层使用的是realloc、memcpy、placement-new ,所以较之于我自己实现的版本效率更高,同时可以减少元素移动的频率,只有realloc调用失败,才开始移动元素
动态数组实现如下:
#define INITSIZE 8//要求数据元素是遵循严格弱序的
/*设计时,把错误处理留给调用者,在设计算法是不考虑外部的调用是否合理(如边界问题,由调用者处理)
*/
template<typename T>
class Vector
{
public:Vector();Vector(size_t, const T& tmp);~Vector();Vector<T>& operator=(const Vector& data);
public:size_t size() { return _size; }; //返回sizebool empty() { return !_size; };void clear(); //清空//增删改查T& operator[](size_t index); //改void push_back(T& data); //增void push_back(T&& data);size_t earse(size_t indexl,size_t indexr); //数组范围删除size_t insert(size_t index,const T& data); //元素随机插入void pop(); //删除尾部元素T& search(T& data); //查:返回第一个匹配元素,方案二:区间范围查找实现全区间范围查找private:void expand(); //扩张size_t _size;size_t _capacity;T* _array; //底层数组};
容量扩张
缓冲如何实现?就是通过容量,默认容量为8,容量就是底层数组实际大小,当添加元素导致容量满,就调用expand进行构造新空间,元素移动操作等
template<typename T>
void Vector<T>::expand()
{//1、创建新数组,新数组容量为原来的两倍,即数组下次扩张更加遥远。可以减少移动次数,但同时增加空间上的维护成本//解决方法有:实现一个缩容方法,一旦size和capacity相差过多,则进行缩容,如果底层是realloc实现效率更高T* tmp = new T[_capacity = _capacity << 1];for (int i = 0; i < _size; ++i) //把原数组中所有元素拷贝到新数组{tmp[i] = _array[i];}delete[]_array;_array = tmp;
}//提供一种实现,这个接口,只需要放在删除操作前
template<typename T>
void shink()
{if(_size-1 <= _capacity>>2)return;//否则缩容T* tmp = new T[_capacity = _capacity >> 1]; for (int i = 0; i < _size; ++i) //把原数组中所有元素拷贝到新数组{tmp[i] = _array[i];}delete[]_array;_array = tmp;
}
插入操作
insert接口实现如下:
template<typename T>
size_t Vector<T>::insert(size_t index, const T& data)
{if (_size+1 >=_capacity) expand(); //如果容量不足for (int i = _size; i < index; --i){ _array[i] = _array[i - 1];}_array[index] = data; //改变目标元素++_size;return index; //成功返回下标
}
删除
删除操作也是一个亮点,这也是size + capacity 结构带来的好处。被删除元素完全不需要管理,直接覆盖掉,然后改变 size 即可。
template<typename T>
size_t Vector<T>::earse(size_t indexl, size_t indexr)
{if (indexl > indexr) return -1; //左下标小于右下标if (indexl >= _size && indexr >= _size) return -1; //左、右下标不能等于数组大小//接下来就是不越界的情况//删除元素,采用吧后续元素前移的方法:时间复杂度为 o(n)、空间复杂度为 o(1);int n = 1 + indexr - indexl;while (indexl+n <_size){_array[indexl] = _array[indexl + n];}_size -= n;return indexl; //返回删除元素第一个位置下标
}
ps:秉承oop思想,类似这种接口,底层都要有一个基础实现,比如push_bach、pop_back都是insert和earse的一个特化实现。所以想要仔细实现一个这样的接口一定要有一个泛化的接口,在简单转发一下成为多个特化的接口。
重载[ ]运算符
重载下标运算符,是为了匹配使用习惯
template<typename T>
T& Vector<T>::operator[](size_t index)
{return _array[index]; //直接返回
}
查找
这里就是应该实现一个 search(int start, int end, const T& data ); 这样一个接口,可以参考C++中的algorithm库,基本上都是基于一个范围操作接口,进行特化成不同接口。其实都是语法糖罢了。
//对于无序数组,只能遍历了。但是有序数组就有很多方法
template<typename T>
T& Vector<T>::search(T& data)
{for (int i = 0; i < _size; ++i)if (data == _array[i]) return _array[i];
}//可以提供一个快排的实现:但是我没有添加进入,因为我觉得动态数组不该内置这个接口
//排序也应该基于泛化的思想,实现一个区间排序
template<typename T>
void quicksort(T* array, size_t low, size_t high){if (low >= high) return;size_t i = low;size_t j = high;T key = array[i];while (i < j){while (i < j && array[j] >= key) --j;if (i < j) array[i] = array[j];while (i < j && array[i] <= key) ++i;if (i < j) array[j] = array[i];}array[i] = key;quicksort(array, low, i - 1);quicksort(array, i + 1, high);}
STL中的vector
和我写的区别最大的地方就在于:1、使用了空间配置器;2、使用了迭代器。
STL中元素范围是以3个迭代器:
使用迭代器可以支持许多算法,find、find_if、search、sort等。但是最大的作用还是对于边界的检查是便捷的。这就是迭代器使用是我们最常用的的一个条件 != end() ,有助于让我们拜托这该死的边界检查,和下标溢出。
template<class T, class Alloc=alloc>
class vector{
...
protected:iterator start; //使用空间头iterator finish; //使用空间尾iterator end_of_storage; //未使用空间尾
}
STL中许多数据结构都值得我们学习。尤其是基础数据结构中一些理念和思想,可以很好的帮助我们解决一些边界问题,使用一点代价,就可以避免很多边界问题,哦,这也许就是编程的魅力。
C++动态数组的简易实现相关推荐
- js 多维数组长度_C++申请与释放动态数组1(学习笔记:第6章 16)
分配和释放动态数组[1] 分配: new 类型名T [ 数组长度 ] 数组长度可以是任何表达式,在运行时计算 释放:delete[] 数组名p 释放指针p所指向的数组. p必须是用new分配得到的数组 ...
- vector,数组,动态数组效率测试
对vector.数组.new创建的动态数组.预先reverse的vector测试代码如下: #include <iostream> #include <vector> #inc ...
- C++中关于[]静态数组和new分配的动态数组的区别分析
一.静态数据及动态数组的创建 静态数据: int a[10]: int a[]={1,2,3}; 数组的长度必须为常量. 动态数组: int len; int *a=new int [len]; de ...
- 基础数据结构【二】————动态数组,单向链表及链表的反转
DEMO1: 动态分配变量(链表,而静态数组是线性表,意味着动态数组访问和遍历复杂度为O(n),而插入和删除复杂度为O(1),而静态数组线性表则完全相反) int* intptr = new ...
- vector 容器 动态数组总结
vector 容器 动态数组总结 二话不说直接上代码 #include <vector> #include <algorithm> #include <iostream& ...
- C++动态数组再总结
动态数组是指在编译时不能确定数组长度,程序在运行时需要动态分配内存空间的数组. 1.变长一维数组 实现变长数组最简单的是变长一维数组,你可以这样做: //文件名:array01.cpp#include ...
- python动态数组的最大值_python实现动态数组的示例代码
实现一个支持动态扩容的数组并完成其增删改查 #通过python实现动态数组 """ 数组特点: 占用一段连续的内存空间,支持随机(索引)访问,且时间复杂度为O(1) 添加 ...
- 【 C 】用动态数组实现堆栈
上篇博文:[ C ]经典抽象数据类型(ADT)之堆栈(用静态数组实现堆栈)讲了堆栈的基础知识以及如何用静态数组实现堆栈. 这篇博文简单记录下用动态数组实现堆栈! 整体的实现过程和用静态数组实现堆栈相似 ...
- xcode新版本single view_动态数组函数系列1|概况-跟以往Excel版本完全不一样玩法的函数...
早就听说在Office2019和Office365版本中增加了动态数组函数(Dynamic arrays),早前一直处于内测阶段,只对部分预览用户开放.昨天试了一下,我用的版本已经更新可用了.当前我使 ...
最新文章
- TCP的协议特点(对于《Linux高性能服务器编程》的部分摘录以及自己的部分见解)
- java response 对象_82 Java基础 Response对象
- 马尔科夫、最大熵、条件随机场
- linux sqlplus 密码有$
- 成功解决Remix Mock compiler: Source not found
- centos yum php apc,centos – PECL APC安装 – 错误:’make’失败
- [BZOJ2654] tree
- PHP读写操作Excel
- ffplay分析 (seek操作处理)
- MATLAB各类函数详细讲解 simulike系统仿真分析
- 如何通过Dataphin构建数据中台新增100万用户?
- linux与windows查看占用端口的进程ID并杀死进程
- 边工作边刷题:70天一遍leetcode: day 56-1
- java获取文件大小_Java获取文件大小
- 程序员应该具备的12种能力
- 淘宝客CMS,公众号,小程序,淘客APP,外卖返利系统
- OpenCV C++安装和配置
- 与你一起学习MS Project——基础篇:Project基础应用
- 小波 奇异点 matlab,用Matlab小波变换检测奇异点.doc
- PMP工作绩效数据、信息和报告三者的区别
热门文章
- python的高级应用
- 艺龙旅行闪退问题分析
- deepin20.4为绿色软件创建桌面图标
- emacs shell插件_非程序员的GNU Emacs使用心得...... 大话Emacs—Shell Mode
- 内存泄露与内存溢出的区别及解决方法
- Win7下vs2013打开鼠标不能用,鼠标失灵
- html5鼠标悬停提示框,HTML5鼠标悬停动画提示框特效源码,前端必备
- 模型涨点的思路,深度学习训练的tricks-计算机视觉
- 2022年最新河北水利水电施工安全员模拟试题题库及答案
- 微信小程序中转换时间格式IOS不兼容的问题