自学内容网 自学内容网

STL中vector实现——简单易懂版

本章内容
在这里插入图片描述

1.迭代器的引入

vector 就是一个动态数组
动态大小:vector的大小可以根据需要自动增长和缩小
连续存储:vector中的元素在内存中是连续存储的,这使得访问元素非常快速
可迭代:vector可以被迭代,你可以使用循环(如for循环)来访问它的元素
元素类型:vector可以存储任何类型的元素,包括内置类型、对象、指针等

1.1 之前写法

学数据结构的时候,我们C表示顺序表都是这样写的

typedef int DataType;
struct SeqList
{
DataType* a;
int size;
int capacity;
};

a指向申请的空间
size代表元素个数
capacity 代表申请的元素个数,也就是当前申请的容量可以存多少个元素

1.2 STL库中的写法

C++中有了模板就不需要再去写typedef int DataType;
直接写成类模板,使用类时给明参数即可

template<class T>
class vector
{
public:
typedef T* iterator;
private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
}

在这里插入图片描述
_start 指向第一个位置
_finish指向最后有效元素的下一个地址
_end_of_storage指向最后一个可存储元素的下一个地址

2.默认成员函数

常用的STL库中的vector构造如下:

vector<int> v1;
vector<int> v2(v1);
vector<int> v3(v1.begin(), v1.end());
vector<int> v4(5,10);
vector<int> v5 = { 1,2,3,4,5 };

2.1构造与拷贝构造

默认构造
什么都不需要传递,三个成员变量都指向nullptr即可
所以我们可以直接给缺省值

iterator _start = nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;

拷贝构造
因为编译器默认生成的拷贝构造都是浅拷贝,所以拷贝构造需要我们自己写
先学习思路,reserve,push_back后面实现
实现思路:
1.直接开和v一样大的空间
1.再把v的元素全部尾插进要构造的类中就可以了

vector(const vector& v)
{
reserve(v.capacity());
for (auto e : v)
{
push_back(e);
}
}

如果我们写了拷贝构造的话,编译器就不会生成默认的构造函数了,但有一种方法可以强制编译器调用默认构造

vector() = default;

构造函数的重载

vector<int> v3(v1.begin(), v1.end());
vector<int> v4(5,10);
vector<int> v5 = { 1,2,3,4,5 };

1.迭代器版本
2.初始化n个相同类型的值
3.用 {} 初始化
它们实现思路大致相同

// 1
template<class IntputIterator>
vector(IntputIterator begin, IntputIterator end)
{
while (begin != end)
{
push_back(*begin);
++begin;
}
}
// 2.
vector(size_t n, const T& x = T())
{
reserve(n);
for (size_t i = 0; i < n; ++i)
{
push_back(x);
}
}
// 调用 vector<int> v1(10,5); 这句话可能会调用到
//template<class IntputIterator>
//vector(IntputIterator begin, IntputIterator end)
// vector<int> v1(10,5);  它的目的是使 vector中有10个值,每个值都是5
//要解决此问题 需要在下面 定义 vector(int n, const T& x = T()) 
//编译器在调用函数时 会调用最合适它的函数,如果没有合适的函数才回去考虑使用模板
vector(int n, const T& x = T())
{
reserve(n);
for (int i = 0; i < n; ++i)
{
push_back(x);
}
}
// 3.
vector(initializer_list<T> il)
{
reserve(it.size());

for (auto e : il)
{
push_back(e);
}
}

1.
template< class IntputIterator>
IntputIterator 可以接收任意类型自定义类型的迭代器
例如可以用链表初始vector

2.
vector(size_t n, const T& x = T())
用n个T类型的值初始化 vector
1.如果是自定义类型调用它的默认构造
2.那如果是 内置类型 呢????
3.C++支持内置类型也有自己的 默认构造
缺省值给一个匿名对象的默认构造
所以可以C++有如下代码:
在这里插入图片描述

什么是 initializer_list
答:它是可以接收 { } 内容的类模板
在这里插入图片描述
它的成员函数如下:
在这里插入图片描述
initializer_list 内成员变量只有两个
一个指向{}的开头位置的地址,一个指向结束位置的下一个地址

2.2拷贝赋值

传统写法
释放原空间,申请新空间,再把需要拷贝的内容拷贝过去
写起来相对后面这种方法比较繁琐

