一.物理框架
通过上次vector的使用的学习
我们知道vector也是顺序存储,即使用连续的地址空间存储数据,设计原理同string,但vector是通过迭代器表示开始,结束,容量
以下是SGI的STL的部分源码
vector使用类模板,可接收,适配不同数据类型,同样适配自定义类型
iterator是封装的接收的数据的指针
二.成员变量
对标STL标准库,我们也使用这样的指针,但仅简单实现就好
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
vector()
:_start(nullptr)
, _finish(nullptr)
,_end_of_storage(nullptr)
{}
~vector()
{
delete[] _start;
_start = nullptr;
_finish = nullptr;
_end_of_storage = nullptr;
}
private:
T*_start;
T*_finish;
T*_end_of_storage;
};
typedef T*iterator要定义在public中,因为类外需要直接使用迭代器
_start,_finish,_end_of_storage均定义在类内
注意:因为存在const引用传参,所以还需要const的迭代器,并且相关函数还需要重载const this指针
三.数据存取
因为是顺序存储,所以在我们插入数据时,仍需考虑是否需要扩容。
而扩容则需要获取当前的大小和容量:通过迭代器区间获取
int size() const
{
return _finish - _start;
}
int capacity() const
{
return _end_of_storage - _start;
}
注意:size和capacity需要外加const,因为若是const T&x,x需要调用这两个函数,就会因为权限扩大而无法使用
- 接下来是扩容函数–reserve
//扩容
void reserve(size_t n)
{
if (n > capacity())
{
T*tmp = new T[n];
size_t sz = size();
//原来有数据才拷贝
if (_start)
{
memcpy(tmp, _start, sizeof(T)*size());
delete[] _start;
}
_start = tmp;
//不可以_finish=_start+size();
//因为_start已经指向新空间,_finish还指向旧空间
_finish = _start + sz;
_end_of_storage = _start + n;
}
}
注意:不可以_finish=_start+size();
因为_start已经指向新空间,_finish还指向旧空间
size的_finish-_start已不是原来的连续空间的大小了
- 尾插–push_back较为简单,直接上代码
void push_back(const T&x)
{
//满了需要扩容
if (_finish == _end_of_storage)
{
//不可直接reserve(capacity()*2);
//因为capacity()最初是0
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = x;
++_finish;
}
因为是自定义类型,所以数据的访问需要重载[ ]
同时还有const对象
T& operator[](size_t i)
{
return _start[i];
}
const T& operator[](size_t i) const
{
assert(i < size());
return _start[i];
}
- 测试
void vector_Test1()
{
vector<int>v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
for (size_t i = 0; i < v1.size(); i++)
{
cout << v1[i] << " ";
}
cout << endl;
}
- 访问我们同样还可以使用迭代器和更为简洁的范围for
同样也要适配const对象
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
void vector_Test1()
{
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 (auto e : v1)
{
cout << e << " ";
}
cout << endl;
}
- resize调整大小
//调整大小
void resize(size_t n,T val=T())
{
if (n < size())
{
//删除数据
_finish = _start + n;
}
else
{
//扩容
if (n > capacity())
{
reserve(n);
}
//初始化
while (_finish != _start + n)
{
*_finish = val;
++_finish;
}
}
}
- pop_back删除
因为内部是指针,所以删除需要判断是否为空,为空则直接报错
bool empty()
{
return _start == _finish;
}
void pop_back()
{
assert(!empty());
--_finish;
}
- insert–指定位置插入
void insert(iterator pos, const T&val)
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _end_of_storage)
{
//记录pos的相对l位置
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : 2 * capacity());
//更新pos位置,防止pos迭代器失效
pos = _start + len;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = val;
++_finish;
}
迭代器失效
注意:因为可能会扩容,一旦扩容,_start等迭代器会更新,但是pos仍指向原来的区域,会出现pos迭代器失效的问题,直接像string那样移动数据会有无法预料的错误
所以,我们需要记录原pos相对_start的位置,然后再扩容后,更新pos指向的位置
但是
vector的insert是有支持返回值是迭代器的版本的?
这是什么意思呢
小场景
void vector_Test3()
{
vector<int>v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
fun(v1);
v1.insert(v1.begin(), 30);
auto pos = find(v1.begin(), v1.end(), 2);
if (pos != v1.end())
{
v1.insert(pos, 666);
}
//如果我们还要再外部修改pos指向的值呢?
(*pos)++;
fun(v1);
}
如果我们还要修改在外部的pos位置呢
那当然是不行的,因为如果发生扩容,外部的pos仍指向原来空间。函数内部的pos因为是值传递,形参改变不会影响实参。
那我们能在参数列表里修改为引用吗?
答案也是不行的
因为有这样的插入方式,而begin是传值返回,是创建临时对象,有常量性,不能用引用接收
所以有返回iterator的函数类型
所以完整insert代码如下
iterator insert(iterator pos, const T&val)
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _end_of_storage)
{
//记录pos位置
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : 2 * capacity());
//更新pos位置,防止pos迭代器失效
pos = _start + len;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = val;
++_finish;
return pos;
}
- erase
void erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator start = pos + 1;
while (start != _finish)
{
*(start - 1) = *start;
start++;
}
_finish--;
}
erase理论上不会出现迭代器失效的情况,因为删除并不会改变存储地址
但是vs的pj版的STL内部实现和我们实现的并不相同,并且有严格的检查(为了防止越界访问,如删除最后一个数据,但却还通过迭代器访问)
g++内部是原生指针,并不会报错
小场景
比如我们再vector内插入1,2,3,4,5然后删除其中的偶数
我们先用std库里的看看结果
首先是VS下
void vector_Test4()
{
std::vector<int>v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
std::vector<int>::iterator it = v1.begin();
while (it != v1.end())
{
if (*it % 2 == 0)
{
v1.erase(it);
}
else
{
it++;
}
}
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
}
理论而言,如果是我们自己的实现的话,应该没有问题,但是VS进行了强制的检查,认为只要调用了erase,相应的迭代器就失效
Linux下
删除成功了
因为g++所采用的SGI版本的STL,内部实现迭代器也是使用原生指针,所以检查不严格。
VS的强制检查会更合理,因为我们可能会进行尾删,那么迭代器指向的就是尾删的数据,按理来说,我们不应该还能访问到删除的数据,但是erase后迭代器依旧使用,我们就可以访问。所以VS的强制检查会更加合理
四.构造
PS: explict是不允许隐式类型转换
函数定义 | 函数说明 |
---|---|
vector(size_type n,const value_type&val=value_type()) | n个val初始化 |
vector(InputIterator first,InputItreator last) | 迭代器区间初始化 |
vector(const vector& x) | 拷贝构造 |
n个val初始化
匿名对象
匿名对象生命周期只在当前一行,因为这行之后没有会用他了。
class A
{
public:
A()
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
};
int main()
{
A a1;
cout << endl;
A();
cout << endl;
A a2;
return 0;
}
但const引用会延长匿名对象的生命周期,延长到引用对象域的结束,因为以后用引用就是用匿名对象了
int main()
{
A a1;
cout << endl;
const A&a = A();
cout << endl;
A a2;
return 0;
}
实现
//n个val初始化
//使用匿名对象作缺省参数
vector(size_t n, const T&val = T())
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
reverse(n);
for (int i = 0; i < n; i++)
{
push_back(val);
}
}
迭代器区间
为了适配各种数据类型的迭代器,所以迭代器区间的初始化需要使用函数模板。
类模板允许内部继续使用函数模板
//迭代器区间初始化
template<class InputIterator>
vector(InputIterator first, InputIterator last)
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
while (first != last)
{
push_back(*first);
++first;
}
}
小优化
我们发现有参构造,n个val构造,迭代器区间构造,我们是不是都使用了初始化列表
那么我们可以使用C++11的新语法简化我们的代码。
因为_start,_finish,_end_of_storage都是内置类型,所以我们可以在定义的时候给值,该值系统会在初始化列表使用
private:
T*_start=nullptr;
T*_finish=nullptr;
T*_end_of_storage=nullptr;
小问题
当我们测试上述两个构造时,我们发现报错了
并且报错位置在迭代器区间处
这是为什么呢?
原来,我们查看n个val初始化的参数列表,第一个参数的类型是size_t的,而迭代器区间是两个相同的参数,那么我们传过去的10,5都是 int,会优先匹配迭代器区间构造的函数,然后内部的*first就报错了,因为匹配后InputItera是int类型,int类型怎么能解引用呢?
SGI的STL的解决办法就是再重载n个val构造函数
//int的重载
vector(int n, const T&val = T())
{
reserve(n);
for (int i = 0; i < n; i++)
{
push_back(val);
}
}
测试
void vector_Test5()
{
//10个5构造
vector<int>v1(10, 5);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
//迭代器区间构造
vector<int>v2(v1.begin()+1, v1.end()-1);
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
//string的迭代器构造
std::string s1("hello");
vector<int>v3(s1.begin(), s1.end());
for (auto e : v3)
{
cout << e << " ";
}
cout << endl;
//数组的指针构造
int a[] = { 10,20,30 };
vector<int>v4(a, a + 3);
for (auto e : v4)
{
cout << e << " ";
}
cout << endl;
}
五.构造函数–深拷贝
我们首先试着写深拷贝并测试
- 代码
//拷贝构造--深拷贝
vector(const vector<T>&v)
{
reserve(v.capacity());
memcpy(_start, v._start, sizeof(T)*v.size());
_finish = _start + v.size();
}
- 测试
void vector_Test6()
{
vector<int>v1(10, 5);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
vector<int>v2(v1);
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
}
- 运行结果
看样子,好像没问题,也就这样,但问题真的解决了吗?
我们不妨再测试一下
我们使用string的vector
int main()
{
string s1("hello");
vector<string>v3(3, s1);
for (auto e : v3)
{
cout << e << " ";
}
cout << endl;
vector<string>v4(v3);
for (auto e : v4)
{
cout << e << " ";
}
cout << endl;
}
报错了。
为什么呢?
通过调试,我们发现是在析构函数程序崩溃的。这时我们发现,还是深浅拷贝的问题,对相同空间重复释放了
虽然vector的_start所指向的空间是独立的,但是因为memcpy是按字节拷贝,内部的string的_str指向还是同样的空间。这样在程序结束时,会先调用string的析构函数,再调用vector的析构函数,那这样string的空间重复释放,就报错了
那我们使用operator=赋值构造,因为string的operator=是深拷贝,这样string所指空间就不会相同了
//拷贝构造--深拷贝
vector(const vector<T>&v)
{
reserve(v.capacity());
//memcpy(_start, v._start, sizeof(T)*v.size());
for (int i = 0; i < v.size(); i++)
{
_start[i] = v._start[i];
}
_finish = _start + v.size();
}
但是,回看我们写的扩容的函数,其内部是不是也使用了memcpy,那么扩容时也会造成浅拷贝,而重复析构空间崩溃
所以我们进行以下调整
//扩容
void reserve(int n)
{
if (n > capacity())
{
T*tmp = new T[n];
size_t sz = size();
//原来有数据才拷贝
if (_start)
{
//memcpy(tmp, _start, sizeof(T)*size());
for (size_t i = 0; i < sz; i++)
{
tmp[i] = _start[i];
}
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_end_of_storage = _start + n;
}
}
但是其实问题还没有完全解决,已经解决了99%了
剩下的问题是vector内部套用vector
int main()
{
vector<int>v1(3, 3);
vector<vector<int> >vv1(3,v1);
for (auto e : vv1)
{
for (auto x : e)
{
cout << x << " ";
}
cout << endl;
}
cout << endl;
return 0;
}
在即将return 0结束程序时,vv1的值成功被打印出来了,但是一return 0调用析构函数,程序又崩溃了? 因为上述的operator=解决了其他STL的问题,因为他们内部提供了深拷贝的opeartor=,但是我们自己写的vector还没有编写深拷贝的operator=,所以系统调用默认的operator=,其是浅拷贝
所以我们接下来完成一下operator=的编写
operator=
//拷贝构造——深拷贝
vector(const vector<T>&v)
{
reserve(v.capacity());
//memcpy(_start, v._start, sizeof(T)*v.size());
for (int i = 0; i < v.size(); i++)
{
_start[i] = v._start[i];
}
_finish = _start + v.size();
}
//swap封装函数
void swap(vector<T> v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
//operator=重载
vector<T>operator=(vector<T> v)
{
swap(v);
return *this;
}
我们要拷贝vector<vector< int> >,首先外层的vector,是深拷贝,是新开辟的空间,然后
_start[ i ]实际指向内部的vector< int>
,此时,我们调用operator=,此时opeartor的参数是传值,会调用拷贝构造,但是是vector< int>的深拷贝,因为其调用的拷贝构造内部的_start指向的int
,直接浅拷贝值,vector< int>是新开辟的空间,然后operator=内部再调用swap,将传值拷贝构造的新空间和我们要深拷贝的vector的指针交换
,就实现深拷贝了
以此基础,我们可以再将拷贝构造改造一下。我们复用迭代器区间构造
//拷贝构造--深拷贝
vector(const vector<T>&v)
{
vector<T>tmp(v.begin(), v.end());
swap(tmp)
}
完整代码
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
vector()
{}
//n个val初始化
vector(size_t n, const T&val = T())
{
reserve(n);
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
//int类型的重载
vector(int n, const T&val = T())
{
reserve(n);
for (int i = 0; i < n; i++)
{
push_back(val);
}
}
//迭代器区间初始化
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
//拷贝构造--深拷贝
vector(const vector<T>&v)
{
vector<T>tmp(v.begin(), v.end());
swap(tmp)
}
//swap封装函数
void swap(vector<T> v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
//operator=重载
vector<T>operator=(vector<T> v)
{
swap(v);
return *this;
}
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
int size() const
{
return _finish - _start;
}
int capacity() const
{
return _end_of_storage - _start;
}
bool empty()
{
return _start == _finish;
}
void pop_back()
{
assert(!empty());
--_finish;
}
void push_back(const T&x)
{
//满了需要扩容
if (_finish == _end_of_storage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = x;
++_finish;
}
//扩容
void reserve(int n)
{
if (n > capacity())
{
T*tmp = new T[n];
size_t sz = size();
//原来有数据才拷贝
if (_start)
{
for (size_t i = 0; i < sz; i++)
{
tmp[i] = _start[i];
}
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_end_of_storage = _start + n;
}
}
//调整大小
void resize(size_t n,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&val)
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _end_of_storage)
{
//记录pos位置
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : 2 * capacity());
//更新pos位置,防止pos迭代器失效
pos = _start + len;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = val;
++_finish;
return pos;
}
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator start = pos + 1;
while (start != _finish)
{
*(start - 1) = *start;
start++;
}
_finish--;
return pos;
}
T& operator[](int i)
{
assert(i < size());
return _start[i];
}
const T& operator[](int i) const
{
assert(i < size());
return _start[i];
}
~vector()
{
delete[] _start;
_start = nullptr;
_finish = nullptr;
_end_of_storage = nullptr;
}
private:
T*_start=nullptr;
T*_finish=nullptr;
T*_end_of_storage=nullptr;
};
结束语
vector的模拟实现基本结束了。
文章如果有不对或者不足的地方,欢迎大佬们指正,补充。感谢大家的阅读,如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下,阿里嘎多。拜托了,这对我真的很重要~