在上一篇博客中,我们学习了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::vectorfifth (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); }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)