小编个人主页详情<—请点击
小编个人gitee代码仓库<—请点击
c++系列专栏<—请点击
倘若命中无此运,孤身亦可登昆仑,送给屏幕面前的读者朋友们和小编自己!
目录
前言
在学习vector的模拟实现的时候一定要了解vector的各个接口是什么作用,所以在学习本文前,请读者友友们先阅读【c++】STL容器-vector的使用—书接上文 详情请点击<—
本文会在已经学习了vector的使用的基础上进行讲解
本文由小编为大家介绍—【c++】STL容器-vector的模拟实现
一、基本框架
对于vector的实现,我们要实现的是一个模拟版本,小编会带领读者友友们实现vector的基本接口,帮助读者友友们更好的理解vector的底层,并且更深一步的掌握vector
- 我们知道在STL容器中,都是使用模板来实现的,那么既然设计到模板,模板是声明和定义不分离的,本文vector的模拟实现,小编采用声明和定义不分离的写法
- 并且注意由于我们在test.cpp中需要包含头文件#include<iostream>和展开std命名空间,那么我们自己实现的vector就会和库里的vector重名,所以我们在vector.h中开辟一个我们自己的命名空间,在这个命名空间中用于我们vector的模拟实现
- 那么我们就需要两个文件,vector.h用于放我们的声明和定义,并且进行测试模拟实现的正确性,test.cpp文件放main函数,包#include "vector.h"的头文件,声明类域进行测试
二、模拟实现
注意事项:
- 对于绝对不可能为负数的整型值,我们使用size_t进行定义,例如用于接收元素个数的n变量和size或capacity的返回值
- 文中所指的普通对象是不被const修饰的对象,权限是可读可写,const对象是指被const修饰的对象,权限是只读
- 在vector.h中进行了断言需要包头文件#include <assert.h>
铺垫
-
vector是类模板,所以这里我们也要采用类模板的形式模拟实现vector,关于allocator<T>是空间配置器,内存池,用于避免重复在堆上频繁申请空间,提高效率,读者友友们暂时不需要了解,这里小编在实现的时候也会默认将其忽略
-
在使用类模板的时候要使用类型进行显示实例化,显示实例化之后,T就会被识别为用于显示实例化的类型,即T就为我们使用vector存储的值的类型
-
我们知道vector其实就是一个可以存放不同数据类型的顺序表,那么与我们的常规不同认知,常规实现顺序表应该有一个指针指向堆上的一块空间,有大小,有容量,这里不同,这里采用了三个指针进行替代,并且我们还可以使用这三个指针进行计算大小和容量,在经过计算后两者表示并无区别,三个指针的指向位置和具体计算如下图
-
c++语法支持在类的成员变量声明的时候给缺省值,那么在调用构造函数时,初始化列表按照成员变量声明的顺序依次进行成员变量的初始化的时候如果用户没有传参,那么就使用缺省值对成员变量进行初始化,这里我们可以给三个指针的初始值为nullptr,这样就不用在构造函数和拷贝构造函数的定义的时候重复在初始化列表给三个指针nullptr值了
namespace wzx
{
template<typename T>
class vector
{
public:
private:
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _endofstorage = nullptr;
};
}
构造函数 vector
- 由于我们在类模板中的成员变量进行声明的时候就已经给出三个指针为nullptr空的缺省值,在调用构造函数的时候,会自动使用nullptr缺省值对成员变量进行初始化,那么在这里我们就不需要进行重复处理了
vector()
{}
析构函数 ~vector
- 当指向堆上申请的空间的指针_start为空的时候,那么代表此时vector中没有数据,即没有资源需要进行清理,对于nullptr没有必要进行清理释放,这里我们使用if语句判断一下即可
- 如果_start不为空,那么就是代表有空间,这里我们调用delete[] 进行释放一下_start指向的空间即可,同时不忘及时将指针置为nulllptr空
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
}
size
capacity
-
针对capacity和size的计算规则,学习下图即可
-
由于我们这里仅仅是获取size和capacity的值,并不对vector的成员变量进行修改,所以我们可以使用const修饰this指针,让普通对象和const对象都可以进行调用
size_t size() const
{
return _finish - _start;
}
size_t capacity() const
{
return _endofstorage - _start;
}
reserve
- 针对reserve我们通常是只扩不缩的,因为缩容同样也要异地缩容,消耗代价也大,所以这里我们要判断一下,当用户传入的n大于capacity的时候我们才进行扩容,如果要是小于等于capacity我们不采取任何操作
- 由于我们要进行扩容的时候默认采取异地扩容,所以当进行异地扩容的时候,_start指向的空间必然会被释放,并且_start指向新空间,那么在调用size的时候获取的当前元素的有效个数大小就必然会不正确,所以在进行正式扩容之前,我们提前使用sz变量保存一下当前元素的有效个数
- 使用tmp指向使用new[]申请n个T类型的大小的空间,接下来就是拷贝_start指向空间上的数据到tmp指向的空间上,这里有可能_start为空即没有数据也就不需要进行拷贝,所以这里使用if语句判断一下
- 这里进行接下来的拷贝数据,很多读者友友可能想使用memcpy进行按字节进行拷贝数据,在这里不完全可以,当要拷贝的数据是内置类型的时候进行按字节进行拷贝不会出错,但是如果要拷贝的数据类型是需要进行深拷贝的自定义类型的时候就会出错
5. 那么我们应该如何做才能让vector上存储的自定义类型在拷贝的时候去调用它的拷贝构造完成深拷贝呢?这里使用赋值运算符重载函数即可,并且使用for循环加下标和[]的方式,完成对应位置的自定义类型对象的深拷贝即可,因为赋值运算符重载函数对于自定义类型的赋值会去调用这个自定义类型对象的赋值运算符重载函数,完成深拷贝
6. 当深拷贝中的深拷贝完成之后,调用delete[]释放原空间
7. 让_start指向新空间,接下来更新_finish和_endofstorage即可
void reserve(size_t n)
{
if (n > capacity())
{
size_t sz = size();
T* tmp = new T[n];
if (_start)
{
for (int i = 0; i < size(); i++)
{
tmp[i] = _start[i];
}
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_endofstorage = _start + n;
}
}
push_back
- 对于参数设计,由于vector中的类型不确定,有可能会存储内置类型,也有可能会存储自定义类型,针对自定义类型如果采用传值的方式消耗大,所以我们使用引用传参的方式,并且由于我们并不需要对x进行修改,所以加上一个const修饰x即可
- 接下来我们需要判断我们的空间是否满了,那么根据_finish和_endofstorage指针的特性,由于_finish指针是指向有效数据的最后一个,_endofstorage是指向空间的结尾的下一个位置,当这两个指针指向的位置相同的时候,即代表空间满了
- 接下来如果满了,那么我们进行扩容,一般是采用2倍扩容,即capacity()*2,这里需要考虑一点当最初的时候容量为0,如果贸然使用capacity()*2进行扩容,那么扩容后值仍然为0,所以需要特殊处理判断一下,当容量为0的时候,我们开4个空间,容量不为0我们才capacity()*2,接下来使用reserve开对应的空间即可
- 尾插很简单,由于_finish指向的是最后一个有效数据的下一个位置,那么我们直接使用解引用在_finish位置处赋值x即可,这里也必须采用赋值,如果x的类型是需要进行深拷贝的自定义类型就去调用其对应的赋值运算符重载函数完成深拷贝,最后赋值完成之后,不要忘记调整_finish的位置即可
void push_back(const T& x)
{
if (_finish == _endofstorage)
{
size_t n = capacity() == 0 ? 4 : capacity() * 2;
reserve(n);
}
*_finish = x;
_finish++;
}
pop_back
- 在进行尾删的时候我们要确定此时有效数据不为0,即有数据可删,那么我们使用assert断言一下即可
- 这里需要尾删一个数据,我们仅需要让有效个数减1,即可进行尾删,因为vector的数据个数界定和访问遍历数据都是使用有效个数来限定,有效个数的计算是_finish-_start,由于空间是连续的,那么即让_finish减1即可
void pop_back()
{
assert(size() != 0);
_finish--;
}
迭代器 begin end 对应 iterator 和 const_iterator
- 迭代器是被typedef出来的,可能是指针也可能不是指针,但是在vector这里小编实现iterator迭代器的类型为T类型的指针,同时还有一个const_iterator类型的迭代器,其使用const修饰T即指针指向的是为const对象,iterator是提供给普通对象使用,权限为可读可写,const_iterator是提供给const对象使用,权限为只读
- 并且编译器会根据对象的类型,自动调用最为适合类型的迭代器,当遇到普通对象的时候会去调用普通的迭代器当没有对应的普通迭代器才会去调用const类型的迭代器,当遇到const对象的时候会去调用const类型的迭代器
- 那么我们根据上图的位置关系,分别返回迭代器的对应指针位置_start和_finish即可,同时还应该提供一个const类型的迭代器,专门让const类型的对象进行调用,那么其对应的begin和end的this指针应该使用const进行修饰才能专门被const对象进行调用
typedef T* iterator;
typedef const T* const_iterator;
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator end() const
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
operator[] 和 由const修饰的operator []
- 我们知道vector的底层实际上是一个顺序表数组,那么在类对象的私有成员变量中有指向顺序表数组的指针_start,使用_start[pos]的方式可以访问pos位置的元素在底层等于*(_start+pos)的方式,_start有是私有成员变量,在类外无法访问,但是在类内可以突破访问限定符进行访问,所以我们直接返回_start[pos]即可,并且由于出了作用域这个T类型的元素还在,这个元素不属于全局变量或局部变量,而是属于对象,只要对象不销毁这个元素就一直存在,所以我们使用返回引用的方式进行返回即可
- 同时我们还应该提供一个const版本的operator[],专门用于const对象调用,权限是只读不可修改,普通版本的operator[]是专门用于不被const对象修饰的对象进行调用,权限是可读可写
T& operator[](size_t pos)
{
assert(pos<size());
return _start[pos];
}
const T& operator[](size_t pos) const
{
assert(pos < size());
return _start[pos];
}
测试
- 我们知道范围for的本质就是被编译器替换成迭代器进行调用,既然vector支持迭代器,那么同样也应该支持范围for,这里小编一并进行测试
void test_vector1()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
vector<int>::iterator it = v.begin();
while (it != v.end())
{
cout << *it << ' ';
it++;
}
cout << endl;
for (auto e : v)
{
cout << e << ' ';
}
cout << endl;
v.pop_back();
v.pop_back();
v.pop_back();
v.pop_back();
for (size_t i = 0; i < v.size(); i++)
{
cout << v[i] << ' ';
}
cout << endl;
}
测试结果如下,正确
insert
- insert在pos迭代器位置插入T类型的值x,涉及到迭代器失效的问题,那么我们继续往下探究
- 首先插入的位置的范围可以是头插尾插,头尾之间任何一个位置的值都可以,这里的尾插的位置是_finish,所以我们要先进行断言以下pos位置
- 接下来判断空间是否满了,如果满了这里需要进行扩容,一旦涉及到扩容,在c++中的扩容机制就是异地扩容,那么就代表_start指向的空间位置要改变,那么此时pos仍然指向原位置,此时就出大问题了,一旦不及时更新pos位置,那么我们插入的位置就成了原位置,但是此时原位置已经被释放掉了,那么再去访问已经被释放的空间,那么必然出错,所以在进行扩容之后要对pos位置进行及时更新
- 那么如何进行更新,这简单,因为pos指向的位置所处的空间的是_start指向的空间,空间是连续的,那么我们计算出两者的相对差值个数sz即可,那么计算出差值之后,在新的_start位置指向的空间加上两者的相对差值个数sz就可以计算出pos的新对应位置
- 如果扩容了pos指向的对应的是新空间上的位置,如果没有扩容pos指向的仍是旧空间位置上的值,接下来就是挪动数据,由于是插入一个数据,那么将从最后一个有效数据位置上对应的值到pos位置的值依次向后挪动1个位置,这里注意必须是从最后一个有效数据位置开始向后挪动,如果是从pos位置开始向后挪动数据会覆盖数据
- 在挪动完之后在pos位置放数据x,赋值即可,由于插入了一个值,不要忘记更新_finish
- 并且由于我们在调用insert的时候不能确定不同平台下的扩容机制,以及什么时候扩容,并且迭代器是传值传参的,所以更新了insert函数里的pos位置迭代器不影响调用位置处的迭代器it,所以我们要返回这个迭代器位置,如果用户在传参it迭代器之后由于我们不能确定不同平台下的扩容机制,以及什么时候扩容,一旦扩容用于外面用于传参是迭代器it就失效了,所以失效是有概率的,这里我们为了严谨考虑统一认为在调用完insert之后迭代器都失效了,如果想要继续使用迭代器,那么应该使用一个迭代器接收insert返回的迭代器
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start && pos <= _finish);
if (_finish == _endofstorage)
{
size_t sz = pos - _start;
size_t n = capacity() == 0 ? 4 : capacity() * 2;
reserve(n);
pos = _start + sz;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
*pos = x;
_finish++;
return pos;
}
erase
- erase删除pos迭代器位置的值的时候,同样涉及到迭代器失效的问题,那么我们继续往下探究
- 进行删除pos位置的值的时候,删除的位置可以是头可以是尾,可以是头尾中间的任意一个位置,这里的尾是_finish-1即为有效数据的最后一个有效数据的位置,这里进行删除的时候不可以是_finish位置,因为这个位置为最后一个有效数据的下一个位置,所以要断言以下pos确保合法性
- 那么删除pos位置我们使用一个迭代器end将pos+1位置到有效数据的最后一个位置的数据依次向前挪动即可
- 挪动完成之后数据少了一个,更新一下_finish即可
- 那么进行pos位置的删除之后我们认为,此时pos位置处的迭代器失效了,因为pos位置处的原对应位置的值已经改变了,已经不是当初它所指向的位置的值了,那么erase规定返回原pos位置的下一个位置数据对应的位置,由于pos位置的值已经被删除了,原pos位置的下一个位置数据对应的位置的值被挪动覆盖到了pos位置处,所以我们返回的还是pos位置的迭代器,当删除的是最后一个位置的有效数据的时候,我们返回的是最后一个有效数据的下一个位置的迭代器
- 并且不光是pos迭代器位置的值改变,pos位置的迭代器的意义改变,pos位置及其向后一直到最后一个有效数据的原迭代器全部失效了,所以通常的我们就认为在erase之后,pos位置迭代器失效了
iterator erase(iterator pos)
{
assert(pos >= _start && pos < _finish);
iterator end = pos + 1;
while (end != _finish)
{
*(end - 1) = *end;
end++;
}
_finish--;
return pos;
}
测试
- 使用如下代码测试insert和erase即可
void test_vector2()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
for (auto e : v)
{
cout << e << ' ';
}
cout << endl;
vector<int>::iterator pos = v.begin();
pos = v.insert(pos, 11);
for (auto e : v)
{
cout << e << ' ';
}
cout << endl;
v.erase(v.end() - 1);
//这里小编想要删除最后一个位置的值,应该使用
//v.end()-1,因为end()返回的是_finish位置
//的迭代器,为最后一个有效数据的下一个位置的
//迭代器,所以这里要-1让其为最后一个有效数据
//的迭代器
for (auto e : v)
{
cout << e << ' ';
}
cout << endl;
}
测试结果如下,无误
resize
- 参数这里我们给const T&了一个缺省值T(),我们知道对于自定义类型可以使用匿名对象即T()调用默认构造函数进行构造匿名对象,作为缺省参数,可是对于内置类型却没有办法使用模板的时候给对应类型的缺省值,于是在c++中对内置类型进行了全面升级,使内置类型在模板中也可以使用T()进行调用默认构造函数进行构造出属于那个对应内置类型的值作为缺省值
- 这里有两种情况,给定的n小于等于size()和给定n大于size()
- 第一种情况,此时不需要进行扩容,因为给定的个数n小于等于有效个数的大小,当前容量一定满足需求不需要进行扩容,那么由于vector的数据个数界定和访问遍历数据都是使用有效个数来限定,有效个数的计算是_finish-_start,由于空间是连续的,那么即让_finish的值为_start+n即可,这样计算出来的有效个数就为n
- 第二种情况,当给定的个数n大于有效个数的大小,那么有可能此时的n小于等于容量,此时不需要进行扩容,也有可能n大于容量,此时需要进行扩容,这里我们干脆一点,统一将其进行扩容,那么扩容之后,使用_finish迭代器在有效数据的最后一个位置的下一个位置处放T,持续循环到_start+n位置处即可
void resize(size_t n,const T& x = T())
{
if (n <= size())
{
_finish = _start + n;
}
else
{
reserve(n);
while (_finish != _start + n)
{
*_finish = x;
_finish++;
}
}
}
测试
- 我们进行如下代码的测试,在v.resize(2)的时候将有效个数的值设置为2,那么接下来使用v.resize(4)此时进行填充值,即由于vector类模板使用int类型进行实例化出对象,那么T就为int,那么会去调用int()进行默认构造出0,使用0进行填充有效数据,当我们v.resize(20,7),我们指定了值进行填充,那么就不会使用默认的0进行填充而是使用我们指定的值7进行填充
void test_vector5()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
for (auto e : v)
{
cout << e << ' ';
}
cout << endl;
v.resize(2);
for (auto e : v)
{
cout << e << ' ';
}
cout << endl;
v.resize(4);
for (auto e : v)
{
cout << e << ' ';
}
cout << endl;
v.resize(20, 7);
for (auto e : v)
{
cout << e << ' ';
}
cout << endl;
}
测试结果如下,正确
拷贝构造函数 vector
- 首先我们将当前容量扩容至和进行拷贝的v的容量相同
- 那么进行拷贝构造函数这里同样不可以使用memcpy进行按字节进行拷贝,具体原因小编在reserve中有讲解,读者友友可以向上翻查看reserve的讲解,这里应该使用for循环加[]加赋值进行我们的拷贝工作
- 接下来按照v的有效个数大小和容量更新_start和_endofstorage即可
vector(const vector<T>& v)
{
reserve(v.capacity());
for (int i = 0; i < v.size(); i++)
{
_start[i] = v._start[i];
}
_finish = _start + v.size();
_endofstorage = _start + v.capacity();
}
swap
- 这里的需要进行交换的参数列表中的vector<T>类型的对象v要采用传引用的方式,对象v就为要进行交换的对象的别名
- 此时调用库中的交换函数去进行三个指针的交换,由于这两个对象所对应的在堆上申请的用于存储对象的两块空间没有改变,对象中对于用于存储数据的顺序表数组的控制是使用的这三个对象的指针,所以我们使用库中的交换函数去进行三个指针的交换就实现了对象的交换
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
operator=
- 这里小编直接采用现代写法实现赋值,在进行参数接收对象的时候采用拷贝构造进行构造出目标v此时赋值的结果为我们想要的对象v,采用交换函数交换一下this指针指向的对象和v即可实现我们的目的
- 并且对象v是一个局部对象,在operator=调用结束后,v对象的生命周期结束,此时会自动调用析构函数将this指针原指向的对象的无用资源进行释放,一举两得
- 同时由于我们要支持连续赋值,所以返回this指针的解引用即为this指针指向的vector对象,由于这个对象出了operator=之后还在,所以我们采用引用返回即可
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
测试
- 使用如下代码测试拷贝构造和赋值运算符重载函数即可
void test_vector6()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(4);
vector<int> v1(v);
for (auto e : v1)
{
cout << e << ' ';
}
cout << endl;
vector<int> v2;
v2 = v1;
for (auto e : v2)
{
cout << e << ' ';
}
cout << endl;
}
测试结果如下,正确
构造函数使用n个T类型的值进行构造
- 这里想起我们之间的resize就是使用的n个T类型的值进行构造,那么这里我们复用一下即可完成
- 同时当我们T是int类型的值的时候会出现和构造函数使用迭代器区间进行构造的调用歧义,例如 vector<int> v(5,6) 因为编译器总是会去调用和自己类型最为匹配的,那么这里的n为size_t无符号类型,T为int类型,n传参的时候需要强制类型转化,但是构造函数使用迭代器区间进行构造的时候类型会被识别为两个int,那么就最为匹配,所以编译器会去调用构造函数使用迭代器区间进行构造,这时候5和6被识别为int,那么在构造函数使用迭代器区间进行构造的时候本质是将参数作为迭代器,作为指针去使用,那么指针就要进行解引用,那么这里的实际情况不就是对int整形类型进行解引用,那不崩了嘛,所以我们不能让其调用构造函数使用迭代器区间进行构造
- 所以这里我们单独为调用参数列表为int类型 int类型的参数提供一个构造函数即可,这样在进行调用的时候就不会去构造函数使用迭代器区间进行构造,而是使用我们这个使用n个T类型的值进行构造
vector(size_t n, const T& val)
{
resize(n, val);
}
vector(int n, const T& val)
{
resize(n, val);
}
构造函数使用迭代器区间进行构造
- 这里要使用迭代器区间进行构造,由于vector中可以存放不同类型的值,所以这里的迭代器不只会使用vector的迭代器,还有可能会使用string类型的迭代器,list类型的迭代器,甚至是数组的首元素的指针和数组的首元素的指针加n的形式进行构造,所以这里我们希望设计成为一个模板,模板参数类型为迭代器,这样就可以进行接收任意类型的迭代器区间进行构造了
- 接下来使用first遍历迭代器区间,解引用first将每一个值使用push_back进行尾插进this指针指向的对象即可
template<typename InputIterator>
vector(InputIterator first, InputIterator end)
{
while (first != end)
{
push_back(*first);
first++;
}
}
测试
- 我们使用如下代码进行构造vector类模板对象即可完成我们的测试
void test_vector8()
{
vector<int> v0(5, 6);
for (auto e : v0)
{
cout << e << ' ';
}
cout << endl;
vector<string> v(5,"hello wzx");
for (auto e : v)
{
cout << e << ' ';
}
cout << endl;
string s("xxxxxxxxxxxxxxxxxxxxxxxxxxxx");
vector<char> v1(s.begin(), s.end());
for (auto e : v1)
{
cout << e;
}
cout << endl;
}
测试结果如下,无误
三、源代码
vector.h
#pragma once
#include <assert.h>
#include <string.h>
namespace wzx
{
template<typename T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator end() const
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
vector()
{}
vector(size_t n, const T& val)
{
resize(n, val);
}
vector(int n, const T& val)
{
resize(n, val);
}
template<typename InputIterator>
vector(InputIterator first, InputIterator end)
{
while (first != end)
{
push_back(*first);
first++;
}
}
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
}
vector(const vector<T>& v)
{
reserve(v.capacity());
for (int i = 0; i < v.size(); i++)
{
_start[i] = v._start[i];
}
_finish = _start + v.size();
_endofstorage = _start + v.capacity();
}
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
void reserve(size_t n)
{
if (n > capacity())
{
size_t sz = size();
T* tmp = new T[n];
if (_start)
{
for (int i = 0; i < size(); i++)
{
tmp[i] = _start[i];
}
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_endofstorage = _start + n;
}
}
void resize(size_t n,const T& x=T())
{
if (n <= size())
{
_finish = _start + n;
}
else
{
reserve(n);
while (_finish != _start + n)
{
*_finish = x;
_finish++;
}
}
}
void push_back(const T& x)
{
if (_finish == _endofstorage)
{
size_t n = capacity() == 0 ? 4 : capacity() * 2;
reserve(n);
}
*_finish = x;
_finish++;
}
void pop_back()
{
assert(size() != 0);
_finish--;
}
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start && pos <= _finish);
if (_finish == _endofstorage)
{
size_t sz = pos - _start;
size_t n = capacity() == 0 ? 4 : capacity() * 2;
reserve(n);
pos = _start + sz;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
*pos = x;
_finish++;
return pos;
}
iterator erase(iterator pos)
{
assert(pos >= _start && pos < _finish);
iterator end = pos + 1;
while (end != _finish)
{
*(end - 1) = *end;
end++;
}
_finish--;
return pos;
}
size_t size() const
{
return _finish - _start;
}
size_t capacity() const
{
return _endofstorage - _start;
}
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 print(const vector<int>& v)
{
for (auto e : v)
{
cout << e << ' ';
}
cout << endl;
}
void test_vector1()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
vector<int>::iterator it = v.begin();
while (it != v.end())
{
cout << *it << ' ';
it++;
}
cout << endl;
for (auto e : v)
{
cout << e << ' ';
}
cout << endl;
v.pop_back();
v.pop_back();
v.pop_back();
v.pop_back();
for (size_t i = 0; i < v.size(); i++)
{
cout << v[i] << ' ';
}
cout << endl;
}
void test_vector2()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
for (auto e : v)
{
cout << e << ' ';
}
cout << endl;
vector<int>::iterator pos = v.begin();
pos = v.insert(pos, 11);
for (auto e : v)
{
cout << e << ' ';
}
cout << endl;
v.erase(v.end() - 1);
//这里小编想要删除最后一个位置的值,应该使用
//v.end()-1,因为end()返回的是_finish位置
//的迭代器,为最后一个有效数据的下一个位置的
//迭代器,所以这里要-1让其为最后一个有效数据
//的迭代器
for (auto e : v)
{
cout << e << ' ';
}
cout << endl;
}
void test_vector3()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(4);
v.push_back(4);
vector<int>::iterator it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
{
it = v.erase(it);
}
else
{
it++;
}
}
//v.erase(it);
//it++;
//cout << *it << endl;
//*(it) = 10;
//it++;
//cout << *it << endl;
for (auto e : v)
{
cout << e << ' ';
}
cout << endl;
}
void test_vector5()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
for (auto e : v)
{
cout << e << ' ';
}
cout << endl;
v.resize(2);
for (auto e : v)
{
cout << e << ' ';
}
cout << endl;
v.resize(4);
for (auto e : v)
{
cout << e << ' ';
}
cout << endl;
v.resize(20, 7);
for (auto e : v)
{
cout << e << ' ';
}
cout << endl;
}
void test_vector6()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(4);
vector<int> v1(v);
for (auto e : v1)
{
cout << e << ' ';
}
cout << endl;
vector<int> v2;
v2 = v1;
for (auto e : v2)
{
cout << e << ' ';
}
cout << endl;
}
void test_vector7()
{
vector<string> v;
v.push_back("111111111111111111111");
v.push_back("222222222222222222222");
v.push_back("333333333333333333333");
v.push_back("444444444444444444444");
v.push_back("444444444444444444444");
vector<string> v1(v);
for (auto e : v1)
{
cout << e << ' ';
}
cout << endl;
}
void test_vector8()
{
vector<int> v0(5, 6);
for (auto e : v0)
{
cout << e << ' ';
}
cout << endl;
vector<string> v(5,"hello wzx");
for (auto e : v)
{
cout << e << ' ';
}
cout << endl;
string s("xxxxxxxxxxxxxxxxxxxxxxxxxxxx");
vector<char> v1(s.begin(), s.end());
for (auto e : v1)
{
cout << e;
}
cout << endl;
}
}
test.cpp
#include <iostream>
using namespace std;
#include "vector.h"
int main()
{
wzx::test_vector8();
return 0;
}
总结
以上就是今天的博客内容啦,希望对读者朋友们有帮助
水滴石穿,坚持就是胜利,读者朋友们可以点个关注
点赞收藏加关注,找到小编不迷路!