一、vector的迭代器
迭代器(iterator)是一个比较抽象的概念,其在STL中扮演着十分重要的角色。迭代器可以理解为一种行为类似指针的对象(可以是指针也可以不是指针),而指针的各种行为中最常见最重要的操作就是*
和->
,因此迭代器最重要的编程工作就是operator*
和operator->
。
我们知道vector使用的是连续线性的空间,所以无论其元素类型是什么,普通指针都可以作为vector的迭代器来满足所有需求,因为vector的迭代器所需的操作,(如operator*
、operator->
、operator++
、operator--
等),普通指针本来就具备。所以vector的迭代器就是普通指针
template<class T>
class vector
{
public:
// vector的迭代器是一个普通指针
typedef T* iterator;
typedef const T* const_iterator;
由上述我们可以知道,如果用户写出这样的代码:
vector<int>:: ivit;
vector<Date>:: dvit;
ivit和dvit和类型分别是int*
和Date*
二、vector的数据结构
vector采用的数据结构十分简单:线性连续空间。它用两个迭代器_start
和_finish
分别指向连续空间的已被使用范围的开始和结束,并以迭代器_endOfStorage
指向整块连续空间(容量)的尾端。
为了降低扩容成本,vector实际空间大小可能比用户需求量更大一些,换句话说,一个vector的容量永远 >= 其大小。
增加新元素时,如果超过当前容量,则容量会扩充至两倍。至于为什么是两倍,这并不是硬性规定,只是两倍比较合适,仅此而已。
注意,上图扩容时是直接在原空间之后新增空间,实际并没有这么简单。
扩容必须经历『重新配置空间、移动元素、释放原空间』等过程,工程浩大。
使用_start
、 _finish
、_endOfStorage
三个迭代器便可以轻易的实现获取首尾、大小、容量、容器判空等功能。
template <class T>
class vector {
//……
public:
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;//最后一个元素的下一个
}
//重载const版本,以适应const对象调用begin和end
iterator begin() const
{
return _start;
}
iterator end() const
{
return _finish;
}
const_iterator cbegin() const
{
return _start;
}
const_iterator cend() const
{
return _finish;
}
size_t size() const
{
return _finish - _start;
}
size_t capacity() const
{
return _endOfStorage - _start;
}
bool empty() const
{
return _finish == _start;
}
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);
return _start[pos];
}
//……
private:
iterator _start; // 指向数据块的开始
iterator _finish; // 指向有效数据的尾
iterator _endOfStorage; // 指向存储容量的尾
};
三、vector的构造和析构
1. 构造
- 无参构造
vector()
:_start(nullptr)
, _finish(nullptr)
, _endOfStorage(nullptr)
{
}
- n个value构造
注意这里的n是int类型而不是size_t,假如我们这样构造:vector<int> v(3, 8)
,会调用区间构造而发生错误
vector(int n, const T& value = T())
{
reserve(n);//扩容
for (int i = 0; i < n; ++i)
{
_start[i] = value;
}
}
- 迭代器区间构造
template<class InputIterator>
vector(InputIterator first, InputIterator last)
:_start(nullptr)
, _finish(nullptr)
, _endOfStorage(nullptr)
{
while (first != last)
{
push_back(*first);
++first;
}
}
2. 拷贝构造和赋值运算符重载
vector实现swap与string实现swap的原因相同:如果用户直接使用库中swap(v1, v2)
将会发生1次拷贝构造,2次赋值,3次深拷贝代价高,而使用v1.swap(v2)
仅仅交换成员变量,代价小。
库中swap的实现:
- 拷贝构造
void swap(vector<T>& v)
{
//交换成员
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endOfStorage, v._endOfStorage);
}
//使用初始化列表初始化是为了
//避免交换成员后tmp的成员变量为随机值,析构时出错
vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _endOfStorage(nullptr)
{
//如果使用begin和end需要重载出const版本
vector<T> tmp(v.cbegin(), v.cend());复用迭代器区间构造
swap(tmp);
}
- 赋值运算符重载
vector<T>& operator= (vector<T> v)//注意不要传引用
{
swap(v);
return *this;
}
3. 析构
~vector()
{
delete[] _start;
_start = _finish = _endOfStorage = nullptr;
}
四、vector的元素操作
1. push_back
当我们 push_back插入新元素时,函数首先检查是否还有备用空间,如果有就直接在_finish位置构造元素,并调整_finish;如果没有就扩容。
void push_back(const T& x)
{
//容量满则扩容两倍
if (_finish == _endOfStorage)
{
//注意首次扩容,容量初始化为4
size_t newCapacity = _endOfStorage == 0 ? 4 : 2 * capacity();
reserve(newCapacity);
}
*_finish = x;
_finish++;
}
2. pop_back
void pop_back()
{
assert(!empty());
_finish--;
}
3. insert
实现insert应注意迭代器失效问题,即扩容引起的野指针问题
// 在pos位置前面插入
iterator insert(iterator pos, const T& val)
{
assert(pos >= _start);
assert(pos < _finish);
//1. 检查容量
if (_finish == _endOfStorage)
{
size_t len = pos - _start;
size_t newCapacity = _endOfStorage == 0 ? 4 : 2 * capacity();
reserve(newCapacity);//扩容
//扩容会导致 pos 迭代器失效,需要更新pos
pos = _start + len;
}
//2. 挪动数据
iterator end = _finish - 1;//_finish是有效元素的下一个位置
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = val;
_finish++;
//返回pos是为了解决迭代器失效问题
//让用户可以在外部更新迭代器的位置
return pos;
}
4. erase
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator end = pos + 1;
while (end != _finish)
{
*(end - 1) = *(end);
++end;
}
_finish--;
return pos;//解决迭代器失效问题
}
五、vector的空间操作
1. reserve的深浅拷贝问题
reserve的原型是void reserve(size_t n)
,我们知道当n > capacity时才需要对容器进行扩容,扩容必须经历『重新配置空间、移动元素、释放原空间』。
void reserve(size_t n)
{
if (n > capacity())
{
int oldSize = size();//原size
//1. 重新配置空间
T* tmp = new T[n];
//2. 移动元素
//_start不为空才拷数据
if (_start)
{
// 不能用memcpy,如果是自定义类型会发生错误
// memcpy是浅拷贝
//memcpy(tmp, _start, sizeof(T) * oldSize);
for (int i = 0; i < oldSize; ++i)
{
//调用赋值运算符重载完成深拷贝
tmp[i] = _start[i];
}
//3. 释放原空间
delete[] _start;
}
_start = tmp;
_finish = tmp + oldSize;//更新为原来对应位置
_endOfStorage = _start + n;
}
第一步和第三步都比较简单,而容易出现错误的地方是第二步将原空间的元素拷贝到新空间,假设模拟实现vector中的reserve接口时,使用memcpy进行拷贝,以下代码会发生什么问题?
void test()
{
nb::vector<std::string> v;
v.push_back("1111");
v.push_back("2222");
v.push_back("3333");
v.push_back("4444");
v.push_back("5555");
//test结束时需要析构v
}
int main()
{
test();
return 0;
}
运行程序,程序会崩溃。
我们在push_back中使用reserve扩容,第一次扩容_start
是nullptr
,我们加了if判断不会使用memcpy,就算没有if判断,拷贝0字节memcpy不会做任何事。
第一次扩容后容量被初始化为4,前4次程序不会崩溃,当第五次push_back时发生第二次扩容。
前4次插入操作后v的内存图:
插入"5555"期间发生第二次扩容:
1.重新配置空间
- 拷贝元素:使用memcpy拷贝
- 释放原空间
这样tmp中的_str变成了野指针,其指向一块已经释放了的空间,test函数结束时v发生析构,进而string对象析构对_str进行delete,此时发生错误。解决办法是调用赋值运算符重载完成深拷贝。
总结:
- memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中
- 如果拷贝的是非自定义类型(int、double)的元素,memcpy既高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,会引起内存泄漏甚至程序崩溃,因为memcpy的拷贝实际是浅拷贝。
2. resize实现
void resize(size_t n, const T& value = T())
{
if (n > capacity())
{
reserve(n);
}
if (n > size())
{
while (_finish < _start + n)
{
*_finish = value;
_finish++;
}
}
else
{
_finish = _start + n;
}
}
整体代码
#pragma once
#include <assert.h>
namespace nb
{
template<class T>
class vector
{
public:
// vector的迭代器是一个原生指针
typedef T* iterator;
typedef const T* const_iterator;
iterator begin()
{
return _start;
}
iterator begin() const
{
return _start;
}
iterator end()
{
return _finish;
}
iterator end() const
{
return _finish;
}
const_iterator cbegin() const
{
return _start;
}
const_iterator cend() const
{
return _finish;
}
// construct and destroy
vector()
:_start(nullptr)
, _finish(nullptr)
, _endOfStorage(nullptr)
{
}
vector(int n, const T& value = T())
{
reserve(n);
for (int i = 0; i < n; ++i)
{
_start[i] = value;
}
}
template<class InputIterator>
vector(InputIterator first, InputIterator last)
:_start(nullptr)
, _finish(nullptr)
, _endOfStorage(nullptr)
{
while (first != last)
{
push_back(*first);
++first;
}
}
vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _endOfStorage(nullptr)
{
//精简复用
vector<T> tmp(v.cbegin(), v.cend());//需要调用const版本的begin、end
swap(tmp);
}
vector<T>& operator= (vector<T> v)
{
swap(v);
return *this;
}
~vector()
{
delete[] _start;
_start = _finish = _endOfStorage = nullptr;
}
// capacity
size_t size() const
{
return _finish - _start;
}
size_t capacity() const
{
return _endOfStorage - _start;
}
bool empty() const
{
return _finish == _start;
}
void clear()
{
_finish = _start;
}
void reserve(size_t n)
{
if (n > capacity())
{
int oldSize = size();//原size
T* tmp = new T[n];
//memcpy(tmp, _start, sizeof(T) * oldSize); // --> 不能用memcpy,如果是自定义类型会发生错误
// memcpy是浅拷贝
//_start不为空才需要拷数据
if (_start)
{
for (int i = 0; i < oldSize; ++i)
{
tmp[i] = _start[i];//调用赋值运算符重载完成深拷贝
}
delete[] _start;
}
_start = tmp;
_finish = tmp + oldSize;
_endOfStorage = _start + n;
}
}
void resize(size_t n, const T& value = T())
{
if (n > capacity())
{
reserve(n);
}
if (n > size())
{
while (_finish < _start + n)
{
*_finish = value;
_finish++;
}
}
else
{
_finish = _start + n;
}
}
///access///
T& operator[](size_t pos)
{
assert(pos < size());
return *(_start + pos);
}
const T& operator[](size_t pos) const
{
assert(pos < _finish);
//return *(_start + pos);
return _start[pos];
}
///modify/
void push_back(const T& x)
{
if (_finish == _endOfStorage)
{
size_t newCapacity = _endOfStorage == 0 ? 4 : 2 * capacity();
reserve(newCapacity);
}
*_finish = x;
_finish++;
}
void pop_back()
{
assert(!empty());
_finish--;
}
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)//在pos位置前面插入
{
assert(pos >= _start);
assert(pos < _finish);
//检查容量
if (_finish == _endOfStorage)
{
size_t len = pos - _start;
size_t newCapacity = _endOfStorage == 0 ? 4 : 2 * capacity();
reserve(newCapacity);
//扩容会导致 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 end = pos + 1;
while (end != _finish)
{
*(end - 1) = *(end);
++end;
}
_finish--;
return pos;
}
private:
iterator _start; // 指向数据块的开始
iterator _finish; // 指向有效数据的尾
iterator _endOfStorage; // 指向存储容量的尾
};
}