目录
之前的一篇博客讲到了vector的介绍及其运用:vector的介绍及使用说明 但是我们不仅要会用,还要理解它的底层原理,今天我们通过手撕一个自己的vector,来进一步加深对vector容器的理解。
01.成员变量
namespace my_std
{
template<typename T>
class vector
{
public:
// Vector的迭代器是一个原生指针
typedef T* iterator;
typedef const T* const_iterator;
private:
iterator _start; // 指向数据块的开始
iterator _finish; // 指向有效数据的尾
iterator _endOfStorage; // 指向存储容量的尾
};
}
vector是一个通过类模版实现的容器,通过类定义,可以使其更为灵活,泛用性更强。
在我们定义的vector类中,我们把iterator定义为原生指针T*,虽然vector迭代器严格意义上并不等同于指针,但是其在很多方面与原生指针行为类似,比如可以通过‘++’来递增,通过‘*’来解引用等等。因此这里我们将其定义为原生指针也是比较贴近标准库中的vector的实现的。
-
iterator _start;
:指向 vector 内部数据块的开始位置的迭代器。 -
iterator _finish;
:指向 vector 内部有效数据的尾部。有效数据是指在 vector 中存储的实际元素,而_finish
指向的位置是有效数据的下一个位置。 -
iterator _endOfStorage;
:指向 vector 内部存储容量的尾部的下一个位置。存储容量是指 vector 内部实际分配的内存空间的大小,而_endOfStorage
指向的位置是分配的内存空间的末尾的下一个位置。这个位置之后的内存空间是 vector 可以进行扩展的空间。
迭代器声明为私有的成员类型可以确保用户不能直接访问和操作迭代器,这样可以保护vector内部的状态和数据。但是用户必须通过迭代器才能访问并操作vector内的元素,所以需要有获取迭代器的成员函数,分为读写和只读访问两个版本:
iterator begin() {
return _start;
}
iterator end() {
return _finish;
}
const_iterator cbegin() {
return _start;
}
const_iterator cend() const {
return _finish;
}
02.构造与析构
1.无参构造:
vector()
:_start(nullptr)
,_finish(nullptr)
,_endOfStorage(nullptr)
{}
都初始化为空指针
2.带参构造:
vector(int n, const T& value = T()) {
_start = new T[n];
for (int i = 0; i < n; i++) {
_start[i] = value;
}
_finish = _endOfStorage = _start + n;
}
_start迭代器是T*类型的原生指针,实际上是一个数组,因此可以使用new关键字为其动态分配内存,元素添加后要记得将_finish移到正确的偏移位置。
3.迭代器构造:
template<class InputIterator>
vector(InputIterator first, InputIterator last) {
int size = last - first;
_start = new T[size];
int i = 0;
for (auto it = first; it != last; it++) {
_start[i] = *it;
i++;
}
_finish = _endOfStorage = _start + size;
}
vector还支持迭代器进行构造,通过迭代器遍历源数组,将数据拷贝到目标容器中。迭代器相减可以得到构造的元素数量,再用new关键字对_start进行空间分配,迭代器遍历实现数据拷贝。
4.拷贝构造:
vector(const vector<T>& v) {
_start = new T[v.size()];
for (size_t i = 0; i < v.size(); i++) {
_start[i] = v[i];
}
_finish = _endOfStorage = _start + v.size();
}
拷贝构造其实与迭代器构造类似,不过拷贝构造只支持vector容器类型之间的拷贝,而迭代器构造还支持数组的拷贝以及片段的拷贝,这两者是有区别的。这里的size()函数用于获取元素个数,后面会有关于函数的声明及定义。
5.析构函数:
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _endOfStorage = nullptr;
}
}
进行测试:
03.管理内存
首先我们需要两个函数分别获取vector的数据个数以及容量大小:
size_t size() const {
return _finish - _start;
}
size_t capacity() const {
return _endOfStorage - _start;
}
将其申明为const,确保不会修改成员变量的值。
1.reserve函数
vector的reserve函数用于预留一定的容量,只开辟空间,不进行赋值,空间足够就不进行操作:
void reserve(size_t n) {
if (n > capacity()) {
size_t old_size = size();
T* tmp = new T[n];
if (_start != nullptr) {
for (int i = 0; i < size(); i++) {
tmp[i] = _start[i];
}
}
delete[] _start;
_start = tmp;
_finish = _start + old_size;
_endOfStorage = _start + n;
}
}
使用reserve函数后,会在堆上重新申请一块空间,原来的空间就失效了,需要进行空间释放,这里使用delete操作符,因为_start是通过new操作符进行空间开辟的。_finish与_endOfStorage实际上表示的是相对于_start的偏移量,因此我们需要记录这个偏移量,使得_start指针修改之后_finish与_endOfStorage可以来到正确的位置。
2.reszie函数
resize函数用于改变vector的大小,调整vector中元素的数量,如果调整后的大小大于当前大小,则会在末尾添加新的元素并进行赋值(默认构造值),如果调整后的大小小于当前大小,则会删除末尾的元素。
void resize(size_t n, const T& value = T())
{
T* tmp = new T[n];
if (n <= size()) {
for (size_t i = 0; i < n; i++) {
tmp[i] = _start[i];
}
}
else {
for (size_t i = 0; i < size(); i++) {
tmp[i] = _start[i];
}
for (size_t i = size(); i < n; i++) {
tmp[i] = value;
}
}
delete[] _start;
_start = tmp;
_finish = _endOfStorage = _start + n;
}
同样的,需要开辟一块新的空间并将数据进行迁移,释放原空间内存,由于空间可能扩大也可能缩小,所以要分情况讨论。
运行测试:
04.访问元素
1.‘[]’运算符的重载
为了方便对容器内元素的访问,我们需要重载‘[]’运算符,模拟数组的下标访问:
T& operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
const T& operator[](size_t pos)const
{
assert(pos < size());
return _start[pos];
}
由于迭代器_start实际上就是数组的头指针,因此可以直接调用
2.front、back函数
vector的front函数可以直接访问容器的首元素,back函数可以直接访问容器的尾元素:
T& front()
{
return *_start;
}
const T& front()const
{
return *_start;
}
T& back()
{
return *(_finish - 1);
}
const T& back()const
{
return *(_finish - 1);
}
05.修改元素
1.尾插尾删
在尾部插入元素时,首先要考虑内存是否充足,内存不足的情况下,我们考虑二倍扩容。
void push_back(const T& x) {
if (_finish = _endOfStorage){
int newcapacity = capacity() == 0 ? 2 : 2 * capacity();
}
*_finish = x;
++_finish;
}
void pop_back() {
assert(size() > 0);
--_finish;
}
修改元素后要记得实时更新迭代器。
2.swap交换
vector支持不同类型元素的交换,其原理是通过交换两个容器内部的指针和大小,而并不修改实际的内存块,所以不受元素类型的影响:
void swap(vector<T>& v) {
vector<T> tmp(v);
delete[] v._start;
v._start = _start;
v._finish = _finish;
v._endOfStorage = _endOfStorage;
_start = tmp._start;
_finish = tmp._finish;
_endOfStorage = tmp._endOfStorage;
}
C++标准库也提供了‘std::swap’函数,因此可以直接用‘std::swap’函数实现vector的swap:
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endOfStorage, v._endOfStorage);
}
3.随机插入
vector也支持在指定位置进行元素插入的操作,由于vector内元素是顺序连续存储的,因此每次插入操作都要对当前位置之后的所以元素进行挪动,同样的,在插入操作前需要进行内存检查:
iterator insert(iterator pos, const T& x)
{
assert(pos <= _finish);
// 空间不够先进行增容
if (_finish == _endOfStorage)
{
//size_t size = size();
size_t newCapacity = (0 == capacity()) ? 1 : capacity() * 2;
reserve(newCapacity);
// 如果发生了增容,需要重置pos
pos = _start + size();
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = x;
++_finish;
return pos;
}
pos迭代器指向插入的位置,其本质是在_start迭代器地址基础上的一个偏移,扩容操作会使_start指向一块新的空间,其值会发生改变,因此pos迭代器也要进行改变。
4.随机删除
删除元素也同样需要挪动数据,将pos位置之后的元素以此往前移动覆盖,就可以实现删除效果。删除元素后记得更新_finish指针:
iterator erase(iterator pos)
{
// 挪动数据进行删除
iterator begin = pos + 1;
while (begin != _finish) {
*(begin - 1) = *begin;
++begin;
}
--_finish;
return pos;
}
下面给出完整代码:
#pragma once
#include<iostream>
using namespace std;
#include<assert.h>
namespace my_std
{
template<typename T>
class vector
{
public:
// Vector的迭代器是一个原生指针
typedef T* iterator;
typedef const T* const_iterator;
iterator begin() {
return _start;
}
iterator end() {
return _finish;
}
const_iterator cbegin() {
return _start;
}
const_iterator cend() const {
return _finish;
}
// 创建和销毁
vector()
:_start(nullptr)
,_finish(nullptr)
,_endOfStorage(nullptr)
{}
vector(int n, const T& value = T()) {
_start = new T[n];
for (int i = 0; i < n; i++) {
_start[i] = value;
}
_finish = _endOfStorage = _start + n;
}
template<class InputIterator>
vector(InputIterator first, InputIterator last) {
int size = last - first;
_start = new T[size];
int i = 0;
for (auto it = first; it != last; it++) {
_start[i] = *it;
i++;
}
_finish = _endOfStorage = _start + size;
}
vector(const vector<T>& v) {
_start = new T[v.size()];
for (size_t i = 0; i < v.size(); i++) {
_start[i] = v[i];
}
_finish = _endOfStorage = _start + v.size();
}
vector<T>& operator= (vector<T> v) {
vector<T> new_v(v);
return new_v;
}
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _endOfStorage = nullptr;
}
}
// 内存管理
size_t size() const {
return _finish - _start;
}
size_t capacity() const {
return _endOfStorage - _start;
}
void reserve(size_t n) {
if (n > capacity()) {
size_t old_size = size();
T* tmp = new T[n];
if (_start != nullptr) {
for (int i = 0; i < size(); i++) {
tmp[i] = _start[i];
}
}
delete[] _start;
_start = tmp;
_finish = _start + old_size;
_endOfStorage = _start + n;
}
}
void resize(size_t n, const T& value = T()) {
T* tmp = new T[n];
if (n <= size()) {
for (size_t i = 0; i < n; i++) {
tmp[i] = _start[i];
}
}
else {
for (size_t i = 0; i < size(); i++) {
tmp[i] = _start[i];
}
for (size_t i = size(); i < n; i++) {
tmp[i] = value;
}
}
delete[] _start;
_start = tmp;
_finish = _endOfStorage = _start + n;
}
// 元素访问
T& operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
const T& operator[](size_t pos)const
{
assert(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& x) {
if (_finish = _endOfStorage){
int newcapacity = capacity() == 0 ? 2 : 2 * capacity();
}
*_finish = x;
++_finish;
}
void pop_back() {
assert(size() > 0);
--_finish;
}
void swap(vector<T>& v) {
vector<T> tmp(v);
delete[] v._start;
v._start = _start;
v._finish = _finish;
v._endOfStorage = _endOfStorage;
_start = tmp._start;
_finish = tmp._finish;
_endOfStorage = tmp._endOfStorage;
}
iterator insert(iterator pos, const T& x) {
assert(pos < _finish&& pos >= _start);
if (_finish == _endOfStorage)
{
size_t site = pos - _start;
int newcapacity = capacity() == 0 ? 2 : 2 * (capacity());
reserve(newcapacity);
pos = _start + site;//pos到新空间的位置上
}
iterator end = _finish - 1;
while (end >= pos)//开始整体向后退
{
*(end + 1) = *end;
end--;
}
*pos = x;
++_finish;
return pos;
}
iterator erase(iterator pos){
assert(pos < _finish&& pos >= _start);
assert(size() > 0);
//开始向前移动
iterator start = pos + 1;
while (start < _finish)
{
*(start - 1) = *start;
start++;
}
_finish--;
return pos;//返回删除的位置
}
private:
iterator _start; // 指向数据块的开始
iterator _finish; // 指向有效数据的尾
iterator _endOfStorage; // 指向存储容量的尾
};
}
那么以上就是vector的模拟实现了,欢迎在评论区留言,觉得这篇博客对你有帮助的可以点赞关注收藏支持一波喔~😉