目录
前言
vector其实就是数据结构中的顺序表,建议先学会顺序表
成员变量结构
cpp
template<class T>
class vector
{
public:
private:
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;
};
这里是使用了iterator迭代器来定义了三个成员变量
_start指向顺序表的开始位置
_finish指向顺序表最后元素的下一个位置
_end_of_storage指向顺序表开辟的最后一个空间
当然我们也可以使用顺序表里那种方法用一个*a,size,capacity来完成vector,但库里是用这三个变量,那么就保持一致好了
那么这个iterator是什么?
iterator定义
cpp
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
private:
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;
};
这个iterator其实本质就和上一节string类的iterator一致,只不过这里是T*
C++:string类(auto+范围for,typeid)-CSDN博客
有iterator就顺带把const_iterator定义了,方便稍后实现const迭代器的函数
既然有了迭代器,那么begin和end也就很容易实现出来了
cpp
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
size
cpp
size_t size() const
{
return _finish - _start;
}
指针相减是指针之间的偏移量个数,首尾指针相减即是顺序表的大小
capacity
cpp
size_t capacity() const
{
return _end_of_storage - _start;
}
与size同理
empty
cpp
bool empty() const
{
return _finish == _start;
}
开始和结束指针指向同一块即为空
clear
cpp
void clear()
{
_finish = _start;
}
swap
cpp
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
与string类一致
[]运算符重载
cpp
T& operator[](size_t i)
{
return _start[i];
}
const T& operator[](size_t i) const
{
return _start[i];
}
这里是实现了两个版本
第一个是vector<T>对象使用,第二个是给const vector<T>对象使用
push_back
cpp
void push_back(const T& x)
{
*_finish = x;
_finish++;
}
直接在finish指针指向的位置填值即可
但我们哪来的空间给我们放数据?所以这里需要一个扩容逻辑,我们可以封装成与库里一样的函数
cpp
void push_back(const T& x)
{
if (_finish == _end_of_storage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = x;
_finish++;
}
当空间满时则需要扩容(reserve)
pop_back
cpp
void pop_back()
{
_finish--;
}
--秒了
reserve
cpp
void reserve(size_t n)
{
if (capacity() < n)
{
size_t len = size();
T* tmp = new T[n];
memcpy(tmp, _start, sizeof(T) * size);
delete[] _start;
_start = tmp;
_finish = tmp + len;
_end_of_storage = tmp + n;
}
}
这里扩容的思路是用tmp指针创建一个大小为n的空间,将旧空间的内容拷贝到新空间中,释放旧空间,然后让_start,_finish,_end_of_storage指向相应的位置
为了记录finish在原空间中的偏移量,我们需要用一个len变量来保存
但是这段代码是有问题的!!!
首先我们要知道memcpy是浅拷贝(值拷贝),不了解的可以先看下面深浅拷贝的章节
C++:类和对象 II(默认成员函数,深浅拷贝)-CSDN博客
这里如果T是int,double这些内置类型就没什么问题
但是如果T是string,vector这些需要再开空间的容器就会出问题
假设T是string类
那么_start每一块空间都是一个string类型,都有一个指针又指向一串字符串
这时候如果用memcpy会发生值拷贝,只会将string里的指针拷贝给tmp新空间中,这个浅拷贝会导致大家都指向一块空间
所以我们需要一个深拷贝,具体实现方法如下:
cpp
void reserve(size_t n)
{
if (capacity() < n)
{
size_t len = size();
T* tmp = new T[n];
//memcpy(tmp, _start, sizeof(T) * size);
for (size_t i = 0; i < len; i++)
{
tmp[i] = _start[i];
}
delete[] _start;
_start = tmp;
_finish = tmp + len;
_end_of_storage = tmp + n;
}
}
用一个for循环将里面的值赋值给新空间
如果是string,我们会调用string的赋值重载,而string的赋值重载也是深拷贝,所以就完成了
总结:T类型如果是内置类型,则正常赋值,若为自定义类型,调用自定义类型的赋值重载函数
resize
cpp
void resize(size_t n, T val = T())
{
if (n < size())
{
_finish = _start + n;
}
else
{
reserve(n);
while (_finish < _start + n)
{
*_finish = val;
_finish++;
}
}
}
reserve是扩容,调整的是_end_of_storage,而resize是调整大小,变的是_finish
若resize的值小于容量大小,那么直接调整_finish到相应位置即可
若resize的值大于容量大小,那么不仅需要变_finish,还需要变_end_of_storage扩容
扩容只需要复用reserve即可
构造函数
默认构造函数
cpp
vector() = default;
由于我们的成员变量是三个指针,只需要让它们走初始化列表初始化为nullptr即可,系统自动生成的就够用了
default
default关键字用于显式地要求编译器为特殊成员函数生成默认实现
可以用于6个默认成员函数中的任意一个,使用方法都如上一样
迭代器构造
cpp
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
first++;
}
}
该构造函数可以用其他容器的迭代器来构造自己
所以为了能够接收其他类的迭代器我们需要新用一个模板来接收
让迭代器里的值依次push_back到当前vector中即可
cpp
vector(size_t n, const T& val = T())
{
reserve(n);
while (n--)
{
push_back(val);
}
}
该构造函数也是扩容+复用push_back与上面相同的逻辑
但是这里有个问题!!
若是我们要调用该函数是这样的:
cpp
vector<int> v(5, 1);
它的意思是构造顺序表空间为5值为1
看起来这里会调用到该函数,其实它会调用到上面的迭代器构造函数
这是为什么呢?
分析:
5的类型是int,1的类型也是int,但是我们定义的是size_t和T类型,实例化后就是size_t和int类型
但是这个InputIterator如果实例化也是int就是两个int,显然这个构造会让我们编译器选择,自然就货不对板了
所以我们需要自己写其他类型来构成重载
cpp
vector(int n, const T& val = T())
{
reserve(n);
for (int i = 0; i < n; i++)
{
push_back(val);
}
}
vector(unsigned int n, const T& val = T())
{
reserve(n);
for (int i = 0; i < n; i++)
{
push_back(val);
}
}
这样无论是int,还是size_t,还是unsigned int都可以正常调用了
拷贝构造函数
cpp
vector(const vector<T>& v)
{
reserve(v.size());
for (auto& e : v)
{
push_back(e);
}
}
扩容
将v里的数据遍历并复用push_back到当前vector类中
赋值重载函数
写法1:
cpp
vector<T>& operator=(const vector<T>& v)
{
if (this != &v)
{
clear();
reserve(v.size());
for (auto& e : v)
{
push_back(e);
}
}
return *this;
}
与前面的拷贝构造思路一致
写法2:现代写法
cpp
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
与string类中的写法思路一致
C++:string类(auto+范围for,typeid)-CSDN博客
析构函数
cpp
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
}
清理工作:释放空间
insert
cpp
iterator insert(iterator pos, const T& x)
{
if (_finish == _end_of_storage)
{
size_t n = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + n;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
*pos = x;
_finish++;
return pos;
}
首先是扩容逻辑,但这里并不是简单的扩容就可以了,这里的扩容存在迭代器失效问题
首先我们的pos是在旧空间的,若是贸然扩容那么pos的位置就无法在新空间找到了
所以我们需要先用一个变量n来记录pos相对于_start的偏移量
然后只需要重新让pos指向新空间相应位置即可
剩下的就是从后往前挪动数据,最后在pos位置插入x即可,与string的insert逻辑相同
erase
cpp
void erase(iterator pos)
{
iterator cur = pos + 1;
while (cur != _finish)
{
*(cur - 1) = *cur;
cur++;
}
_finish--;
}
挪动覆盖数据即可,与顺序表逻辑一致
迭代器失效问题
insert失效
insert的使用是存在迭代器失效的问题的
cpp
int main()
{
vector<int> v = { 1, 2, 3, 4, 5 };
auto it = v.begin() + 2;
v.insert(it, 99);
for (auto x : v)
cout << x << " ";
cout << endl;
cout << *it;
return 0;
}
在这段代码中,如果在vs的环境下运行是会报错的!
因为我们用it这个迭代器插入后,it这个迭代器就失效了,此时再访问则会有问题,vs的选择是直接报错!
为什么会失效?
迭代器失效问题具体是因为内存管理机制所导致的
当在任意位置插入新元素时,如果当前容量不足以容纳更多元素,会重新分配内存。这会导致所有指向旧内存的迭代器、引用和指针失效。即使容量足够,插入点之后的迭代器也会因为元素移动而失效
所以在使用insert后迭代器一定不能使用!!!
erase失效
erase也存在迭代器失效问题
cpp
int main()
{
vector<int> v = { 1, 2, 4, 5 };
auto it = v.begin();
for (auto x : v)
{
if (x % 2 == 0)
{
v.erase(it);
}
it++;
}
return 0;
}
这是一段删除偶数的代码
这里迭代器失效就体现在连续偶数的情况下
若删除2,此时4会在2的位置,那么it还要++就会跳过这个4,所以这也是一种迭代器失效的体现
总结:当从容器中删除元素时,虽然通常不会导致内存重新分配,但删除点之后的迭代器会因为元素的移动而失效
在vs中运行会直接报错!和insert一样
正确写法:
cpp
int main()
{
std::vector<int> v = { 1, 2, 3,4, 5 };
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
{
it = v.erase(it);
}
else
{
it++;
}
}
return 0;
}
若进行了删除操作,我们只需要让it指向删除的位置即可,反之it++
resize失效
当使用resize函数改变vector的大小时,如果新大小大于当前容量,vector会重新分配内存,导致所有迭代器、引用和指针失效。如果新大小小于当前大小,则会发生元素删除,删除点之后的迭代器会失效
clear失效
虽然clear函数不会减少vector的容量,但它会删除所有元素,因此所有指向元素的迭代器、引用和指针都会失效
总结
为了避免迭代器失效问题,建议在执行插入、删除、调整大小或清空操作后,重新获取或更新迭代器。例如,insert和erase函数都会返回指向新位置的有效迭代器,可以使用这些返回的迭代器进行后续操作
完整代码
cpp
#pragma once
#include<iostream>
#include<vector>
#include<list>
#include<string>
using namespace std;
namespace lyw
{
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
//C++11 前置生成默认构造
vector() = default;
vector(const vector<T>& v)
{
reserve(v.size());
for (auto& e : v)
{
push_back(e);
}
}
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
first++;
}
}
vector(size_t n, const T& val = T())
{
reserve(n);
while (n--)
{
push_back(val);
}
}
vector(int n, const T& val = T()) // test6的v7
{
reserve(n);
for (int i = 0; i < n; i++)
{
push_back(val);
}
}
vector(unsigned int n, const T& val = T()) // test6的v7
{
reserve(n);
for (int i = 0; i < n; i++)
{
push_back(val);
}
}
void clear()
{
_finish = _start;
}
//vector<T>& operator=(const vector<T>& v)
//{
// if (this != &v)
// {
// clear();
// reserve(v.size());
// for (auto& e : v)
// {
// push_back(e);
// }
// }
// return *this;
//}
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
}
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
void reserve(size_t n)
{
if (capacity() < n)
{
size_t len = size();
T* tmp = new T[n];
//memcpy(tmp, _start, sizeof(T) * size); 浅拷贝 vector<string>...中会报错
for (size_t i = 0; i < len; i++)
{
tmp[i] = _start[i];
}
delete[] _start;
_start = tmp;
_finish = tmp + len;
_end_of_storage = tmp + n;
}
}
void resize(size_t n, T val = T())
{
if (n < size())
{
_finish = _start + n;
}
else
{
reserve(n);
while (_finish < _start + n)
{
*_finish = val;
_finish++;
}
}
}
size_t size() const
{
return _finish - _start;
}
size_t capacity() const
{
return _end_of_storage - _start;
}
bool empty() const
{
return _finish == _start;
}
void push_back(const T& x)
{
if (_finish == _end_of_storage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = x;
_finish++;
}
void pop_back()
{
_finish--;
}
iterator insert(iterator pos, const T& x) // 存在迭代器失效问题
{
if (_finish == _end_of_storage)
{
size_t n = pos - _start; // 记录偏移量,否则一旦扩容pos的位置就无效了
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + n;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
end--;
}
*pos = x;
_finish++;
return pos;
}
void erase(iterator pos)
{
iterator cur = pos + 1;
while (cur != _finish)
{
*(cur - 1) = *cur;
cur++;
}
_finish--;
}
T& operator[](size_t i)
{
return _start[i];
}
const T& operator[](size_t i) const // 为了给const vector<T>对象使用
{
return _start[i];
}
private:
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;
};
}
完