【C++】---STL之vector的模拟实现
- 一、vector在源码中的结构:
- 二、vector类的实现:
- 1、vector的构造
- 2、析构
- 3、拷贝构造
- 4、赋值运算符重载
- 5、迭代器
- 6、operator[ ]
- 7、size()
- 8、capacity()
- 9、reserve()
- 10、resize()
- 11、empty()
- 12、push_back()
- 13、pop_back()
- 14、insert()
- 15、erase()
一、vector在源码中的结构:
vector的成员变量:
start,finish,end_of_storage的返回类型都是迭代器!
他的成员变量是三个迭代器:
(1)start:指向第1个元素(开始)
(2)finish:指向数据的结束
(3)end_of_storage:指向空间的结束
二、vector类的实现:
实现以下vector相关内容:
1、vector的构造
1、无参数构造,构造出一个空容器:
//1、构造函数:
vector()// 空的构造函数,所有变量都初始化为空!
:_start(nullptr)
,_finish(nullptr)
, _end_of_storage(nullptr)
{
}
2、填充构造函数 向容器中插入n个值为val的元素
// 1.1 填充构造函数 向容器中插入n个值为val的元素
vector(size_t n, const T& val)
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
reserve(n);
while (n)
{
push_back(val);
n--;
}
}
3、完整代码:
#include<iostream>
#include<string>
using namespace std;
namespace yjl
{
template<class T>
class vector
{
private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
public:
//1、构造函数:
vector()// 空的构造函数,所有变量都初始化为空!
:_start(nullptr)
,_finish(nullptr)
, _end_of_storage(nullptr)
{
}
// 1.1 填充构造函数 向容器中插入n个值为val的元素
vector(size_t n, const T& val)
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
reserve(n);
while (n)
{
push_back(val);
n--;
}
}
};
}
2、析构
// 2、析构
~vector()
{
if (_start)
{
delete[] _start;// 释放空间!
}
_start = _finish = _end_of_storage = nullptr;
// 对vector的三个迭代器置空!
}
3、拷贝构造
1、传统的写法:
注意: 当我们不写vector的拷贝构造函数,编译器会自动生成一份,但编译器生成的只能完成数据的浅拷贝,然而vector的三个成员变量都是迭代器,也就是指针T*,如果T是内置类型,那么就不会出现问题,但是如果T是自定类型,会出现拷贝对象和被拷贝对象指向同一块空间,根据后定义的先析构的原则,此程序会析构两次,会导致程序崩溃
// 3、拷贝构造:
// (1)传统写法:v1(v)
vector(const vector<T>& v)
{
// 1.申请空间
_start = new T[v.capacity()];
// 2.拷贝数据
for (int i=0;i<v.size();i++)
{
_start[i] = v._start[i];
}
// 3.更新数据
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
2、现代的拷贝构造:开空间+逐个尾插
使用现代的拷贝构造时必须初始化,否则_start、_finish、_end_of_storage都是随机值,拷贝数据时可能会导致越界。如果T是自定义类型,那么会调用T的拷贝构造函数进行深拷贝
//(2)现代写法:复用
vector(const vector<T>& v)
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
// 1.先开空间:reserve
reserve(v.capacity());
// 2.逐个尾插
for (auto& e : v)
{
push_back(e);
}
}
4、赋值运算符重载
1、传统的赋值运算符重载:
// 4、赋值运算符重载
// (1)传统写法:
vector<T> operator=(vector<T> v)
{
if (this != v)
{
delete[] _start;// 如果赋值运算符(=),左右两边不相等,我直接把调用该函数的this清空,
//然后把右边的值赋给左边,后面的代码相当于前面的拷贝构造
// 1.申请空间
_start = new T[v.capacityu()];
// 2.拷贝数据
for (size_t i = 0; i < v.size(); i++)
{
_start = v._start[i];
}
// 3.更新数据
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
}
2、现代写法:
// (2)现代写法:
// v1 = v2 (在成员函数中,v1调用operator=,v1就是this指针,v2相当于 v )
vector<T>& operator=(vector<T> v)//这里面的参数故意没有传引用,目的就是不改变原来v2的值!!!
{
swap(v);//swap是库里的的成员函数,有隐含的this指针!(即:v1 )
return *this;
}
viod swap(vector<T> v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
5、迭代器
(1)普通迭代器
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
(2)const迭代器
const iterator begin()const
{
return _start;
}
const iterator end()const
{
return _finish;
}
6、operator[ ]
//6、 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];
}
7、size()
// 7、size()
size_t size()
{
return _finish - _start;
}
8、capacity()
// 8、capacity()
size_t capacity()
{
return _end_of_storage - _start;
}
9、reserve()
1.开辟新空间
2.拷贝数据
3.释放旧空间
4.更新数据
// 9.reserve
void reserve(size_t n)
{
size_t old_size = size();
// 1.开辟新空间
T* tmp = new T[n];
// 2.拷贝数据
for (size_t i = 0; i < old_size; i++)
{
tmp[i] = _start[i];
}
// 3.释放旧空间
delete[] _start;
// 4.更新数据
_start = tmp;
_finish = _start + old_size;
_end_of_storage = _start + n;
}
为啥要事先保存一个old_size???
注意:错误案例:
10、resize()
(1)当resize的大小比原来小,说明空间够,只需要修改大小即可
(2)当resize的大小比原来大,说明空间不够,同时也说明容量可能不够,要判断是否需要申请容量
// 10、resize()
void resize(size_t n, const T& val= T())// 匿名函数的使用:
{
if (n > size())
{
// 扩容:
reserve(n);
while (_finish < _start + n)
{
*_finish = val;
_finish++;
}
}
else
{
_finish = _start + n;
}
}
11、empty()
// 11、empty
bool empty()
{
return _start == _finish;//起始空间是否为结束空间
}
12、push_back()
尾插时,需要:
(1)判断增容
(2)赋值
(3)更新大小
// 12、push_back()
void push_back(const T& val)
{
// 1、判断是否需要扩容
if (_finish == _end_of_storage)
{
int newcapacity = capacity()==0 ? 4 : capacity() * 2;
reserve(newcapacity);
}
// 2.赋值:
*_finish = val;
// 3.更新数据:
_finish++;
}
13、pop_back()
尾删:
(1)判空
(2)直接更新大小
// 13、pop_back
void pop_back()
{
assert(!empty());
_finish--;
}
14、insert()
1、先断言要插入的pos的位置要在start和finish范围之内。
2、然后再判断是否需要扩容,如果进行了扩容,那么就需要先保存要插入的pos位置到start之间的相对位置。(因为如果进行了扩容操作,那么原来的start指针就会被销毁,所以说要先保存相对位置。)
3、定义一个指针,从尾部往前遍历直到post位置的下一个元素,依次往后挪动。
// 14、insert
void insert(iterator pos, const T& val)
{
// 1、先断言
assert(pos >= _start);
assert(pos <= _finish);
// 2、判断是否需要扩容
if (_finish == _end_of_storage)
{
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + len;
}
// 3、将要插入pos的位置,后面的数据依次往后挪。
iterator it = _finish - 1;
while (it >= pos)
{
*(it + 1) = *it;
it--;
}
_start[pos] = val;
_finish++;
}
15、erase()
1、先断言检查要删除位置pos是否在start和finish之内。
2、然后再要删除位置的下一个位置定义一个指针,然后从这个指针到结尾的位置的数据依次往前挪动覆盖数据。
3、_finish- -
// 15、erase
void erase(iterator pos)
{
//1、先断言
assert(pos >= _start);
assert(pos <= _finish);
//2、定义挪动数据的指针
iterator it = pos + 1;
while (it < _finish)
{
*(it-1) = *it ;
it++;
}
_finish--;
}
好了,今天的分享就到这里了
如果对你有帮助,记得点赞👍+关注哦!
我的主页还有其他文章,欢迎学习指点。关注我,让我们一起学习,一起成长吧!