文章目录
一、什么是vector?
向量vector是C++中最常用的STL之一,它的优点在于:
- 动态大小,不像数组一般大小固定
- 可以很高效的在它尾部进行元素的增删
- 快速随机访问
- 支持迭代器
- 内存连续
这篇文章我们将动手实现一个简单的vector,在提升C++代码能力的同时理解为什么vector具有这些特点。
PS:由于水平等总总原因,有的方法我没法一次性实现,故这篇文章内容将不断更新完善,望谅解,也希望大家能指出这篇文章的不足和错误
二、需要实现的操作介绍
函数名称 | 实现的操作 |
---|---|
push_back(num) | 在vector的尾部增加元素num |
size() | 返回vector的大小 |
binary_search() | 二分查找,找出第一次出现的指定元素 |
[ ] | 使得vector能像数组一样进行快速的下标访问 |
shrink() | 范围缩小函数,避免浪费内存 |
bubble_sort() | 冒泡排序 |
三、目前已经实现的操作
- push_back()
- size()
- [ ]
- binary_search();
- bubble_sort()
- shrink()
四、完整代码
//避免重复定义
#ifndef VECTOR_CPP
#define VECTOR_CPP
#define MINSIZE 3
template <typename T>
class Vector{
public:
Vector();
Vector(const Vector<T>&);
~Vector();
T operator[](int index);
int size();
void push_back(T value);
void shrink();
//选择排序函数
void sort(int value);
//二分查找1
int binary_search(T value);
//冒泡排序1
void bubble_sort();
private:
int sizeVal = 0;
//元素容量
int length = MINSIZE;
//数组规模
T* arr;
//空间
};
template<class T>
Vector<T>::Vector(){
arr = new T[length];
}
template <class T>
Vector<T>::Vector(const Vector<T>& temp){
this->length = temp.length;
this->sizeVal = temp.sizeVal;
this->arr = new T[length];
for(int i=0;i<this->sizeVal;i++){
this->arr[i] = temp.arr[i];
}
}
template<class T>
Vector<T>::~Vector(){
delete[] arr;
}
template<class T>
T Vector<T>::operator[](int index){
return this->arr[index];
}
template<class T>
int Vector<T>::size(){
return this->sizeVal;
}
template<class T>
void Vector<T>::push_back(T value){
this->sizeVal++;
if(sizeVal>length){
int* newArr = arr;
this->length *= 2;
arr = new int[length];
for(int i=0;i<sizeVal-1;i++){
arr[i]=newArr[i];
}
arr[sizeVal-1]=value;
delete[] newArr;
}
else{
arr[sizeVal-1]=value;
}
}
template<class T>
void Vector<T>::shrink(){
T* newArr = new T[sizeVal];
for(int num=0;num<sizeVal;num++){
std::swap(neaArr[num],this->arr[num]);
}
delete[] this->arr;
this->arr=newArr;
}
template<class T>
void Vector<T>::sort(int value){
switch (value)
{
case 1:
this->bubble_sort();
break;
default:
break;
}
}
template<class T>
//优化二分查找
int Vector<T>::binary_search(T value){
//无重复有序,中间值
int low=0,high=this->sizeVal-1;
while(low<high){
int mid=(low+high)>>1;
if(value<this->arr[mid]){
high=mid-1;
}
else if(value>arr[mid]){
low=mid+1;
}
else if(value==arr[mid]){
return mid;
}
}
return -1;
}
template<class T>
void Vector<T>::bubble_sort(){
//sizeVal是元素个数
//冒泡
for(int i=0;i<this->sizeVal;i++){
for(int j=i;j<this->sizeVal;j++){
if(arr[i]>arr[j]){
int temp=arr[j];
arr[j]=arr[i];
arr[i]=temp;
}
}
}
}
#endif
五、代码逐步实现
(1) 声明Vector类
这一步声明Vector和要实现的函数,由于vector是一个模板类,所以我们也运用C++的模板来进行实现
#define MINSIZE 3//向量最小的数据空间的大小
template<class T>
class Vector{
public:
Vector();
Vector(const Vector<T>& temp);
~Vector();
void push_back(T value);
int size();//返回向量的大小
T operator[](int index);
private:
int sizeVal = 0;//向量的长度
int length = MINSIZE;//向量的空间大小,MINSIZE为初始的最小空间
int *arr;//向量的数据空间
};
(2) 定义Vector的构造函数
我们先要明确在构造函数中,我们需要完成什么内容:给Vector分配一个初始的数据空间,这个数据空间不必太大,因为假如我们给的数据很小,但是我们却分配了大块空间,这会浪费内存,因此我们使用宏MINSIZE,定义了Vector的初始最小内存空间,并使用它初始化length。
template<class T>
Vector<T>::Vector(){
arr = new T[length];
}
(3) 定义Vector的拷贝构造函数
简单的赋值操作
template <class T>
Vector<T>::Vector(const Vector<T>& temp){
this->length = temp.length;
this->sizeVal = temp.sizeVal;
this->arr = new T[length];
for(int i=0;i<this->sizeVal;i++){
this->arr[i] = temp.arr[i];
}
}
(4) 定义Vector的析构函数
在Vector的析构函数中,我们需要释放之前分配的内存,来避免内存泄漏
template<class T>
Vector<T>::~Vector(){
delete[] arr;
}
(5) 实现push_back()
这个函数的作用是在Vector的尾部增加元素。需要注意的是:它需要进行的不单是一个简单的增加,由于在添加之后,我们需要对其数据空间的大小进行判断,如果在元素插入之后数据空间所规定的大小已经不够用了,我们就需要对数据空间进行扩容处理
template<class T>
void Vector<T>::push_back(T value){
this->sizeVal++;//先将元素个数增加
//判断元素个数有没有超过数据空间的大小
//如果超过了,空间大小翻倍
if(sizeVal>length){
int* newArr = arr;
this->lenght *= 2;
//分配一个新的、更大的空间
arr = new int[length];
//将原来的元素复制
for(int i=0;i<sizeVal-1;i++){
arr[i]=newArr[i];
}
//添加元素
arr[sizeVal-1]=value;
delete[] newArr;
}
//如果空间足够,直接添加
else{
arr[sizeVal-1]=value;
}
}
相信理解了这个步骤之后也就明白了为什么vector的迭代器在vector进行了改变之后可能失效了,因为在插入元素后可能因为扩容使得数据空间的位置发生了改变,原来的迭代器指向的内存被释放,也就无效了
(6) 实现size()
这个函数是最简单的一个了,因为我们在定义的时候就已经使用了一个变量sizeVal来储存其元素个数:
template<class T>
int Vector<int>::size(){
return this->sizeVal;
}
(7) 重载下标运算符
这一步使得vector能够像数组一样进行下标访问,也没什么难度。
template<class T>
T Vector<T>::operator[](int index){
return this->arr[index];
}
(8) 实现binary_search()函数
这个函数使用的是二分查找,但是由于是要找到这个元素第一次出现的位置,所以是一个二分查找的边界查找:
template<class T>
//优化二分查找
int Vector<T>::binary_search(T value){
int low=0,high=this->sizeVal-1;
while(low<high){
int mid=(low+high)>>1;
if(value<this->arr[mid]){
high=mid-1;
}
else if(value>arr[mid]){
low=mid+1;
}
else if(value==arr[mid]){
return mid;
}
}
return -1;
}
(9)实现bubble_sort()函数
这是最简单的排序方法,这个函数的增加只是为了掌握冒泡排序这个算法,但是由于这个算法的效率极低,通常是不会使用的。在数据量较大的时候,程序的运行时间就已经很长了。在后面还会有很多更好的排序算法。
template<class T>
void Vector<T>::bubble_sort(){
//sizeVal是元素个数
//冒泡
for(int i=0;i<this->sizeVal;i++){
for(int j=i;j<this->sizeVal;j++){
if(arr[i]>arr[j]){
int temp=arr[j];
arr[j]=arr[i];
arr[i]=temp;
}
}
}
}
(10)实现shrink函数
这个函数起到容器的缩容作用,就是说在停止输入之后,我们需要调用这个函数,删除分配了空间但是没有储存元素的部分,避免内存的浪费。但是由于我这个Vector在设计的时候没有设计好,这个需要我们在结束了push_back()之后进行手动调用,实际上vector的调用形式比我的聪明多了:
template<class T>
void Vector<T>::shrink(){
T* newArr = new T[sizeVal];
for(int num=0;num<sizeVal;num++){
std::swap(neaArr[num],this->arr[num]);
//利用交换,提高效率
}
delete[] this->arr;
this->arr=newArr;
}