C++STL——vector类与模拟实现
创始人
2024-03-12 11:17:34
0

vector类

  • vector
    • 常用接口介绍
      • 初始化
      • reserve与resize
      • assign
      • 缩容接口
    • 算法库中的find
    • vector的底层小部分框架
  • 模拟实现vectot
    • 模拟vector的整体代码
    • 迭代器失效问题
    • 深层深浅拷贝问题

vector

vector是表示可变大小数组的序列容器,就像数组一样,采用连续存储空间来存储元素,功能和数组类似,但是vector可以管理动态内存,并且在vector中的元素可以是自定义类型。
vector的文档介绍:
在这里插入图片描述
在这里插入图片描述
arr与str中已经存放进了我们初始化的数据。

常用接口介绍

这里很多都和string的接口相似,就不一一介绍了

初始化

vector (const allocator_type& alloc = allocator_type());//无参构造函数
参数:是库里面写的空间配置器组件
vector (size_type n, const value_type& val = value_type(),const allocator_type& alloc = allocator_type());//构造并且初始化n个val
参数:第一个是数据数量,第二个是数据本身,第三个是空间配置器组件
template
vector (InputIterator first, InputIterator last,const allocator_type& alloc = allocator_type());//使用迭代器进行初始化构造
参数:第一个是头第二个是尾,第三个是空间配置器组件
vector (const vector& x);//拷贝构造

#include 
#include 
using namespace std;int main()
{vectorarr;for (int i = 0; i < arr.size(); i++){cout << arr[i];}cout << endl;vectorarr1 = { 1,2 };for (int i = 0; i < arr1.size(); i++){cout << arr1[i];}cout << endl;vectorarr2(5, 2);for (int i = 0; i < arr2.size(); i++){cout << arr2[i];}cout << endl;vectorarr3(arr1);for (int i = 0; i < arr3.size(); i++){cout << arr3[i];}cout << endl;arr.push_back(1);arr.push_back(2);arr.push_back(3);arr.push_back(4);vectorarr4(arr.begin(), arr.end());for (auto e : arr4){cout << e;}cout << endl;return 0;
}

在这里插入图片描述

reserve与resize

在这里插入图片描述
这个和string的类似,都是先开辟一处指定空间大小,但是不会缩容:

#include 
#include 
using namespace std;int main()
{vectorarr;arr.push_back(1);arr.push_back(2);arr.push_back(3);arr.push_back(4);cout << arr.capacity() << endl;arr.reserve(10);//size并不会变大cout << arr.capacity() << endl;arr.reserve(3);cout << arr.capacity() << endl;//不缩容return 0;
}

在这里插入图片描述

在这里插入图片描述
这里要注意一下,val这个参数是一个模板,缺省值是一个匿名的模板的对象,因为传过来的不一定就是内置类型,很有可能是自定义类型。

#include 
#include 
using namespace std;int main()
{vectorarr;arr.push_back(1);arr.push_back(2);arr.push_back(3);arr.push_back(4);arr.push_back(5);arr.resize(8);//空位补0for (auto e : arr){cout << e;}cout << endl << arr.capacity() << endl;arr.resize(15, 1);//空位补1for (auto e : arr){cout << e;}cout << endl << arr.capacity() << endl;arr.resize(3);//减小长度但不缩容for (auto e : arr){cout << e;}cout << endl << arr.capacity() << endl;return 0;
}

在这里插入图片描述

assign

在这里插入图片描述

#include 
#include 
using namespace std;int main()
{vectorarr;arr.push_back(1);arr.push_back(2);arr.push_back(3);for (auto e : arr){cout << e << ' ';}cout << endl;arr.assign(10, 1);for (auto e : arr){cout << e << ' ';}cout << endl;vectorarr1;arr1.push_back(4);arr1.push_back(5);arr1.push_back(6);arr1.push_back(7);arr.assign(arr1.begin(), arr1.end());for (auto e : arr){cout << e << ' ';}cout << endl;string str("hello world");arr.assign(str.begin(), str.end());for (auto e : arr){cout << e << ' ';}cout << endl;return 0;
}

