vector模拟实现
Posted DR5200
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vector模拟实现相关的知识,希望对你有一定的参考价值。
文章目录
一.vector接口总览
namespace lyp
{
//模拟实现vector
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
//默认成员函数
vector(); //构造函数
vector(size_t n, const T& val); //构造函数
template<class InputIterator>
vector(InputIterator first, InputIterator last); //构造函数
vector(const vector<T>& v); //拷贝构造函数
vector<T>& operator=(const vector<T>& v); //赋值运算符重载函数
~vector(); //析构函数
//iterator
iterator begin();
iterator end();
const_iterator begin()const;
const_iterator end()const;
//capacity
size_t size()const;
size_t capacity()const;
void reserve(size_t n);
void resize(size_t n, const T& val = T());
bool empty()const;
//modifiers
void push_back(const T& x);
void pop_back();
void insert(iterator pos, const T& x);
iterator erase(iterator pos);
void swap(vector<T>& v);
//access
T& operator[](size_t i);
const T& operator[](size_t i)const;
private:
iterator _start; //指向容器的头
iterator _finish; //指向有效数据的尾
iterator _endofstorage; //指向容器的尾
};
}
二.vector模拟实现
默认成员函数
构造函数
(1). 构造函数1
构造一个空vector,size 和 capacity 为 0,将 _start,_finish, _endofstorage 都置为空指针即可
vector()
:_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr)
{}
(2). 构造函数2
用 n 个 val 值进行初始化,可以复用 resize 接口,但要注意调用resize()接口需要计算size()和capacity(),所以要将成员变量进行初始化
vector(size_t n, const T& val = T())
// 调用resize()接口需要计算size()和capacity(),所以要进行初始化
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
resize(n,val);
}
(3). 构造函数3
用一段迭代器区间进行初始化,由于不同类型的容器迭代器类型可能不同,因此设计成函数模板,将区间内的内容尾插入vector即可,但注意调用push_back接口通过 _finish == _endofstorage 判断是否满,需要初始化
template<class InputIterator>
vector(InputIterator first, InputIterator last)
// 调用push_back接口通过 _finish == _endofstorage 判断是否满,需要初始化
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
while (first != last)
{
push_back(*first);
++first;
}
}
注意 : 构造函数3和构造函数2写在一起时,那么如果按照下面这样构造会优先调用函数模板,会因为对int类型解引用而报错,所以我们还应该重载两个版本
vector<int> v(10,5);
vector(int n, const T& val = T())
// 调用resize()接口需要计算size()和capacity(),所以要进行初始化
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
resize(n,val);
}
vector(long n, const T& val = T())
// 调用resize()接口需要计算size()和capacity(),所以要进行初始化
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
resize(n,val);
}
拷贝构造函数
传统写法 :
1). 新开辟一块和 v 同样容量的空间,更新 _start, _finish, _endofstorage
2). 将 v 中的数据拷贝到新开辟的空间中
//传统写法
vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
_start = new T[v.capacity()]; //开辟一块和容器v大小相同的空间
for (size_t i = 0; i < v.size(); i++) //将容器v当中的数据一个个拷贝过来
{
_start[i] = v[i];
}
_finish = _start + v.size(); //容器有效数据的尾
_endofstorage = _start + v.capacity(); //整个容器的尾
}
注意 : 不要使用memcpy函数拷贝数据,如果数据是内置类型或浅拷贝的自定义类型,使用memcpy是没有什么问题的,但如果数据是需要深拷贝的自定义类型(string),问题就出现了,拷贝的数据和源数据指向同一块空间
因此,我们使用for循环依次赋值,调用string的赋值运算符重载完成深拷贝
现代写法 :
使用范围for进行遍历,变量e是v中元素的拷贝,如果v中元素是需要深拷贝的自定义类型,会调用拷贝构造函数构造e,从而使e和v中元素所指向的空间不一样 (auto& e : v 也可以,因为push_back在实现的时候还会调用深拷贝类型的赋值运算符重载)
//现代写法
vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
reserve(v.capacity()); //调用reserve函数将容器容量设置为与v相同
for (auto e : v) //将容器v当中的数据一个个尾插过来
{
push_back(e);
}
}
赋值运算符重载
传统写法
1).释放原空间,新开一块容量和v一样大的空间,更新_start,_finish, _endofstorage
2).将v中的数据拷贝到新空间中
注意 : 不能使用memcpy进行拷贝
vector<T>& operator=(const vector<T>& v)
{
if (this != &v)
{
delete[]_start; // 释放原空间
_start = new T[v.capacity()]; // 开辟新空间
for (size_t i = 0; i < v.size(); i++) // 拷贝数据
{
_start[i] = v[i];
}
_finish = _start + v.size(); // 更新_finish
_endofstorage = _start + v.capacity(); // 更新_capacity
}
return *this;
}
现代写法
1).调用拷贝构造函数生成tmp对象
2).分别交换tmp和this的_start,_finish, _endofstorage
vector<T>& operator=(const vector<T>& v)
{
if (this != &v) // 防止自己给自己赋值
{
vector<T> tmp(v); // 拷贝构造tmp对象
swap(tmp); // 交换_start,_finish, _endofstorage
}
return *this;
}
vector<T>& operator=(vector<T>& v) // 拷贝构造v对象
{
swap(v); // 交换_start,_finish, _endofstorage
return *this;
}
析构函数
1). 判断容器是否为空,若为空无需析构
2). 若不为空,将空间释放掉,_start,_finish, _endofstorage置为空指针
~vector()
{
// 不为空,空间释放掉
if (_start)
{
delete[]_start;
}
// _start,_finish, _endofstorage置为空指针
_start = _finish = _endofstorage = nullptr;
}
iterator
begin/end
begin()返回第一个元素的地址,end()返回最后一个元素下一位置的地址,为了能够让const对象调用,加入const版本的begin()和end()
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin()
{
return _start;
}
const_iterator end()
{
return _finish;
}
capacity
size
返回容器中有效数据的个数,用 _finish - _start 即可
size_t size()const
{
return _finish - _start;
}
capacity
返回容器的容量,用_endofstorage - _start 即可
size_t capacity()const
{
return _endofstorage - _start;
}
reserve
1). 当n大于对象当前的capacity时,将capacity扩大到n或大于n。
2). 当n小于对象当前的capacity时,什么也不做。
实现步骤
1). 新开辟一块空间,若容器为空,将_start,_finish指向新开辟空间的首元素地址, _endofstorage指向新开辟空间的最后一个元素下一个位置
2). 若容器不为空,将数据拷贝到新空间,释放掉旧空间,更新_start,_finish, _endofstorage的位置
注意 : 将数据拷贝到新空间,仍然不能用memcpy函数,因为对于需要深拷贝的自定义类型,使用memcpy函数以后,新开辟空间里的元素和原空间里的元素所指向的内存空间是一样的,当旧空间被释放时,会调用自定义类型的析构函数,从而使得新开辟空间里的元素指向的内存空间也被释放掉了
void reserve(size_t n)
{
if (n > capacity())
{
T* tmp = new T[n]; // 新开辟一块空间
// 容器不为空
if (_start)
{
size_t sizecp = size(); // 计算原来容器size个数
for (size_t i = 0; i < size(); i++) // 拷贝数据
{
tmp[i] = _start[i];
}
delete[]_start; // 释放旧空间
_start = tmp; // 更新_start
_finish = _start + sizecp; // 更新_finish
_endofstorage = _start + n; // 更新_endofstorage
}
// 容器为空,更新_start,_finish,_endofstorage的位置
else
{
_start = _finish = tmp;
_endofstorage = _start + n;
}
}
}
resize
1). 当 n < size 时,直接将 _finish = _start + n (将有效数据长度缩小)即可
(2).当 size < n <= capacity 时,我们将有效数据的长度增加到 n,增加出来的有效数据内容是val
(3).当 n > capacity时,先调用上面的 reserve 函数进行增容,再将有效数据的长度增加到 n,增加出来的有效数据内容是val
void resize(size_t n,const T& val = T())
{
// 第一种 n < size()
if (n < size())
{
_finish = _start + n;
}
// n > size()
else
{
// 增容
if (n > capacity())
reserve(n);
// 填充数据val
size_t count = n - size();
while (count--)
{
*_finish = val;
++_finish;
}
}
}
empty
判断 size() == 0 即可
bool empty()const
{
return size() == 0;
}
modifiers
push_back
尾插入数据,首先要检查是否已满,已满则进行增容,增容后尾插即可
void push_back(const T& val)
{
// 增容
if (_finish == _endofstorage)
{
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
}
// 尾插入数据
*_finish = val;
// 使 size++
++_finish;
}
pop_back
尾删时,首先要判断容器是否为空,若为空,则断言报错,不为空,_finish-- 即可
void pop_back()
{
assert(!empty()); // 断言处理
--_finish;
}
insert
1). 容量不够,先增容,增容之前先记录下 pos - _start 的值,否则增容之后,pos 还指向原来已经被释放的空间
2). 将 pos 位置往后的数据往后挪动一位,在pos位置插入值val
void insert(iterator pos, const T& val)
{
// 增容
if (_finish == _endofstorage)
{
// 记录pos相对于_start的位置
size_t poscp = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
// 更新pos位置
pos = _start + poscp;
}
// 挪动pos位置后的数据
for (iterator i = _finish - 1; i != pos; i--)
{
*(i + 1) = *i;
}
// 在pos位置插入值val
*pos = val;
// 使size++
++_finish;
}
erase
容器若为空,则做断言处理,若不为空,将pos位置往后的数据向前挪动一位
iterator erase(iterator pos)
{
// 断言
assert(!empty());
// 将pos位置往后的数据向前挪动一位
for (iterator i = pos + 1; i != _finish; i++)
{
*(i - 1) = *i;
}
// --size
--_finish;
// 返回删除数据的下一个位置
return pos;
}
access
operator[]
返回底层数组对应位置的引用即可
T& operator[](size_t i)
{
// 检查下标有效性
assert(i < size());
// 返回对应位置的引用
return _start[i];
}
const T& operator[](size_t i)const
{
// 检查下标有效性
assert(i < size());
// 返回对应位置的引用
return _start[i];
}
以上是关于vector模拟实现的主要内容,如果未能解决你的问题,请参考以下文章