c++stl vector,stl vector用法

  c++stl vector,stl vector用法

  Yyds干货库存

  在写作之前,我们已经了解了字符串。现在我们开始学习向量。我们在这里会发现,STL的设计非常相似。我们学了一个,很简单。我们先简单看一下vector的用法。今天重点讲迭代器失效和深层次复制,这是我们的难点。

  Vector使用vector可以理解为我们之前的动态数组,也就是序列表。它也是一个类模板。我们先来看看它的简单用法。它的第一个参数模板是要存储的数据类型,第二个是为内存池打开空间。如果你觉得库中的内存池不够高效,你也可以自己写一个传过去,但这不是我们想说的。

  如果构造函数向量包含复制构造,则有四个构造函数。下面就简单介绍一下。

  函数名

  解释

  向量();

  无参数结构

  vector (size_type n,const value _ type val=value _ type());

  n的构造值的元素

  vector (InputIterator第一,InputIterator最后);

  迭代区间构造

  向量(常数向量x);

  复制结构

  这里我们还是自测两个常用的。

  int main()

  {

  向量int v1

  向量char v2(10, a );

  返回0;

  }

  Size() capacity()也是我们经常用到的函数,没什么好解释的。

  int main()

  {

  向量int v1(20,15);

  cout size: v1 . size()endl;

  cout capacity: v1 . capacity()endl;

  返回0;

  }

  函数reserve()是为了开辟空间,和string一样,规则还是一样的。

  int main()

  {

  向量int v1(20,15);

  v1 .储备(10);

  cout capacity: v1 . capacity()endl;

  v1 .储备(100);

  cout capacity: v1 . capacity()endl;

  返回0;

  }

  Resize()并不是什么新东西。还是和以前一样。

  int main()

  {

  向量int v1(20,15);

  v1.resize(10,0);

  cout size: v1 . size()endl;

  v1.resize(100,0);

  cout size: v1 . size()endl;

  返回0;

  }

  运算符[]的底层是一个连续数组,所以我们这里最好支持下标访问。

  int main()

  {

  向量int v1(20,15);

  for(int I=0;I v1 . size();我)

  {

  cout v1[I]“”;

  }

  cout endl

  返回0;

  }

  迭代器这里的迭代器分为正向迭代器和反向迭代器,每一个都用const修改。这里不做过多解释,用法和之前一样。

  int main()

  {

  向量int v1

  v1 . push _ back(1);

  v1 . push _ back(2);

  v1 . push _ back(3);

  v1 . push _ back(4);

  v1 . push _ back(5);

  vector int:iterator it=v1 . begin();

  而(它!=v1.end())

  {

  cout * it“”;

  它;

  }

  cout endl

  返回0;

  }

  Push_back()在末尾插入一个数据,在末尾插入一个数据。

  int main()

  {

  向量int v1

  v1 . push _ back(1);

  v1 . push _ back(2);

  v1 . push _ back(3);

  v1 . push _ back(4);

  v1 . push _ back(5);

  for(int I=0;I v1 . size();我)

  {

  cout v1[I]“”;

  }

  cout endl

  返回0;

  }

  Vector实现了,终于过了上面的认知阶段。最主要的原因是我们无话可说,和string几乎一模一样。不用说?我们现在想看的是他们的实现,这很有意思。

  我们先推一波向量的底层。应该有一个大小和容量来记录有效的数据和容量,还应该有一个数组来存储数据。我认为这一点不应该有任何疑问。我们来看看SGI版本的实现,里面存储了三个指针(类型化def)。这个方法也是可以的,而且比我们的简单。我们就用这个来实现吧。

  让我们画出它的物理图,以便大家更好地理解它。

  现在我们可以建立框架,让我们建立一个简单的。

  模板类T

  分类向量

  {

  公共:

  typedef T*迭代器;//本机指针

  typedef const T * const _ iterator

  向量()

  :_start(nullptr)

  ,_finish(nullptr)

  ,_endOfStoage(nullptr)

  {

  }

  私人:

  iterator _ start

  iterator _ finish

  iterator _ endOfStoage

  }迭代器vector和string是一样的,都是原生指针,我们可以直接使用。

  迭代器begin()

  {

  返回_开始;

  }

  const_iterator begin() const

  {

  返回_开始;

  }

  迭代器结束()

  {

  返回_完成;

  }

  const_iterator end() const

  {

  返回_完成;

  }size() capacity()的实现就更简单了。我们知道,数据的个数可以通过指针减去指针得到,这里也适用。这里直接用吧。

  size_t size()常量

  {

  return _ finish-_ start;

  }

  //计算容量

  size_t容量()常量

  {

  return _ endOfStoage-_ start;

  }operator[]既然vector的物理地址是连续的,那我们最好支持operator[]的随机访问,太简单了。

  测试运算符[](size_t pos)

  {

  assert(pos=0 pos size());

  return *(_开始位置);

  }

  常量T运算符[](size_t pos)常量

  {

  assert(pos=0 pos size());

  return *(_开始位置);

  }reserve()这是扩展和释放热量的功能,但是这里面可以有一个很重要的问题,深浅复制的深层次问题,这是我们今天博客最重要的内容之一。我们稍后将分享它们。

  空预留(size_t n=0)

  {

  size _ t oldsize=size();

  if(n capacity())

  {

  //打开一个空间

  T * tmp=new T[n];

  //确定原始空间中是否有数据。

  if(_start)

  {

  memcpy(tmp,_start,size()* sizeof(T));

  delete[]_ start;

  }

  _ start=tmp

  _ finish=tmp oldsize

  _ endOfStoage=tmp n;

  }

  }resize()是调整_size大小的函数。这里我们仍然只实现一种情况。

  void resize(size_t n,const T val=T())

  {

  //三种情况

  储备(n);

  if(n size())

  {

  while(size() n)

  {

  //

  * _ finish=val

  _完成;

  }

  }

  其他

  {

  _ finish=_ start n;

  }

  }insert()这是在一个地址插入一个元素,我们默认插入在元素前面,有迭代器失效的问题。

  迭代器插入(迭代器位置,常数T x)

  {

  assert(pos=_ start pos=_ finish);

  if(_finish==_endOfStoage)

  {

  size _ t len=pos-_ start;//记录以防止无效

  size _ t new cap=_ endOfStoage-_ start==0?4 : 2 *容量();

  储备(new cap);

  //更新pos解决了部分迭代器失败问题。

  pos=_ start len

  }

  //开始插入数据

  迭代器it=_ finish

  而(它!=位置)

  {

  * it=*(it-1);

  它-;

  }

  * pos=x;

  _完成;

  退货位置;

  }push_back()复用插入函数。

  void push_back(常量值)

  {

  插入(_finish,val);

  }erase()这次复杂度达到了O(N),确实有点高,但是我们也无能为力。

  迭代器擦除(迭代器位置)

  {

  assert(pos=_ start pos _ finish);

  迭代器it=pos 1;

  而(它!=_完成)

  {

  *(it-1)=* it;

  它;

  }

  _完成-;

  退货位置;

  }pop_back()尾部删除的时间复杂度O(1)。

  void pop_back()

  {

  if(size()!=0) //在这里,最好不要用空的空间来判断清的恐惧。

  {

  -_完成;

  }

  }swap的主要功能是复制结构等。我们只需要交换三个指针。

  无效交换(向量转换)

  {

  std:swap(_start,v . _ start);

  std:swap(_finish,v . _ finish);

  std:swap(_endOfStoage,v . _ endOfStoage);

  }迭代器失败现在来说说向量中最难理解的问题。迭代器失败,我们需要控制向量的使用以避免错误。向量的迭代器失效大致可以分为三种类型。我们一个一个来举例。

  先来看看标准库,还是会有野指针的问题。

  #包括iostream

  #包含矢量

  使用命名空间std

  int main()

  {

  向量int v(10,1);

  vector int:iterator pos=v . begin();

  int I=0;

  而(i 100)

  {

  v .插入(位置2);

  我;

  }

  返回0;

  }

  让我们看看问题发生在哪个步骤,并将其打印出来。我们发现它出现在我等于1的时候。

  我们需要看看第一次插入后pos是否失败。VS有点严格。

  int main()

  {

  向量int v(10,1);

  vector int:iterator pos=v . begin();

  printf(pos : %p\n ,pos);

  int I=0;

  而(i 100)

  {

  如果(i==1)

  {

  printf(pos : %p\n ,pos);

  Cout test endl

  }

  v .插入(位置2);

  我;

  }

  返回0;

  }

  所以我们要看看Linux环境,不同的编译器有不同的处理机制,这一点我们之前已经知道了。

  从这里我们可以看到,只有在我们插入一些数据的时候才会出现错误,而且是在i=10的时候。这个现象可以解释迭代器失败的原因之一。先调试一下吧。

  先说原因。当我们插入数据时,我们使用迭代器,准确地说,是原生指针。如果我们扩展hungry oh中的原始指针,那么现在发生的事情就会发生,迭代器将无法指向内容。

  我们可以用某种方式解决这个插入问题。我们可以记录pos和_start之间的相对距离,扩展后做相应的改变。

  迭代器插入(迭代器位置,常数T x)

  {

  assert(pos=_ start pos=_ finish);

  if(_finish==_endOfStoage)

  {

  size _ t len=pos-_ start;//记录以防止无效

  size _ t new cap=_ endOfStoage-_ start==0?4 : 2 *容量();

  储备(new cap);

  //更新pos解决了部分迭代器失败问题。

  pos=_ start len

  }

  //开始插入数据

  //.

  退货位置;

  }对不起,你觉得你现在写的是对的吗?看一下下面的代码。我们想在它前面加上这个偶数的十倍。怎么样?

  # include iostream # include vector . HPP

  使用STD:cout;

  使用STD:CIN;

  使用STD:endl;

  int main()

  {

  bit:Vector int v;

  五. push _ back(1);

  五. push _ back(2);

  五. push _ back(3);

  五. push _ back(4);

  五. push _ back(5);

  五. push _ back(6);

  bit:Vector int:iterator it=v . begin();

  而(它!=v.end())

  {

  if (*it % 2==0)

  {

  int ret=* it * 10

  v.insert(it,ret);

  }

  它;

  }

  for (int val : v)

  {

  cout val“”;

  }

  返回0;

  }

  好吧,还有一个问题。看这里,我们可以看到迭代器失败的另一个问题。我们不是更新了pos吗?这里面还是有一些问题的。我们引入形式参数,改变形式参数不会影响实际参数。那么,我们可以介绍推荐人吗?是的,我们能,但是我们不能在标准图书馆里做它。

  我们以返回值的形式解决这个问题。

  迭代器插入(迭代器位置,常数T x)

  {

  assert(pos=_ start pos=_ finish);

  //更新pos解决了部分迭代器失败问题。

  //开始插入数据

  //.

  退货位置;

  }

  还有一个原因,为什么我们不必引用这个。要知道,insert支持以下用法。临时变量有恒定性,所以我们要用const来修改。

  导致返回值迭代器失败。如果你认为我们的代码现在可以正常运行,那你就太天真了。运行之后你会发现还是有问题。

  # include iostream # include vector . HPP

  使用STD:cout;

  使用STD:CIN;

  使用STD:endl;

  int main()

  {

  bit:Vector int v;

  五. push _ back(1);

  五. push _ back(2);

  五. push _ back(3);

  五. push _ back(4);

  五. push _ back(5);

  五. push _ back(6);

  bit:Vector int:iterator it=v . begin();

  而(它!=v.end())

  {

  if (*it % 2==0)

  {

  int ret=* it * 10

  v.insert(it,ret);

  }

  它;

  }

  for (int val : v)

  {

  cout val“”;

  }

  返回0;

  }

  现在我们用标准库看看这种情况是否也存在。

  #包括iostream

  #包含矢量

  使用命名空间std

  int main()

  {

  向量int v;

  五. push _ back(1);

  五. push _ back(2);

  五. push _ back(3);

  五. push _ back(4);

  五. push _ back(5);

  五. push _ back(6);

  vector int:iterator it=v . begin();

  而(它!=v.end())

  {

  if (*it % 2==0)

  {

  int ret=* it * 10

  v.insert(it,ret);

  }

  它;

  }

  for (int val : v)

  {

  cout val“”;

  }

  返回0;

  }

  这个也崩溃了,说明至少我们的实现没有错误。这样做的原因是返回值。让我们看一看。

  现在你应该有线索了。我们的返回值是新插入的迭代器。检查一下。

  int main()

  {

  向量int v;

  五. push _ back(1);

  五. push _ back(2);

  五. push _ back(3);

  vector int:iterator it=v . begin();

  it=v.insert(it,10);

  返回0;

  }

  也就是说,我们在插入元素后需要移动迭代器,移动与否由你决定。

  #包括iostream

  #包含矢量

  使用命名空间std

  int main()

  {

  向量int v;

  五. push _ back(1);

  五. push _ back(2);

  五. push _ back(3);

  五. push _ back(4);

  五. push _ back(5);

  五. push _ back(6);

  vector int:iterator it=v . begin();

  而(它!=v.end())

  {

  if (*it % 2==0)

  {

  int ret=* it * 10

  v.insert(it,ret);

  它;

  }

  它;

  }

  for (int val : v)

  {

  cout val“”;

  }

  返回0;

  }

  擦除导致迭代器失败。vector还有第三个迭代器失败,主要是保留的情况。我们先来看看Erase在图书馆的大致情况。一般这种情况是不可以遇到的,因为无论是VS编译器还是G编译器似乎都不会收缩,但是我们需要知道这种情况。

  使用命名空间std

  int main()

  {

  向量int v;

  五. push _ back(1);

  五. push _ back(2);

  五. push _ back(3);

  五. push _ back(4);

  五. push _ back(5);

  五. push _ back(6);

  vector int:iterator it=v . begin();

  而(它!=v.end())

  {

  if (*it % 2==0)

  {

  it=v . erase(it);//注意it验收就行了。

  }

  其他

  {

  它;

  }

  }

  for (int val : v)

  {

  cout val“”;

  }

  返回0;

  }

  总结vector的迭代器故障主要有两种类型。

  由于扩展(插入)或收缩(擦除),它变成了一个通配符指针。因为指向的意义改变了,就像erase删除元素的下一个位置,insert也是插入元素的下一个位置,这就导致了迭代器的自增或自减的合理使用。不同编译器对迭代器失败的处理机制还是有很大差别的。这里重点讲一下VS和g,这个VS是严格的。只要插入或删除一次,无论是扩展还是收缩,都无法访问当前地址。

  #包括iostream

  #包含矢量

  使用命名空间std

  int main()

  {

  向量int v(10,1);

  vector int:iterator it=v . begin();

  v .插入(it,2);

  cout v . capacity()endl;

  * it

  返回0;

  }

  int main()

  {

  向量int v(10,1);

  vector int:iterator it=v . begin();

  v .擦除(它);

  cout v . capacity()endl;

  * it

  返回0;

  }

  但是按照G中的佛教体系,一般是可以访问的。如果遇到通配符指针,将单独报告一个错误。

  #包括iostream

  #包含矢量

  使用命名空间std

  int main()

  {

  向量int v(10,1);

  vector int:iterator it=v . begin();

  v .插入(it,2);

  cout v . capacity()endl;

  * it

  返回0;

  }

  int main()

  {

  向量int v(10,1);

  vector int:iterator it=v . begin();

  v .擦除(它);

  cout v . capacity()endl;

  * it

  返回0;

  }

  完善vector这里需要完善vector,包括复制构造,赋值重载等。这些都比较简单。先抄施工吧。

  文案建设这里我们需要写现代文,还是比较简单的。

  Vector(const Vector T v) //这里建议这样写。当然,类中的const Vector v也可以这样做,但不推荐。

  :_start(nullptr)

  ,_finish(nullptr)

  ,_endOfStoage(nullptr)

  {

  //现代写作

  向量T vv

  for(常量值:v)

  {

  vv . push _ back(val);

  }

  //我让你帮我写,你要把变量vv解构。

  互换(vv);

  }赋值重载更简单,可以说是厉害的一批。

  向量T算子=(向量T v)

  {

  //这里我们不传入引用,这里编译器自动调用copy构造,我们可以直接传入。

  互换(五);

  返回* this

  析构函数在这里写析构函数可以避免内存泄漏。

  ~向量()

  {

  if(_start)

  {

  delete[]_ start;

  _ finish=nullptr

  _ endOfStoage=nullptr

  }

  }多值构造这里,我们把这个构造过程中一次初始化多个值的构造函数写下来。在这里,您可以重用尾部插入。

  Vector(size_t n,const T value=T())

  :_start(nullptr)

  ,_finish(nullptr)

  ,_endOfStoage(nullptr)

  {

  向量T vv

  for(size _ t I=0;I n;我)

  {

  vv.push_back(值);

  }

  互换(vv);

  }区间构造有时候我们可能会用区间构造,也有一个问题,主要是上面的多值构造有问题。你会知道什么时候轮到你叫牌。

  模板类输入运算符

  Vector(先输入运算符,后输入运算符)

  :_start(nullptr)

  ,_endOfStoage(nullptr)

  ,_finish(nullptr)

  {

  向量T vv

  而(先!=最后一个)

  {

  vv . push _ back(* first);

  第一;

  }

  互换(vv);

  }这里先测试一下,主要是看这个错误信息。

  int main()

  {

  //排除方法

  向量int v1(10,2);

  用于(自动e : v1)

  {

  cout e“”;

  }

  cout endl

  向量字符v2(10, x );

  用于(自动e : v2)

  {

  cout e“”;

  }

  cout endl

  返回0;

  }

  先排除第一个,你会发现第一个构造函数是错的。我们需要分析它。

  int main()

  {

  向量字符v2(10, x );

  用于(自动e : v2)

  {

  cout e“”;

  }

  cout endl

  返回0;

  }

  先说明一下这个情况。对于编译器,我们会自动推导出参数的类型。我们发现都是int,所以编译器有理由相信我们调用的是区间构造。由于多值构造的两个参数不同,编译器必须优先考虑区间构造,这就是报错的原因。

  源代码中对此的解决方案是将第一个参数的类型改为int。

  向量(int n,const T value=T())

  :_start(nullptr)

  ,_finish(nullptr)

  ,_endOfStoage(nullptr)

  {

  向量T vv

  for(int I=0;I n;我)

  {

  vv.push_back(值);

  }

  互换(vv);

  }更深更深复制到这里,剩下vector最后一大内容。这个模块有点难,需要我们很好的理解。这里,我先举个简单的例子。在标准库中,我们可以嵌套vector,就像下面这样。

  #包括iostream

  #包含矢量

  使用命名空间std

  int main()

  {

  //排除方法

  vector向量int vv

  向量int v1(10,1);

  向量int v2(10,2);

  向量int v3(10,3);

  向量int v4(10,4);

  向量int v5(10,5);

  vv . push _ back(v1);

  vv . push _ back(v2);

  vv . push _ back(v3);

  vv . push _ back(v4);

  vv . push _ back(V5);

  for(向量整数值:vv)

  {

  for(整数:整数)

  {

  cout e“”;

  }

  cout endl

  }

  返回0;

  }

  但是我们不能为了自己的写作而这样做。我们先运行一下,你就知道了。

  #包括iostream

  #包含“Vector.hpp”

  使用命名空间std

  int main()

  {

  bit:Vector bit:Vector int vv;

  bit:Vector int v1(10,1);

  bit:Vector int v2(10,2);

  bit:vector int v3(10,3);

  bit:vector int v4(10,4);

  bit:vector int v5(10,5);

  vv . push _ back(v1);

  vv . push _ back(v2);

  vv . push _ back(v3);

  vv . push _ back(v4);

  vv . push _ back(V5);

  for(位:向量int val : vv)

  {

  for(整数:整数)

  {

  cout e“”;

  }

  cout endl

  }

  返回0;

  }

  这个报告有错误,这里就不让你想了。这里的主要问题在于插入和保留函数。我们先来解释一下向量嵌套向量的性质。

  当vector\中的T是自定义类型的数组时,通常会出现浅拷贝的问题。我们在这里的实现在于memcpy函数。对于一般的整数数组,它复制数组中的值,这是一种深度复制。但是T这里是自定义类型的数组,也就是我们的vector存储的是地址,所以这个时候地址是复制的。如果你知道我们以后还需要删除,你会为自定义类型调用自己的析构函数,这会导致原指针变成野指针。我们先过一遍原码,这样你会有一点了解。这里的赋值重载是深度复制,没有问题。

  不扩展容量时,插入数据。步骤如下。vector的数组被复制到vector的数组中,数组中的每个元素都会被调用赋值重载。这是一份深度拷贝。

  插入的时候会有问题。我们使用memcpy函数,它将原始数组的所有元素复制到tmp,知道复制的指针,

  下一个关键步骤是我们把original _start给删除了。要知道,自定义类型数组的所有指针都存储在里面,编译器会逐个释放空间。我们的tmp变成了野指针。

  解决这个问题是个好主意。我们不用memcpy函数,一个一个赋值。内置类型也一样,我们会为自定义类型调用自己的赋值重载,所以没问题。

  空预留(size_t n=0)

  {

  size _ t oldsize=size();

  if (n capacity())

  {

  //打开一个空间

  T * tmp=new T[n];

  //确定原始空间中是否有数据。

  if (_start)

  {

  //在这里,直接赋值会自动调用自定义类型的赋值重载-深度复制。

  for(int I=0;I size();我)

  {

  tmp[I]=_ start[I];

  }

  //memcpy(tmp,_start,size()* sizeof(T));

  delete[]_ start;

  }

  _ start=tmp

  _ finish=tmp oldsize

  _ endOfStoage=tmp n;

  }

  }

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

相关文章阅读

  • office2010激活密钥大全 怎么永久激活office2010
  • project2010产品密钥免费_project2010激活密钥永久激活码
  • c语言调用退出函数 c语言退出整个程序怎么写
  • c语言中怎么给函数初始化 c语言的初始化语句
  • c语言编写函数计算平均值 c语言求平均函数
  • chatgpt是什么?为什么这么火?
  • ChatGPT为什么注册不了?OpenAI ChatGPT的账号哪里可以注册?
  • OpenAI ChatGPT怎么注册账号?ChatGPT账号注册教程
  • chatgpt什么意思,什么是ChatGPT ?
  • CAD中怎么复制图形标注尺寸不变,CAD中怎么复制图形线性不变
  • cad中怎么创建并使用脚本文件,cad怎么运行脚本
  • cad中快速计算器的功能,cad怎么快速计算
  • cad中快速修改单位的方法有哪些,cad中快速修改单位的方法是
  • cad中心点画椭圆怎么做,cad轴测图怎么画椭圆
  • CAD中常用的快捷键,cad各种快捷键的用法
  • 留言与评论(共有 条评论)
       
    验证码: