在上一篇博客中,我们学习了vector 的基本使用,以及 迭代器的失效问题:
【C++】深入理解vector类(一)
今天我们来模拟实现以下vector类。
我们先从原码中找出其成员变量:
可以看到 ,原码中有三个成员变量:
数据类型是 iterator
很显然,这和 string 类中的类似,分别对应 start size capacity
所以iterator 是一个模板类型。可以根据用户的输入 而 变成相应的数据类型。
所以,vector 的基本框架这样写:
namespace yyk
{
typedef T* iterator;
typedef const T* const_iterator;
class vector{
public:
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
};
}
vector()
:_start(nullptr),
_finish(nullptr),
_endofstorage(nullptr)
{}
在上一篇文章中,我们在介绍构造函数的时候,提到了迭代器构造函数。
简单来说,就是我们可以用其他的容器的迭代器来初始化自己。
vector (Inputlterator first, Inputiterator last)
使用范例:
int myints[] = {16,2,77,29};
std::vector<int> fifth (myints, myints + 4 );
std::vector<int> second (4,100);
std::vector<int> third (second.begin(),second.end());
这里的类模板的成员函数,为什么还要定义一个模板参数 InputIterator?
为什么不直接使用iterator呢?
很显然,如果我们写出iterator ,那么我们只能使用vector的迭代器去初始化。如果想使用其他容器,比如list,string中的元素来初始化我们的vector容器,那么就不行了。
比如:
string s("abcde");
vector<int>v4(s.begin(),s.end());
模拟实现:
template<class InputIterator>
vector(InputIterator first, InputIterator last)
:_start(nullptr),
_finish(nullptr),
_endofstorage(nullptr)
{
while(first != last)
{
push_back(*first);
++first;
}
}
对于迭代器我设置两种,一种是可读可写的,一种是只可读的
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;
}
T& operator[] (size_t n)
{
assert(n < size());
return *(_start + n);
}
const T& operator[] (size_t n)const
{
assert(n < size());
return *(_start + n);
}
与之前模拟string 类的时候相同,对于拷贝构造,我们有不止一种的写法。
很显然,我们需要使用深拷贝,因为浅拷贝有两个问题:
同时为了避免传值时候的深拷贝,我们使用引用传值。没有印象的同学可以去看看以下string类的模拟实现博客。
vector(cosnt vector<T>& v)
{
_start =new T[v.capacity()];
memcpy(_start,v.start,sizeof(T)*v.size());
_finish=_start+v.size();
_endofststorage=_start+v.capacity();
]
这种写法与上一种大同小异,也是先开辟新空间,再将v中的元素逐个插入到这篇空间里
vector(const vector<T>& v)
:_start(nullptr),
_finish(nullptr),
_endofstorage(nullptr)
{
reserve(v.capacity);
for(const auto& e: v)
{
push_back(e);
}
}
这个写法与string类的现代写法,也就是“坐享其成”。这里不做过多讲解。
vector(const vector<T>& v)
:_start(nullptr),
_finish(nullptr),
_endofstorage(nullptr)
{
vector<T> tmp (v.begin(), v.end());
swap(tmp);
}
但是,由于连续空间的深拷贝并不复杂,这里的现代写法相较于传统写法的实际优势并不大。
对于赋值,我们也有传统写法和现代写法。
传统写法 是先把待赋值对象销毁,再重新开空间,然后向这块空间赋值。
vector<T>& operator=(const vector<T>& v)
{
if (this != &v)
{
delete[]_start;
_start = _finish = _endofstorage = nullptr;
reserve(v.capacity());
for (cosnt auto& e : v)
{
push_back(e);
}
}
return *this;
}
-现代写法
这个在string类的模拟中也写过了,不再赘述。
vector<T>& operator=(const vector<T>& v)
{
vector<T> tmp(v.begin(), v.end());
swap(tmp);
return *this;
}
vector<T>& operator=( vector<T> v)
{
swap(this,v);
return *this;
}
void reserve(size_t n)
{
if (n > capacity())
{
size_t sz = size();
T* tmp = new T[n];
memcpy(tmp, _start, sizeof(T) * sz);
_start = tmp;
_finish = tmp + sz;
_endofstorage = tmp + n;
}
}
void resize(size_t n ,const T& val=T())
{
if (n <= capacity())
{
_finish = _start + n;
}
else
{
if (n > capacity())
{
reserve(n);
}
while (_finish < _start+n)
{
*_finish = val;
_finish++;
}
}
}
void push_back(const size_t x)
{
if (_start == _endofstorage)
{
int newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
}
*_finish = x;
_finish++;
}
void pop_back()
{
assert(size()!=0);
--_finish;
}
void pop_back()
{
assert(size()!=0);
--_finish;
}
T& operator[] (size_t n)
{
assert(n < size());
return *(_start + n);
}
const T& operator[] (size_t n)const
{
assert(n < size());
return *(_start + n);
}