vector
🎩前言
vector的中文意思是向量,他可以容纳很多类型的数据,因此vector也被称为容器。
看到前面的解释可能你一脸蒙逼,下面我给出两种简单的理解。
- 可以把vector理解为是一个可以动态增长的数组,一个数组可以存储多个相同类型的数据。
- 也可以理解成是一个顺序表,里面可以存储不同类型的数据
- 其实这两种说法本质都是一样的,因为顺序表就是用数组实现的。
-
vector是c++的一个类,他像前面我介绍过的string类(没看过的同学看这里:)一样,有public函数,也有private成员。vector高级的点在于它广泛应用迭代器,这样极大的提高了效率,
也增加了难度。在vector中,可以把迭代器理解成是一个指针,通过指针实现各个函数接口。但这并不意味着所有的迭代器都是指针。比如在list中,就不是指针。
我们先看一下整体框架
namespace jmy
{
//使用模板
template<class T>
class vector
{
public:
typedef T* iterator;
//这句话必须放在里面,他是public类型的,才能被访问,否则就是私有的,不能访问
//函数实现...
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
};
}
下面看图加深理解
🎩构造函数
构造函数的作用是给成员初始化。
让所有的指针都指向null即可。
//不带参数的构造函数
vector()
:_start(nullptr)
,_finish(nullptr)
,_endofstorage(nullptr)
{}
🎩析构函数
析构函数的作用是释放空间,并且置成空指针。
//清理资源
~vector()
{
delete[]_start;
_start=nullptr;
_finish=_endofstroage=nullptr;
}
🎩reserve
扩容函数,当要插入数据单数容量不够的时候调用这个函数
void reserve(const size_t n)
{
int sz = size();
if (n > capacity())
{
T* tmp = new T[n];
//第一次进来的时候,capacity是0,给了个2,旧空间是空。(_start是空),就不需要拷贝。为了避免这种情况,判断一下
if (_start)
{
for (int = 0; i < sz; ++i)
{
tmp[i] = _start[i];
}
delete[]_start;
}
_start = tmp;
//增完容之后,要让指针指向对的位置
_finish = tmp + sz;
_endofstorage = tmp + n;
}
}
注意这里我把原数据拷贝过来用的是赋值。有的同学可能会想到使用memcpy或者使用strcpy。
下面我解释一下这两个函数为什么不行
- memcpy。用于内存复制,这是内存拷贝函数
用memcpy的主要问题:他是浅拷贝,但是我们需要使用深拷贝。- strcpy,用于复制字符串到另一个空间里,这是字符串拷贝函数。
- mem系列的函数是按字节拷贝的,如果传过来的T是int类型,那么就可以使用mem系列的函数,但是T也可能是其他类型的,这时使用mem系列的函数就会出现问题。
🎩push_back
- 这个函数是在最后位置插入x
- 基本操作步骤:
先检查容量是否够用,如果不够就扩容—>在最后的位置插入x—>更新_finish
- 一共有两种写法:
- 普通方式
- 复用insert(我将在后面实现)
void push_back(const T& x)
{
if (_endofstorage == _finish)
{
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
}
*_finish = x;//finish是最后一个元素的位置,直接解引用放进去数据即可
_finish++;
}
下面是复用insert之后的代码
void push_back(const T& x)
{
// 如果复用insert
insert(_finish,x);
}
🎩pop_back
这个函数很简单,让_finish减1即可。
- 一共有两种写法:
- 普通方式
- 复用erase(我将在后面实现)
void pop_back()
{
assert(_start<_finish);
_finish--;
}
下面是复用erase的代码
void pop_back()
{
//如果复用erase
erase(_finish - 1);
}
🎩 insert
- 这个函数是在pos位置插入x
- 基本操作步骤:
如果需要增容,就先增容–》把pos之后的数据依次往后移一位—〉在pos位置插入x----》更新指针
void insert(iterator pos, const T& x)
{
assert(pos <=_finish);
//可能需要扩容
if (_endofstorage == _finish)
{
size_t n = pos - _start;
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
pos = _start + n;
}
iterator end = _finish-1;
while(end>=pos)//这里可以相等,就可以让pushback复用他
{
*(end+1) = *end;
--end;
}
*pos = x;
++_finish;
}
🎩 erase
- 这个函数是在pos位置删除一个数据,结果返回当前位置的下一个位置。
- 基本操作步骤:
从pos位置开始,后面的值依次往前覆盖—》更新指针,返回pos
//删除pos位置的元素。结果返回当前位置的下一个位置
iterator erase(iterator pos)
{
assert(pos < _finish);
iterator it = pos;
while (it<_finish)
{
*it = *(it + 1);
++it;
}
--_finish;
return pos;
}
🎩size
size_t size() const
{
return _finish - _start;
}
🎩capacity
size_t capacity() const
{
return _endofstorage - _start;
}
🎩resize
- 不仅开空间,还要初始化(初始化成缺省值)。
- 它分成3种情况
- n<size时,让_finish指向n的位置即可。
- n>capacity,先进行扩容–》把从_finish开始位置往后支撑缺省值–〉更新_finish指针
- size<n<capacity,直接改变_finish即可。
void resize(size_t n,const T&val=T())
{
if (n < size())
{
_finish = _start+n;
}
else
{
iterator it = _finish;
if (n > capacity())
{
reserve(n);
//这里不用memset的原因
// memxx系类的函数是按字节赋值的,但是对于自定义类型,他不能处理
while (it < _start+n)
{
*_finish = val;
++it;
}
_finish = _start+n;
}
}
}
🎩operator[]
实现这个函数的目的:可以像数组一样的到某个数据。
T& operator[](const size_t pos)
{
assert(pos < size());
return _start[pos];
}
🎩operator=
- 赋值函数,把v3的值赋给v1
- 这个函数有两种写法
1.普通方法
2. 调用swap(推荐使用这个)
方法1
//赋值函数v1=v3
//方法1
vector<T>& operator=(const vector<T> & v)
{
if(this!=&v)
{
//先释放v1(旧空间)
delete[]_start;
//让旧空间指向新的空间
_start = new T[v.capacity()];
memcpy(_start, v._start, sizeof(T)* v.size());
}
return *this;
}
方法2
//赋值函数v1=v3
//方法2
vector<T>& operator=( vector<T> v)
{
swap(v);//this省略不写
return *this;
}
🎩swap
自己构造函数swap,这是一个浅拷贝,可以提高效率。如果使用默认的swap,是深拷贝,代价很大。
void swap(vector<T>& v)
{
//这里调用的是std命名空间的swap函数
:: swap(_start, v._start);
:: swap(_finish, v._finish);
:: swap(_endofstorage, v._endofstorage);
}