通过上篇文章,我们知道vector的接口实际上和string是差不多的,但是他俩的内部结构却大不一样,vector内有三个成员变量:_start、_finish、_endofstorage:
_start指向容器的头元素,_finish指向有效元素末尾的元素,_endofstorage指向整个容器的尾。
而vector的迭代器就是typedef T*,也就是元素的指针。
所以我们在模拟之前就可以先做出一个小框架,为了不与库中的vector冲突,我们把他放在我们自己的命名空间中:
namespace CYF
{
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
private:
iterator _start;//指向容器的头
iterator _finish;//指向有效数据的尾
iterator _endofstorage;//指向容器的尾
};
}
构造函数
构造函数1
这个是无参构造函数,我们将他所有的成员对象都制成空指针就行:
//构造函数1
vector()
:_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr)
{}
构造函数2
vector还支持使用一段迭代区间进行对象的构造。因为该迭代器区间可以是其他容器的迭代器区间,也就是说函数接收的迭代器的类型是不确定的,所以我们这里需要将构造函数设计为一个函数模板,在函数体内将该迭代区间的数据一个个尾插到容器当中即可:
//构造函数2
template<class InputIterator>//模板参数,因为迭代器区间可以是其他容器的迭代器
vector(InputIterator first, InputIterator last)
: _start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
while (first != last)//将迭代器区间在[first,last)中的数据一个个尾插到容器中
{
push_back(*first);
first++;
}
}
构造函数3
vector还支持构造一种初始化有n个值为val的元素的对象。我们可以先使用reserve函数将容器的容量设为n,然后尾插n个值为val的数据到该对象即可:
vector(size_t n, const T& val)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
reserve(n);//调用reserve函数将容器容量设为n,可以避免效率降低
for (size_t i = 0; i < n; i++)//尾插n个值为val的元素
{
push_back(val);
}
}
但是这个函数需要再实现两个重载函数:
vector(long n, const T& val)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
reserve(n);
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
vector(int n, const T& val)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
reserve(n);
for (int i = 0; i < n; i++)
{
push_back(val);
}
}
这两个重载函数和他不同的就是第一个参数n的类型,如果没有这两个重载函数的话,我们调用下面的代码时,编译器就会优先和构造函数2匹配。
vector<int> v1(1, 3); //这里调用的是构造函数2
并且因为构造函数2当中对两个参数进行了解引用(即将int类型解引用)报错:
补充:若我们知道需要多少空间,我们最好先使用reserve函数一次性将空间开辟好,避免调用push_back函数时再增容,导致效率降低。
拷贝构造函数
传统写法1
这里仍然需要深拷贝,先开一段与拷贝对象大小相等的空间,然后将该对象内的元素一个个拷贝过来即可,最后记得更新成员对象的值:
拷贝构造(传统写法1)
vector(const vector<T>& v)
:_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr)
{
_start = new T[v.capacity()];//开辟一块和容器cipacity大小相同的空间
for (size_t i = 0; i < v.size(); i++)//将v中数据拷贝过来,注意这里不能用memcpy
{
_start[i] = v[i];
}
_finish = _satrt + v.size();//有效数据的尾
_endofstorage = _start + v.capacity();//整个容器的尾
}
注意我们这里数据需要一个个拷贝过来,不能使用memcpy函数。若vector内部存储的是内置类型或者无需深拷贝的自定义类型时,可以使用memcpy函数,但是当vector内存储的数据是需要深拷贝的自定义类型的时候,使用memcpy就不行了。例如:我们拿存储的数据类型是string举例:
如图:我们vector对象内元素都是string类型,每个元素都指向自己的一个字符串。若我们使用memcpy函数的话,就会出现下面的情况:
memcpy会将每个string元素内成员变量的值原封不动地拷贝过去,也就导致了两个vector对象内的元素都指向同一组string元素。最后析构环节也就会导致内存错误。
而我们拷贝时采用string类的operator=的方式,采用的是深拷贝,最终的结果是这样:
就不会导致内存问题了。
传统写法2
这里我们直接调用reserve函数进行空间开辟,而后通过范围for将原有对象一个个尾插:
//拷贝构造(传统写法2)
vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
reserve(v.capacity());
for (const auto& e : v)
{
push_back(e);
}
}
现代写法
这里我们将v1作为临时对象,通过构造函数构造出来。而后将v1与*this互换即可,经典的现代写法,无需多言:
//拷贝构造(现代写法)
vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
vector<T>v1(v.begin(),v.end());
swap(v1);
}
赋值运算符重载
传统写法
先判断是否为自己给自己赋值,若给自己赋值则不用做任何操作。若不是,则:先开辟一块和要拷贝对象capacity大小一样的空间,然后将数据一个个拷过来,最后更新成员对象的值:
//赋值运算符重载函数(传统写法)
vector<T>& operator=(const vector<T>& v)
{
if (this != &v)
{
delete[]_start;
_start = new T[v.capacity()];
_finish = _start + v.size();
_endofstorage = _satrt + v.capacity();
for (const auto& e : v)
{
push_back(e);
}
}
return *this;
}
现代写法
operator=的现代写法很巧妙,右值传参的时候使用传值传参而不是引用传参,这样就直接调用了vector的拷贝构造函数,然后用这个拷贝出来的局部对象和左值进行交换,就相当于做了赋值操作,而v是局部变量,在函数结束时会自动调用析构函数销毁。
//现代写法
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this; //支持连续赋值
}
析构函数
先判断是否为空指针,若不是,将空间释放,再将成员变量全都置为nullptr:
//析构函数
~vector()
{
delete[]_start;
_start = nullptr;
_finish = nullptr;
_endofstorage = nullptr;
}
迭代器相关的函数(begin&&end)
我们上面讲过,vector的迭代器实际上就相当于存储数据类型的指针,begin()就是返回第一个元素地址,end()就是返回最后一个元素的下一个数据的地址:
typedef T* iterator;
typedef const T* const_iterator;
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin()const
{
return _start;
}
const_iterator end()const
{
return _finish;
}
所以我们使用迭代器遍历vector对象实际上也就是使用指针遍历。并且实现了迭代器后,也就可以使用范围for,因为范围for在编译的时候,会自动替换为迭代器的形式。
关于容量和大小的函数
size && capacity
这里的size和capacity的算法跟string不同,string内直接就有两个成员变量表示size和capacity,而vector这里需要用_finish - _start和_endofstorage - _start分别得到:
size_t size()const
{
return _finish - _start;
}
size_t capacity()const
{
return _endofstorage - _start;
}
reserve
1、当n大于对象当前的capacity时,将capacity扩大到n或大于n。
2、当n小于对象当前的capacity时,不用做任何操作
我们先判断n是否大于容量,若大于,就开辟一块容量为n的空间,将原来容器中的有效数据拷贝进去,再将原来的空间释放,记得更新容器内的成员变量:
void reserve(size_t n)
{
if (n > capacity())
{
size_t sz = size(); //记录当前容器当中有效数据的个数
T* tmp = new T[n]; //开辟一块可以容纳n个数据的空间
if (_start) //判断是否为空容器
{
for (size_t i = 0; i < sz; i++) //拷贝数据
{
tmp[i] = _start[i];
}
delete[] _start; //释放原有空间
}
_start = tmp; //更新成员对象
_finish = _start + sz;
_endofstorage = _start + n;
}
}
我们这里要注意要先记录元素的个数,否则我们最后更新_finish指针指向的时候,若调用size()函数,而size()函数是通过_finish-_start得到的,而此时_start已经指针指向已经改变,我们就会得到一个随机值,所以我们需要提前记录。
resize
1、当n大于当前的size时,将size扩大到n,扩大的数据为val,若val未给出,则默认为容器所存储类型的默认构造函数所构造出来的值。
2、当n小于当前的size时,将size缩小到n。
我们需要先判断n是否小于size,若小于,将_finish改变即可,若不小于,还需要判断是否需要增容,再将扩大的数据赋值为val即可:
void resize(size_t n, const T& val = T())
{
if (n < size()) //当n小于size时
{
_finish = _start + n;
}
else
{
if (n > capacity())
{
reserve(n);
}
while (_finish < _start + n)
{
*_finish = val;
_finish++;
}
}
}
在C++中内置类型也可以看作是一个类,也有自己的默认构造函数,所以val的缺省参数可以设为T()临时对象。
empty
判断对象是否为空:
bool empty()const
{
return _start == _finish;
}
有关修改对象的函数
push_back
先判断是否需要增容,再尾插,最后更新_finish的指向:
void push_back(const T& x)
{
if (_finish==_endofstorage)
{
size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
reserve(newcapacity);
}
*_finish = x;
_finish++;
}
pop_back
判断对象是否为空,不为空将_finish--即可:
void pop_back()
{
assert(!empty());//容器不能为空
_finish--;
}
insert
我们需要先判断pos是否合法,再判断是否需要增容,然后将pos后的元素都向后移一位,将x插在pos的位置,最后将_finish++:
void insert(iterator pos, const T& x)
{
if (pos <= _finish)//判断pos是合法
{
size_t n = pos - _start;//记录pos和_start之间的间隔
if (_finish == _endofstorage)
{
size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
reserve(newcapacity);
}
pos = _start + n;//根据记录的间隔数算出pos
iterator i = _finish;
while (i != pos)
{
*(i + 1) = *i;
i--;
}
*pos = x;
_finish++;
}
}
提示:这里我们需要提前记录pos和_start之间的间隔,通过间隔确定增容后的pos的指向,否则pos还是指向原来已经被释放的空间。(我自己在写的时候就出现了这样的错误)
erase
先判断对象不能为空,删除时将pos后面的数据统一前移一位,覆盖掉pos的数据,返回值还为pos,即删除元素的下一个元素的位置:
iterator erase(iterator pos)
{
assert(!empty());
iterator it = pos;
while (it != _finish)
{
*it = *(it+1);
it++;
}
_finish--;
return pos;
}
swap
我们调用库中的swap函数将两个对象中的成员变量交换即可(这样就相当于交换了两个对象内部指针的指向,不耗费太多资源,若我们直接调用swap交换两个对象,若遇见例如string这样的对象,swap交换时做的全都是深拷贝操作,太耗费资源了,所以我们只交话两个对象内部的成员变量即可):
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
operator[ ]
通过下标访问vector内元素,直接返回数据即可:
T& operator[](size_t i)
{
if(i<size())
{
return *(_start + i);
}
}
const T& operator[](size_t i)const
{
if (i < size())
{
return *(_start + i);
}
}
最后贴上整体代码:
#pragma once
#include<iostream>
#include<cassert>
namespace CYF
{
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
//构造函数1
vector()
:_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr)
{}
//构造函数2
template<class InputIterator>//模板参数,因为迭代器区间可以是其他容器的迭代器
vector(InputIterator first, InputIterator last)
: _start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
while (first != last)//将迭代器区间在[first,last)中的数据一个个尾插到容器中
{
push_back(*first);
first++;
}
}
//构造函数3
vector(size_t n, const T& val)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
reserve(n);//调用reserve函数将容器容量设为n,可以避免效率降低
for (size_t i = 0; i < n; i++)//尾插n个值为val的元素
{
push_back(val);
}
}
//拷贝构造(传统写法1)
//vector(const vector<T>& v)
// :_start(nullptr)
// ,_finish(nullptr)
// ,_endofstorage(nullptr)
//{
// _start = new T[v.capacity()];//开辟一块和容器cipacity大小相同的空间
// for (size_t i = 0; i < v.size(); i++)//将v中数据拷贝过来,注意这里不能用memcpy
// {
// _start[i] = v[i];
// }
// _finish = _satrt + v.size();//有效数据的尾
// _endofstorage = _start + v.capacity();//整个容器的尾
//}
//拷贝构造(传统写法2)
//vector(const vector<T>& v)
// :_start(nullptr)
// , _finish(nullptr)
// , _endofstorage(nullptr)
//{
// reserve(v.capacity());
// for (const auto& e : v)
// {
// push_back(e);
// }
//}
//拷贝构造(现代写法)
vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
vector<T>v1(v.begin(),v.end());
swap(v1);
}
赋值运算符重载函数(传统写法)
//vector<T>& operator=(const vector<T>& v)
//{
// if (this != &v)
// {
// delete[]_start;
// _start = new T[v.capacity()];
// _finish = _start + v.size();
// _endofstorage = _satrt + v.capacity();
// for (const auto& e : v)
// {
// push_back(e);
// }
// }
// return *this;
//}
//赋值运算符重载函数(现代写法)
vector<T>& operator=(const vector<T>& v)
{
if (this != &v)
{
vector<T> v1(v.begin(),v.end());
swap(v1);
}
return *this;
}
//析构函数
~vector()
{
delete[]_start;
_start = nullptr;
_finish = nullptr;
_endofstorage = nullptr;
}
//迭代器相关函数
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin()const
{
return _start;
}
const_iterator end()const
{
return _finish;
}
//容量和大小相关函数
size_t size()const
{
return _finish - _start;
}
size_t capacity()const
{
return _endofstorage - _start;
}
void reserve(size_t n)
{
if (n >= capacity())
{
iterator _start_ = new T[n];
_finish = _start_ + size();
_endofstorage = _start_ + n;
size_t i = 0;
while (_start_ + i != _finish)
{
_start_[i] = _start[i];
i++;
}
delete[]_start;
_start = _start_;
_start_ = nullptr;
}
}
//void resize(size_t n, const T& val = T())
//{
// if (n <= size())
// {
// _finish = _start + n - 1;//这时候只会size,不会改变capacity
// }
// else if (n > size() && n <= capacity())
// {
// size_t more = n - size();
// while (more > 0)
// {
// *_finish = val;
// _finish++;
// more--;
// }
// }
// else
// {
// reserve(n);
// while (_finish != _start + n)
// {
// *_finish = val;
// _finish++;
// }
// }
//}
void resize(size_t n, const T& val = T())
{
if (n < size()) //当n小于当前的size时
{
_finish = _start + n; //将size缩小到n
}
else //当n大于当前的size时
{
if (n > capacity()) //判断是否需要增容
{
reserve(n);
}
while (_finish < _start + n) //将size扩大到n
{
*_finish = val;
_finish++;
}
}
}
bool empty()const
{
return _start == _finish;
}
//修改容器内容相关函数
void push_back(const T& x)
{
if (_finish==_endofstorage)
{
size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
reserve(newcapacity);
}
*_finish = x;
_finish++;
}
void pop_back()
{
assert(!empty());//容器不能为空
_finish--;
}
void insert(iterator pos, const T& x)
{
if (pos <= _finish)//判断pos是合法
{
size_t n = pos - _start;
if (_finish == _endofstorage)
{
size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
reserve(newcapacity);
}
pos = _start + n;
iterator i = _finish;
while (i != pos)
{
*(i + 1) = *i;
i--;
}
*pos = x;
_finish++;
}
}
iterator erase(iterator pos)
{
assert(!empty());
iterator it = pos;
while (it != _finish)
{
*it = *(it+1);
it++;
}
_finish--;
return pos;
}
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
//访问容器相关函数
T& operator[](size_t i)
{
if(i<size())
{
return *(_start + i);
}
}
const T& operator[](size_t i)const
{
if (i < size())
{
return *(_start + i);
}
}
private:
iterator _start;//指向容器的头
iterator _finish;//指向有效数据的尾
iterator _endofstorage;//指向容器的尾
};
}
以上就是这篇文章的全部内容,谢谢大家!!