> 作者简介:?旧言~,目前大二,现在学习Java,c,c++,Python等
> 座右铭:松树千年终是朽,槿花一日自为荣。> 目标:能手撕vector模拟
> 毒鸡汤:在等待的日子里,刻苦读书,谦卑做人,养得深根,日后才能枝叶茂盛。
> 望小伙伴们点赞👍收藏?加关注哟💕💕?
????????我们已经了解了vector的知识点,学完知识点必然需要来手搓一个vector,这样对知识点掌握更加熟练,如果大家写过string模拟的话,手撕vector就更加容易些😚😚。那咱们话不多说进入今天的主题---->【c++】vector模拟。
这里我们就不分解成三文件啦,这里就创建两个文件vector.h(头文件),test.c(测试代码文件)
而我们今天就不像string模拟一样啦,咱们按照下面的方式来模拟vector。
为了避免和库里的vector产生冲突,在自己的命名空间内实现vector
namespace lyk
{
template<class T>//通过模板能够实现不同类型的数据存储
class vector
{
public:
typedef T* iterator;
/*
各类函数功能实现
*/
private:
iterator _start; //指向容器中的起始位置
iterator _finish; //指向容器中最后一个有效数据的下一个位置
iterator _end_of_storage; //指向容器中现有容量的位置
};
}
这里我们需要简单了解成员变量的作用,以下面的图解来解析作用:
这个板块中我们得讲解vector的初始化,销毁.....
在构造函数中无非就是初始化列表,简单来讲是实现初始化。
无参的构造函数,把三个成员变量设置为空指针。
//构造函数 --- 无参构造
vector()
//初始化成员列表
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{}
迭代器区间构造函数,由于不确定元素类型我们就用模板来解析元素类型,之后再尾插。
//构造函数2
template<class InputIterator> //模板函数
vector(InputIterator first, InputIterator last)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
//将迭代器区间在[first,last)的数据一个个尾插到容器当中
while (first != last)
{
push_back(*first);
first++;
}
}
实例:
int a[5] = { 1,2,3,4,5 };
vector<int>v1(a, a + 5);
该容器当中含有n个值为val的数据。将容器容量先设置为n,尾插n个值为val的数据。
//构造函数3
vector(size_t n, const T& val)
:_start(nullptr)
, _finish(nullptr)
, _endofstorage(nullptr)
{
reserve(n); //调用reserve函数将容器容量设置为n
for (size_t i = 0; i < n; i++) //尾插n个值为val的数据到容器当中
{
push_back(val);
}
}
在析构函数中无非就是销毁数据,简单来讲释放内存。
~vector() // 析构函数(销毁)
{
if (_start)
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
}
对于拷贝构造函数,由于涉及到深浅拷贝的问题,我们这里提供传统写法与现代写法。
咱们先看看思路:
1.
//传统写法2.
//出现深浅拷贝问题
//v2(v1)
vector(const vector<T>& v) // 拷贝构造
{
_start = new T[v.capacity()];
memcpy(_start, v._start, v.size()* sizeof(T));
_finish = _start + v.size();
_endofstorage = _start + v.capacity();
}
2.
//传统写法2.
//解决深浅拷贝问题
vector(const vector<T>& v)
{
_start = new T[v.capacity()]; //让v2开辟一块和v1一样大小的空间
for (size_t i = 0; i < v.size(); i++)
{
_start[i] = v[i];//通过循环进行赋值
}
//最后调整_finish和_end_of_storage的大小
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
在这里就会出现一个问题:出现深浅拷贝问题。
两者在拷贝数据的方式上对于内置类型或不需要进行深拷贝的自定义类型,完全是满足要求的,但是当vector存储的是string时,一定存在问题。
采用图解:
存储了5个数据,每个数据都是string类,vector<string>v2(v1),v2也开辟了5个空间。
//现代写法
// v2(v1)
vector(const vector<T>& v) // 拷贝构造
{
reserve(v.capacity()); // 判断是否需要扩容
for (const auto& e : v)
{
push_back(e); // 拷贝尾插
}
}
?赋值运算符重载的进行是深拷贝,是将深拷贝出来的对象与左值进行了交换。
// v1 = v3
vector<T>& operator=(vector<T> v) // 赋值运算重载
{
swap(v);
return *this;
}
这里也有传统写法和现代写法,博主这里是现代写法,有兴趣的小伙伴们大家可以写写传统写法。
首先判断该容器是否为空容器。
然后将容器的各个成员变量设置为空指针即可。
代码实现:
~vector() // 析构函数(销毁)
{
if (_start)
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
}
begin函数返回容器的首地址,end函数返回容器当中有效数据的下一个数据的地址。
iterator begin() // 返回容器的首地址
{
return _start;
}
iterator end() // 返回容器当中有效数据的下一个数据的地址
{
return _finish;
}
const对象调用begin和end函数时所得到的迭代器只能对数据进行读操作,而不能进行修改。
const_iterator begin() const // 返回容器的首地址
{
return _start;
}
const_iterator end() const //返回容器当中有效数据的下一个数据的地址
{
return _finish;
}
小试牛刀:
int main()
{
vector<int> v{ 1,2,3,4,5 };
//范围for进行遍历
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
return 0;
}
运行结果:
咱们再看看这张图:
这两个指针之间对应类型的数据个数,所以size可以由_finish - _start得到,而capacity可以由_endofstorage - _start得到。
size_t size() const // 返回容器当中有效数据的个数
{
return _finish - _start;
}
size_t capacity() const // 返回当前容器的最大容量
{
return _endofstorage - _start;
}
reserve函数:
void reserve(size_t n) // 判断扩容
{
if (n > capacity())
{
size_t old = size();
T* tmp = new T[n];
if (_start)
{
memcpy(tmp, _start, old * sizeof(T));
delete[] _start;
}
_start = tmp;
_finish = _start + old;
_endofstorage = _start + n;
}
}
首先得算好增容前的数据个数,因为增容完后,就需要释放旧空间。
1.如果没有对增容前的数据个数进行记录:?
2.如果增容前后的数据拷贝使用memcpy:
resize规则:
// 判断缩括容
// T()是匿名对象,它的生命周期本来只是存在于当前行,
// 但是被const修饰以后,可以延长它的生命周期
void resize(size_t n, const T& val = T())
{
if (n < size())
{
_finish = _start + n;
}
else
{
if (n > capacity())
{
reserve(n);
}
while (_finish != _start + n)
{
*_finish = val;
++_finish;
}
}
}
通过比较容器当中的_start和_finish指针的指向,若所指位置相同且为空,则该容器为空。
bool empty()const
{
return _start == _finish;
}
void push_back(const T& x) // 尾插
{
if (_finish == _endofstorage)
{
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
}
*_finish = x;
++_finish;
}
void pop_back() // 尾删
{
assert(size() > 0);
--_finish;
}
小试牛刀:
//测试一
void test_vector1()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
for (size_t i = 0; i < v.size(); i++)
{
cout << v[i] << " ";
}
cout << endl;
vector<int>::iterator it1 = v.begin();
while (it1 != v.end())
{
cout << *it1 << " ";
++it1;
}
cout << endl;
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
test_vector1();
return 0;
}
运行结果:
insert函数可以在指定的pos位置插入数据。
void insert(iterator pos, const T& x) // 某个位置插入数据
{
assert(pos >= _start && pos <= _finish);
if (_finish == _endofstorage)
{
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + len;
}
memmove(pos + 1, pos, sizeof(T) * (_finish - pos));
*pos = x;
++_finish;
}
erase函数可以删除所给迭代器pos位置的数据
iterator erase(iterator pos) // 删除所给迭代器pos位置的数据
{
assert(pos >= _start);
assert(pos < _finish);
iterator begin = pos + 1;
while (begin < _finish)
{
*(begin - 1) = *begin;
++begin;
}
--_finish;
return pos;
}
swap函数简单来说就是交换两个容器的数据。
void swap(vector<T>& v) // swap函数用于交换两个容器的数据
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
print_vector函数来打印我们容量的数据。
void print_vector(const vector<int>& v) // 打印
{
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
这里有两个操作符重载函数:
T& operator[](size_t i)
{
assert(i < size()); //检测下标的合法性
return _start[i]; //返回对应数据
}
const T& operator[](size_t i)const
{
assert(i < size()); //检测下标的合法性
return _start[i]; //返回对应数据
}
? ? ? ?今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。