目录
1.结构
vector是一个类似于数组一样的容器,里面可以放各种各样的元素。我们只需在其中储存空间的地址即可,所以包含三个私有元素,分别是_start,_finish,_end_of_storage。使用模板T可以使vector储存各种各样的元素。将元素指针类型typedef成迭代器。代码如下。
template <class T>
typedef T* iterator;
typedef const T* const_iterator;
private:
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;
2.构造函数
包含无参构造, 指定参数构造,迭代器构造,初始化列表构造。
无参构造
不用传参,使用缺省参数或者初始化列表中的参数。
指定参数构造
第一个参数n表示空间大小,第二个参数const T& val = T()是一个缺省参数,当没有传递第二个参数的时候,会调用T的默认构造函数,用val来引用它。比如说T是int类型,那么val就是0的引用所以这里要加const,然后再用val去初始化剩余的空间。
为什么要n有size_t和int两种类型呢?
如果只定义size_t,那么传参(6,5)的时候,编译器默认6是int类型,与size_t类型不符合,那么就会找更匹配的构造函数,这时候会找到迭代器构造,而此时就会出现非法的间接寻址(int类型的值无法解引用)编译错误。
如果只定义int,那么传递的参数指定是size_t类型,如(6u,5)(后缀加u表示unsign int类型),同样会产生相同的问题。所以两者都显示定义了。
迭代器构造
参数为迭代器,迭代器是与指针类似的东西,可以进行解引用,+,-等操作。
这里为什么不使用前面的迭代器iterator,而是要使用一个成员函数模板?前面的iterator只能表示vector容器的迭代器,当我们想用其他容器的迭代器来初始化vector的时候就不适用了,所以要定义一个新的函数模板。
其实我自己在学的时候还有一个疑问就是为什么std::list<double> iterator能初始化vevtor<int>?
一是本质上使用的是(*first)来初始化的,拿到的是double类型的元素,
二是这其中实际上涉及到了隐式类型转换将double转换成了int。
初始化列表构造
std::initializer_list<T>是C++11 标准类型,专门处理花括号 {}
初始化(如 {1, 2, 3}
),之后再遍历尾插。这种方法可以避免重复地push_back。
//无参
vector<int> v1;
//指定参数
vector<int> v2(6);
//迭代器构造
string s1("wit lzk");
vector<int> v3(s1.begin(), s1.end());
//花括号
vector<int> v4 = { 1,2,3,5 };
//构造函数
vector()
{}
vector(size_t n, const T& val = T())
{
reserve(n);
for (size_t 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);
}
}
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
vector(std::initializer_list<T> il)
{
reserve(il.size());
for (auto t& : il)
{
push_back(t);
}
}
3.拷贝构造
旧版拷贝构造分为开空间,遍历,拷贝。
//拷贝构造
vector(const vector<T>& v)
{
reserve(v.capacity());
for (auto& t : v)
{
push_back(t);
}
}
赋值本质上也复用了拷贝构造。比如说v2 = v1把v1赋值给v2,v1进行传值传参,调用拷贝构造函数,v1会生成一个临时副本拷贝给v,然后利用swap将v的值和v2交换,实现赋值操作。同时v是一个临时对象,在和v2进行交换后函数结束时就会将原空间析构。
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;
}
4.析构函数
析构函数还是看构造函数构造了什么,析构函数就析构什么。
/析构函数
~vector()
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
5.遍历
得到对象空间大小,和实际数据个数。
size_t size() const
{
return _finish - _start;
}
size_t capacity() const
{
return _end_of_storage - _start;
}
重载【】
T operator[](size_t n)
{
assert(n < size());
return _start[n];
}
vector<int> v1 = { 1,2,3,4 };
//[]遍历
for (size_t t = 0; t < v1.size(); t++)
{
cout << v1[t] << " ";
}
cout << endl;
//范围for遍历
for (auto ch : v1)
{
cout << ch << " ";
}
cout << endl;
5.插入
插入前需要判断空间是否足够,不够需要扩容。拷贝数据到新空间时,不能使用memcpy,如果是内置类型还好,如果是自定义类型,浅拷贝将会导致空间析构两次。所以直接暴力拷贝数据。因为是异地扩容,所以拷贝完毕要重新给类成员变量赋值。
扩容
//扩容
void reserve(size_t n)
{
//扩容是改变(_end_of_storage-start)的值
/*if (n <= _end_of_storage - _start)
{
_finish = _start + n;
}*/
if(n > capacity())
{
size_t pre_size = size();
T* tmp = new T[n];
//_start不能为空
if (_start)
{
//使用memcpy为浅拷贝,资源会被析构两次
//memcpy(tmp, _start, sizeof(_start));
for (size_t i = 0; i < size(); i++)
{
tmp[i] = _start[i];
}
//数据拷贝完要把旧空间删除
delete[] _start;
}
_start = tmp;
_finish = _start + pre_size;
_end_of_storage = _start + n;
}
}
尾插
//尾插
void push_back(const T& v)
{
size_t len = _finish - _start;
//写法太冗余
//if (_finish < _end_of_storage)
//{
// _start[len] = v;
//}
//else {
// //空间不够扩容
// reserve(capacity() == 0 ? 4 : 2 * capacity());
// _start[len] = v;
//}
//_finish++;
//相等就扩容
if (_finish == _end_of_storage)
{
reserve(capacity() == 0 ? 4 : 2 * capacity());
}
//无论满没满都要进行这两步
(*_finish) = v;
_finish++;
}
指定位置插入
这里需要注意迭代器失效的问题,异地扩容后_start被改变,pos也就失效了。所以要先记录pos到_start的距离,扩容后重新赋值。
//指定位置插入
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _end_of_storage)
{
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : 2 * capacity());
//扩容完后的_start会改变
//原来的pos会失效(迭代器失效了)
pos = _start + len;
}
//移动数据
iterator it = _finish;
while (it >= pos)
{
*it = *(it - 1);
it--;
}
*pos = x;
_finish++;
return pos;
}
6.删除
尾删
改变一下_finish的位置即可。
//尾删
void pop_back()
{
assert(_finish > _start);
_finish--;
}
指定位置删除
那么在这个地方vs实现的vector会出现迭代器失效的问题。vs会认为erase一次后it就失效了,所以必须在使用erase之后,重新给it赋值。 当然自己实现的vector中不会出现这个问题。
vector<int> v = { 1,1,2,3,3,4,5,6,7,8,9,9 };
for (auto ch : v)
{
cout << ch << " ";
}
cout << endl;
//删除所有奇数
vector<int>::iterator it = v.begin();
while (it != v.end())
{
if (*it % 2 == 1)
{
//重新赋值
it = v.erase(it);
}
else {
it++;
}
}
for (auto ch : v)
{
cout << ch << " ";
}
cout << endl;
iterator erase(iterator pos)
{
assert(pos >= _start);
assert(pos <= _finish);
iterator it = pos;
while (it < _finish)
{
*it = *(it + 1);
it++;
}
_finish--;
return pos;
}
"Happy Coding! 🚀"