vector容器实际是一个动态数组,它与array的操作非常相似。最大的区别就在于vector的“动态”,动态的理解就是数组的扩容由vector内部机制实现动态开辟空间,无需用户手动操作(申请新空间-复制元素-释放原空间)。
下面我将通过对vector的理解,自己模拟实现vector的基本操作方法,供大家更好的理解这个容器。
下面是vector函数实现的思想,最下面附有笔者完整代码和测试类,有需要的自取~
1.定义类模板,扩展vector的使用
首先我们要知道,vector数组中可存放的数据类型有很多,例如常见的int,float,string 都可以用vector进行操作。但我们如果针对每一个数据类型写一个容器的实现,不止会有很多重复的代码,同样也失去了“容器”的意义。所以下面这段代码不属于vector的方法,但却是我们不可忽略的。
template<class T> //定义
//类模板的使用实例
Vector<int> v1; //v1中存储的类型为int
Vector<std::string> v; //v中存储的类型为std空间的string
笔记: 上面这段代码,相信大家在很多地方都见到过,可是它到底是怎么用的呢?
由于类模板包含类型参数,因此又称为参数化的类。如果说类是对象的抽象,对象是类的实例,则类模板是类的抽象,类是类模板的实例。概念理解了,下面是如何使用的简单介绍。
1.我们在类声明前加入该行代码,如下:
template < class T> //注意本行末尾无分号
class vector
{…}; //类体
2.使用类模板定义对象时,尖括号内写入实际类型名,如下:
类模板名<实际类型名> 对象名;
类模板名<实际类型名> 对象名(实参表列);
3.在类模板外定义成员函数的方法如下:
函数类型 类模板名<虚拟类型参数>::成员函数名(函数形参表列) {…}
4.类模板的类型参数可以有一个或多个,但每个类型前面都必须加class,如:
template <class T1,class T2>
class someclass
{…};
someclass<int,double> obj; //定义对象时代入实际类型名即可
2.无参构造函数
vector无参构造函数的作用是用户未传参时,对象也能被正确的创建出来,此时元素个数是0。
Vector()
:_start(nullptr)
, _finish(nullptr)
, _endOfStorage(nullptr)
{} // 等同于Vector() = default;
3.拷贝构造函数
拷贝构造函数,作用是创建一个与v1数组相同的copy数组对象。使用示例:vector < int > copy(v2);
或者vector < int > copy = v2;
注意: vector即动态数组的拷贝构造函数实现应该用深拷贝。深浅拷贝的区别可以参考博客深浅拷贝的认识 。
Vector(const Vector<T>& v)
{
_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();
_endOfStorage = _start + v.Capacity();
}
思考: 为什么拷贝构造函数必须用深拷贝实现呢?
答:如下图所示的区别,浅拷贝时仅仅改变了copy数组的指针方向,那么数组v2和copy在被析构时就会出现"v2申请的空间"被释放两次,这显然是不合理的,并且会造成程序崩溃。
而我们用深拷贝实现就能完美避免这样的问题出现,此时每个空间只会被释放一次。
4.赋值运算符 = 重载
通过重载赋值运算符,我们就可以实现数组的 = 赋值操作,例如 v3 = v2;
使得v3的数组元素与v2相同。
Vector <T>& operator =(Vector <T> v) //形参
{
Swap(v); //将参数中数组的值
return *this;
}
void Swap(Vector <T> &v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endOfStorage, v._endOfStorage);
}
思考: 赋值运算符的实现为什么不需要将形参中的值依次拷贝到新数组中?
答:形式参数是一个临时对象,在函数调用结束后,析构函数会自动处理形参对象,故此时指向“V2内存空间”的只有一个指针,无需再重新开辟空间。
5.析构函数
vector的析构函数,将实现申请的空间自动释放的功能。
~Vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _endOfStorage = nullptr;
}
}
6.扩容函数
vector的扩容方法有两个,其中reserve()
可以扩充数组的容量大小,resize()
可以扩充数组元素个数。
要理解这两个函数的区别,我们需要清楚capacity不等于size,capacity容量是指容器在分配新存储空间之前可以存储的元素总数,而size是指容器当前可使用的元素个数;
打个比方,reserve(5)表示私家车的容量是5个座位但不是一定有5个座位,而resize(4)表示设置了4个座位。换个说法的理解就是私家车里最多可以设置5个座位,但是当前只设置了4个座位所以只能用4个座位,可以继续通过resize(1)再增加一个座位。
区别也可参考文档STL容器的reserve()函数和resize()函数解析
注意:
1.实际我们在使用vector时,resize()使用的频率更高一些,因为当size>capacity时,resize()会自动调用reserve()实现扩容;而capacity的扩容函数reserve()不会使可用的元素个数增加。
2.reserve()函数扩容步骤:开辟新空间 – 复制元素 – 释放原空间;reserve()函数一般作为其他方法实现时调用的底层函数使用,无需单独调用。
3.resize(n,a)扩容可以设置填充元素的默认值为a。
void Reserve(size_t n) //容量的扩充,实际可用元素个数不变
{
if (n > Capacity())
{
size_t size = Size();
T* tmp = new T[n]; //1.开辟新空间
if (_start)
{
for (size_t i = 0; i < size; i++)
{
tmp[i] = _start[i]; //2.复制元素
}
delete[] _start; //3.释放原空间
}
_start = tmp; //新空间指针重定义
_finish = _start + size;
_endOfStorage = _start + n;
}
}
void Resize(size_t n, const T& value = T()) //可用元素个数的扩充
{
if (n <= Size()) //预扩充个数n比原来总元素Size()少,可能删减元素
{
_finish = _start + n;
return;
}
else
{
if (n > Capacity()) //预扩充个数n比容量大,则调用Reserve(n)增容
{
Reserve(n);
}
while (_finish != _start + n) //预扩充个数n比容量小,扩展可使用的个数
{
*_finish = value;
++_finish;
}
}
}
7.插入函数
insert()插入操作对vector来说,是效率很低的一件事情,因为vector要求元素存储空间是连续的,所以我们每插入一次都意味着有元素的挪动。效率较高的是PushBack()即尾插,可以不移动其他元素位置直接插入。
增容可能引起迭代器的失效。
void PushBack(const T&x) //尾插函数
{
Insert(end(),x); //直接插入到最后一个元素的位置
}
void Insert(iterator pos, const T& x) //在POS 的前面插入
{
assert(pos <= _finish); //预插入的当前位置pos有效
size_t posindex = pos - _start; //找到插入位置pos的下标
if (_finish == _endOfStorage) //若当前vector中的元素已满,则按2倍扩容
{
size_t newCapacity = Capacity() == 0 ? 2 : Capacity() * 2;
Reserve(newCapacity);
pos = _start + posindex; //重新找到需要插入位置pos的下标
}
iterator end = _finish; //当前尾指针赋值给迭代器
while (end>pos) //从最后一个元素开始,依次向后移一位,直到移动pos位置的元素
{
*end = *(end - 1);
--end;
}
*pos = x; //pos位置插入值x
++_finish; //数组的尾指针向后移一位
}
8.删除函数
erase()删除操作对vector来说,不仅是效率很低的一件事情(与insert原因相同),还很容易使当前迭代器失效。所以我们在实现函数时,一定要注意保证当前迭代器的指针不为野指针。
void PopBack() //尾删
{
//--_finish; //直接将尾指针向前移一位
Erase(--end()); //删除迭代器所指的最后一位元素
}
iterator Erase(iterator pos) //删除POS 位置
{
assert(pos < end()); //预删除的当前位置pos有效
iterator next = pos; //保证迭代器指针有效
while (pos < _finish - 1) //用后一位的元素依次覆盖前一个值
{
*pos = *(pos + 1);
++pos;
}
--_finish; //尾指针向前移一位
return next; //返回当前迭代器
}
9.下标运算符[] 重载
实现vector通过下标找到正确的值,因此具有高效的随机访问能力
T& operator[](size_t pos)
{
assert(pos < Size());
return _start[pos]; //返回当前下标的值
}
const T&operator[](size_t pos)const
{
assert(pos < Size());
return _start[pos];
}
附:完整代码实例
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <iostream >
#include <string.h>
using namespace std;
namespace my_vector //防止与库内的方法重名,使用自己的命名空间
{
template<class T> //定义类模板
class Vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
Vector() //无参构造函数
:_start(nullptr)
, _finish(nullptr)
, _endOfStorage(nullptr)
{}
//Vector() = default;
Vector(const Vector<T>& v) //拷贝构造函数
{
_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();
_endOfStorage = _start + v.Capacity();
}
Vector <T>& operator =(Vector <T> v) //赋值重载函数
{
Swap(v); //交换值和位置
return *this;
}
void Swap(Vector <T> &v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endOfStorage, v._endOfStorage);
}
~Vector() //析构函数
{
if (_start)
{
delete[] _start;
_start = _finish = _endOfStorage = nullptr;
}
}
iterator begin() { return _start; }
iterator end() { return _finish; }
const_iterator cbegin()const { return _start; }
const_iterator cend()const { return _finish; }
size_t Size() { return _finish - _start; } //返回数组大小
size_t Capacity(){ return _endOfStorage - _start; } //返回数组容量
size_t Size() const { return _finish - _start; }
size_t Capacity() const { return _endOfStorage - _start; }
void Reserve(size_t n) //动态扩容--改变当前空间大小
{
if (n > Capacity())
{
size_t size = Size();
T* tmp = new T[n];
if (_start)
{
for (size_t i = 0; i < size; i++)
{
tmp[i] = _start[i];
}
delete[] _start;
}
_start = tmp;
_finish = _start + size;
_endOfStorage = _start + n;
}
}
void Resize(size_t n, const T& value = T()) //改变当前使用数据大小
{
if (n <= Size())
{
_finish = _start + n;
return;
}
else
{
if (n > Capacity())
{
Reserve(n);
}
while (_finish != _start + n)
{
*_finish = value;
++_finish;
}
}
}
void PushBack(const T&x) //在数组最后添加一个数
{
Insert(end(),x);
}
void Insert(iterator pos, const T& x) //在pos 的前面插入一个数
{
assert(pos <= _finish);
size_t posindex = pos - _start;
if (_finish == _endOfStorage)
{
size_t newCapacity = Capacity() == 0 ? 2 : Capacity() * 2;
Reserve(newCapacity);
pos = _start + posindex;
}
iterator end = _finish;
while (end>pos)
{
*end = *(end - 1);
--end;
}
*pos = x;
++_finish;
}
void PopBack() //删除最后一个数
{
//--_finish;
Erase(--end());
}
iterator Erase(iterator pos) //删除POS 位置的数
{
assert(pos < end());
iterator next = pos;
while (pos < _finish - 1)
{
*pos = *(pos + 1);
++pos;
}
--_finish;
return next;
}
T& operator[](size_t pos) //重载下标运算符[]
{
assert(pos < Size());
return _start[pos];
}
const T&operator[](size_t pos)const
{
assert(pos < Size());
return _start[pos];
}
private: //成员函数
iterator _start = nullptr; //指向数据块的开始
iterator _finish = nullptr; //指向有效数据的结尾
iterator _endOfStorage = nullptr; //指向储存容量的尾
};
附:测试示例
void Test1() //测试函数
{
Vector<int> v1;
v1.PushBack(1);
v1.PushBack(2);
v1.PushBack(3);
v1.PushBack(4);
cout << "尾插四个数: ";
for (size_t i = 0; i < v1.Size(); i++)
{
cout << v1[i] << " ";
}
cout << endl;
v1.Resize(3);
Vector<int>::iterator it1 = v1.begin();
cout << "Resize(3): ";
while (it1 != v1.end())
{
cout << *it1 << " ";
++it1;
}
cout << endl;
v1.Resize(10, 5);
cout << "Resize(10, 5): ";
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
}
void Test2()
{
Vector<int> v1;
v1.PushBack(1);
v1.PushBack(2);
v1.PushBack(3);
v1.PushBack(4);
v1.PushBack(5);
auto pos = std::find(v1.begin(), v1.end(), 3);
if (pos != v1.end())
{
v1.Insert(pos, 30);
}
cout << "插入数据后: ";
for (size_t i = 0; i < v1.Size(); ++i)
{
cout << v1[i] << " ";
}
cout << endl;
pos = std::find(v1.begin(), v1.end(), 3);
v1.Erase(pos);
cout << "删除数据后: ";
for (size_t i = 0; i < v1.Size(); ++i)
{
cout << v1[i] << " ";
}
cout << endl;
}
void Test3()
{
Vector<int> v1;
v1.PushBack(1);
v1.PushBack(2);
v1.PushBack(3);
v1.PushBack(4);
v1.PushBack(5);
Vector<int> copy = v1; // copy(v1)
for (auto e : copy)
{
cout << e << " ";
}
cout << endl;
Vector<int> v2;
v2.PushBack(10);
v2.PushBack(20);
copy = v2;
for (auto e : copy)
{
cout << e << " ";
}
cout << endl;
}
void Test4()
{
Vector<std::string> v;
v.PushBack("111");
v.PushBack("222");
v.PushBack("333");
v.PushBack("333");
v.PushBack("333");
v.PushBack("333");
v.PushBack("333");
v.PushBack("333");
v.PushBack("333");
v.PushBack("333");
v.PushBack("333");
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
}