vector<T>& operator= (const vector& v)
{
if (this != &v)
{
delete[] _start;
_start = new T[v.capacity()];
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
memcpy(_start, v._start, sizeof(T) * v.size());
}

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 v)
{
if (this != &v)
{
swap(v);
}

return *this;
}

画图解释如下
在这里插入图片描述

2.3析构函数

析构函数也是的我们手动去释放内存的

~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
}

3.增删查改功能

3.1插入

push_back 尾插一个元素
插入元素就需要扩容
扩容时需要注意一个 迭代器失效的问题
这个问题我会再写一篇文章来阐述这个问题,这篇文章主要是vector的实现

void reserve(size_t n)
{
if (n > capacity())
{
T* tmp = new T[n];
size_t oldsize = size();
if (_start)
{
memcpy(tmp, _start, sizeof(T) * size());
delete[] _start;
}

_start = tmp;
_finish = _start + oldsize;
_end_of_storage = _start + n;
}
}

void push_back(const T& x)
{
if (_finish == _end_of_storage)
{
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);
}

*_finish = x;
++_finish;
}

insert的实现
C++中vector的插入只提供了迭代器的版本
也存在迭代器失效问题

iterator insert(iterator pos ,const T& x)
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _end_of_storage)
{
size_t len = pos - _start;
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);

pos = _start + len;

}

iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *(end);
--end;
}
*pos = x;
++_finish;

return pos;
}

3.2删除

尾删

void pop_back()
{
assert(_start != _finish);
--_finish;
}

删除指定位置的元素

void erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);

iterator it = pos + 1;
while (it != _finish)
{
*(it - 1) = *(it);
it++;
}
--_finish;
}

4.为什么STL中vector没有find函数?

原因是STL把find函数写在了algorithm文件下。
find是模板函数 InputIterator 类型来接收传过来的迭代器。
一劳永逸,体现了程序高内聚
无论vector还是list,都可以使用find。

在这里插入图片描述

5.🔥🔥迭代器失效场景(重点)

实现时会失效的函数

reserve

假设现在vector里已经有四个数了,现在还要插入一个数,但是空间不够了需要扩容。扩容后要达成下面的场景。
在这里插入图片描述

代码实现

void reserve(size_t n)
{
if (n > capacity())
{
T* tmp = new T[n];
size_t oldsize = size();
if (_start)
{
memcpy(tmp, _start, sizeof(T) * size());
delete[] _start;
}

_start = tmp;
_finish = _start + oldsize;
_end_of_storage = _start + n;
}
}

注意事项:
在这里插入图片描述
size()的实现如下

size_t size() const 
{
return _finish - _start;
}

这里一定要记录旧空间的size
因为如果我们先执行 _start = tmp;
再执行 _finish = _start + size();
此时的size()返回的时旧 _finish - 新_start 的值
所以正确写法:_finish = _start + oldsize;

在这里插入图片描述

insert

STL中的 insert 只支持迭代器版本
iterator insert(iterator pos ,const T& x)
假设现在有四个值,要在pos位置插入一个值,插入需要扩容。
要求插入结果如下
在这里插入图片描述

代码实现

iterator insert(iterator pos ,const T& x)
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _end_of_storage)
{
size_t len = pos - _start;
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);

pos = _start + len;

}

iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *(end);
--end;
}
*pos = x;
++_finish;

return pos;
}

注意事项:
在这里插入图片描述
如果需要扩容的话,还是得更新pos,使pos指向新空间的对应位置 。
所以需要提前记录pos与_start的位置关系。

使用后会导致迭代器失效的函数

insert

insertpos是传值调用,所以我们扩容后修改的是函数内的pos
因为旧空间释放导致外部的pos失效。
在这里插入图片描述

可能会有人说这个pos使用引用就不会有迭代器失效的问题了
但是程序会报错

在这里插入图片描述

在这里插入图片描述
begin返回的时临时对象,临时对象具有常性,不可以被改变
所以这里只能使用传值调用。
我们使用时就得直接去注意这些问题。

erase

erase导致的迭代器失效有两种情况

  1. 有的编译器删除元素后可能会进行缩容,缩容的话pos就会失效
  2. 如果删除的是最后一个元素,我们认为这个元素已经被删除,不能被访问,所以就认为它失效

在这里插入图片描述

但这样还是可以访问,所以我们在使用erase时不要这样使用

vs2019不允许这样使用:
在这里插入图片描述


原文地址:https://blog.csdn.net/sgbscx/article/details/143865020

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!