目录
一、成员变量
二、迭代器
2.1 正向迭代器
三、容量相关
3.1 得到vector的属性
3.2 申请扩容 —— reserve
3.3 改变vector的有效长度 —— resize
四、元素访问
4.1 通过下标访问vector —— operator[]
4.2 访问vector的第一个元素 —— front
4.3 访问vector的最后一个元素 —— back
五、修改相关
5.1 尾插数据 —— push_back
5.2 尾删数据 —— pop_back
5.3 在任意位置插入数据 —— insert
5.4 在任意位置删除数据 —— erase
5.5 交换两个vector对象 —— swap
5.6 清空vector —— clear
六、构造函数、析构函数以及赋值运算符重载
6.1 默认构造函数和拷贝构造
6.2 迭代器构造
6.3 填充构造
6.4 析构函数
6.5 赋值运算符重载 —— operate=
七、源码
vector和我们之前学过string非常像,都是通过数组来实现的容器,那么vector的成员变量是不是和string的成员变量一样,都是_str、_size和_capacity呢?但很遗憾并不是,vector的成员变量是三个迭代器。
namespace simulation {
template
class vector {
public:
//vector容器的迭代器是指针
typedef T* iterator;
typedef const T* const_iterator;
private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
};
}
注:vector是一个模板容器,记得在vector类的上方要带上template
初见这三个迭代器你可能不知道它们分别指向谁,有什么作用,那让我们来看一张图。
从图中我们可以看出:
_start和string中的_str一样,都指向数据的起始位置(第一个数据)。
_finish和string中的end()一样,都指向最后一个数据的后面一个位置。
_end_of_storage则和string中末尾自带的'\0'一样,指向容量(当前容器的最大长度)的后面一个位置。
有了这三个迭代器我们就能知道vector的有效长度_size和容量_capacity:
有效长度_size = _finish - _start
容量_capacity = _end_of_storage - _start
所以vector看似没有记录有效长度和容量,其实这两个属性能够通过vector中的三个成员变量相减得到。
/*
迭代器
*/
//可读可写
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
//可读不可写
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
//得到vector的有效长度
size_t size() const
{
return _finish - _start;
}
//得到vector的容量
size_t capacity() const
{
return _end_of_storage - _start;
}
//判断vector是否为空
bool empty() const
{
return _start == _finish;
}
void reserve(size_t n)
{
if (n > capacity())
{
size_t oldSize = size();
iterator temp = new T[n];
//避免因_start为nullptr时造成的错误 空指针不能被解引用
if (_start)
{
for (size_t i = 0; i < oldSize; ++i)
{
temp[i] = _start[i];
}
}
//空指针可以被delete 所以delete放在 if (_start)外面统一处理
//因为delete会检查被delete的目标是否为空指针 如果是空指针则不做处理
//一定要先delete再给_start赋值 不然会找不到_start指向的旧空间 这样会内存泄漏
delete[] _start;
_start = temp;
_finish = _start + oldSize;
_end_of_storage = _start + n;
}
}
这里有两个需要注意的地方:
① 我们需要提前记录扩容前的有效长度oldSize,因为_finish是通过_start加上有效长度得到的。你可能会感到疑惑,为什么不直接用函数size()呢?那是因为在修改_finish的时候,_finish和_start已经不指向同一块空间了,而size()中求有效长度是通过_finish - _start实现的,不指向同一块空间的两个指针相减是没有意义的!
并且reserve扩容是不会改变vector的有效长度的,所以扩容前后的有效长度是不变的,因此可以通过_start + oldSize得到_finish在新空间上的位置。
② 实现reserve时,我是用operator[]和for循环搭配来完成将旧空间的数据拷贝到新空间。你在实现时可能会用memcpy来完成拷贝工作,但我可以很负责任的告诉你,memcpy并不能很好的完成拷贝工作。因为memcpy是将一块空间的内容原封不动的拷贝到另一块空间,这就决定了memcpy是浅拷贝而不是深拷贝。
因此memcpy在拷贝内置类型的时候不会出错;而在拷贝自定义类型,并且自定义类型涉及到资源管理时就会出问题。
void resize(size_t n, const T& val = T())
{
if (n <= size())
{
//情况一:n小于size()时 直接改变标志着有效长度的_finish
//因为正常调用api是不能访问到_finish后面的数据的 简介达到了删除的目的
//当n == size()时 下面的语句可以视为_finish = _finish
_finish = _start + n;
}
else
{
//提前扩容 然后把val一个一个插入进去 同时还能调整_finish的位置
//_start + n为resize后_finish应该在的位置
reserve(n);
while (_finish != _start + n)
{
*_finish = val;
++_finish;
}
}
}
你有没有发现一个东西非常奇怪,这个奇怪的东西就是const T& val = T(),其中T可以是任意类型,如int、double等内置类型或者是string等自定义类型。当T为string等自定义类型时我们知道T()就是在构建一个匿名对象,这个匿名对象会调用相应类型的默认构造函数,那当T为int等内置类型呢?难道也是在调用默认构造函数吗?是的,C++为了使模板更具通用性,对内置类型进行了升级,让内置类型也有了构造函数。
因此在resize延长vector时,如果不指定val就会填充默认值。这个默认值与T有关,如果T为int那么默认值是0;T为double,默认值是0.0;T为char,默认值为'\0'……。
T& operator[](size_t pos)
{
assert(pos >= 0 && pos < size());
return _start[pos];
}
const T& operator[](size_t pos) const
{
assert(pos >= 0 && pos < size());
return _start[pos];
}
T& front()
{
return *_start;
}
const T& front() const
{
return *_start;
}
T& back()
{
return *(_finish - 1);
}
const T& back() const
{
return *(_finish - 1);
}
void push_back(const T& val = T())
{
//如果_finish == _end_of_storage就说明vector已经存满数据了 需要扩容
if (_finish == _end_of_storage)
{
reserve(capacity() == 0 ? 2 : 2 * capacity());
}
*_finish = val;
++_finish;
}
void pop_back()
{
//判断vector内是否为空 同时也能判断_start是否为空指针
//因为当_start为空指针是_finish也一定为空指针 空指针不可能会大于另一个空指针
assert(_finish > _start);
--_finish;
}
iterator insert(iterator pos, const T& val)
{
assert(pos >= _start && pos <= _finish);
if (_finish == _end_of_storage)
{
//记录pos到_start的距离
size_t len = pos - _start;
reserve(capacity() == 0 ? 2 : 2 * capacity());
//如果发生了增容 就需要重置pos 因为pos发生了迭代器失效
pos = _start + len;
}
//从后开始挪动数据 为将插入的数据腾出位置
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = val;
++_finish;
//返回值为第一个新插入的数据
return pos;
}
insert在实现时最需要注意的点就是扩容后需要更新pos的位置。因为vector扩容并不是在原空间上进行延长,而是在别的地方开辟一块更大的空间,将旧空间的数据拷贝到新空间后再释放原空间。这就会导致_start等成员变量指向扩容后的新空间,而pos还指向已经释放掉的旧空间。
如果不对pos进行修正就直接解引用的话,就相当于对野指针进行解引用了。 因此在扩容之后一定要对pos进行修正。
iterator erase(iterator pos)
{
//同时检查下标有效性和vector是否为空
//当容器为空时 _start == _finish
//所以下面的语句在vector为空时可改成 assert(pos >= _start && pos < _start)
//显然 >= 和 < 不可能同时成立
assert(pos >= _start && pos < _finish);
iterator begin = pos;
while (begin < _finish)
{
*begin = *(begin + 1);
++begin;
}
--_finish;
//返回值为指向已删除数据的下一个数据的迭代器
return pos;
}
void swap(vector& vT)
{
std::swap(_start, vT._start);
std::swap(_finish, vT._finish);
std::swap(_end_of_storage, vT._end_of_storage);
}
void clear()
{
_finish = _start;
}
//默认构造
vector()
: _start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{}
//拷贝构造
vector(const vector& vT)
: _start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
//提前开辟好与vT相同的容量 这样插入过程中就不会发生扩容
//减少一边push_back一边扩容的消耗
reserve(vT.capacity());
for (const auto& k : vT)
{
push_back(k);
}
}
template
vector(inputIterator first, inputIterator last)
: _start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
while (first != last)
{
push_back(*first); // this->push_back(*first)
++first;
}
}
这里不能写成vector(iterator first, iterator last),因为这里的iterator只代表vector的迭代器,这样写的话就只能用vector的迭代器来构造vector了,但是STL的vector能用其他容器的迭代器来构建vector。
所以我们还需要一个模板template
vector(size_t n, const T& val = T())
: _start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
resize(n, val);
}
vector(int n, const T& val = T())
: _start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
resize(n, val);
}
你肯定会很疑惑,这两个函数到底有什么区别,除了一个是size_t n,一个是int n外就没有任何区别了。这就让我来告诉你吧,这两个函数真的没啥区别,两个函数的功能一模一样,都是填充构造,写第二个(int n)属实是无奈之举。
你猜猜看,如果不写第二个填充构造(int n),那么vi的实例化编译器会选择哪个构造函数?
vector vi(2, 100);
我猜你会认为编译器一定会选择填充构造,因为vi一眼就是用2个100来构造的。那你就大错特错了,因为编译器会选择迭代器构造。
这是因为编译器会把2和100都看作int类型,但第一个填充构造(size_t n)的第一个参数是size_t,而迭代器构造的两个参数都是同一类型inputIterator,所以编译器会把inputIterator实例化为int类型,可2和100根本不是迭代器,故而导致了错误。因此我们必须写第二个填充构造(int n)来避免这种乌龙事件。
~vector()
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
vector& operator=(const vector& vT)
{
if (this != &vT)
{
vector temp(vT);
swap(temp);
}
return *this;
}
#pragma once
#include
#include
namespace simulation {
template
class vector {
public:
typedef T* iterator;
typedef const T* const_iterator;
/*
构造函数
*/
//默认构造
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);
}
vector(int n, const T& val = T())
: _start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
resize(n, val);
}
template
vector(inputIterator first, inputIterator last)
: _start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
while (first != last)
{
push_back(*first); // this->push_back(*first)
++first;
}
}
vector(const vector& vT)
: _start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
reserve(vT.capacity());
for (const auto& k : vT)
{
push_back(k);
}
}
~vector()
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
vector& operator=(const vector& vT)
{
if (this != &vT)
{
vector temp(vT);
swap(temp);
}
return *this;
}
/*
迭代器
*/
iterator begin()
{
return _start;
}
const_iterator begin() const
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator end() const
{
return _finish;
}
/*
容量相关
*/
//得到vector的有效长度
size_t size() const
{
return _finish - _start;
}
//得到vector的容量
size_t capacity() const
{
return _end_of_storage - _start;
}
void resize(size_t n, const T& val = T())
{
if (n <= size())
{
//情况一:n小于size()时 直接改变标志着有效长度的_finish
//因为正常调用api是不能访问到_finish后面的数据的 简介达到了删除的目的
//当n == size()时 下面的语句可以视为_finish = _finish
_finish = _start + n;
}
else
{
//提前扩容 然后把val一个一个插入进去 同时还能调整_finish的位置
//_start + n为resize后_finish应该在的位置
reserve(n);
while (_finish != _start + n)
{
*_finish = val;
++_finish;
}
}
}
//判断vector是否为空
bool empty() const
{
return _start == _finish;
}
void reserve(size_t n)
{
if (n > capacity())
{
size_t oldSize = size();
iterator temp = new T[n];
//避免因_start为nullptr时造成的错误 空指针不能被解引用
if (_start)
{
for (size_t i = 0; i < oldSize; ++i)
{
temp[i] = _start[i];
}
}
//空指针可以被delete 所以delete放在 if (_start)外面统一处理
//因为delete会检查被delete的目标是否为空指针 如果是空指针则不做处理
//一定要先delete再给_start赋值 不然会找不到_start指向的旧空间 这样会内存泄漏
delete[] _start;
_start = temp;
_finish = _start + oldSize;
_end_of_storage = _start + n;
}
}
/*
元素访问
*/
T& operator[](size_t pos)
{
assert(pos >= 0 && pos < size());
return _start[pos];
}
const T& operator[](size_t pos) const
{
assert(pos >= 0 && pos < size());
return _start[pos];
}
T& front()
{
return *_start;
}
const T& front() const
{
return *_start;
}
T& back()
{
return *(_finish - 1);
}
const T& back() const
{
return *(_finish - 1);
}
/*
修改相关
*/
void push_back(const T& val = T())
{
if (_finish == _end_of_storage)
{
reserve(capacity() == 0 ? 2 : 2 * capacity());
}
*_finish = val;
++_finish;
}
void pop_back()
{
//判断vector内是否为空 同时也能判断_start是否为空指针
//因为当_start为空指针是_finish也一定为空指针 空指针不可能会大于另一个空指针
assert(_finish > _start);
--_finish;
}
iterator insert(iterator pos, const T& val)
{
assert(pos >= _start && pos <= _finish);
if (_finish == _end_of_storage)
{
//记录pos到_start的距离
size_t len = pos - _start;
reserve(capacity() == 0 ? 2 : 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)
{
//同时检查下标有效性和vector是否为空
//当容器为空时 _start == _finish,所以下面的语句在vector为空时可改成 assert(pos >= _start && pos < _start)
//显然 >= 和 < 不可能同时成立
assert(pos >= _start && pos < _finish);
iterator begin = pos;
while (begin < _finish)
{
*begin = *(begin + 1);
++begin;
}
--_finish;
return pos;
}
void swap(vector& vT)
{
std::swap(_start, vT._start);
std::swap(_finish, vT._finish);
std::swap(_end_of_storage, vT._end_of_storage);
}
void clear()
{
_finish = _start;
}
private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
};
}