在这里插入图片描述

缩容接口

最好不要缩容,因为vector设计理念就是不动空间,空间换时间,代价很大。
在这里插入图片描述
和原来一样,需要开辟一块新空间,然后释放掉原来的空间,再进行拷贝,此函数是为了节省空间。

#include 
#include 
using namespace std;int main()
{vectorarr;arr.push_back(0);arr.push_back(0);arr.push_back(0);arr.reserve(10);cout << arr.capacity() << endl;arr.shrink_to_fit();cout << arr.capacity() << endl;return 0;
}

在这里插入图片描述

算法库中的find

查看文档发现vector中并没有查找的函数,是但是算法库为STL中提供了一个查找的函数,不然每一个容器都要写查找岂不是很麻烦?
在这里插入图片描述
模板是类模板,函数的参数使用类模板与迭代器实现的。

#include 
#include 
#include 
using namespace std;int main()
{vectorarr;arr.push_back(1);arr.push_back(2);arr.push_back(3);arr.push_back(4);arr.push_back(5);vector::iterator p = find(arr.begin(), arr.end(), 3);if(p != arr.end())arr.insert(p, 10);//在3的位置进行头插for (auto e : arr){cout << e << ' ';}cout << endl;return 0;
}

在这里插入图片描述

vector的底层小部分框架

在模拟实现string的时候,成员变量有三个,存储字符串的空间位置,此对象的字符串有效长度大小和有效空间大小。
如果去看库中实现的vector源码,我们会发现成员变量最主要的有三个:
在这里插入图片描述
一是start,指向了这组数的开头。
二是finish,指向有效数据的最后一个位置的下一个位置,这样与start相减就是有效数据的长度了。
三是end_of_storage,指向了有效空间的末尾。
类型是* iterator。

模拟实现vectot

模拟vector的整体代码

