目录
4.析构函数和计算size、capacity、swap简单函数的实现
1.vector的底层
我们在之前的文章顺序表的实现中了解到:
其底层存在一个指向动态开辟数组的指针、一个表示元素个数的size和一个表示顺序表容量大小的capacity。
而vector使用了一个start、一个finish和一个end_of_storage,虽然二者的表示形式不同,实则都有相同的道理,如图:
现在我们就依据此来实现一下vector。
2.vector构造函数的实现
①构造函数
class vector
{
public:
vector()
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{}
private:
//全缺省为空
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _endofstorage = nullptr;
}
②拷贝构造
//①传统写法
//vector(const vector<T>& v)
//{
// _start = new T[v.capacity()];//先开空间
// memcpy(_start, v._start, v.size() * sizeof(T));
// _finish = _start + v.size();
// _endofstorage = _start + v.capacity();
//}
//②现代写法
vector(const vector<T>& v)
{
reserve(v.capacity());//首先开一个与旧空间一样的新空间
for (const auto& e : v)//添加引用&,防止深拷贝
{
push_back(e);//复用push_back函数
}
}
③析构函数
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
}
3.访问函数实现
3.1迭代器iterator
①普通迭代器可读可写:
//普通迭代器 可读可写
typedef T* iterator;
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
②const迭代器可读不可写:
//const迭代器 可读不可写
typedef const T* const_iterator;
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
3.2下标+[]访问
①普通函数,可读可写:
//①可读可写
T& operator[](size_t pos)
{
assert(pos < size());//检查是否越界
return _start[pos];//返回pos位置字符的引用,既可读又可写
}
②const函数,可读不可写:
//②可读不可写
const T& operator[](size_t pos) const
{
assert(pos < size());//检查是否越界
return _start[pos];//返回pos位置字符的引用,既可读又可写
}
4.析构函数和计算size、capacity、swap简单函数的实现
①析构函数:
//析构函数
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
}
②计算size:
size_t size() const
{
return _finish - _start;
}
③计算capacity:
size_t capacity() const
{
return _endofstorage - _start;
}
④swap函数
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
有了swap函数,我们就可以复用swap函数实现赋值构造函数:
//赋值
vector<T>& operator=(vector<T> v)
//注意这里不能添加&引用,如v3=v1
//无&引用表示v是v1的临时拷贝,v3再拷贝v,出了作用域v就会被销毁,不会改变v1
//而加上&引用则直接表示v1与v3交换了,即可以认为这里实现了swap函数的功能
{
swap(v);//复用swap函数
return *this;
}
5. reserve(提前开空间函数实现)
void reserve(size_t n)
{
if (n > capacity())//如果原容量<需要的新空间,就开辟新空间
{
T* tmp = new T[n];//开辟新空间
if (_start)//判断原vector是否为空
{
//如果原vector不为空,就将从_start位置开始的n*sizeof(T)个数据拷贝到新空间tmp里
memcpy(tmp, _start, n * sizeof(T));
delete[] _start;//释放旧空间
}
//再指向新空间
_start = tmp;
_finish = _start + size();
_endofstorage = _start + capacity();
}
这样写程序出现了bug:
这是因为
因为size()=_finish - _start;
所以_finish=_start+_finish-_start=_finish
可是这里的_start已经指向了新空间,而_finish指向的还是旧空间
一个空间的末尾 - 另一个空间的开头 ==== 错误!
_endofstorage = _start + capacity();
同样的道理,capacity()还是旧空间,_start是新空间 也= 错误!
所以这里正确的做法应该是_endofstorage = _start + n;
正确代码:
void reserve(size_t n)
{
if (n > capacity())//如果原容量<需要的新空间,就开辟新空间
{
size_t old = size();//在扩容之前将size()先保存一下
T* tmp = new T[n];//开辟新空间
if (_start)//判断原vector是否为空
{
//如果原vector不为空,就将从_start位置开始的old*sizeof(T)个数据拷贝到新空间tmp里
memcpy(tmp, _start, old * sizeof(T));
delete[] _start;//释放旧空间
}
//再指向新空间
_start = tmp;
_finish = _start + old;
_endofstorage = _start + n;
}
}
完成了reserve函数,我们就可以复用reserve函数实现拷贝构造的现代写法:
//②现代写法
vector(const vector<T>& v)
{
reserve(v.capacity());//首先开一个与旧空间一样的新空间
for (const auto& e : v)//添加引用&,防止深拷贝
{
push_back(e);//复用push_back函数
}
}
6.resize(提前开空间并初始化函数实现)
使用resize开空间时,我们需要分3种情况:
①要开辟的空间小于原有的有效元素数,即:n<size(),那么这时我们就需要删除数据;
②要开辟的空间大于原有的有效元素数而小于原有的容量,即:size()<n<capacity(),那么这时我们就需要插入数据;
③要开辟的空间大于原有的容量,即:n>capacity(),那么这时我们就需要扩容+插入数据。
void resize(size_t n, T val = T())
{
if (n > size())//判断是否需要插入数据
{
reserve(n);//无关扩容,先调用reserve扩容
while (_finish < _start + n)
{
*_finish = val;
++_finish;
}
}
else//删除数据
{
_finish = _start + n;
}
}
对于resize()参数的说明:
val不传值时,整个resize函数处于半缺省状态,val默认传缺省值;
如果T是自定义类型,那么就去调用自定义类型的构造函数;
如果T是内置类型,为了兼容C++的模板,int的缺省值就是0,double的缺省值就是0.0,指针的缺省值就是空指针,以此类推。
7.erase函数
//erase函数
void erase(iterator pos)
{
assert(pos >= _start && pos < _finish);
iterator it = pos + 1;
while (it < _finish)
{
*(it - 1) = *it;
++it;
}
_finish--;
}
8.尾插尾删函数
//尾插函数
void push_back(const T& x)
{
if (_finish == _endofstorage)//判断vector有没有满
{
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;//如果满了就扩容
reserve(newcapacity);
}
//扩容之后赋值
*_finish = x;
++_finish;
}
//尾删函数
void pop_back()
{
assert(size() > 0);
--_finish;
}
9.insert指定位置插入实现
void insert(iterator pos, const T& x)//在pos位置之前插入x
{
assert(pos <= _finish && pos >= _start);//检查pos是否合法
//检查vector是否充满
if (_finish == _endofstorage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);//若充满则扩容
}
//把从pos位置开始,往后的sizeof(T) * (_finish - pos)个元素复制到pos+1位置
memmove(pos + 1, pos, sizeof(T) * (_finish - pos));
*pos = x;//挪动之后再把x放到pos位置
++_finish;
}
这段代码在实际运行时有时会出现错误,这是为什么呢?
原因是:
reserve(capacity() == 0 ? 4 : capacity() * 2);//若充满则扩容
观察reserve函数:不扩容还好,当发生扩容时,reserve会销毁旧空间
此时_start指向新空间,pos仍然指向旧空间,pos在扩容之后失效了
因此这里的解决办法就是计算偏移量,更新一下pos的值
正确代码:
void insert(iterator pos, const T& x)//在pos位置之前插入x
{
assert(pos <= _finish && pos >= _start);//检查pos是否合法
//检查vector是否充满
if (_finish == _endofstorage)
{
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);//若充满则扩容
pos = _start + len;
}
//把从pos位置开始,往后的sizeof(T) * (_finish - pos)个元素复制到pos+1位置
memmove(pos + 1, pos, sizeof(T) * (_finish - pos));
*pos = x;//挪动之后再把x放到pos位置
++_finish;
}
10.设计程序验证
void test_vector()
{
vector <int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
//迭代器访问
vector <int>::iterator it = v1.begin();
while (it != v1.end())
{
cout << *it << " ";
++it;
}
cout << endl;
//范围for访问
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
//下标+[]访问
for (size_t i = 0; i < v1.size(); i++)
{
cout << v1[i] << " ";
}
cout << endl;
}
源代码(vector.h)
#pragma once
#include <iostream>
#include <assert.h>
using namespace std;
namespace xxk
{
template<class T>
class vector
{
public:
typedef T* iterator;//普通迭代器 可读可写
typedef const T* const_iterator;//const迭代器 可读不可写
//构造函数
vector()
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{}
//拷贝构造
//①传统写法
//vector(const vector<T>& v)
//{
// _start = new T[v.capacity()];//先开空间
// memcpy(_start, v._start, v.size() * sizeof(T));
// _finish = _start + v.size();
// _endofstorage = _start + v.capacity();
//}
//②现代写法
vector(const vector<T>& v)
{
reserve(v.capacity());//首先开一个与旧空间一样的新空间
for (const auto& e : v)//添加引用&,防止深拷贝
{
push_back(e);//复用push_back函数
}
}
//赋值
vector<T>& operator=(vector<T> v)
//注意这里不能添加&引用,如v3=v1
//无&引用表示v是v1的临时拷贝,v3再拷贝v,出了作用域v就会被销毁,不会改变v1
//而加上&引用则直接表示v1与v3交换了,即可以认为这里实现了swap函数的功能
{
swap(v);//复用swap函数
return *this;
}
//析构函数
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
}
//迭代器begin()和end()实现
//普通迭代器 可读可写
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
//const迭代器 可读不可写
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
//size capacity函数
size_t size() const
{
return _finish - _start;
}
size_t capacity() const
{
return _endofstorage - _start;
}
//下标+[]访问函数的实现
//①可读可写
T& operator[](size_t pos)
{
assert(pos < size());//检查是否越界
return _start[pos];//返回pos位置字符的引用,既可读又可写
}
//②可读不可写
const T& operator[](size_t pos) const
{
assert(pos < size());//检查是否越界
return _start[pos];//返回pos位置字符的引用,既可读又可写
}
//reserve扩容函数
//error:
//void reserve(size_t n)
//{
// if (n > capacity())//如果原容量<需要的新空间,就开辟新空间
// {
// T* tmp = new T[n];//开辟新空间
// if (_start)//判断原vector是否为空
// {
如果原vector不为空,就将从_start位置开始的n*sizeof(T)个数据拷贝到新空间tmp里
// memcpy(tmp, _start, n * sizeof(T));
// delete[] _start;//释放旧空间
// }
// //再指向新空间
// _start = tmp;
// _finish = _start + size();
// //因为size()=_finish - _start;
// //所以_finish=_start+_finish-_start=_finish
// //可是这里的_start已经指向了新空间,而_finish指向的还是旧空间
// //一个空间的末尾 - 另一个空间的开头 ==== 错误!
// _endofstorage = _start + capacity();
// //同样的道理,capacity()还是旧空间,_start是新空间 也= 错误!
// //所以这里正确的做法应该是_endofstorage = _start + n;
// }
//}
void reserve(size_t n)
{
if (n > capacity())//如果原容量<需要的新空间,就开辟新空间
{
size_t old = size();//在扩容之前将size()先保存一下
T* tmp = new T[n];//开辟新空间
if (_start)//判断原vector是否为空
{
//如果原vector不为空,就将从_start位置开始的old*sizeof(T)个数据拷贝到新空间tmp里
memcpy(tmp, _start, old * sizeof(T));
delete[] _start;//释放旧空间
}
//再指向新空间
_start = tmp;
_finish = _start + old;
_endofstorage = _start + n;
}
}
//resize函数
void resize(size_t n, T val = T())
//val不传值时,整个resize函数处于半缺省状态,val默认传缺省值。
//如果T是自定义类型,那么就去调用自定义类型的构造函数;
//如果T是内置类型,为了兼容C++的模板,int的缺省值就是0,double的缺省值就是0.0,指针的缺省值就是空指针,以此类推
{
if (n > size())//判断是否需要插入数据
{
reserve(n);//无关扩容,先调用reserve扩容
while (_finish < _start + n)
{
*_finish = val;
++_finish;
}
}
else//删除数据
{
_finish = _start + n;
}
}
//erase函数
void erase(iterator pos)
{
assert(pos >= _start && pos < _finish);
iterator it = pos + 1;
while (it < _finish)
{
*(it - 1) = *it;
++it;
}
_finish--;
}
//尾插函数
void push_back(const T& x)
{
if (_finish == _endofstorage)//判断vector有没有满
{
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;//如果满了就扩容
reserve(newcapacity);
}
//扩容之后赋值
*_finish = x;
++_finish;
}
//尾删函数
void pop_back()
{
assert(size() > 0);
--_finish;
}
//指定位置插入(insert)函数
//error:
//void insert(iterator pos, const T& x)//在pos位置之前插入x
//{
// assert(pos <= _finish && pos >= _start);//检查pos是否合法
// //检查vector是否充满
// if (_finish == _endofstorage)
// {
//reserve(capacity() == 0 ? 4 : capacity() * 2);//若充满则扩容
观察reserve函数:不扩容还好,当发生扩容时,reserve会销毁旧空间
此时_start指向新空间,pos仍然指向旧空间,pos在扩容之后失效了
因此这里的解决办法就是计算偏移量,更新一下pos的值
// }
// //把从pos位置开始,往后的sizeof(T) * (_finish - pos)个元素复制到pos+1位置
// memmove(pos + 1, pos, sizeof(T) * (_finish - pos));
// *pos = x;//挪动之后再把x放到pos位置
// ++_finish;
//}
void insert(iterator pos, const T& x)//在pos位置之前插入x
{
assert(pos <= _finish && pos >= _start);//检查pos是否合法
//检查vector是否充满
if (_finish == _endofstorage)
{
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);//若充满则扩容
pos = _start + len;
}
//把从pos位置开始,往后的sizeof(T) * (_finish - pos)个元素复制到pos+1位置
memmove(pos + 1, pos, sizeof(T) * (_finish - pos));
*pos = x;//挪动之后再把x放到pos位置
++_finish;
}
//swap函数
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
private:
//全缺省为空
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _endofstorage = nullptr;
};
}