文章目录
前言
模拟实现不是为了做出更好的轮子,而是为了在使用STL的时候做到心中有数
1.vector模拟实现
1.1.vector的成员变量
在vector类的成员变量中,和普通的模拟顺序表不同的是,顺序表使用一个指针指向动态开辟的空间,还有两个变量_size()
和_capacity()
来保存顺序表中的元素的个数和可用空间的大小。而在vector中是使用三个指针来控制这一切的。
下图为 《STL源码剖析》 中的图片:
所以可以知道vector的成员变量为_start
、_finish
和_endofstorage
。
template <class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
private:
iterator _start; // 指向使用空间的开头
iterator _finish; // 指向使用空间的结尾
iterator _endOfStorage;// 指向可用空间的结尾
}
1.1.vector成员函数实现
构造函数
// 构造函数
vector() :
_start(nullptr),
_finish(nullptr),
_endOfStorage(nullptr)
{}
// 因为是简易版的实现,所以并没有给容器带上内存分配器
vector(int n, const T& val = T()) :
_start(nullptr),
_finish(nullptr),
_endOfStorage(nullptr)
{
reserve(n);
for (size_t i = 0; i < n; i++)
{
(*_finish++) = val;
}
}
template<class InputIterator>
vector(InputIterator first, InputIterator last) :
_start(nullptr),
_finish(nullptr),
_endOfStorage(nullptr)
{
reserve(last - first);
while (first != last)
{
push_back(*first);
first++;
}
}
拷贝构造函数
在拷贝构造的过程中,将v向量的内容复制一份给*this
的时候切记不能使用memcpy
,因为这样就不能实现深层次的深拷贝了,这其中的原理会在最后总结模拟实现问题的时候分析。
// 拷贝构造
vector(const vector<T>& v) :
_start(nullptr),
_finish(nullptr),
_endOfStorage(nullptr)
{
size_t n = v.capacity();
reserve(n);
// 这里使用memcpy会导致不能深层次的深拷贝
//memcpy(_start, v._start, sizeof(T)*n);
for (size_t i = 0; i < v.size(); i++)
{
_start[i] = v[i];
}
_finish = _start + v.size();
_endOfStorage = _start + v.capacity();
}
下面这种拷贝构造是复用了push_back()的接口
//vector(const vector<T>& v) :
// _start(nullptr),
// _finish(nullptr),
// _endOfStorage(nullptr)
//{
// const_iterator tmp = v.beign();
// while (tmp != v.end())
// {
// push_back(*tmp);
// tmp++;
// }
//}
赋值运算符重载
vector<T>& operator= (vector<T> v)// 现代写法,直接利用拷贝构造函数,深拷贝复制一个对象
{
swap(v);
return *this;
}
下面这个版本可以避免对象赋值给自己
因为通常情况下,不会将对象赋值给自己,所以也可以写上面的版本不用判断this!=&v
//vector<T>& operator= (const vector<T>& v)
//{
// if (this != &v)
// {
// vector<T> tmp(v);
// swap(tmp);
// }
// return *this;
//}
void swap(vector<T>& v)
{
std::swap(v._start, _start);
std::swap(v._finish, _finish);
std::swap(v._endOfStorage, _endOfStorage);
}
析构函数
// 析构函数
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _endOfStorage = nullptr;
}
}
1.2.vector常用内置函数实现
添加元素
push_back()
void push_back(const T& val)
{
if (_finish == _endOfStorage)
{
size_t newCapacity = capacity() * 2 == 0 ? 4 : capacity() * 2;
reserve(newCapacity);
}
*_finish = val;
_finish++;
}
insert()
iterator insert(iterator pos, const T& val)
{
if (_finish == _endOfStorage)
{
size_t newCapacity = capacity() * 2 == 0 ? 4 : capacity() * 2;
reserve(newCapacity);
}
iterator tmp = _finish;
while (tmp != pos)
{
*tmp = *(tmp - 1);
tmp--;
}
*pos = val;
_finish++;
return pos;
}
删除元素
pop_back()
void pop_back()
{
assert(_start != _finish);
--_finish;
}
erase()
iterator erase(iterator pos)
{
assert(pos > 0 && _start != _finish);
iterator tmp = pos;
while (tmp != _finish)
{
*tmp = *(tmp + 1);
tmp++;
}
_finish--;
return pos;
}
查询元素
// 可用空间大小
size_t capacity() const
{
return _endOfStorage - _start;
}
// 已经使用空间大小
size_t size() const
{
return _finish - _start;
}
// 尾部元素
T& back()
{
return *(_finish - 1);
}
// 头部元素
T& front()
{
return *(_start);
}
const T& back() const
{
return *(_finish - 1);
}
const T& front() const
{
return *_start;
}
// 迭代器的头
iterator begin()
{
return _start;
}
// 迭代器的尾
iterator end()
{
return _finish;
}
// const对象不能调用非const成员函数
// 但是可以调用const成员函数
// 所以可以用cosnt成员函数,返回const_iterator的方式来限制迭代器不可以修改
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
改动空间
reserve()
动态的开辟空间分为3步走:
1.开辟新空间
2.将原空间的内容复制一份到新空间中
3.指针指向新空间,并释放原空间
其中复制一份的过程切记不能使用memcpy
,因为这样就不能实现深层次的深拷贝了,这其中的原理会在最后总结模拟实现问题的时候分析。
void reserve(size_t n)
{
if (n > capacity())
{
size_t sz = size();// 元素的个数
T* tmp = new T[n];
// 这里的memcpy会造成深层次的浅拷贝
// memcpy(tmp, _start, size(T)*size());
for (size_t i = 0; i < sz; i++)
{
tmp[i] = _start[i];
}
delete[] _start;
_start = tmp;
//_finish = _start + size();// 此时的size()是用_finish-_start出来的,但是_finish不知道在哪
_finish = _start + sz;
_endOfStorage = _start + n;
}
}
resize()
因为resize()需要扩充元素的个数,所以resize()需要考虑三种情况,如下图
void resize(size_t n, const T& val = T())
{
if (n <= size())
{
_finish = _start + n;
}
else
{
if (n > capacity())
{
reserve(n);
}
while (_finish != _endOfStorage)
{
*_finish = val;
_finish++;
}
}
}
2.vector模拟实现中的问题
2.1.迭代器失效问题
迭代器失效两种情况:
1.迭代器iterator所指向的空间已经被释放,对于vector这样的序列容器来说,此时iterator已经是野指针了。
2.迭代器iterator的代表的意义改变了。
如果你不懂上面的两种情况也没事,在看完下面的例子时候,再回头看一遍就懂了。
1.iterator变成"野指针"
vector<int> v = { 1, 2, 3, 4, 5 };
vector<int>::iterator it = v.begin();
cout << *it << endl;// 输出1
// 将向量v扩容至10个元素
v.reserve(10);
cout << *it << endl;// 程序崩溃
因为it = v.begin()
,所以当*it
的时候,输出的应该是1。但是在扩容之后,*it
就不能再输出了。这就是迭代器失效导致的。
解释:我们要有一个前置只是就是在vector
扩容的时候,并不是直接在容器的后面再开一段连续的空间,而是先拷贝一份更大的空间,然后讲原空间的vector中的元素一个一个的拷贝到先开的空间当中,最后释放掉原空间。虽然空间的数值都被拷贝到新空间中,但是原来的迭代器还在指向原有空间的开头位置,这就是迭代器失效。
所以只要当vector发生扩容的时候就会发生这种迭代器失效,所以平时使用insert
和push_back()
的时候都要小心。
2.iterator的意义改变了
vector<int> v = { 2, 4, 5 };
vector<int>::iterator it = v.begin();
while (it != v.end()) {
if (*it % 2 == 0) v.erase(it);// 如果*it为偶数就删掉
it++;// 迭代器往后走一步
}
for (int i = 0; i < v.size(); i++) {
cout << v[i] << ' ';// 输出4,5
}
为什么输出了4和5呢?(在Linux平台上输出4,5,在vs平台上直接程序崩溃)。
解释:因为2是偶数,所以一上来就要erase掉2,然后iterator往后走一步指向4。等等在这一步的时候就已经错了,iterator不指向4,而是指向5。???为什么呢?因为vector是序列式容器,在erase之后所有在iterator之后的元素都会往前移动一位,这样覆盖掉了原来iterator指向的位置了
所以,如果要改正这个错误的话,就要保证当删除元素的时候,迭代器it就不能往后走了,只有当没有删除元素的时候,迭代器才可以往后走。
vector<int> v = { 2, 4, 5 };
vector<int>::iterator it = v.begin();
while (it != v.end()) {
if (*it % 2 == 0) it = v.erase(it);// 更新一下it指向的位置
else it++;// 迭代器往后走一步
}
for (int i = 0; i < v.size(); i++) {
cout << v[i] << ' ';// 输出4,5
}
在erase数值之后,我们以为迭代器指向的位置,却不是它的位置,这种就是迭代器的意义改变了。也就是第二中迭代器失效。
总结:迭代器失效的两种情况
1.当insert或者erase的时候,因为iterator不会随着指向元素的改变(增加或者删除)而改变,单只iterator的意义发生改变。
2.当insert或者erase的时候,导致容器扩容或者缩容时,iterator变成了"野指针"。
2.2.更深层次的深拷贝问题(相对于memcpy浅拷贝)
首先需要搞清楚memcpy(destion, source, num)
函数是将source
中的内容原封不动的将num
个字节复制一份到destion
中。但是这其实就影响了更深层次的深拷贝。
更深层次的深拷贝是指:当vector<T>
中T的类型是自定义类型,并且定义类型中有一个成员变量是指针用来指向动态开辟的空间。如果在这种情况下的时候,在进行拷贝的时候,不仅要考虑vector
本身的结构要深拷贝,vector
中的元素也需要进行深拷贝,而不是进行简单的复制一份,这样会导致新开辟的空间和原空间中的元素都只想同一块空间。
下面几幅图让你彻底了解:
理想拷贝的情况:新开辟的空间中自定义类型对象(这里拿string举例子)指向新开辟出空间。
如果是用 memcpy
实际拷贝情况确实这样:
最后导致的情况是:新开辟出的空间只能指向已经销毁的空间中的内容
在知道不能用memcpy
函数简单的浅拷贝之后,就要解决这个问题。其实就是要在拷贝的时候唤醒并调用自定义类型自己的深拷贝构造就可以了。
也就是让自定义类型=自定义类型
这样就可以调用自定义类型的复制运算符重载实现vector
中元素的深拷贝。
如下:
// memcpy(tmp, _start, size(T)*size());// 错误示范
for (size_t i = 0; i < sz; i++)// 正确姿势
{
tmp[i] = _start[i];
}
3.完整代码实现
#pragma once
#include <iostream>
#include <algorithm>
#include <assert.h>
#include <string>
#include <cstring>
using namespace std;
namespace zhy
{
template <class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
// const对象不能调用非const成员函数
// 但是可以调用const成员函数
// 所以可以用cosnt成员函数,返回const_iterator的方式来限制迭代器不可以修改
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
// 构造函数
vector() :
_start(nullptr),
_finish(nullptr),
_endOfStorage(nullptr)
{}
vector(size_t n, const T& val = T()) :
_start(nullptr),
_finish(nullptr),
_endOfStorage(nullptr)
{
reserve(n);
for (size_t i = 0; i < n; i++)
{
(*_finish++) = val;
}
}
template<class InputIterator>
vector(InputIterator first, InputIterator last) :
_start(nullptr),
_finish(nullptr),
_endOfStorage(nullptr)
{
reserve(last - first);
while (first != last)
{
push_back(*first);
first++;
}
}
// 拷贝构造
vector(const vector<T>& v) :
_start(nullptr),
_finish(nullptr),
_endOfStorage(nullptr)
{
size_t n = v.capacity();
reserve(n);
//memcpy(_start, v._start, sizeof(T)*n);
for (size_t i = 0; i < v.size(); i++)
{
_start[i] = v[i];
}
_finish = _start + v.size();
_endOfStorage = _start + v.capacity();
}
下面这种拷贝构造是复用了push_back()的接口
//vector(const vector<T>& v) :
// _start(nullptr),
// _finish(nullptr),
// _endOfStorage(nullptr)
//{
// const_iterator tmp = v.beign();
// while (tmp != v.end())
// {
// push_back(*tmp);
// tmp++;
// }
//}
// 运算符重载
vector<T>& operator= (vector<T> v)// 现代写法,直接利用拷贝构造函数,深拷贝复制一个对象
{
swap(v);
return *this;
}
下面这个版本可以避免对象赋值给自己
因为通常情况下,不会将对象赋值给自己,所以也可以写上面的版本不用判断this!=&v
//vector<T>& operator= (const vector<T>& v)
//{
// if (this != &v)
// {
// vector<T> tmp(v);
// swap(tmp);
// }
// return *this;
//}
void swap(vector<T>& v)
{
std::swap(v._start, _start);
std::swap(v._finish, _finish);
std::swap(v._endOfStorage, _endOfStorage);
}
// 析构函数
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _endOfStorage = nullptr;
}
}
T& operator[] (size_t i)
{
return _start[i];
}
const T& operator[](size_t i) const
{
return _start[i];
}
void reserve(size_t n)
{
if (n > capacity())
{
size_t sz = size();// 元素的个数
T* tmp = new T[n];
// memcpy(tmp, _start, size(T)*size());
for (size_t i = 0; i < sz; i++)
{
tmp[i] = _start[i];
}
delete[] _start;
_start = tmp;
//_finish = _start + size();// 此时的size()是用_finish-_start出来的,但是_finish不知道在哪
_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 != _endOfStorage)
{
*_finish = val;
_finish++;
}
}
}
void push_back(const T& val)
{
if (_finish == _endOfStorage)
{
size_t newCapacity = capacity() * 2 == 0 ? 4 : capacity() * 2;
reserve(newCapacity);
}
*_finish = val;
_finish++;
}
void pop_back()
{
assert(_start != _finish);
--_finish;
}
bool empty() const
{
return _start == _finish;
}
size_t capacity() const
{
return _endOfStorage - _start;
}
size_t size() const
{
return _finish - _start;
}
T& back()
{
return *(_finish - 1);
}
T& front()
{
return *(_start);
}
const T& back() const
{
return *(_finish - 1);
}
const T& front() const
{
return *_start;
}
iterator insert(iterator pos, const T& val)
{
if (_finish == _endOfStorage)
{
size_t newCapacity = capacity() * 2 == 0 ? 4 : capacity() * 2;
reserve(newCapacity);
}
iterator tmp = _finish;
while (tmp != pos)
{
*tmp = *(tmp - 1);
tmp--;
}
*pos = val;
_finish++;
return pos;
}
iterator erase(iterator pos)
{
assert(pos > 0 && _start != _finish);
iterator tmp = pos;
while (tmp != _finish)
{
*tmp = *(tmp + 1);
tmp++;
}
_finish--;
return pos;
}
private:
iterator _start; // 使用空间的开头
iterator _finish; // 使用空间的尾部
iterator _endOfStorage;// 可用空间的尾部
};
}