C++模拟实现vector
1、成员变量
先来看看SGI版本STL中vector的实现:
我们仿照它的实现方式,成员变量定义跟它一样。当然像string那样定义指针、size、capacity也是可以的。
需要注意的是,vector的实现要加模板,因为vector是一个容器,它可以存储任意类型的数据。
template<class T>
class vector
{
public:
typedef T* iterator;
private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
};
2、迭代器
正向迭代器的实现就非常简单了:
typedef T* iterator;
typedef const T* const_iterator;
iterator begin() { return _start; }
iterator end() { return _finish; }
const_iterator begin() const { return _start; }
const_iterator end() const { return _finish; }
反向迭代器的实现需要创建自定义类型,这个我们后面再说。
3、size、capacity、empty、clear函数
size_t size() const { return _finish - _start; }
size_t capacity()const { return _end_of_storage - _start; }
bool empty() const { return _start == _finish; }
void clear() { _finish = _start; }
4、重载operator[]
重载operator[]可以让vector像数组一样使用。需要注意的是要实现普通对象和const对象两个版本。
T& operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
const T& operator[](size_t pos) const
{
assert(pos < size());
return _start[pos];
}
这里使用了assert断言,也就是说要满足括号里面的条件,如果不满足程序直接崩溃。因为重载operator[]对于越界的检查都是非常严格的。
5、reserve函数
reserve函数是单纯扩容,resize是扩容+初始化,所以可以先实现reserve函数,在resize函数中复用reserve。
void reserve(size_t n)
{
if (n > capacity())
{
T* tmp = new T[n];
if (_start)
{
memcpy(tmp, _start, sizeof(T)*size());
delete[] _start;
}
_start = tmp;
_finish = _start + size();
_end_of_storage = _start + n;
}
}
这么写对吗?
这么写有几个问题,分析如下:
所以我们需要在开空间之前保存size的大小,方便后面给_finish赋值。
修改后代码如下:
void reserve(size_t n)
{
if (n > capacity())
{
size_t sz = size();
T* tmp = new T[n];
if (_start)
{
memcpy(tmp, _start, sizeof(T)*sz);
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_end_of_storage = _start + n;
}
}
但是你以为现在就没有问题了吗?实际上还有一个非常大的问题,分析如下:
那么如何解决呢?
当我们开空间后,由于存储数据类型是string——自定义类型,而且我们使用的是new操作符,所以会去调用string类的默认构造函数,而string类的默认构造函数会开空间并给size和capacity赋值。所以我们直接调用赋值运算符重载函数进行深拷贝即可解决问题。修改后代码如下:
void reserve(size_t n)
{
if (n > capacity())
{
size_t sz = size();
T* tmp = new T[n];
if (_start)
{
for (size_t i = 0; i < size(); i++)
{
tmp[i] = _start[i];
}
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_end_of_storage = _start + n;
}
}
6、resize函数
void resize(size_t n, const T& val = T())
{
if (n <= size())
{
_finish = _start + n;
}
else
{
reserve(n);
while (_finish < _start + n)
{
*_finish = val;
++_finish;
}
}
}
这里T不知道是什么类型,可能是自定义类型,那么如果是自定义类型,缺省值就给默认构造函数。如果是内置类型呢?例如T就是int类型,那么就是const int& val = int(); 那么实际上val的值就是0,内置类型也是支持的。double()就是0.0,指针类型就是nullptr。
7、push_back和pop_back函数
尾插和尾删函数的实现比较容易。只要注意插入的时候扩容,删除的时候断言判断是否有数据删除即可。
void push_back(const T& x)
{
if (_finish == _end_of_storage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = x;
++_finish;
// insert(end(), x); //可以复用后面讲的insert
}
void pop_back()
{
assert(_finish > _start);
--_finish;
// erase(end() - 1); //可以复用erase
}
刚开始容量可能是为0的,例如调用无参的构造函数初始化,所以需要判断容量是否为0
删除的时候满足_finish > _start,这样才有数据可以删除。
8、front和back函数
T& front()
{
assert(size() > 0);
return *_start;
}
T& back()
{
assert(size() > 0);
return *(_finish - 1);
}
9、构造函数
对于构造函数我们可以实现三个:无参的默认构造函数、用n个val初始化、使用迭代器区间初始化
vector()
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{}
vector(size_t n, const T& val = T())
:_start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{
//resize(n, val); 可以直接调用resize来初始化
reserve(n);
for (size_t 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;
}
}
需要注意的是,使用迭代器区间来初始化,如果直接使用iterator来作为参数的话,那就只能支持用vector的迭代器区间来初始化,而不支持其他容器的迭代器区间初始化。所以这里需要写成模板,这样就支持各种不同迭代器区间的初始化。
并且在初始化列表都应该把_start、_finish、_end_of_storage置空,否则某些情况下可能会出问题。由于C++11支持给成员变量缺省值,所以也可以直接给缺省值,这样就不需要在初始化列表置空。
接下来我们使用这段代码进行测试:
发现报错了:非法的间接寻址?
虽然我们写了用n个val来初始化,但是n是size_t类型的。而这里我们给的10和1都是int类型,这里匹配的是迭代器区间初始化。因为迭代器区间初始化的两个参数都是同种类型的,跟我们这里传参的更加匹配,所以调用的是迭代器区间初始化。那么如何解决?——看看SGI版本STL中vector的解决办法:
这里多实现了一个int类型的n构成重载,这样我们初始化就会优先调用这个函数,而不是迭代器区间了。
vector(int n, const T& val = T())
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
reserve(n);
for (size_t i = 0; i < n; i++)
push_back(val);
}
10、拷贝构造函数
对于拷贝构造函数和赋值运算符重载,我们还是给出两种写法:传统写法和现代写法。现代写法需要用到swap函数。我们先给出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);
}
// 传统写法
vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
_start = new T[v.capacity()];
for (size_t i = 0; i < v.size(); i++)
{
_start[i] = v[i];
}
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
// 现代写法
vector(const vector<T>& v)
:_start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{
vector<T> tmp(v.begin(), v.end());
swap(tmp);
}
传统写法不能使用memcpy来拷贝数据,原因跟上面reserve讲的是一样的,如果是内置类型没有区别,如果是自定义类型那就是值拷贝——浅拷贝,所以需要使用for来赋值,如果是自定义类型就会调用拷贝构造函数或赋值运算符重载完成深拷贝。
像下面这么写也是可以的:
vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
reserve(v.capacity());
for (auto e : v)
{
push_back(e);
}
}
11、赋值运算符重载函数
// 传统写法
vector<T>& operator=(const vector<T>& v)
{
if (this != &v)
{
T* tmp = new T[v.capacity()];
for (size_t i = 0; i < v.size(); i++)
{
tmp[i] = v[i];
}
delete[] _start;
_start = tmp;
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
return *this;
}
// 现代写法
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
12、析构函数
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
}
13、insert函数
void insert(iterator pos, const T& x)
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _end_of_storage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = x;
++_finish;
}
分析一下这段代码有什么问题?
当扩容之后,_start、_finish、_end_of_storage会更新,这时候的pos还是指向之前那段空间,所以赋值会出问题。这就是迭代器失效的问题。
当我们在某个位置插入时,由于扩容此时pos迭代器会失效,在下次如果继续使用pos这个位置来插入时,就会出现非法访问,所以insert需要返回插入位置的迭代器,方便外部接收使用。解决办法如下:
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _end_of_storage)
{
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + len;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = x;
++_finish;
return pos;
}
14、earse函数
来看在Linux下的一段代码:使用迭代器实现删除所有偶数
编译后运行,查看结果:
我们发现输出结果是正确的,我们再插入一个2试试看:
我们发现并没有把所有偶数删除,还多了一个2,这时候细心的你可能就发现了,上面的while应该加一个else,接下来我们再看看结果:
现在这份代码已经没有什么问题了,但是这是在linux下g++编译器。如果你在vs中这么写,运行后程序就会直接崩溃。这是因为vs中对迭代器进行了检查,如果你insert或者erase之后,不能再对迭代器进行操作。
所以erase也提供一个返回值,返回被删除的下个数据的位置。实现如下:
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator begin = pos + 1;
while (begin < _finish)
{
*(begin - 1) = *begin;
++begin;
}
--_finish;
return pos;
}
15、完整代码(包含测试代码)
#pragma once
namespace zzy
{
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
iterator begin() { return _start; }
iterator end() { return _finish; }
const_iterator begin() const { return _start; }
const_iterator end() const { return _finish; }
vector()
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{}
vector(size_t n, const T& val = T())
:_start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{
//resize(n, val); 可以直接调用resize来初始化
reserve(n);
for (size_t i = 0; i < n; i++)
push_back(val);
}
vector(int n, const T& val = T())
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
reserve(n);
for (size_t 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;
}
}
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
// 传统写法
//vector(const vector<T>& v)
// :_start(nullptr)
// , _finish(nullptr)
// , _end_of_storage(nullptr)
//{
// _start = new T[v.capacity()];
// for (size_t i = 0; i < v.size(); i++)
// {
// _start[i] = v[i];
// }
// _finish = _start + v.size();
// _end_of_storage = _start + v.capacity();
//}
// 现代写法
//vector(const vector<T>& v)
// :_start(nullptr)
// ,_finish(nullptr)
// ,_end_of_storage(nullptr)
//{
// vector<T> tmp(v.begin(), v.end());
// swap(tmp);
//}
vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
reserve(v.capacity());
for (auto e : v)
{
push_back(e);
}
}
// 传统写法
vector<T> operator=(const vector<T>& v)
{
if (this != &v)
{
T* tmp = new T[v.capacity()];
for (size_t i = 0; i < v.size(); i++)
{
tmp[i] = v[i];
}
delete[] _start;
_start = tmp;
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
return *this;
}
// 现代写法
//vector<T>& operator=(vector<T> v)
//{
// swap(v);
// return *this;
//}
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
}
size_t size() const { return _finish - _start; }
size_t capacity()const { return _end_of_storage - _start; }
bool empty() const { return _start == _finish; }
void clear() { _finish = _start; }
T& operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
const T& operator[](size_t pos) const
{
assert(pos < size());
return _start[pos];
}
void reserve(size_t n)
{
if (n > capacity())
{
size_t sz = size();
T* tmp = new T[n];
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, const T& val = T())
{
if (n <= size())
{
_finish = _start + n;
}
else
{
reserve(n);
while (_finish < _start + n)
{
*_finish = val;
++_finish;
}
}
}
void push_back(const T& x)
{
if (_finish == _end_of_storage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = x;
++_finish;
}
void pop_back()
{
assert(_finish > _start);
--_finish;
}
T& front()
{
assert(size() > 0);
return *_start;
}
T& back()
{
assert(size() > 0);
return *(_finish - 1);
}
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _end_of_storage)
{
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + len;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = x;
++_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 _end_of_storage;
};
void test_vector1()
{
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;
for (size_t i = 0; i < v1.size(); i++)
{
v1[i]++;
}
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
}
void test_vector2()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
v1.push_back(5);
v1.push_back(5);
v1.push_back(5);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
v1.insert(v1.begin(), 100);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
/*vector<int>::iterator p = v1.begin() + 3;
v1.insert(p, 300);*/
vector<int>::iterator p = v1.begin() + 3;
//v1.insert(p+3, 300);
// insert以后迭代器可能会失效(扩容)
// 记住,insert以后就不要使用这个形参迭代器了,因为他可能失效了
v1.insert(p, 300);
// *p += 10;
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
}
void test_vector3()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
v1.push_back(6);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
auto it = v1.begin();
while (it != v1.end())
{
if (*it % 2 == 0)
{
it = v1.erase(it);
}
else
{
++it;
}
}
//v1.erase(v1.begin());
//auto it = v1.begin()+4;
//v1.erase(it);
// erase以后,迭代器失效了,不能访问vs进行强制检查,访问会直接报错
//cout << *it << endl;
//++it;
//cout << *it << endl;
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
}
void test_vector4()
{
vector<int> v;
v.resize(10, 0);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
int i = 0;
int j = int();
int k = int(1);
}
void test_vector5()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
vector<int> v1(v);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
vector<int> v2;
v2.resize(10, 1);
v1 = v2;
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
}
void test_vector6()
{
vector<string> v;
v.push_back("111111111111111111");
v.push_back("222222222222222222");
v.push_back("333333333333333333");
v.push_back("444444444444444444");
v.push_back("555555555555555555");
for (auto& e : v)
{
cout << e << " ";
}
cout << endl;
vector<string> v1(v);
for (auto& e : v1)
{
cout << e << " ";
}
cout << endl;
}
void test_vector7()
{
vector<int> v(10, 1);
vector<string> v1(10, "1111");
vector<int> v2(10, 1);
// vector<int> v;
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
vector<int> v3(v.begin(), v.end());
for (auto e : v3)
{
cout << e << " ";
}
cout << endl;
string str("hello world");
vector<char> v4(str.begin(), str.end());
for (auto e : v4)
{
cout << e << " ";
}
cout << endl;
int a[] = { 16,2,77,29 };
vector<int> v5(a, a + 4);
for (auto e : v5)
{
cout << e << " ";
}
cout << endl;
}
void test_vector8()
{
vector<int> v1(10, 1);
vector<int>::iterator it1 = v1.begin();
while (it1 != v1.end())
{
*it1 += 1;
cout << *it1 << " ";
it1++;
}
cout << endl;
const vector<int> v2(10, 1);
vector<int>::const_iterator it2 = v2.begin();
while (it2 != v2.end())
{
//*it2 = 10;
cout << *it2 << " ";
++it2;
}
cout << endl;
}
}