在这篇博客中,作者将会带领是简单的实现STL库中的vector。
一.vector的基本结构
vector是一个顺序存储的容器,也可以说是一个数组,那么对于它的结构,我们可以用三个迭代器来组成,如下图:
在vector当中,其实迭代器就是一个指针。 所以我们的vector类的基本成员变量可以这样设计。
template<class T>
class vector
{
public:
typedef T* iterator;//可读可写迭代器
typedef const T* const_iterator;//只可读迭代器
public:
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
};
二.Menmber functions的模拟实现
1.构造函数的模拟实现
构造函数最主要的有两个:无参构造函数和拷贝构造函数。
无参构造函数
无参构造函数只需要将全部迭代器赋值空即可。
//无参构造函数:直接全部给nullptr即可
vector()
:_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr)
{}
拷贝构造函数
拷贝构造的模拟实现可以通过一个个尾插的方式实现:
vector<int> v2(v1);
即将v1的数据从头到尾遍历一遍,遍历的时候顺便将v1的数据尾插到v2即可。
//拷贝构造函数
//vector<int> v1;
//vector<int> v2(v1);
vector(const vector<T>& v)
:_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr)
{
const_iterator move = v.begin();//定义一个迭代器指向v1的头
while (move != v.end())
{
push_back(*move);//将v1的数据一个个尾插到v2中,这里尾插在后面实现
move++;
}
}
2.析构函数
//析构函数
~vector()
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
3.赋值=重载
赋值=重载的实现可以先用v拷贝构造一个tmp的临时变量,在用这个临时变量去做交换。
//交换函数,用于交换两个vector
void Swap(vector<T>& v)
{
swap(_start, v._start);
swap(_finish, v._finish);
swap(_endofstorage, v._endofstorage);
}
//赋值=重载函数
//vector<int> v1;
//v1=v2;
vector<T>& operator=(const vector<T>& v)
{
if (this != &v)
{
vector<T> tmp(v);
Swap(tmp);
}
return *this;
}
二.Iterator的模拟实现
迭代器一般有四种:正向的、反向的、const的、非const的,但在这里暂时只实现正向、const、非const的。
1. 正向、反向非const迭代器
//begin迭代器
iterator begin()
{
return _start;
}
//end迭代器
iterator end()
{
return _finish;
}
2.正向、反向const迭代器
//const begin迭代器
const_iterator begin() const
{
return _start;
}
//const end迭代器
const_iterator end() const
{
return _finish;
}
三.Capacity的模拟实现
在这一部分中,我把最常用的模拟实现一下。
1.size、capacity函数
//获取数据个数
size_t size() const
{
return _finish - _start;
}
//获取容量
size_t capacity() const
{
return _endofstorage - _start;
}
2.resize和reserve函数
在这部分的模拟实现中,我们可以先实现reserve,这样在实现resize的时候可以去复用reserve。
reserve
//调整vector的容量
void reserve(const size_t n)
{
//如果要调整的容量大于原来的容量才做调整
if (n > capacity())
{
T* tmp = new T[n];//开辟一块新空间
T* tmp_start = _start;
T* tmp_move = tmp;
while (tmp_start != _finish)
{
*tmp_move++ = *tmp_start++;
}
//释放旧空间,并调整_start,_finish,_endofstorage的位置
_finish = tmp + size();
_endofstorage = tmp + n;
delete[] _start;
_start = tmp;
}
}
注意在调整_start,_finish,_endofstorage位置的时候,_start一定要在最后调整,否则会出现迭代器失效的问题,迭代器失效的问题会在最后讲解。
resize
//调整vector的数据个数
void resize(const size_t n, const T& val = T())//第二个参数为一个T类型的缺省值
{
if (n < size())
{
_finish = _start + n;
}
else if (n > size())
{
if (n > capacity())//如果调整后的n大于容量,则需要扩容
{
reserve(n);
}
iterator it1 = begin() + n;
iterator it2 = end();
while (it2 < it1)
{
push_back(val);//尾插val的值
it2++;
}
}
}
3.empty函数
//判断vector是否为空
bool empty() const
{
return !size();
}
四.Element access的模拟实现
在这部分中,同样的来实现一部分函数。
1.operator[] 和 at函数
operator[]和at的作用很相似,都是去获取某个位置的引用,所以我们只挑operator[]来实现
//重载operator[]
T& operator[](size_t pos)
{
assert(pos < size());
return *(_start + pos);
}
//重载operator[] const
const T& operator[](size_t pos) const
{
assert(pos < size());
return *(_start + pos);
}
2.front和back函数
front和back函数是去获取头元素和尾元素的引用。
//front函数
T& front()
{
assert(size() > 0);
return *_start;
}
//front函数 const
const T& front() const
{
assert(size() > 0);
return *_start;
}
//back函数
T& back()
{
assert(size() > 0);
return *(_start + size() - 1);
}
//back函数 const
const T& back() const
{
assert(size() > 0);
return *(_start + size() - 1);
}
五.Modifiers的模拟实现
在这部分函数的模拟实现中,我们可以先实现insert和erase,这样在实现尾插尾删时可以直接复用insert和erase。
1.insert函数
同样的,在实现这个函数的时候,扩容后,要更新pos的位置,否则会出现迭代器失效的问题,在最后面讲解。
insert的原理就是,将pos及其后面所有数据往后挪一个位置,再在pos的位置插入val。
//中间插入
iterator insert(iterator pos, const T& val)
{
assert(pos <= end());
//如果容量已满,则扩容
if (size() == capacity())
{
size_t n = pos - begin();//扩容之前需要记住pos对于_start的偏移量,以防出现迭代器失效问题
size_t newcapacity = size() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
pos = _start + n;//更新pos位置
}
//将pos位置及其后面所以数据往后挪动
iterator move = end();
while (move != pos)
{
*move = *(move - 1);
move--;
}
*pos = val;
_finish++;
return pos;
}
2.erase函数
//中间删除
iterator erase(iterator pos)
{
assert(pos < end());
iterator tmp = pos + 1;
while (tmp != end())//将pos后面所有数据往前挪一个位置
{
*(tmp - 1) = *tmp;
tmp++;
}
_finish--;
return pos;
}
3.push_back和pop_back函数
实现这两个函数时,我们直接复用insert和erase即可。
//尾部插入
void push_back(const T& val)
{
insert(end(), val);
}
//尾删
void pop_back()
{
erase(end() - 1);
}
4.swap和clear函数
//交换函数,用于交换两个vector
void Swap(vector<T>& v)
{
swap(_start, v._start);
swap(_finish, v._finish);
swap(_endofstorage, v._endofstorage);
}
//clear函数
void clear()
{
_finish = _start;
}
六.各种比较的实现
bool operator==(const vector<T>& v)
{
if (size() == v.size())
{
iterator it1 = begin();
iterator it2 = v.begin();
while (it1 < end() && it2 < v.end())
{
if (*it1 == *it2)
{
it1++;
it2++;
}
else
{
return false;
}
}
return true;
}
else
{
return false;
}
}
bool operator!=(const vector<T>& v)
{
return !(*this == v);
}
bool operator>(const vector<T>& v)
{
iterator it1 = begin();
iterator it2 = v.begin();
while (it1 < end() && it2 < v.end())
{
if (*it1 > *it2)
{
return true;
}
else if(*it1 < *it1)
{
return false;
}
else
{
it1++;
it2++;
}
}
if (it1 == end())
{
return false;
}
else
{
return true;
}
}
bool operator>=(const vector<T>& v)
{
return (*this > v || *this == v);
}
bool operator<(const vector<T>& v)
{
return !(*this >= v);
}
bool operator<=(const vector<T>& v)
{
return !(*this > v);
}
七.所有源代码
写到这里,vector的基本功能都已经实现了。
vector.h
#pragma once
#include<assert.h>
#include<iostream>
#include<vector>
namespace Vector_blog
{
template<class T>
class vector
{
public:
typedef T* iterator;//可读可写迭代器
typedef const T* const_iterator;//只可读迭代器
//begin迭代器
iterator begin()
{
return _start;
}
//end迭代器
iterator end()
{
return _finish;
}
//const begin迭代器
const_iterator begin() const
{
return _start;
}
//const end迭代器
const_iterator end() const
{
return _finish;
}
public:
//无参构造函数:直接全部给nullptr即可
vector()
:_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr)
{}
//拷贝构造函数
//vector<int> v1;
//vector<int> v2(v1);
vector(const vector<T>& v)
:_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr)
{
const_iterator move = v.begin();
while (move != v.end())
{
push_back(*move);
move++;
}
}
//析构函数
~vector()
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
//交换函数,用于交换两个vector
void Swap(vector<T>& v)
{
swap(_start, v._start);
swap(_finish, v._finish);
swap(_endofstorage, v._endofstorage);
}
//赋值=重载函数
//vector<int> v1;
//v1=v2;
vector<T>& operator=(const vector<T>& v)
{
if (this != &v)
{
vector<T> tmp(v);
Swap(tmp);
}
return *this;
}
//调整vector的数据个数
void resize(const size_t n, const T& val = T())//第二个参数为一个T类型的缺省值
{
if (n < size())
{
_finish = _start + n;
}
else if (n > size())
{
if (n > capacity())//如果调整后的n大于容量,则需要扩容
{
reserve(n);
}
iterator it1 = begin() + n;
iterator it2 = end();
while (it2 < it1)
{
push_back(val);
it2++;
}
}
}
//调整vector的容量
void reserve(const size_t n)
{
//如果要调整的容量大于原来的容量才做调整
if (n > capacity())
{
T* tmp = new T[n];//开辟一块新空间
T* tmp_start = _start;
T* tmp_move = tmp;
while (tmp_start != _finish)
{
*tmp_move++ = *tmp_start++;
}
//释放旧空间,并调整_start,_finish,_endofstorage的位置
_finish = tmp + size();
_endofstorage = tmp + n;
delete[] _start;
_start = tmp;
}
}
//判断vector是否为空
bool empty() const
{
return !size();
}
//尾部插入
void push_back(const T& val)
{
insert(end(), val);
}
//尾删
void pop_back()
{
erase(end() - 1);
}
//中间插入
iterator insert(iterator pos, const T& val)
{
assert(pos <= end());
//如果容量已满,则扩容
if (size() == capacity())
{
size_t n = pos - begin();//扩容之前需要记住pos对于_start的偏移量,以防出现迭代器失效问题
size_t newcapacity = size() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
pos = _start + n;//更新pos位置
}
//将pos位置及其后面所以数据往后挪动
iterator move = end();
while (move != pos)
{
*move = *(move - 1);
move--;
}
*pos = val;
_finish++;
return pos;
}
//中间删除
iterator erase(iterator pos)
{
assert(pos < end());
iterator tmp = pos + 1;
while (tmp != end())
{
*(tmp - 1) = *tmp;
tmp++;
}
_finish--;
return pos;
}
//获取数据个数
size_t size() const
{
return _finish - _start;
}
//获取容量
size_t capacity() const
{
return _endofstorage - _start;
}
//重载operator[]
T& operator[](size_t pos)
{
assert(pos < size());
return *(_start + pos);
}
//重载operator[] const
const T& operator[](size_t pos) const
{
assert(pos < size());
return *(_start + pos);
}
//front函数
T& front()
{
assert(size() > 0);
return *_start;
}
//front函数 const
const T& front() const
{
assert(size() > 0);
return *_start;
}
//back函数
T& back()
{
assert(size() > 0);
return *(_start + size() - 1);
}
//back函数 const
const T& back() const
{
assert(size() > 0);
return *(_start + size() - 1);
}
//clear函数
void clear()
{
_finish = _start;
}
bool operator==(const vector<T>& v)
{
if (size() == v.size())
{
iterator it1 = begin();
iterator it2 = v.begin();
while (it1 < end() && it2 < v.end())
{
if (*it1 == *it2)
{
it1++;
it2++;
}
else
{
return false;
}
}
return true;
}
else
{
return false;
}
}
bool operator!=(const vector<T>& v)
{
return !(*this == v);
}
bool operator>(const vector<T>& v)
{
iterator it1 = begin();
iterator it2 = v.begin();
while (it1 < end() && it2 < v.end())
{
if (*it1 > *it2)
{
return true;
}
else if(*it1 < *it1)
{
return false;
}
else
{
it1++;
it2++;
}
}
if (it1 == end())
{
return false;
}
else
{
return true;
}
}
bool operator>=(const vector<T>& v)
{
return (*this > v || *this == v);
}
bool operator<(const vector<T>& v)
{
return !(*this >= v);
}
bool operator<=(const vector<T>& v)
{
return !(*this > v);
}
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
};
//测试构造函数、拷贝构造、赋值=重载
void Test1()
{
vector<int> v1;
v1.insert(v1.end(), 1);
v1.insert(v1.end(), 2);
v1.insert(v1.end(), 3);
v1.insert(v1.end(), 4);
v1.insert(v1.end(), 5);
v1.insert(v1.end(), 6);
vector<int> v2(v1);
vector<int> v3;
v3 = v2;
vector<int>::iterator it1 = v1.begin();
while (it1 != v1.end())
{
cout << *it1 << " ";
it1++;
}
cout << endl;
vector<int>::iterator it2 = v2.begin();
while (it2 != v2.end())
{
cout << *it2 << " ";
it2++;
}
cout << endl;
vector<int>::iterator it3 = v3.begin();
while (it3 != v3.end())
{
cout << *it3 << " ";
it3++;
}
cout << endl;
}
//测试resize和reserve
void Test2()
{
vector<int> v1;
v1.insert(v1.end(), 1);
v1.insert(v1.end(), 2);
v1.insert(v1.end(), 3);
v1.insert(v1.end(), 4);
v1.reserve(10);
v1.resize(15, 20);
vector<int>::iterator it1 = v1.begin();
while (it1 != v1.end())
{
cout << *it1 << " ";
it1++;
}
cout << endl;
cout << v1.empty() << endl;
}
//测试operator[]
void Test3()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
for (int i = 0; i < v1.size(); i++)
{
cout << v1[i] << " ";
}
cout << endl;
cout << v1.front() << endl;
cout << v1.back() << endl;
}
//测试insert和erase
void Test4()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.insert(v1.begin() + 2, 50);
v1.erase(v1.begin());
v1.erase(v1.end() - 1);
v1.erase(v1.begin() + 1);
for (int i = 0; i < v1.size(); i++)
{
cout << v1[i] << " ";
}
cout << endl;
}
//
void Test5()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.clear();
for (int i = 0; i < v1.size(); i++)
{
cout << v1[i] << " ";
}
cout << endl;
}
}
vector.cpp
#include"vector.h"
int main()
{
Vector_blog::Test1();
Vector_blog::Test2();
Vector_blog::Test3();
Vector_blog::Test4();
Vector_blog::Test5();
return 0;
}
八.迭代器失效问题
迭代器失效问题就是当一个迭代器指向的数据不再是它初始化时所指向的数据时,称为迭代器失效。光说很难说通,所以举几个例子来演示。
首先声明,本博客所有代码都是在vs2022下操作的,在别的编译器下不一定会报错。
第一种迭代器失效
void Test_Vector10()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.reserve(10);
vector<int>::iterator it = v1.begin();
v1.insert(it, 10);
v1.insert(it, 20);
for (int i = 0; i < v1.size(); i++)
{
cout << v1[i] << " ";
}
cout << endl;
}
int main()
{
Test_Vector10();
return 0;
}
在这个代码中,it初始化时指向1这个元素,在it这个位置插入10后,it指向了10,此时,去遍历vector的时候,并不会报错,虽然it指向的元素变了,即失效了,但是我们并没有对它进行操作,而在第二次插入20时,就会报错,因为迭代器已经失效了,我们还对它进行了操作。
注意,这种情况在g++编译器下是不会报错的,而vs报错是因为编译器检测出来了迭代器失效。
那么对于这种情况如何解决呢?
insert这个函数会有一个iterator类型的返回值,他会返回一个迭代器指向新插入的元素。所以这样做即可。
void Test_Vector10()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.reserve(10);
vector<int>::iterator it = v1.begin();
it = v1.insert(it, 10);//使用insert后,给it赋上新值
it = v1.insert(it, 20);
for (int i = 0; i < v1.size(); i++)
{
cout << v1[i] << " ";
}
cout << endl;
}
第二种迭代器失效
void Test_Vector11()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
vector<int>::iterator it = v1.begin();
v1.push_back(5);
cout << *it << endl;
}
int main()
{
Test_Vector11();
return 0;
}
在上面这个代码中,it先指向1,后再插入5时,由于vector原来的容量已满,再插入5时,会去开空间,而it还是指向原来的旧空间,一解引用就会报错。
对于这种情况的解决办法时,进行了插入操作后,及时更新迭代器。
第三种迭代器失效
void Test_Vector12()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
vector<int>::iterator it = v1.begin();
v1.erase(it);
cout << *it << endl;
}
int main()
{
Test_Vector12();
return 0;
}
这种情况与第一种相似,it一开始指向1,把1删除后,it指向了2,指向的数据变了,所以迭代器失效了。
解决办法也是一样,更新it的值。
void Test_Vector12()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
vector<int>::iterator it = v1.begin();
it = v1.erase(it);//更新it的值
cout << *it << endl;
}
int main()
{
Test_Vector12();
return 0;
}
reserve函数迭代器失效问题
//调整vector的容量
void reserve(const size_t n)
{
//如果要调整的容量大于原来的容量才做调整
if (n > capacity())
{
T* tmp = new T[n];//开辟一块新空间
T* tmp_start = _start;
T* tmp_move = tmp;
while (tmp_start != _finish)
{
*tmp_move++ = *tmp_start++;
}
//释放旧空间,并调整_start,_finish,_endofstorage的位置
_finish = tmp + size();
_endofstorage = tmp + n;
delete[] _start;
_start = tmp;
}
}
扩容后,如果先调整_start,则_start会指向新空间,而_finish还在旧空间处,当给_finish重新赋值时,_finish=tmp+size(),由于size()是通过_finish-_start来计算的,但是此时_start和-_finish在不同的空间,一相减就会出错。
insert函数迭代器失效问题
//中间插入
iterator insert(iterator pos, const T& val)
{
assert(pos <= end());
//如果容量已满,则扩容
if (size() == capacity())
{
size_t n = pos - begin();//扩容之前需要记住pos对于_start的偏移量,以防出现迭代器失效问题
size_t newcapacity = size() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
pos = _start + n;//更新pos位置
}
//将pos位置及其后面所以数据往后挪动
iterator move = end();
while (move != pos)
{
*move = *(move - 1);
move--;
}
*pos = val;
_finish++;
return pos;
}
再扩容之前,pos指向旧空间的位置 ,扩容之后,pos指向一块被释放的旧空间,一访问就报错,所以扩容后,要更新pos的位置。