👦个人主页:@Weraphael
✍🏻作者简介:目前学习C++和算法
✈️专栏:C++航路
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注✨
目录
- 一、简单剖析vector的源码
- 二、准备工作
- 三、模拟实现vector常见操作
- 3.1 无参的默认构造
- 3.2 获取容量
- 3.3 获取元素个数
- 3.4 扩容 + memcpy的浅拷贝问题
- 3.5 尾插
- 3.6 迭代器
- 3.7 析构函数
- 3.8 operator[]
- 3.9 插入insert
- 3.10 删除某个位置的元素erase
- 3.11 尾删pop_back
- 3.12 增加/缩减有效数据resize
- 3.13 拷贝构造 + memcpy浅拷贝
- 3.14 交换
- 3.15 赋值运算符重载
- 3.16 用n个val构造
- 3.17 用迭代器区间初始化
- 四、模拟实现源码
一、简单剖析vector的源码
我们知道,vector
是一个动态数组,它使用连续的内存存储元素。当我们向vector
中插入元素时,如果导致当前内存不足以容纳所有元素,vector
会重新分配更大的内存空间,并将所有元素复制到新的内存中。因此 ,vector
的结构和string
是一样的:
template<class T>
class vector
{
T* _str;
size_t _size;
size_t _capacity;
};
然而vector
的源码还是和以上有所差别的(详细源码在评论区):
template <class T,class Alloc = alloc>
class vector
{
public:
typedef T* iterator;
private:
iterator start;
iterator finish;
iterator end_of_storage;
}
如上所示,我们并不知道源码中的成员变量start
、finish
、end_of_storage
是什么,只能知道它们是指针。因此接下来我们得去看它的成员函数,想要快速了解应该重点看构造函数以及插入接口:
vector()
:start(0),
finish(0),
end_of_storage(0)
{}
vector(size_type n, const T& value)
{
fill_initialize(n, value);
}
vector(int n, const T& value)
{
fill_initialize(n, value);
}
vector(long n, const T& value)
{
fill_initialize(n, value);
}
explicit vector(size_type n)
{
fill_initialize(n, T());
}
尴尬的是构造函数看不出什么所以然来,因此接下来可以看插入接口:
// 尾插
void push_back(const T* x)
{
if (finish != end_of_storage)
{
construct(finish, x);
++finish;
}
else
{
insert_aux(end(), x);
}
}
有插入操作必定涉及到扩容:
void reserve(size_type n)
{
if (capacity() < n)
{
const size_type old_size = size();
iterator tmp = allocate_and_copy(n, start, finish);
destroy(start, finish);
deallocate();
start = tmp;
finish = tmp + old_size;
end_of_storage = start + n;
}
}
从上我们就猜出:
start
:从名字上来看可能是指向起始位置的指针finish
:从尾插可以看出,相当于指向数据的有效个数的指针end_of_storage
:从扩容接口可以看出,相当于指向当前容量的指针
二、准备工作
为了方便管理代码,分两个文件来写:
Test.cpp
- 测试代码逻辑vector.h
- 模拟实现vector
三、模拟实现vector常见操作
3.1 无参的默认构造
namespace wj
{
template<class T>
class vector
{
typedef T* iterator;
public:
// 无参的默认构造
vector()
:_start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{}
private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
};
}
- 为了防止与库的
vector
冲突,要重新写一个命名空间域wj
- 要注意初始化列表的顺序,是按照成员变量的顺序来赋值的!
3.2 获取容量
size_t capacity() const
{
return _end_of_storage - _start;
}
指针 - 指针
返回的是元素个数。
3.3 获取元素个数
size_t size() const
{
return _finish - _start;
}
3.4 扩容 + memcpy的浅拷贝问题
vector
的扩容并不是原地扩容,而是会重新分配更大的内存空间,并将所有元素复制到新的内存中。并且一般都是只扩容,不缩容。
void reserve(size_t n)
{
// 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;
// 不能像下面这么写
// 原因是_start指向新空间后size就失效了(可以看看size的实现)
// 所以应该提前记录size的大小
/*_finish = _start + size();*/
_end_of_storage = _start + n;
}
}
以上代码看似没有问题,其实漏洞百出:
虽然打印出来了,但是程序还是存在bug
。
这是一个隐藏的深拷贝:
memcpy
是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中- 如果拷贝的是内置类型的元素,
memcpy
既高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到动态内存开辟时,就会出错,因为memcpy
的拷贝实际是浅拷贝
尾插"55555"
就要进行扩容,memcpy拷贝数据到新空间;接下来delete旧空间,而delete的底层先是执行析构函数,完成对象(_str
)中资源的清理,然后再释放对象的空间。
接着_start
就会指向新拷贝的空间,然后tmp
出了作用域会再次调用析构函数,导致_str
所指向的内容被析构了两次,因此导致程序报错。
解决方法是:遍历赋值调用赋值重载,实现深拷贝。
void reserve(size_t n)
{
// 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;
}
}
3.5 尾插
void push_back(const T& x)
{
// 当插入一个数据之前要判断是否扩容
if (_finish == _end_of_storage)
{
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
}
// 尾插
*_finish = x;
++_finish;
}
3.6 迭代器
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.7 析构函数
// 7. 析构
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
}
vector
底层实际上自己实现了一个空间配置器(内存池),而内存池有点复杂,我们选择使用new
和delete
代替,因此要写析构函数
3.8 operator[]
// 可读可写
T& operator[](size_t pos)
{
// 检查下标的有效性
assert(pos < size());
return _start[pos];
}
// 可读不可写
const T& operator[](size_t pos) const
{
assert(pos < size());
return _start[pos];
}
3.9 插入insert
实现insert
在某个位置插入
特别要注意扩容部分:vector的扩容会导致迭代器失效
iterator insert(iterator pos, const T& x)
{
// 判断pos的有效性
assert(pos >= _start && pos <= _finish);
// 可能存在扩容
if (_finish == _end_of_storage)
{
// 防止迭代器失效
// 提前记录pos的位置
size_t len = pos - _start;
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
// 防止迭代器失效
// 还原新空间pos的位置
pos = _start + len;
}
// 挪动数据(从最后一个数据往后挪)
// 1. 记录最后一个数据的位置
iterator end = _finish - 1;
// 2, 向后挪动
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
// 3. 插入数据
*pos = x;
++_finish;
return pos;
}
【挪动数据】
3.10 删除某个位置的元素erase
iterator erase(iterator pos)
{
// 检查下标的有效性
assert(pos >= _start && pos < _finish);
// 元素向前移
iterator it = pos + 1;
while (it != _finish)
{
*(it - 1) = *it;
++it;
}
--_finish;
return pos;
}
【删除数据】
3.11 尾删pop_back
void pop_back()
{
if (_start)
{
--_finish;
}
}
3.12 增加/缩减有效数据resize
void resize(size_t n, const T& val = T())
{
// 相当于删除数据
if (n < size())
{
// 改变_finish即可
_finish = _start + n;
}
else // 否则增加数据
{
// 可能存在扩容
reserve(n);
while (_finish != _start + n)
{
// 填值
*_finish = val;
++_finish;
}
}
}
- 为什么第二个参数是:
const T& val = T()
因为resize
不给第二个参数默认是0
,但缺省值不能直接给0
,如果直接给0
只有int
是适用的,而如果T
是string
就不对了。因此这里可以给一个 匿名对象。若T
是string
就会调用它的默认构造。
但有人奇怪如果T
是内置类型能编译的过吗?理论上是不行的,因为内置类型没有构造函数。但是有了模板以后,C++对内置类型进行了升级,内置类型也支持构造函数
3.13 拷贝构造 + memcpy浅拷贝
要手动写拷贝构造,默认不写的话是浅拷贝。成员变量会指向同一块空间,会导致析构两次的问题
// v1已知
// vector<int> v2(v1);
vector(const vector<T>& v) //v就是v1
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
// 1. 让v2开一块和v1一样大的空间
_start = new T[v.capacity()];
// 2. 拷贝数据
memcpy(_start, v._start, sizeof(T) * v.size());
// 3. 调整
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
在reserve
接口讲解到:使用memcpy
导致string
对象的浅拷贝,因此要改掉memcpy
vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
// 以下是传统写法
_start = new T[v.capacity()];
// memcpy(_start, v._start, sizeof(T) * v.size());
for (size_t i = 0; i < v.size(); i++)
{
_start[i] = v._start[i];
}
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
还有一种写法可以避免以上的问题:
// v1已知
// vector<int> v2(v1);
vector(const vector<T>& v) // v1就是v
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
// 让v2开一块和v1一样大的空间
reserve(v.capacity());
for (auto x : v)
{
// 往v2插入数据
push_back(x);
}
}
3.14 交换
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
3.15 赋值运算符重载
// v1已知,v2也已知
// v1 = v2
// 传参时,v2传值会调用拷贝构造(深拷贝)
vector<T>& operator=(vector<T> v)
{
// this就是v1
// 直接交换即可
this->swap(v);
return *this;
}
3.16 用n个val构造
vector(size_t n, const T& val = T())
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
resize(n, val);
}
可以复用resize
,但之前要对成员变量初始化,因为内置类型的指针默认是随机值,不初始化会造成野指针问题。或者可以直接在成员变量给缺省值。
3.17 用迭代器区间初始化
vector(iterator first, iterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
很多人会写出以上代码,但是这样写就写死了,因为这样写只能用vector的迭代器初始化
因此,我们在一个类中还可以再套模板
template<class InputIterator> //一个类模板还可以再套模板
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
以上代码出现了非法的间接寻址,意思就是不是指针或者不是解引用的对象被解引用,因此以上测试代码调用了迭代器区间初始化的函数。
可是为什么会调用迭代器区间初始化的函数呢?
首先以上两个参数是10
和1
,编译器会认为是int
类型的,本应该调用用n个val构造
,但是其第一个参数是无符号的整型,而迭代器区间初始化的参数是是模板,因此编译器会调用更匹配的函数(迭代器区间初始化),而迭代器本应类似一个指针,这里却是一个普通的变量,解引用就导致了非法的间接寻址。
解决方案:函数重载(库里也是这样实现的)
// 用n个val构造
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);
}
四、模拟实现源码
#pragma once
#include<assert.h>
namespace wj
{
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
// 默认构造函数(
vector()
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{}
// 尾插
void push_back(const T& x)
{
if (_finish == _end_of_storage)
{
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
}
*_finish = x;
++_finish;
}
// capacity()
size_t capacity() const
{
return _end_of_storage - _start;
}
// size()
size_t size() const
{
return _finish - _start;
}
// 迭代器
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
// 析构
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
}
// operator[]
T& operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
const T& operator[](size_t pos) const
{
assert(pos < size());
return _start[pos];
}
// 插入insert
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start && pos <= _finish);
if (_finish == _end_of_storage)
{
size_t len = pos - _start;
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
pos = _start + len;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = x;
++_finish;
return pos;
}
// 10. 删除erase
iterator erase(iterator pos)
{
assert(pos >= _start && pos < _finish);
iterator it = pos + 1;
while (it != _finish)
{
*(it - 1) = *it;
++it;
}
--_finish;
return pos;
}
// 11. 尾删
void pop_back()
{
if (_start)
{
--_finish;
}
}
// 12. 增加/缩减有效数据
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;
}
}
}
// 赋值运算符重载
vector<T>& operator=(vector<T> v)
{
this->swap(v);
return *this;
}
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
// reserve
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;
}
}
// 拷贝构造
vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
_start = new T[v.capacity()];
memcpy(_start, v._start, sizeof(T) * v.size());
for (size_t i = 0; i < v.size(); i++)
{
_start[i] = v._start[i];
}
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
// 第二种写法
// vector<int> v2(v1)
/*vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
reserve(v.capacity());
for (auto x : v)
{
push_back(x);
}
}*/
// 用n个val构造
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<class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
};
}