template<class T>
class vector
{
typedef T* iterator;//迭代器
typedef const T* const_iterator;
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
};
void reserve(size_t n)
{
if (n > capacity())
{
T* temp = new T[n];
size_t OldSzie = size();
if (_start != nullptr)//万一一开始是空,那么就没有必要拷贝数据了!
{
for (size_t i = 0; i < OldSzie; i++)
{
temp[i] = _start[i];
}
delete[] _start;
}
_start = temp;
_finish = _start + OldSzie;
_endofstorage = _start + n;
}
}
常见的错误写法1!
void reserve(size_t n) { if (n > capacity()) { T* temp = new T[n]; if (_start != nullptr) { memcpy(temp, _start, sizeof(T) * size()); delete[] _start; } _start = temp; _finish = temp + size(); _endofstorage = temp + capacity(); } }
这样写会有什么问题呢?答案是如果怎么写的话 _start先发生改变变成了新空间的地址!!但是 _finish却仍然是原来的旧空间的地址!
那么size()接口首先就是变成了一个随机值!我们无法得知旧空间地址- 新空间地址的数值是什么!
所以上面的写法才要在_start改变前,先保存原来的size
但是这个实现方式也有深拷贝的隐患!后面我们会一起解释
错误写法二
void reserve(size_t n) { if (n > capacity()) { T* temp = new T[n]; size_t OldSzie = size(); if (_start != nullptr) { memcpy(temp, _start, sizeof(T) * size()); delete[] _start; } _start = temp; _finish = _start + OldSzie; _endofstorage = _start + n; } }
这种写法的错误很隐蔽,会在后面讲
void push_back(const T& value)
{
if (_finish == _endofstorage)
{
size_t newcapaciy = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapaciy);
}
*_finish = value;
_finish++;
}
常见的错误写法!’
void push_back(const T& value) { reserve(capacity()*2); *_finish = value; _finish++; }
'当capacity的值为 0 的时候那么就会扩容失败!
vector()
:_start(nullptr),
_finish(nullptr),
_endofstorage(nullptr)
{}
template<class InputIterator>
vector(InputIterator first, InputIterator last)
: _start(nullptr),
_finish(nullptr),
_endofstorage(nullptr)
{
while (first != last)
{
push_back(*first);
first++;
}
}
一个是无参的构造
一个是带参的构造!
为什么下面的带参的构造要使用template
作为类型呢?而不是iterator?
答案是因为我们用来初始化的对象一定是vector吗?
不一定!我们也可以是string之类的其他类型!
这样就可以支持更多类型的初始化!
string s1 = "hhhhhhhh"; My_STL::vector<char> v2(s1.begin(), s1.end()); for (auto e : v2) { cout << e << ' '; }
vector(size_t n, const T& val = T())
: _start(nullptr),
_finish(nullptr),
_endofstorage(nullptr)
{
reserve(n);
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
当我们写这个构造函数的时候,会导致一些问题
特别是我们使用vector 这个类型的时候!此时我们传入了两个相同类型的参数
为什么?会出现非法的间接寻址?
答案是我们想让他调用这个构造函数,但是实际上根本就没有调用这个构造函数!
实际上编译器调用的是下面的这个构造函数!为什么?因为下面的构造函数更加的匹配
template<class InputIterator> vector(InputIterator first, InputIterator last);
我们传入的是 10 1 都是int 类型!
而这个构造函数
vector(size_t n, const T& val = T());
是size_t 和 const int 类型
虽然int 会隐式类型转换为 size_t
但是在编译器看来!上面的明显更加的匹配!所以自然的就去调用上面的构造函数了
而上面的构造函数会去解引用 所以就出现了非法寻址!
但是只要不是相同类型的就会去调用下面的构造函数!
解决办法
My_STL::vector<char> v((size_t)10, 'a');//进行强转!
//写一个函数重载只要有一个更匹配的函数就不会去调用上面的模板函数了! //因为调用模板函数还需要实例化 vector(int n, const T& val = T()) : _start(nullptr), _finish(nullptr), _endofstorage(nullptr) { reserve(n); for (int i = 0; i < n; i++) { push_back(val); } }
所以最后的完成版就是
vector() :_start(nullptr), _finish(nullptr), _endofstorage(nullptr) {} template<class InputIterator> vector(InputIterator first, InputIterator last) : _start(nullptr), _finish(nullptr), _endofstorage(nullptr) { while (first != last) { push_back(*first); first++; } } vector(size_t n, const T& val = T()) : _start(nullptr), _finish(nullptr), _endofstorage(nullptr) { reserve(n); for (size_t i = 0; i < n; i++) { push_back(val); } } vector(int n, const T& val = T()) : _start(nullptr), _finish(nullptr), _endofstorage(nullptr) { reserve(n); for (int i = 0; i < n; i++) { push_back(val); } }
~vector()
{
delete[] _start;
_start = _finish _endofstorage = nullptr;
}
size_t size()const
{
return _finish - _start;
}
size_t capacity()const
{
return _endofstorage - _start;
}
iterator begin()const
{
return _start;
}
iterator end()const
{
return _finish;
}
bool empty()const
{
return _finish == _start;
}
void pop_back()
{
assert(!empty());
--_finish;
}
如果不进行判断那么就有可能能删过头!
T& operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
T& operator[](size_t pos)const
{
assert(pos < size());
return _start[pos];
}
因为[]既可用于读也可用于写所以要写两个函数接口!
void resize(size_t n, T value = T())
{
if (n > capacity())
{
reserve(n);
}
if (n > size())
{
while (_finish < _start + n)
{
*_finish = value;
++_finish;
}
}
if (n < size())
{
_finish = _start + n;
}
}
为什么第二参数要使用 T() 作为缺省值?——因为T()是一个匿名对象!我们无法得知传过来的值究竟是一个内置类型还是一个自定义类型!所以最保妥的方案就是使用匿名对象来进行赋值!匿名对象又会调用该类的构造函数来进行初始化!
resize这个接口有三种情况!
- size
- n > capacity 这种情况下就需要扩容 + 填充值!
- n < size 这种情况下只要改变_finish即可!之所以不进行缩容是因为缩容的代价太大不划算!
insert这个接口如果不恰当写有可能会发生迭代器失效的问题
void insert(iterator pos, const T& val) { assert(pos >= _start); assert(pos <= _finish); if (_finish == _endofstorage) { int NewCapacity = capacity() == 0 ? 4 : capacity() * 2; reserve(NewCapacity); } iterator end = _finish - 1; while (end >= pos) { *(end + 1) = *end; --end; } *pos = val; _finish++; }
上面的写法看起来很正常!需要就先扩容再挪动位置最后填入
问题就是出现在扩容上!——一旦进行扩容那么就会在异地开辟新空间,释放原空间
pos指向原来的空间的迭代器! 无论是_start和 _finish此时都是在新空间!旧空间已经被释放了!pos 不在 _start 和 _finish的范围之内
这就导致了在挪动元素无法按照我们预想的进行!
pos的失效的我们也成为迭代器失效
迭代器失效本质就是一个野指针问题!
正确的写法
void insert(iterator pos, const T& val) { assert(pos >= _start); assert(pos <= _finish); if (_finish == _endofstorage) { size_t len = pos - _start; //一旦要进行扩容就先保存pos的相对位置! int NewCapacity = capacity() == 0 ? 4 : capacity() * 2; reserve(NewCapacity); //扩容完毕后修改pos pos = _start + len; } iterator end = _finish - 1; while (end >= pos) { *(end + 1) = *end; --end; } *(pos) = val; _finish++; }
我们要真正保留的是pos在空间的相对位置!
但是即使是上面的写法也不能完全避免迭代器失效的问题!
因为我们只是避免了里面的迭代器失效!还有外面的迭代器失效我们无法避免!
int main() { My_STL::vector<int> v1; v1.push_back(1); v1.push_back(2); v1.push_back(3); v1.push_back(4); My_STL::vector<int> ::iterator it = find(v1.begin(), v1.end(), 3); v1.insert(it, 30); cout << (*it) << endl; return 0; }
因为我们当里面发生扩容的时候!外面的迭代器还是指向的是旧的空间!这就是一个越界访问!是一个野指针!
为了安全考虑所以当我们使用it之后无论它是否真的失效了!我们一律都认为它是失效的!
或许有人回想那么我们外面一起改不就好了传引用
void insert(iterator pos, const T& val);
int main() { My_STL::vector<int> v1; v1.push_back(1); v1.push_back(2); v1.push_back(3); v1.push_back(4); My_STL::vector<int> ::iterator it = find(v1.begin(), v1.end(), 3); v1.insert(v1.begin(), 30);//begin()是传值返回,返回的是一个临时变量!具有常性! cout << (*it) << endl; return 0; }
因为如果我们使用了引用我们就只能可变的变量了!只能传左值!如果我们传一个临时变量,那么就会直接报错!
为了安全如果要再次使用it最好进行更新!
iterator insert(iterator pos, const T& val) { assert(pos >= _start); assert(pos <= _finish); if (_finish == _endofstorage) { size_t len = pos - _start; int NewCapacity = capacity() == 0 ? 4 : capacity() * 2; reserve(NewCapacity); pos = _start + len; //这里一旦扩容!就会导致一个很严重的问题就是地址发生了改变! } iterator end = _finish - 1; while (end >= pos) { *(end + 1) = *end; --end; } *(pos) = val; _finish++; return pos; }
我们可以使用iterator作为返回值!返回插入后值的位置!
这样我们可以在使用it之后 使用it接收这个返回值!更新it
int main() { My_STL::vector<int> v1; v1.push_back(1); v1.push_back(2); v1.push_back(3); v1.push_back(4); My_STL::vector<int> ::iterator it = find(v1.begin(), v1.end(), 3); it = v1.insert(it, 30); cout << (*it) << endl; return 0; }
erase这个接口也具有迭代器失效的问题!——不过这个算争议**(但是为了数据安全我们还是认为这是一个迭代器失效!)**
void erase(iterator pos) { assert(pos >= _start); assert(pos < _finish); iterator begin = pos + 1; while (begin < _finish) { *(begin - 1) = *(begin); ++begin; } _finish--; }
这样子看上去好像没有问题,而且为什么明明是删除也会导致迭代失效呢?
因为erase的迭代器失效是发生在外部的!
int main() { My_STL::vector<int> v1; v1.push_back(1); v1.push_back(2); v1.push_back(3); v1.push_back(4); My_STL::vector<int> ::iterator it = find(v1.begin(), v1.end(), 4); v1.erase(it); cout << (*it) << endl; cout << v1[3] << endl; return 0; }
尾删会发生迭代器失效的问题!
我们好像可以正常的访问it的位置!但是!it这个位置已经被删除了!
从下面的代码我们可以看出其实已经出现了越界访问!只不过上面的代码没有报错!
我们可以看看vs下面的vector是怎么处理这个问题的!
int main() { vector<int> v2 = { 1,2,3,4 }; vector<int>::iterator it2 = find(v2.begin(), v2.end(), 4); v2.erase(it2); *it2; return 0; }
答案是进程直接崩溃!
但是如果是在linux下面的vector会发生什么呢?
答案是可以正常的访问!并不会报错!我们甚至可以对其进行写入!
为什么会有这两种完全截然不同的情况呢?因为vs下的vector的itterator的写法不是像我们一样是一个原生指针!他其实是一个函数调用!
而linux下的g++编译器和我们的实现方式是一样的!而且这个在操作系统层面上也不算越界,因为我们确实持有那块内存的所有权!是一种形式上的越界!
linux下的不报错是以一种巧合的方式来达成的!即使刚刚的情况不报错!不代表linux下的迭代器就真的没有问题!
例如下面的例子
为什么会出现越界访问?答案是因为错过了!当删除最后一个的4的时候,此时的迭代器就等于v1.end()
但是迭代器继续++就导致了直接错过了!这个位置!
即使最后一个位置不是偶数也会有问题!
我们可以看出来!没有将偶数完全的删除!
因为删除掉第一个2之后,第二个2就移动到了it的位置随后it++直接就跳过了!
总结
无论是linux下的编译器还是window下的编译器我们都统一的认为这个erase(it)后
it这个迭代器是失效的!——不要再去使用它进行读写!
为什么呢?即使我们在在linux下使用这个迭代器的使用后使用了各种方法规避了错误,程序能够得到正确的结果(例如上面的程序我们可以使用if else来解决问题)但是也就是在linux下能够运行!
int main() { vector<int> v2 = { 1,2,3,4 }; vector<int>::iterator it = v2.begin(); while (it != v2.end()) { if (*it % 2 == 0) { v2.erase(it); } else { it++; } } for (auto e : v2) { cout << e << " "; } return 0; }
在Windows下面这个程序依旧会崩溃!这使得代码的可移植性就很差了!——不要使用巧合去解决问题!
所以我们都统一的认为这个迭代器是一个失效的迭代器!使用之后就一定要去更新这个迭代器!
最终版本
iterator erase(iterator pos) { assert(pos >= _start); assert(pos < _finish); iterator begin = pos + 1; while (begin < _finish) { *(begin - 1) = *(begin); ++begin; } _finish--; return pos; }
返回删除后的下一个元素的迭代器的位置!让it去接收!
int main() { vector<int> v2 = { 1,2,3,4 }; vector<int>::iterator it = v2.begin(); while (it != v2.end()) { if (*it % 2 == 0) { it = v2.erase(it); } else { it++; } } for (auto e : v2) { cout << e << " "; } return 0; }
这个代码使用的vector都是库里面的vector不是我们自己实现的
我们自己实现的也可以成功的运行!
这样子写就可以在g++下面和vs下面的都是可以运行!就无关平台了!
void clear()
{
_finish - _start;
}
clear 不可以写_finish = _start = nullptr!
因为这样会导致内存泄漏!
原因是因为_start指向的是地址!把 _start 置成nullptr后原来的地址就找不到了!
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
想要交换两个对象我们可以直接交换指针指向的空间!
vector(const vector<T>& v) : _start(nullptr), _finish(nullptr), _endofstorage(nullptr) { reserve(v.capacity()); for (auto& e : v) { push_back(e); } }
我们即可自己手动的去开辟空间来构造!
也可以通过复用来构造!
这里要注意一点!auto e 要使用引用!
为什么呢?因为如果仅仅是内置类型还好!但是一旦是自定义类型,可能会导致大量的深拷贝!
因为for循环的含义就是将v里面的元素依次赋值给 e,一旦是需要深拷贝的内置类型就会消耗很多的性能
vector(const vector<T>& v)
: _start(nullptr),
_finish(nullptr),
_endofstorage(nullptr)
{
vector<T> tmp(v.begin(), v.end());
swap(tmp);
}
我们可以使用带参的构造函数来先创建一个对象!通过构造函数来创建空间和初始化!而不是自己去手动创建!
然后通过swap进行交换!将构造函数创建的空间给我们的对象!
这样就完成了一次拷贝构造!
因为tmp出了作用域后会自动的调用析构,所以我们也不用去处理它
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
返回引用是为了防止链式赋值!
int main() { My_STL::vector<int> v1; v1.push_back(1); v1.push_back(2); v1.push_back(3); v1.push_back(4); v1.push_back(4); My_STL::vector<int>::iterator it = v1.begin(); string s1 = "hhhhhhhh"; My_STL::vector<int> v2(s1.begin(), s1.end()); My_STL::vector <int> v3; v3 = v2 = v1; return 0; }
我们使用传值传参这就是为了让其调用拷贝构造!创建一个对象出来!
然后使用swap来交换指针指向的空间!
其原理和上面的写法二是一样的!
不过上面的写法有点小小的缺点!那就是当自己赋值给自己的时候会造成多余的浪费!相比传统的写法(传统写法是传引用,可以根据地址是不是相同来考虑是否进行拷贝构造!),这种写法一定会去调用拷贝构造!所以当出现自己赋值给自己的时候!就会出现浪费!
但是因为自己赋值给自己的这种情况十分的少见!所以!也不算是多大的缺点
写完上面的代码我们vector的简单模拟实现已经差不多完成了!但是上面的代码的还存在一个深拷贝的缺陷!我们可以看如下的代码!
void test_vector() { My_STL::vector<My_STL::vector<int>> vv; My_STL::vector<int> v(10, 1); vv.push_back(v); vv.push_back(v); vv.push_back(v); vv.push_back(v); vv.push_back(v); for (int i = 0; i < vv.size(); i++) { for (int j = 0; j < vv[i].size(); j++) { cout << vv[i][j] << " "; } cout << endl; } }
这是发生了什么事情?——**答案是扩容出现了问题!**这是哪里出现问题了?
这就是为什么会出现上面现象的原因!所以即使是对于数据的每一个自定义类型的元素的拷贝我们也要使用深拷贝来进行!
void reserve(size_t n) { if (n > capacity()) { T* temp = new T[n]; size_t OldSzie = size(); if (_start != nullptr)//万一一开始是空,那么就没有必要拷贝数据了! { for (size_t i = 0; i < OldSzie; i++) { temp[i] = _start[i]; } delete[] _start; } _start = temp; _finish = _start + OldSzie; _endofstorage = _start + n; } }
为什么这样写就可以解决的呢?
因为当我们使用自定了类型的时候进行
temp[i] = _start[i];
这一段其实本质就是一个深拷贝,我们已经将赋值运算符给进行重载了!每一次调用都是一次深拷贝!
而进行内置类型的时候,就是按正常来使用的!
总结
这也是为什么我们在c++中使用new和delete的原因而不是使用malloc的free
因为new和delete会去自动的调用构造和析构函数而malloc和free却不会!和自定义类型其实不相符合!
#pragma once
#include
#include
#include
using namespace std;
namespace My_STL
{
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
vector()
:_start(nullptr),
_finish(nullptr),
_endofstorage(nullptr)
{}
template<class InputIterator>
vector(InputIterator first, InputIterator last)
: _start(nullptr),
_finish(nullptr),
_endofstorage(nullptr)
{
while (first != last)
{
push_back(*first);
first++;
}
}
vector(size_t n, const T& val = T())
: _start(nullptr),
_finish(nullptr),
_endofstorage(nullptr)
{
reserve(n);
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
vector(int n, const T& val = T())
: _start(nullptr),
_finish(nullptr),
_endofstorage(nullptr)
{
reserve(n);
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
vector(const vector<T>& v)
: _start(nullptr),
_finish(nullptr),
_endofstorage(nullptr)
{
vector<T> tmp(v.begin(), v.end());
swap(tmp);
}
~vector()
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
size_t size()const
{
return _finish - _start;
}
size_t capacity()const
{
return _endofstorage - _start;
}
iterator begin()const
{
return _start;
}
iterator end()const
{
return _finish;
}
T& operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
T& operator[](size_t pos)const
{
assert(pos < size());
return _start[pos];
}
void reserve(size_t n)
{
if (n > capacity())
{
T* temp = new T[n];
size_t OldSzie = size();
if (_start != nullptr)
{
for (size_t i = 0; i < OldSzie; i++)
{
temp[i] = _start[i];
}
delete[] _start;
}
_start = temp;
_finish = _start + OldSzie;
_endofstorage = _start + n;
}
}
void resize(size_t n, T value = T())
{
if (n > capacity())
{
reserve(n);
}
if (n > size())
{
while (_finish < _start + n)
{
*_finish = value;
++_finish;
}
}
if (n < size())
{
_finish = _start + n;
}
}
void push_back(const T& value)
{
if (_finish == _endofstorage)
{
size_t newcapaciy = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapaciy);
}
*_finish = value;
_finish++;
}
bool empty()const
{
return _finish == _start;
}
void pop_back()
{
assert(!empty());
--_finish;
}
void clear()
{
_finish - _start;
}
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
iterator insert(iterator pos, const T& val)
{
assert(pos >= _start);
assert(pos < _finish);
if (_finish == _endofstorage)
{
size_t len = pos - _start;
int NewCapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(NewCapacity);
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 begin = pos + 1;
while (begin < _finish)
{
*(begin-1) = *(begin);
++begin;
}
_finish--;
return pos;
}
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
};
}