文章目录
- 前言
- 成员变量
- 成员函数
- vector ()
- size_t size()
- size_t capacity()
- iterator begin()和const_iterator begin()const
- iterator end()和const_iterator end()const
- ~vector()
- void push_back(const&T val)
- vector<T>(const vector<T>& v)
- vector<T>& operator=(vector<T> v)
- void swap(vector<T> &v)
- void reserve(size_t n)
- void resize(size_t n,const T&val=T( ))
- T& front() 和const T& front()const
- T& back()和const T& back() const
- void pop_back()
- iterator insert(iterator pos, const T& val)
- iterator erase(iterator pos)
- vector(InputIterator first, InputIterator last)
- 完整代码
- 总结
前言
在上篇文章中,我们介绍了vector的使用,在本篇文章中我们将会详细介绍一下vector的底层逻辑,我们会对vector有更深层次的理解
成员变量
在vector中,我们可以是任意类型,所以我们要采用灵活的方式。在C语言中,我们采用typedef的方式改变存储类型。在C++中进入了模版的概念,我们采用模版来实现。
在C语言板块,我们采用三个变量记录vector的变化,分别是int size,int capacity,int *a;
我们如果去查看vector的底层逻辑,我们就会发现,vector底层采用三个指针记录数据以及空间的变化,指针用迭代器的方式实现
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
private:
//内置成员变量声明给缺省值
iterator _start=nullptr;
iterator _finish=nullptr;
iterator _endofstorage=nullptr;
};
我们来简单介绍一下这几个指针:
_start是vector中元素初始位置
_finish是vector中元素的结束位置
_endofstorage是vector中的容量大小
侯捷老师《STL原码剖析》这本书画图的,书上的展示图如下:
成员函数
vector ()
我们在三个成员定义时已经进行了初始化操作
size_t size()
这个函数是用来计算vector中数据的大小
size_t size()const
{
return _finish - _start;
}
size_t capacity()
这个函数是用来计算vector中容量的大小
size_t capacity()const
{
return _endofstorage - _start;
}
iterator begin()和const_iterator begin()const
这个函数用来记录第一个元素的位置
iterator begin()
{
return _start;
}
const_iterator begin()const
{
return _start;
}
iterator end()和const_iterator end()const
这个函数用来记录最后一个元素的下一个位置
iterator end()
{
return _finish;
}
const_iterator end()const
{
return _finish;
}
~vector()
这个函数用来对空间进行清理
~vector()
{
if (_start)//判断是否有资源需要清理
{
delete[]_start;
_start = _finish = _endofstorage = nullptr;
}
}
void push_back(const&T val)
这个函数是用来在尾部插入数据
void push_back(const T& val)
{
//首先判断是否有空间,进行插入数据
if (_finish == _endofstorage)
{
//空间不足,需要开空间,提前计算好需要多大的空间
size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;
//我们一般采用reserve函数进行开空间
//因为这个函数是专门用来进行对空间的开辟的
reserve(newcapacity);
}
*_finish = val;
_finish++;
}
vector(const vector& v)
拷贝构造
vector<T>(const vector<T>& v)
{
//开空间+尾插
reserve(v.capacity());
for (auto& e : v)
{
push_back(e);
}
}
vector& operator=(vector v)
赋值功能
我们可以开辟一块空间,在一个个把值拷贝过去,再释放不用的空间,下面我们采取一种灵活的方式
我们已tmp[i]=_start[i]为例
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
vector v这个地方不能加引用!!!
我们来看一下具体的实现
🌟 在调用之前,会发生拷贝,拷贝一份相同的内容
🌟 把v和tmp进行交换,指针指向发生变化
🌟 v是局部变量,出了作用域进行销毁,完成深拷贝工作
我们不开空间而是直接构造
void swap(vector &v)
交换两个vector
void swap(vector<T> &v)
{
//仅仅通过交换两个指针的位置就可以解决
//必须指定是库里的,要不然就会发生死递归,最终栈溢出
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
void reserve(size_t n)
这个函数是用来对空间进行开辟的,开n个大小的空间
void reserve(size_t n)
{
//判断是否需要开空间
if (n > capacity())
{
//开空间
T* tmp = new T[n];
if (_start)
{
//拷贝之前的数据到新空间
memcpy(tmp, _start, sizeof(T) * n);
//释放旧空间
delete[]_start;
}
//更新指针
_start = tmp;
_finish = _start +size();
_endofstorage = _start + capacity();
}
}
我们来测试一下下面这段代码
void test1()
{
vector<int>v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
for (auto& e : v)
{
cout << e << " ";
}
cout << endl;
}
我们会发现,直接报错了,经过调试我们发现了错误所在
我们来画图看一看
我们发现,再对指针_finish更新时,不能用size(),因为之前的_start已经进行了更新,计算不到之前的数据个数了。
我们需要在进行更新指针之前,对这个size()进行备份,对于_endofstorage,容量也发生来更新,不能用原来的
void reserve(size_t n)
{
//判断是否需要开空间
if (n > capacity())
{
//提前记录数据个数
size_t old = size();
//开空间
T* tmp = new T[n];
//拷贝之前的数据到新空间
memcpy(tmp, _start, sizeof(T) * old);
//更新指针
_start = tmp;
_finish = _start + old;
_endofstorage = _start + n;
}
}
我们这里还有问题需要处理,memcpy拷贝呢是一个字节一个字节的拷贝,属于浅拷贝。
我们画个图来看一下
浅拷贝对于没有资源的类型是没有问题的,但是对于有资源的成员变量就有问题了,开辟新空间,拷贝数据,两块空间的内容都指向同一块空间,旧空间需要被释放,指向的那块空间就被释放掉了,新空间就没法玩了。
我们在处理vector中挪动数据的问题是不能用memcpy进行浅拷贝,我们应该开一块同一样的空间,把数据插入进去。
我们这里采取一种全新的方法
void reserve(size_t n)
{
//判断是否需要开空间
if (n > capacity())
{
size_t old = size();
//开空间
t* tmp = new t[n];
if (_start)
{
//拷贝之前的数据到新空间
//memcpy(tmp, _start, sizeof(t) * n);
for (int i = 0; i < size(); i++)
{
tmp[i] = _start[i];
}
//释放旧空间
delete[]_start;
}
//更新指针
_start = tmp;
_finish = _start + old;
_endofstorage = _start + n;
}
tmp[i] = _start[i];就可以完成我们的任务。
void resize(size_t n,const T&val=T( ))
这个函数用于对空间开辟+初始化
我们来看const T&val=T( )这个,这个其实就是缺省值
对于自定义类型会调用它的默认构造,那麽对于内置类型也要调用它的默认构造,这个是为了适应模板才加入的。
比如int默认为0,char默认为’/0‘,double默认为0.0等等
对于resize的大小,有三种情况:
🌟 size()<n<capacity,我们仅需要插入数据即可
🌟 size()>capacity(),我们先需要开空间,再进行数据的插入
🌟 size()>n,我们需要对数据进行删除
我们可以把前两种情况合并到一起,直接开空间
void resize(size_t n,const T&val=T( ))
{
//需要开空间
if (n >size())
{
reserve(n);
while (_finish < _start + n)
{
*_finish = val;
_finish++;
}
}
else
{
_finish = _start + n;
}
}
T& front() 和const T& front()const
获取vector中第一个元素
T& front()
{
return *_start;
}
const T& front()const
{
return *_start;
}
T& back()和const T& back() const
获取vector中最后有一个元素
T& back()
{
return *(_finish - 1);
}
const T& back() const
{
return *(_finish - 1);
}
void pop_back()
删除最后一个元素
void pop_back()
{
assert(size() > 0);
_finish--;
}
iterator insert(iterator pos, const T& val)
在pos位置插入数据val
void insert(iterator pos, const T& val)
{
//进行断言
assert(pos >= _start);
assert(pos <= _finish);
//先判断扩容
if (_endofstorage == _finish)
{
int len = pos - _start;
size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;
reserve(newcapacity);
pos = _start + len;
}
//挪动数据,必须从后往前挪
iterator end = _finish-1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
//插入数据
*pos = val;
_finish++;
}
我们来关注一下这段代码的实现
如果不加上的话程序会产生错误,就会发生迭代器失效
当程序发生扩容,原来的空间会进行销毁,vector中三个元素都会发生变化,而pos所处的位置不变,pos的那块空间被销毁,以后进行插入进找不到了。
在解决这类问题时:我们仅仅需要记录一下pos的位置就可以完成操作。
iterator insert(iterator pos, const T & val)
{
assert(pos >= _start);
assert(pos <= _finish);
//先判断扩容
if (_endofstorage == _finish)
{
int len = pos - _start;
size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;
reserve(newcapacity);
pos = _start + len;
return pos;
}
//挪动数据,必须从后往前挪
iterator end = _finish-1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
//插入数据
*pos = val;
_finish++;
}
iterator erase(iterator pos)
删除pos位置的元素,erase也可能会引发迭代器失效
void erase(iterator pos)
{
//断言检查
assert(pos >= _start);
assert(pos < _finish);
//有、数据移动
iterator it = pos + 1;
while (it < _finish)
{
*(it+1) = *it;
it++;
}
//删除数据
_finish--;
}
我们来看一下这段代码
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
print(v);
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
v.erase(it);
else {
++it;
}
}
}
这段代码其实是有问题的,如果erase发生缩容,pos是一个迭代器,位置就会发生变化,
有的编译器下size()减小就会发生缩容,那么空间就会发生变化,外边pos位置的值还是指向那块旧空间,我们再继续使用的话就会发生问题
我们画个图来看一下
我们在使用前,对迭代器进行重新赋值就可以
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator it = pos + 1;
while (it < _finish)
{
*(it-1) = *it;
it++;
}
_finish--;
return pos;
}
insert和erase形参pos都可能迭代器失效
原则是insert和erase过的迭代器不要进行使用。
迭代器的作用就是让算法不关心底层数据结构,但是底层迭代器就是一个指针,或者是对指针进行一定程度的封装。
比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃。
如果我们想要删除vector中的所有偶数,下面代码是否有问题??
int main()
{
vector<int> v{ 1, 2, 3, 4 };
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
v.erase(it);
++it;
}
return 0;
}
这也会造成迭代器失效的问题,我们需要进行修改
int main()
{
vector<int> v{ 1, 2, 3, 4 };
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
it = v.erase(it);
else
++it;
}
return 0;
}
vector(InputIterator first, InputIterator last)
一段迭代器区间初始化
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
指向连续物理空间的指针就是天然迭代器
int a[] = { 100, 200, 300 };
vector<int> v4(a, a+3);
for (auto e : v4)
{
cout << e << " ";
}
cout << endl;
完整代码
模板不能声明和定义在同一个文件中
namespace peng
{
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
//初始化列表初始化
vector()
{ }
size_t size()
{
return _finish - _start;
}
size_t capacity()
{
return _endofstorage - _start;
}
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin()const
{
return _start;
}
const_iterator end()const
{
return _finish;
}
~vector()
{
if (_start)
{
delete[]_start;
_start = _finish = _endofstorage = nullptr;
}
}
//拷贝构造
vector<T>(const vector<T>& v)
{
//开空间+尾插
reserve(capacity());
for (auto& e : v)
{
push_back(e);
}
}
//常见错误
//void reserve(size_t n)
//{
// //判断是否需要开空间
// if (n > capacity())
// {
// //开空间
// T* tmp = new T[n];
// if (_start)
// {
// //拷贝之前的数据到新空间
// memcpy(tmp, _start, sizeof(T) * n);
// //释放旧空间
// delete[]_start;
// }
// //更新指针
// _start = tmp;
// _finish = _start +size();
// _endofstorage = _start + capacity();
// }
//}
//void reserve(size_t n)
//{
// //判断是否需要开空间
// if (n > capacity())
// {
// size_t old = size();
// //开空间
// T* tmp = new T[n];
// if (_start)
// {
// //拷贝之前的数据到新空间
// memcpy(tmp, _start, sizeof(T) * n);
// //释放旧空间
// delete[]_start;
// }
// //更新指针
// _start = tmp;
// _finish = _start + old;
// _endofstorage = _start + n;
// }
//}
void reserve(size_t n)
{
//判断是否需要开空间
if (n > capacity())
{
size_t old = size();
//开空间
T* tmp = new T[n];
if (_start)
{
//拷贝之前的数据到新空间
//memcpy(tmp, _start, sizeof(t) * n);
for (size_t i = 0; i < size(); i++)
{
tmp[i] = _start[i];
}
//释放旧空间
delete[]_start;
}
//更新指针
_start = tmp;
_finish = _start + old;
_endofstorage = _start + n;
}
}
void push_back(const T & val)
{
//插入之前判断扩容
if (_finish == _endofstorage)
{
//计算开多大空间
size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;
reserve(newcapacity);
}
//插入数据
*_finish = val;
_finish++;
}
///* vector<T>& operator=(vector<T> v)
// {
// swap(v);
// return *this;
// }*/
T& operator[](size_t pos)
{
return _start[pos];
}
const T& operator[](size_t pos)const
{
return _start[pos];
}
void resize(size_t n, const T & val = T())
{
//需要开空间
if (n > size())
{
size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;
reserve(newcapacity);
//插入数据
while (_finish < _start + n)
{
*_finish = val;
*_finish++;
}
}
else
{
_finish = _start + n;
}
}
T& front()
{
return *_start;
}
T& back()
{
return *(_finish - 1);
}
const T& front()const
{
return *_start;
}
const T& back() const
{
return *(_finish - 1);
}
void pop_back()
{
assert(size() > 0);
_finish--;
}
iterator insert(iterator pos, const T & val)
{
assert(pos >= _start);
assert(pos <= _finish);
//先判断扩容
if (_endofstorage == _finish)
{
int len = pos - _start;
size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;
reserve(newcapacity);
pos = _start + len;
return pos;
}
//挪动数据,必须从后往前挪
iterator end = _finish-1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
//插入数据
*pos = val;
_finish++;
}
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
//vector<T>& operator=(vector<T> v)
//{
// //开空间+数据插入
// reserve(capacity());
// _start = new T[v.capacity()]; //这里要开辟容量的空间大小
// _finish = _start + v.size(); //指定里面存到的数据位置
// _endofstorage = _start + v.capacity();
// memcpy(_start, v._start, v.size() * sizeof(T)); //注意这里有一个Bug
// return *this;
//}
void swap(vector<T> &v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
//void erase(iterator pos)
//{
// assert(pos >= _start);
// assert(pos < _finish);
// iterator it = pos + 1;
// while (it < _finish)
// {
// *it = *(it + 1);
// it++;
// }
// _finish--;
//}
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator it = pos + 1;
while (it < _finish)
{
*(it-1) = *it;
it++;
}
_finish--;
return pos;
}
//template<class InputIterator>
//vector(InputIterator first, InputIterator last)
//{
// while (first != last)
// {
// push_back(*first);
// ++first;
// }
//}
vector(size_t n, const T& value = T())
{
resize(n,value);
}
private:
T* _start = nullptr;
T* _finish = nullptr;
T* _endofstorage = nullptr;
};
}
总结
以上就是今天要讲的内容,本文仅仅详细介绍了C++vector的模拟实现,希望对大家的学习有所帮助,仅供参考 如有错误请大佬指点我会尽快去改正 欢迎大家来评论~~ 😘 😘 😘