前几天写了一篇vector的使用,这次来讲一下vector的模拟实现。我们不仅要学习STL库的使用,还要了解它的底层,有助于我们更好的记忆和未来更好的学习。所以我们不能只停留在STL的使用层面上,今天带大家来走进vector的底层~
当然我们模拟实现vector并不是不是造更好的轮子,我们是为了了解它,学习它~
目录
成员变量定义
成员函数
vector()
vector(InputIterator first, InputIterator last)
vector(vector& v)
void swap(vector& v)
vector& operator=(vector v)
~vector()
const_iterator begin()const
const_iterator end()const
iterator begin()
iterator end()
T& operator[](size_t i)
const T& operator[](size_t i) const
size_t size()
size_t capacity()
void reserve(size_t n)
void resize(size_t n, const T& val = T())
iterator insert(iterator pos, const T& x)
iterator erase(iterator pos)
void push_back(const T& x)
void pop_back()
完整代码
vector.h
测试test.cpp
public:
typedef T* iterator;
typedef const T* const_iterator;
private:
iterator _start; //T* 可以修改不要 const_iterator
iterator _finish;
iterator _endofstorage;
这个和之前讲的string有些不一样,在这里使用指针来控制起始位置、最后一个数据位置、最大容量位置。那我们就简单看一下它的内存结构~
我这是参考侯捷老师《STL原码剖析》这本书画图的,书上的展示图如下:
vector()
:_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr)
{}
vector() //构造函数
:_start(nullptr) 初始化把成员变量置空,表示构造出来的对象刚开始还没有被使用
,_finish(nullptr)
,_endofstorage(nullptr)
{}
//一个类模板的成员函数,又可以是一个函数模板
template
vector(InputIterator first, InputIterator last)
:_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr)
{
while (first != last)
{
push_back(*first); //在这里不用考虑new多大空间
++first;
}
}
//一个类模板的成员函数,又可以是一个函数模板
template//InputItrtator是定义的一个迭代器类型
vector(InputIterator first, InputIterator last) //用迭代器进行构造函数
:_start(nullptr) //构造函数之前还是要把下面3个成员函数都置成nullptr,因为这里也是一个构造函数,调用了这个,那么上面的构造函数就不会走了。
,_finish(nullptr)
,_endofstorage(nullptr)
{
while (first != last) //当初始位置的迭代器不等于最后一个数据下一个位置的迭代器时,就继续走入循环。---> last代表最后一个数据的下一个位置的迭代器
{
push_back(*first); //在这里不用考虑new多大空间,push_back里面把细节解决过了。不断往要构造的对象里面插入数据。
++first; //first往后走,更新位置
}
}
//v2(v1) v2是未初始化的对象
//传统写法
vector(vector& v)
{
_start = new T[v.capacity()]; //这里要开辟容量的空间大小
_finish = _start + v.size(); //指定里面存到的数据位置
_endofstorage = _start + v.capavity();
memcpy(_start, v._start, v.size() * sizeof(T)); //注意这里有一个Bug
}
//v2(v1)
//现代写法
//vector(const vector& v)
vector(vector& v)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
vector tmp(v.begin(), v.end()); //调用构造函数
swap(tmp);
}
//v2(v1) v2是未初始化的对象
//传统写法
vector(vector& v)
{
_start = new T[v.capacity()]; //这里要开辟容量的空间大小
_finish = _start + v.size(); //指定里面存到的数据位置
_endofstorage = _start + v.capavity(); //vector开辟的空间大小memcpy(_start, v._start, v.size() * sizeof(T)); //注意这里有一个Bug 如果我们要修改的的话就用赋值的方式,string对象就会调用自己深拷贝赋值完成拷贝(这里博主就不实现了,大家可以尝试一下,如果没有想到的话可以私信我哦~)
如果vector里面存的是内置类型这种写法是没有问题的,但是对于自定义类型是不正确的。
memcpy是按照字节序拷贝,若果要拷贝的内容时string类的对象,那么每次拷贝都是把对应的_str拷贝过去了,这时候就存在一个问题,两个vector对象存了同一个在堆上开辟空间的地址。这就会造成在调用~vector()时会对同一块空间析构两次,这时候程序会崩溃!
-----------------------------------------------------------------------------------------------------------------------
//v2(v1)
//现代写法
//vector(const vector& v)
vector(vector& v)
:_start(nullptr) //构造前把三个成员变量进行置空初始化
, _finish(nullptr)
, _endofstorage(nullptr)
{
vectortmp(v.begin(), v.end()); //复用构造函数
swap(tmp);//将构造好的临时对象和要构造的对象进行交换(这里的swap是自己实现的,实际上就是改变指针的指向来控制资源的交换,并没有涉及深拷贝代价太大的问题)
}
void swap(vector& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
void swap(vector
& v)
{
std::swap(_start, v._start); //在这里的swap是库里面的swap,如果不指定std::或者::时,就会出现死递归的情况,因为swap(_start,v._start)的调用会就近原则,不断调用自己的swap,最后因为栈溢出导致程序崩溃。
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
//v1 = v3
//现代写法
//vector& operator=(vector v)
vector& operator=(vector v) //this不是临时对象
{
swap(v);
return *this;
}
//v1 = v3
//现代写法
//vector& operator=(vectorv)
vector& operator=(vector v) //this不是临时对象所以传引用返回。这里在传参的时候是复用了拷贝构造,所以这里v是深拷贝过的临时对象。
{
swap(v); 将被赋值的对象和新构造的临时对象进行swap交换资源,完之后v拿到了被赋值对象的_start、_finish、_endofstorage,这时候v出了该作用域就会调用自己的析构函数,完成对应的资源清理。(就好比这个临时对象就是干苦力的,假设v是一个外卖小哥,他辛苦的把饭带给客户(在这里就相当于swap交换资源)后,又被客户要求把他们家的垃圾袋待下去(在这里就相当于对v1内资源的清理)~)
return *this; //返回被赋值好后的对象
}
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
}
~vector() //析构函数
{
if (_start) //如果_start不为空时就开始进行下面的资源清理,当然这里不判断也是可以的
{
delete[] _start; //delete释放资源,析构函数(如果对内部有自定义类型而言,调用其内部的析构函数) + operator delete
_start = _finish = _endofstorage = nullptr; //三个变量置空
}
}
const_iterator begin()const //权限缩小
{
return _start;
}
const_iterator end()const //权限缩小
{
return _finish;
}
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
上面四个迭代器实现比较简单,在之前string模拟实现的博客中有详细介绍,所以在这里就不多费话了~
T& operator[](size_t i)
{
assert(i < size());
return _start[i];
}
T& operator[](size_t i)
{
assert(i < size()); //保证所用的下标有对应的数据
return _start[i]; //[]相当于解引用,返回的对象可读可写
}
const T& operator[](size_t i) const
{
assert(i < size());
return _start[i];
}
const T& operator[](size_t i) const
{
assert(i < size());
return _start[i]; //返回的对象是只读的,不可以被修改
}
size_t size()
{
return _finish - _start;
}
size_t size()
{
return _finish - _start; //指针 - 指针等于元素个数,对于下标控制问题,之前也在string的使用的find和substr功能时(可以去找找哦~),详细讲过类似的,如果是铁粉的话肯定知道的~
}
size_t capacity()
{
return _endofstorage - _start;
}
size_t capacity()
{
return _endofstorage - _start; //计算vector开辟的容量大小
}
void reserve(size_t n)
{
if (n > capacity())
{
size_t sz = size(); //提前记录原来空间sz的大小
T* tmp = new T[n];
if (_start) //有数据就拷贝
{
for (size_t i = 0; i < sz; i++)
{
tmp[i] = _start[i];
}
delete[] _start;
}
_start = tmp; //注意这个地方_start位置变了,不是原来的_start,size()、capacity() 已经失效了
_finish = _start + sz;
_endofstorage = _start + n;
}
}
void reserve(size_t n)
{
if (n > capacity()) //如果要开辟的空间大于当前容量就执行
{
size_t sz = size(); //提前记录原来空间sz的大小
T* tmp = new T[n]; //开辟空间
if (_start) //有数据就拷贝
{
for (size_t i = 0; i < sz; i++) //注意,拷贝数据不能memcpy!原因呢在上面讲构造函数时讲过了~
{
tmp[i] = _start[i]; //如果是像stack这样的类就是深拷贝
}
delete[] _start; //释放旧空间
}
_start = tmp; //注意这个地方_start位置变了,不是原来的_start,size()、capacity() 已经失效了,在这个在下面就不能在使用size()、capacity()了。
_finish = _start + sz; //sz已经提前保存过了
_endofstorage = _start + n; //n代表实际能存数据的个数(容量)
}
}
void resize(size_t n, const T& val = T())
{
if (n < size())
{
_finish = _start + n; //截取数据
}
else
{
if (n > capacity()) //要扩的空间大于当前空间 --- 扩容
{
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; //截取数据
}
else//要扩的空间(n)大于当前数据个数(size())
{
if (n > capacity()) //要扩的空间(n)大于当前空间(capacity()) --- 扩容
{
reserve(n);
}
while (_finish != _start + n) //直到第n个数据就停止
{
*_finish = val; //上面扩过容的,所以可以这么写,不用考虑越界问题
++_finish;
}
}
}
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start);
assert(pos <= _finish);
//满了就扩容
if (_finish == _endofstorage)
{
//扩容会导致pos失效,扩容需要更新一下pos
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + len;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end; //不能使用_start[end]...
end--;
}
*pos = x;
++_finish;
return pos;
}
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start);
assert(pos <= _finish);//满了就扩容
if (_finish == _endofstorage)
{
//扩容会导致pos失效,扩容需要更新一下pos
size_t len = pos - _start; //提前记录pos在原来空间中是第几个数据
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + len; //在新的空间中pos映射到对应的第len个数据
}
iterator end = _finish - 1;
while (end >= pos) //挪动数据,之前在讲string模拟实现也详细讲过了(大致类似)~
{
*(end + 1) = *end; //不能使用_start[end]...
end--;
}
*pos = x;
++_finish;
return pos;//返回新插入数据的迭代器,注意pos是形参,上面的pos = _start + len并没有改变pos!!!
}
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator end = pos + 1;
while (end < _finish)
{
*(end - 1) = *end;
end++;
}
--_finish;
return pos;
}
void push_back(const T& x)
{
if (_finish == _endofstorage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = x;
++_finish;
}
void pop_back()
{
assert(_start < _finish);
--_finish;
}
void pop_back()
{
assert(_start < _finish);
--_finish; //直接把最后一个数据的指向改变就行了(_finish指向最后一个数据的下一个位置的迭代器)
}
在这里vector模拟实现放在了cyq的命名空间中,是为了防止命名冲突。
#pragma once
#include
#include
#include
using namespace std;
namespace cyq
{
template
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
vector()
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{}
//v2(v1) v2是未初始化的对象
//传统写法
//vector(vector& v)
//{
// _start = new T[v.capacity()]; //这里要开辟容量的空间大小
// _finish = _start + v.size(); //指定里面存到的数据位置
// _endofstorage = _start + v.capavity();
// memcpy(_start, v._start, v.size() * sizeof(T)); //注意这里有一个Bug
//}
//一个类模板的成员函数,又可以是一个函数模板
template
vector(InputIterator first, InputIterator last)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
while (first != last)
{
push_back(*first); //在这里不用考虑new多大空间
++first;
}
}
void swap(vector& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
//v2(v1)
//现代写法
//vector(const vector& v)
vector(vector& v)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
vector tmp(v.begin(), v.end()); //调用构造函数
swap(tmp);
}
//v1 = v3
//现代写法
//vector& operator=(vector v)
vector& operator=(vector v) //this不是临时对象
{
swap(v);
return *this;
}
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
}
const_iterator begin()const //权限缩小
{
return _start;
}
const_iterator end()const //权限缩小
{
return _finish;
}
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
T& operator[](size_t i)
{
assert(i < size());
return _start[i];
}
const T& operator[](size_t i) const
{
assert(i < size());
return _start[i];
}
size_t size()
{
return _finish - _start;
}
size_t capacity()
{
return _endofstorage - _start;
}
void reserve(size_t n)
{
if (n > capacity())
{
size_t sz = size(); //提前记录原来空间sz的大小
T* tmp = new T[n];
if (_start) //有数据就拷贝
{
for (size_t i = 0; i < sz; i++)
{
tmp[i] = _start[i];
}
delete _start;
}
_start = tmp; //注意这个地方_start位置变了,不是原来的_start,size()、capacity() 已经失效了
_finish = _start + sz;
_endofstorage = _start + n;
}
}
void resize(size_t n, const T& val = T())
{
if (n < size())
{
_finish = _start + n; //截取数据
}
else
{
if (n > capacity()) //要扩的空间大于当前空间 --- 扩容
{
reserve(n);
}
while (_finish != _start + n)
{
*_finish = val; //上面扩过容的,所以可以这么写
++_finish;
}
}
}
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start);
assert(pos <= _finish);
//满了就扩容
if (_finish == _endofstorage)
{
//扩容会导致pos失效,扩容需要更新一下pos
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + len;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end; //不能使用_start[end]...
end--;
}
*pos = x;
++_finish;
return pos;
}
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator end = pos + 1;
while (end < _finish)
{
*(end - 1) = *end;
end++;
}
--_finish;
return pos;
}
void push_back(const T& x)
{
if (_finish == _endofstorage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = x;
++_finish;
}
void pop_back()
{
assert(_start < _finish);
--_finish;
}
private:
iterator _start; //T* 可以修改不要 const_iterator
iterator _finish;
iterator _endofstorage;
};
}
void TestVector()
{
cyq::vector v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.push_back(6);
for (size_t i = 0; i < v.size(); i++)
{
cout << v[i] << " ";
}
cout << endl;
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
cout << "capacity: " << v.capacity() << endl;
v.erase(v.begin());
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
v.insert(v.begin()+2,40);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
v.resize(20, 10);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
v.reserve(30);
cout << v.capacity() << endl;
cyq::vector::iterator it = v.begin();
//auto it = v.begin();
while (it != v.end())
{
cout << *it << " ";
it++;
}
cout< v1(v.begin(),v.end());
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
cyq::vector v2(v);
v2.push_back(100);
v2.push_back(100);
v2.pop_back();
for (auto e : v2)
{
cout << e << " ";
}
}
int main()
{
TestVector();
return 0;
}
运行结果:
谢谢观看,看到这里希望大家给个一键三连支持~