个人主页:点我进入主页
专栏分类:C语言初阶 C语言进阶 数据结构初阶 Linux C++初阶 算法
欢迎大家点赞,评论,收藏。
一起努力,一起奔赴大厂
目录
一.成员变量
二.默认构造
三.size和capacity函数
3.1库中的size函数和capacity函数
3.2模拟实现size和capacity
四.扩容
4.1库中的扩容函数解析
4.2模拟实现扩容函数
4.3resize
五.push_back
5.1库中的push_back
5.2模拟实现push_back
六.迭代器
6.1库中迭代器
6.2模拟
七.insert以及push_back的复用
7.1库中的insert
7.2模拟实现inser以及迭代器失效问题
7.3push_back复用insert
八.删除
8.1pop_back
8.1.1库中的pop_back
8.1.2模拟实现pop_back
8.2erase
8.2.1库中的erase
8.2.1模拟实现erase以及迭代器失效问题
九.拷贝构造
十.再次分析默认构造
十一.赋值构造
十二.析构
一.成员变量
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _endofstorage = nullptr;
在这里我们采用给缺省值,这样可以省去默认构造,成员的类型是迭代器,在后面会分析迭代器是什么样子,在这里我们将他看成指针就可以了。
二.默认构造
vector()
{}
由于我们在声明成员变量时采用了给缺省值,所以不需要我们进行默认构造。其余的默认构造需要使用迭代器,后面会分析。
三.size和capacity函数
3.1库中的size函数和capacity函数
由于我们的成员是用指针进行写的,所以我们需要得到它的大小和空间,我们需要通过函数来获取,库中给的是这两个函数,由于有const版本和非const这两个,又由于不需要修改数据,所以全部使用const版本。
3.2模拟实现size和capacity
size_t capacity() const
{
return _endofstorage - _start;
}
size_t size() const
{
return _finish - _start;
}
我们直接利用指针相减来获取对应的大小和内存。
四.扩容
4.1库中的扩容函数解析
参数是我们当前的容量,当满是就扩容,每一次插入时都需要进行一次检查
4.2模拟实现扩容函数
void reserve(size_t n)
{
size_t oldsize = size();
T* tmp = new T[n];
//memcpy(tmp, _start, size() * sizeof(T));
T* p = tmp;
for (int i = 0; i < oldsize; i++)
{
*p = *(_start + i);
p++;
}
delete[] _start;
_start = tmp;
_finish = _start + oldsize;
_endofstorage = _start + n;
}
当我们的vector是内置类型时可以使用memcpy进行拷贝,因为它只需要进行浅拷贝,但是我们的类型时自定义类型,例如string或者vector等需要深拷贝的类型由于memcpy会进行浅拷贝,所以我们需要进行深拷贝,这个时候我们的解决方法就是直接进行变量插入。
4.3resize
resize是当n大于capacity时进行扩容,小于capacity时更新_finish的位置,我们看代码
void resize(size_t n,const T&val=T())
{
if (n > capacity())
{
reserve(n);
while (_finish < _start + n)
{
*_finish = val;
_finish++;
}
}
else
{
_finish = _start + n;
}
}
五.push_back
5.1库中的push_back
我们使用时直接调用例如我们创建了一个vector<int> v,对象,我们想要插入1,我们直接调用v.push_back(1);
5.2模拟实现push_back
void push_back(const T& val)
{
if (_finish == _endofstorage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = val;
_finish++;
}
和库中的一样,我们的const T&val中的T就是value_type,我们使用const的主要原因就是为了支持隐式类型转换,例如我们向要插入一个string类型我们插入方式就可以有下面几种
vector<string> v;
string s("abcde");
v.push_back(s);
v.push_back("aaaaa");
这个第二种就是单参数的隐式类型转换, 它转换成了一个临时对象,由于临时对象具有常性所以加const。
六.迭代器
6.1库中迭代器
这两个函数都是具有非const版本和const版本,那它们是如何实现的呢?我们看,模拟
6.2模拟
typedef T* iterator;
typedef const T* const_iterator;
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
非const调用非const版本的,const调用const版本的,有了迭代器我们就可以进行变量,我们看下面代码
template <class T>
void print_vector(const vector<T>&v)
{
for (int i = 0; i < v.size(); i++)
{
cout << v[i] << " ";
}
cout << endl;
typename vector<T>::const_iterator it = v.begin();
//auto it = v.begin();
while (it != v.end())
{
cout << *it << " ";
it++;
}
cout << endl;
for (auto& x : v)
{
cout << x << " ";
}
cout << endl;
}
七.insert以及push_back的复用
7.1库中的insert
在这里面有三种,我们只模拟一种,给出插入位置,然后进行插入
7.2模拟实现inser以及迭代器失效问题
void insert(iterator pos, const T& val)
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _endofstorage)
{
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
// 如果扩容了要更新pos
pos = _start + len;
}
iterator it = _finish - 1;
while (it >= pos)
{
*(it + 1) = *it;
--it;
}
*pos = val;
++_finish;
}
当我们扩容后,由于pos位置没有变化,就会导致迭代器失效,这时候我们需要更新pos的位置才可ui继续使用迭代器,所以我们在扩容器那先保存pos和_start的距离,更新后再更新pos的位置,然后进行插入。
7.3push_back复用insert
有了迭代器,我们可以利用insert来实现我们的push_back,代码如下:
void push_back(const T& val)
{
insert(end(), val);
}
push_back就是在尾部进行插入,我们将我们的尾部传进行就可以进行插入。
八.删除
8.1pop_back
8.1.1库中的pop_back
这里就是直接将最后一个数据删除。
8.1.2模拟实现pop_back
void pop_back()
{
assert(!empty());
--_finish;
}
bool empty()
{
return _start == _finish;
}
在这里我们需要判断是否为空,不为空就进行删除,删除只需要我们直接挪动_finish指针就可以。
8.2erase
8.2.1库中的erase
erase就是删除某位置到某位置的的内容,至于返回值是iterator什维利应对迭代器失效问题。
8.2.1模拟实现erase以及迭代器失效问题
在这里我们只模拟实现删除pos位置的内容,代码如下:
void erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator it = pos +1;
while (it != _finish)
{
*(it - 1) = *it;
it++;
}
_finish--;
//return pos;
}
我们直接挪动数据,然后将_finish进行左挪动,由于erase会造成和迭代器失效,例如我们删除一些数据中的偶数,
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(4);
v1.push_back(4);
v1.push_back(5);
v1.push_back(6);
auto it = v1.begin();
while (it != v1.end())
{
cout << *it << " ";
it++;
}
cout << endl;
it = v1.begin();
while (it != v1.end())
{
if (*it % 2 == 0)
{
v1.erase(it);
}
else
{
it++;
}
}
cout << endl;
it = v1.begin();
while (it != v1.end())
{
cout << *it << " ";
it++;
}
cout << endl;
在非扩容的版本中这个代码可以很好的实现,但是在缩容的版本中会造成it位置的改变,造成迭代器失效,所以库中返回值为iterator,当我们每次删除后都让it重新指向它,更新it的指向,erase就改为
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator it = pos +1;
while (it != _finish)
{
*(it - 1) = *it;
it++;
}
_finish--;
return pos;
}
九.拷贝构造
vector(const vector<T>& v)
{
reserve(v.capacity());
for (auto x : v)
{
push_back(x);
}
}
我们先开辟空间,由于我们可能需要进行深拷贝,所以我们直接进行迭代赋值就可以实现我们的拷贝构造。
十.再次分析默认构造
在库中支持先开辟n个空间,里面都是val,也可以支持迭代器进行构造,我们先看代码
template <class InputIterator>
vector(InputIterator begin, InputIterator end)
{
while (begin != end)
{
push_back(*begin);
begin++;
}
}
vector(size_t n, const T& val = T())
{
reserve(n);
for (int i = 0; i < n; i++)
{
push_back(val);
}
}
vector(int n, const T& val = T())
{
reserve(n);
for (int i = 0; i < n; i++)
{
push_back(val);
}
}
由于可以是很多类型的迭代器进行构造,我们可以写成模板,进行构造,对于第二种,我们可以先开n个空间,然后都赋值为val,由于val可能需要深拷贝,所以我们用push_back。对于第三种,由于我们传的两个参数都是int,我们向用第二种,但是第二种会有类型转换,第一种只有一种参数,很符合第一种,所以会选择第一种进行实例化,所以会造成出现问题,我们单独写一种就可以解决这个问题。
十一.赋值构造
vector<T>& operator=(vector<T> val)
{
swap(val);
return *this;
}
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
我们传一个参数,不使用引用,让他进行拷贝构造,然后进行swap就可以实现我们的赋值构造
十二.析构
~vector()
{
delete[] _start;
_start = _finish = _endofstorage=nullptr;
}
直接将空间释放,就可以了。