#include 
#include 
#include 
using namespace std;
namespace baiye
{templateclass vector{public:// Vector的迭代器是一个原生指针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;}// construct and destroyvector(): _start(nullptr), _finish(nullptr), _endOfStorage(nullptr){ }vector(size_t n, const T& value = T()): _start(nullptr), _finish(nullptr), _endOfStorage(nullptr){reserve(n);for (size_t i = 0; i < n; i++){push_back(value);}}vector(int n, const T& value = T())//这里有一个参数n为int类型是防止传参是两个整形,如果是size_t需要隐式类型转换: _start(nullptr)              //这会导致调用的不是这个构造函数而是带有模板的构造函数了, _finish(nullptr), _endOfStorage(nullptr){reserve(n);for (size_t i = 0; i < n; i++){push_back(value);}}templatevector(InputIterator first, InputIterator last)//模板的优先级大于隐式类型转换: _start(nullptr), _finish(nullptr), _endOfStorage(nullptr){while (first != last){push_back(*first);++first;}}vector(const vector& v): _start(nullptr), _finish(nullptr), _endOfStorage(nullptr){vectorgcc(v.begin(), v.end());swap(gcc);}vector& operator= (vector v){swap(v);return *this;}~vector(){delete[] _start;_start = _finish = _endOfStorage = nullptr;}// capacitysize_t size() const{return _finish - _start;}size_t capacity() const{return _endOfStorage - _start;}void reserve(size_t n){if (n > capacity()){T* p = new T[n];int a = size();if (_start){for (int i = 0; i < a; i++){p[i] = _start[i];}delete[] _start;}_start = p;_finish = _start + a;_endOfStorage = _start + n;}}void resize(size_t n, const T& value = T()){if (n > capacity()){reserve(n);}if (n > size()){while (_finish != _start + n){*_finish = value;_finish++;}}else{_finish = _start + n;}}///access///T& operator[](size_t pos){assert(pos < size());return _start[pos];}const T& operator[](size_t pos)const{assert(pos < size());return _start[pos];}///modify/void push_back(const T& x){if (_finish == _endOfStorage){int sum = capacity() == 0 ? 4 : 2 * capacity();reserve(sum);}*_finish = x;_finish++;}bool empty() const{return  !(_finish - _start);}void pop_back(){assert(!empty());_finish--;}void swap(vector& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endOfStorage, v._endOfStorage);}iterator insert(iterator pos, const T& x){assert(pos <= _finish);assert(pos > _start);if (_finish == _endOfStorage){int n = pos - _start;int sum = capacity() == 0 ? 4 : 2 * capacity();reserve(sum);pos = _start + n;}iterator p1 = _finish;while (p1 > pos){*p1 = *(p1 - 1);p1--;}*pos = x;++_finish;return pos;}iterator erase(iterator pos){assert(pos >= _start);assert(pos < _finish);iterator p = pos + 1;while (p < _finish){*(p - 1) = *p;p++;}_finish--;return pos;}void clear(){_finish = _start;}private:iterator _start; // 指向数据块的开始iterator _finish; // 指向有效数据的尾iterator _endOfStorage; // 指向存储容量的尾};}

不过,在模拟vector的过程中,最致命的问题有两个。

迭代器失效问题

在实现到vector的接口insert时,我写的代码是这样的。

iterator insert(iterator pos, const T& x)
{assert(pos <= _finish);assert(pos > _start);if (_finish == _endOfStorage){int sum = capacity() == 0 ? 4 : 2 * capacity();reserve(sum);}iterator p1 = _finish;while (p1 > pos){*p1 = *(p1 - 1);p1--;}*pos = x;++_finish;return pos;
}

在测试的时候发现结果是这样的:
在这里插入图片描述
然后我进行了调试发现:
在这里插入图片描述
上面是最初的位置。
然后向下走,需要扩容。
在这里插入图片描述
当扩容之后我们发现,vector的成员变量地址都变了,但是pos指向的还是原来的位置,导致pos指向的内容也就变成了我们上面看到的随机值。
这就是扩容需要重新开辟一块空间并且释放掉原来的空间导致的迭代器失效问题。
我们传过来的参数pos是没有重新分配空间的地址,那么在扩容时失效应该如何避免呢?
这里只需要记录原来pos与_start的距离,然后重新让pos指向有效位置即可。

iterator insert(iterator pos, const T& x)
{assert(pos <= _finish);assert(pos > _start);if (_finish == _endOfStorage){int n = pos - _start;int sum = capacity() == 0 ? 4 : 2 * capacity();reserve(sum);pos = _start + n;}iterator p1 = _finish;while (p1 > pos){*p1 = *(p1 - 1);p1--;}*pos = x;++_finish;return pos;
}

在这里插入图片描述
迭代器失效归根结底就是野指针的问题。
那么再来看这一段代码:

int main()
{vectorarr;arr.push_back(1);arr.push_back(2);arr.push_back(3);arr.push_back(4);for (auto e : arr){cout << e;}cout << endl;vector::iterator p = find(arr.begin(), arr.end(), 3);if (p != arr.end())arr.insert(p, 5);//这里会发生扩容p++;//这里再次对于p进行改动会怎么样?return 0;
}

在这里插入图片描述
这里也是野指针的问题,所以这里记得利用返回值。

int main()
{vectorarr;arr.push_back(1);arr.push_back(2);arr.push_back(3);arr.push_back(4);for (auto e : arr){cout << e;}cout << endl;vector::iterator p = find(arr.begin(), arr.end(), 3);if (p != arr.end())p = arr.insert(p, 5);for (auto e : arr){cout << e;}cout << endl;(*p)++;//这里再次对于p进行改动会怎么样?for (auto e : arr){cout << e;}cout << endl;return 0;
}

至于为什么insert这个接口的函数pos参数为什么不是引用,这是因为如果是引用就要考虑传值时候的权限放大与缩小的问题了。
现在来看看第二种迭代器失效的问题:
在实现erase接口的时候代码是这样写的:

iterator erase(iterator pos)
{assert(pos >= _start);assert(pos < _finish);iterator p = pos + 1;while (p < _finish){*(p - 1) = *p;p++;}_finish--;return pos;
}

测试了一下没什么问题:
在这里插入图片描述
那么如果这样呢(这里先用库里面的vector来测试)

int main()
{vectorarr;arr.push_back(1);arr.push_back(2);arr.push_back(3);arr.push_back(4);for (auto e : arr){cout << e;}cout << endl;vector::iterator p = find(arr.begin(), arr.end(), 3);arr.erase(p);for (auto e : arr){cout << e;}cout << endl;p++;return 0;
}

在这里插入图片描述
那么这里也是野指针的问题。
这是为什么呢?我们的这个对象并没有重新分配地址啊。
在g++下这里是并没有报错的,而VS这里报错说明底层的实现原理是不同的。
这里失效时更好一点的,如果是尾删的话就是失效了。
在这里插入图片描述
所以按照库里面来返回删除元素的下一个元素的位置就好了。

深层深浅拷贝问题

注意我的操作:

int main()
{baiye::vector>arr;baiye::vectorcpp(10, 1);arr.push_back(cpp);arr.push_back(cpp);arr.push_back(cpp);arr.push_back(cpp);for (int i = 0; i < arr.size(); i++){for (int j = 0; j < cpp.size(); j++){cout << arr[i][j] << ' ';}cout << endl;}return 0;
}

在这里插入图片描述
OK,暂时没问题,再尾插一个cpp呢?
在这里插入图片描述
没让你失望,他崩了。
为什么4个不崩5个崩呢?
因为这也和重新开辟空间有关,我们来调试看看:
我直接快进到第五次尾插cpp

在这里插入图片描述
这里看好他们的地址:
在这里插入图片描述
这里地址p的成员地址和this指针中的成员地址相同了,这是为何?
图解:
在这里插入图片描述
这里是压入前四个的cpp的状态,此时并没有发生扩容。
当压入第五个cpp的时候发生扩容,我们看到代码是运行到memcpy的地方出问题的。
我们都知道memcpy拷贝是一个字节一个字节进行拷贝的,这里拷贝的是对标类型为vector类型的_start进行开辟空间的p,那么拷贝的就是_start和p所指向位置的所有内容。
但是他们的成员都是指针,拷贝的都是地址编号,这就导致了新开辟的空间的前四个内容的成员指针指向了旧空间的成员指向的内容。
在这里插入图片描述
那么,下一步就是释放掉原来的旧空间了,释放的位置是_start指向的位置,这也就导致p空间中的前四个类型指向的空间也同样被释放掉了。
这就是整个问题的所在之处,解决问题方法就是不让他一个字节一个字节拷贝,可以用赋值来避免自定义类型的这种情况,如下:

void reserve(size_t n)
{if (n > capacity()){T* p = new T[n];int a = size();if (_start){for (int i = 0; i < a; i++){p[i] = _start[i];//这里改成赋值,就是调用拷贝构造去了}delete[] _start;}_start = p;_finish = _start + a;_endOfStorage = _start + n;}
}

在这里插入图片描述

相关内容

热门资讯

AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWR报告解读 WORKLOAD REPOSITORY PDB report (PDB snapshots) AW...
AWS管理控制台菜单和权限 要在AWS管理控制台中创建菜单和权限,您可以使用AWS Identity and Access Ma...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
群晖外网访问终极解决方法:IP... 写在前面的话 受够了群晖的quickconnet的小水管了,急需一个新的解决方法&#x...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
Azure构建流程(Power... 这可能是由于配置错误导致的问题。请检查构建流程任务中的“发布构建制品”步骤,确保正确配置了“Arti...