C++vector模拟实现
Posted 可乐不解渴
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++vector模拟实现相关的知识,希望对你有一定的参考价值。
我可以接受失败,但绝对不能接受自己都未曾奋斗过。
👨🎓vector类实现
🎓vector介绍
- vector是表示动态的变换大小数组的序列容器。
- 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
- 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
🎓vector构造函数
这里我们写了三个的构造函数,其中第一个是默认构造函数。其使用方法如下:
vector < 类型T > 变量名v;
vector() //默认构造函数
:m_start(nullptr)
,m_finish(nullptr)
,m_endofstorage(nullptr)
{}
第二个构造函数是方便我们对已经有n个元素的vector其值为value进行初始化。其中value默认初始化的值为0。其使用方法如下:
vector<类型>变量名v (元素个数size,初始化的值value);
//n---元素个数 value---默认初始化的值,初始值为0
vector(int n, const T& value = T()) //带缺省值的构造函数
:m_start(nullptr)
,m_finish(nullptr)
,m_endofstorage(nullptr)
{
assert(n > 0);
reserve(n);
while (n--)
{
*this->m_finish = value;
++this->m_finish;
}
}
第三个构造函数是用一个迭代器区间进行初始化我们的vector,其使用方法如下:
vector<类型>变量名v (起始迭代器begin,结束迭代器end);
这里面迭代器区间是一个前闭后开的一个区间[begin,end)
。
template <class InputIterator>
vector(InputIterator first, InputIterator last)
:m_start(nullptr)
,m_finish(nullptr)
,m_endofstorage(nullptr)
{
reserve(last - first);
while (first != last)
{
*this->m_finish = (*first);
++first;
++this->m_finish;
}
}
🎓拷贝构造函数
由于vector的内部带有指针变量,故拷贝构造函数和赋值运算符重载函数要进行深拷贝,若进行浅拷贝则会发生错误。下面就是我们拷贝构造函数的传统写法。
Ⅰ、
首先我们先new一块堆上的空间,大小为x.size(),以便存储数据。
Ⅱ、
其次把x里的元素赋值给this对象。
Ⅲ、
最后把m_m_finish和m_endofstorage 指针指向修改至正确位置。
vector(const vector<T>& x) //拷贝构造函数
{
this->m_start = new T[x.capacity()];
//不能用memcpy 原因是因为如果碰到是内置类型或者自定义类型中带有指针的,会出现浅拷贝错误
for (size_t i = 0; i < x.size(); ++i)
{
this->m_start[i] = x[i];
}
this->m_finish = this->m_start + x.size();
this->m_endofstorage = this->m_start + x.capacity();
}
上面代码块中说不能使用memcpy来进行拷贝,这是为什么呢?
答:这是由于memcpy进行的只是简单的值拷贝,相当于默认的拷贝构造函数的功能。如果碰到vector这种类型,这就会导致浅拷贝同一块内存析构了多次导致程序崩溃的问题。因为string的内部实现是一个char *的指针来实现的。而memcpy是把原来空间的值拷贝过去,如下图动画演示。
那我们要怎么去解决这个问题呢,其实很简单使用for循环,让其调用string的赋值运算符重载函数,这样就能避免浅拷贝的问题。
🎓赋值运算符重载函数
在上面的拷贝构造函数中我们已经说明这里面要防止浅拷贝文集以及自我赋值的情况,但我们这里采用的新的方式来写。
原理:基于临时的局部对象用已有的对象给其初始化,然后调用拷贝构造函数,同样局部vector对象出了函数就会调用析构函数释放其内存。
①首先创建一个临时变量,让其调用拷贝构造函数。
②在让this对象与临时对象temp交换。
vector<T>& operator=(const vector<T>& x) //赋值函数重载
{
if (this != &x)
{
vector<T>temp(x);
this->swap(temp);
}
return *this;
}
🎓析构函数
析构函数就没有什么好说的,但是还是要处理一些已经delete释放的空间,再一次delete释放,造成程序崩溃。
~vector() //析构函数
{
if (this->m_start != nullptr)
{
delete[] this->m_start;
this->m_start = this->m_endofstorage = this->m_finish = nullptr;
}
}
🎓迭代器
在C++中迭代器共有以上的8中,但大差不差,这里我们只模拟实现最常用的begin(起始迭代器)和end(结束迭代器)。begin()它指向的是第一的元素,end()指向的最后一个元素的下一个位置。如下图所示:
iterator begin() //起始迭代器
{
return this->m_start;
}
iterator end() //结束迭代器
{
return this->m_finish;
}
const_iterator begin() const //常起始迭代器
{
return this->m_start;
}
const_iterator end() const //常结束迭代器
{
return this->m_finish;
}
可能有人会说为什么写一个带const修饰成员变量的同名函数呢?
答:这是因为如果我们的vector是的对象是常对象时,该对象就无法调用不带const
的begin
和end
。因为常对象只能调用常函数。
🎓容量相关函数
✏️size
功能:是用来获取元素的个数。
返回值:size_t(无符号整型)。
size_t size() const //得到元素个数函数
{
return this->m_finish - this->m_start;
}
✏️capacity
功能:是用来获取容量的大小。
返回值:size_t(无符号整型)。
size_t capacity() const //得到容量大小函数
{
return this->m_endofstorage - this->m_start;
}
在上面的capacity()与size()函数我们均采用指针减指针的方式来得到元素的个数,这里的指针减指针必须得是同一段连续的内存空间
才能使用指针减指针的方法。
✏️empty
功能:是用来获取容器中是否为空。
返回值:bool。
bool empty() const //判断是否为空
{
return this->m_start == this->m_finish;
}
✏️reserve
功能:reserve是用来预留空间大小
规则:
1、当newsize大于对象当前的capacity时,将capacity扩大到newsize。
2、当newsize小于等于对象当前的capacity时,什么也不做。
在这里扩完容量之后,我们要把原来数组中的数据拷贝到创建的临时空间temp中,这里同上面的拷贝构造函数相同都不能使用memcpy来拷贝,而是使用使用for循环,让其调用string的赋值运算符重载函数,这样就能避免浅拷贝的问题。
//newsize---要扩充到的容量大小
void reserve(const size_t newsize)
{
if (this->capacity() < newsize) //判断是否大于当前容量值,如果大于这需要扩容
{
T* temp = new T[newsize];
int size = this->size(); //这个临时遍历size存储扩容前的数据个数
//如果不存储size,后面的m_finish+this->size()会出现错误
//因为我们的size()是用this->m_finish - this->m_start得到的
//因为下面我们先改变了m_start的地址,此时m_finish的地址并没有改变,这里相减得到的是一个新的size()
//并且size()可能是一个很大的数,后面的插入可能会造成空指针访问错误
if (this->m_start != nullptr)
{
//memcpy(temp, this->m_start, sizeof(T) * size);
//不能用memcpy 原因是因为如果碰到是内置类型或者自定义类型中带有指针的,会出现浅拷贝错误
for (size_t i = 0; i < this->size(); ++i)
{
temp[i] = this->m_start[i];
}
delete[] this->m_start;
}
this->m_start = temp;
this->m_finish = this->m_start+size;
this->m_endofstorage = this->m_start + newsize;
}
}
✏️resize
功能:将字符串大小调整为n个字符的长度。
规则:
1、如果newsize小于当前size(),则当前有效值将缩短为其前newsize个,删除第newsize个以后的元素。
2、如果newsize大于当前size()但却小于我们当前的capacity()时,则通过在末尾插入所需数量的元素来扩展当前内容,以达到n的大小,插入的元素默认值为0。
3、如果newsize大于当前size()也大于我们当前的capacity()时,此时我们便需要扩容后,在末尾插入所需数量的元素来扩展当前容器的大小,插入的元素默认值为0。
//newsize---指定的大小 val----如果newsize大于我们的size,则后面的值都为val,默认值为0
/*
有三种情况:
1、newsize<size(),此时我们只需要把我们的有效位m_finish改至m_start+newsize即可
2、newsize>size()&&newsize<capacity(),此时我们只需要把m_finish后面的newsize个元素赋予值val即可
3、newsize>size()&&newsize>capacity(),此时我们需要扩容,扩完容之后把m_finish后面的newsize个元素赋予值val即可
*/
void resize(const size_t newsize, const T &val = T())
{
if (this->size() < newsize)
{
if (newsize > capacity())
{
reserve(newsize);
}
while (this->m_finish < this->m_start + newsize)
{
*this->m_finish = val;
++this->m_finish;
}
}
else
{
this->m_finish = this->m_start + newsize;
}
}
🎓元素访问
✏️ front
功能:获取第一个元素的值。
返回值:
T& front()
{
assert(!this->empty());
return *(this->m_start);
}
const T& front()const
{
assert(!this->empty());
return *(this->m_start);
}
✏️ back
功能:获取最后一个元素的值。
T& back()
{
assert(!this->empty());
return *(this->m_finish-1);
}
const T& back() const
{
assert(!this->empty());
return *(this->m_finish - 1);
}
✏️ at
功能:返回对向量中位置n处元素的引用。
T& at(const size_t& index)
{
assert(index < this->size());
return this->m_start[index];
}
T& at(const size_t& index) const
{
assert(index < this->size());
return this->m_start[index];
}
✏️ operator[]
功能:返回对向量中位置n处元素的引用。
const T& operator[](const size_t& index) //重载operator[],以便向数组一样使用
{
assert(index < this->size());
return this->m_start[index];
}
const T& operator[](const size_t& index) const
{
assert(index < this->size());
return this->m_start[index];
}
🎓修改相关函数
✏️push_back
功能:在尾部插入一个元素。
//尾插 val----要插入的值
void push_back(const T&val)
{
if (this->m_finish == this->m_endofstorage)
{
size_t newCapacity = capacity() == 0 ? 1 : capacity() * 2;
reserve(newCapacity);
}
*this->m_finish = val;
++this->m_finish;
}
✏️insert
insert的重载版本有很多种,这里我们只模拟实现最常用的一个。
功能:在指定位置插入一个元素。
返回值:指向第一个新插入元素的迭代器。
//position---插入的位置,插入范围[m_start,m_finish] val-----要插入的值 返回值---返回插入的val的迭代器位置
iterator insert(iterator position, const T& val)
{
assert(this->m_start<=position&&position<=this->m_finish); //判断插入的位置是否给错
if (this->m_finish == this->m_endofstorage) //判断是否要扩容
{
size_t len = position - this->m_start; //记录我们要插入的位置,防止扩容后迭代器失效的问题
size_t newCapacity = capacity() == 0 ? 1 : capacity() * 2;
reserve(newCapacity);
// 更新position,解决增容后position失效的问题
position = this->m_start + len; //得到扩容后所要插入的对应位置
}
iterator end = this->m_finish-1;
while (end>=position)
{
*(end + 1) = *end;
--end;
}
*position = val;
++this->m_finish;
return position; //返回插入的val的迭代器位置
}
✏️pop_back
功能:删除尾部的元素。
//尾删
void pop_back()
{
assert(!this->empty());
--this->m_finish;
}
✏️erase
功能:删除给定位置的值或者删除某一个范围的值。
返回值:一个迭代器,指向在函数调用删除的最后一个元素之后的元素的迭代器新位置。如果操作删除了序列中的最后一个元素,则这是容器结束。
//position---要删除的位置 返回值---删除位置后面元素的迭代器
iterator erase(const iterator position)
{
assert(this->m_start <= position && position < this->m_finish); //判断删除的位置是否越界
iterator end = const_cast<iterator>(position);
while (end < this->m_finish)
{
*end = *(end + 1);
++end;
}
--this->m_finish;
return position;
}
//first---删除起始位置 last---删除的结束位置 前闭后开的区间
iterator erase(iterator first, iterator last)
{
assert(first<last&&this->m_start <= first && last <= this->m_finish); //判断删除的位置是否越界
iterator begin = first;
iterator end = last;
while (end < this->m_finish)
{
*begin = *last;
++begin;
++end;
}
this->m_finish =begin;
if (this->empty()) //如果该空间没有数据时,此时该空间的内容为空不能被访问直接返回nullptr指针
{
return nullptr;
}
else
{
return first;
}
}
✏️clear
功能:从容器中删除所有元素,使容器的大小为0。
void clear() //清空
{
this->m_finish = this->m_start;
}
✏️swap
功能:用来交换两个相同类型的容器。
void swap(vector<T>& x) //两个vector交换函数
{
std::swap(this->m_start, x.m_start);
std::swap(this->m_finish, x.m_finish);
std::swap(this->m_endofstorage, x.m_endofstorage);
}
完整代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include <assert.h>
#include<iostream>
using namespace std;
namespace ZJ
{
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
public:
vector() //默认构造函数
:m_start(nullptr)
,m_finish(nullptr)
,m_endofstorage(nullptr)
{}
//n---元素个数 value---默认初始化的值,初始值为0
vector(int n, const T& value = T()) //带缺省值的构造函数
:m_start(nullptr)
,m_finish(nullptr)
,m_endofstorage(nullptr)
{
assert(n > 0);
reserve(n);
while (n--)
{
*this->m_finish = value;
++this->m_finish;
}
}
template <class InputIterator>
vector(InputIterator first, InputIterator last)
:m_startC++STL之vector的使用和实现