在上一篇博客中,我们学习了vector 的基本使用,以及 迭代器的失效问题:
【C++】深入理解vector类(一)
今天我们来模拟实现以下vector类。
目录- 成员变量
- 接口实现
- 构造函数
- 迭代器
- 拷贝构造
- 赋值
- reserve
- resize
- push_back
- pop_back
- 实现[ ]访问
我们先从原码中找出其成员变量:
可以看到 ,原码中有三个成员变量:
- start
- finish
- end_of_storage
数据类型是 iterator
很显然,这和 string 类中的类似,分别对应 start size capacity
那么iterator 是如何声明的?
所以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 fifth (myints, myints + 4 );
std::vector second (4,100);
std::vector third (second.begin(),second.end());
这里的类模板的成员函数,为什么还要定义一个模板参数 InputIterator?
为什么不直接使用iterator呢?
很显然,如果我们写出iterator ,那么我们只能使用vector的迭代器去初始化。如果想使用其他容器,比如list,string中的元素来初始化我们的vector容器,那么就不行了。
比如:
string s("abcde");
vectorv4(s.begin(),s.end());
模拟实现:
template迭代器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 类的时候相同,对于拷贝构造,我们有不止一种的写法。
- 传统写法1
很显然,我们需要使用深拷贝,因为浅拷贝有两个问题:
- 一段空间会析构两次
- 拷贝出的vector容器改变会导致原vector容器也发生改变
同时为了避免传值时候的深拷贝,我们使用引用传值。没有印象的同学可以去看看以下string类的模拟实现博客。
vector(cosnt vector& v) { _start =new T[v.capacity()]; memcpy(_start,v.start,sizeof(T)*v.size()); _finish=_start+v.size(); _endofststorage=_start+v.capacity(); ]
- 传统写法2
这种写法与上一种大同小异,也是先开辟新空间,再将v中的元素逐个插入到这篇空间里
vector(const vector& v) :_start(nullptr), _finish(nullptr), _endofstorage(nullptr) { reserve(v.capacity); for(const auto& e: v) { push_back(e); } }
- 现代写法
这个写法与string类的现代写法,也就是“坐享其成”。这里不做过多讲解。
vector(const vector& v) :_start(nullptr), _finish(nullptr), _endofstorage(nullptr) { vector tmp (v.begin(), v.end()); swap(tmp); }
但是,由于连续空间的深拷贝并不复杂,这里的现代写法相较于传统写法的实际优势并不大。
赋值对于赋值,我们也有传统写法和现代写法。
- 传统写法
传统写法 是先把待赋值对象销毁,再重新开空间,然后向这块空间赋值。
vector& operator=(const vector & 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& operator=(const vector & v) { vector tmp(v.begin(), v.end()); swap(tmp); return *this; }
vectorreserve& operator=( vector 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;
}
}
resize
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++;
}
}
}
push_back
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;
}
pop_back
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);
}



