【总结】知识点巩固STL源码学习-------Vector,List

REF:

https://blog.csdn.net/woalss/article/details/78917564

准模板库就是类与函数模板的大集合。STL共有6种组件:容器,容器适配器,迭代器,算法,函数对象和函数适配器。

本文主要介绍前两点

容器:

序列容器:

关联容器:

容器适配器:

容器

Vector

1.高效的使用

https://blog.csdn.net/netyeaxi/article/details/83277810

一、在std::vector尾部添加对象时应尽量使用emplace_back,而不要使用push_back

二、添加多个元素前应使用reserve设置容量,防止扩容时发生元素复制

三、删除元素时应从最后一个元素开始删除,不要从中间开始删除

四、添加新对象时应从结尾处添加,而不要从中间添加

五、使用std::vector(std::vector)和std::vector(std::initializer_list)对std::vector赋初始值会将std::vector和std::initializer_list中所有对象复制

六、需要将对象全部转移到另外一std::vector时,应使用std::vector.swap()、std::swap()、std::move(std::vector)

七、如果std::vector中在存放指针对象,即std::vector,则应使用智能指针
https://blog.csdn.net/weixin_42205987/article/details/82960342(扩容原理)

2.扩容原理概述

  • 新增元素:Vector通过一个连续的数组存放元素,如果集合已满,在新增数据的时候,就要分配一块更大的内存,将原来的数据复制过来,释放之前的内存,在插入新增的元素;
  • 对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了 ;
  • 初始时刻vector的capacity为0,塞入第一个元素后capacity增加为1;
  • 不同的编译器实现的扩容方式不一样,VS2015中以1.5倍扩容,GCC以2倍扩容。

vector的初始的扩容方式代价太大,初始扩容效率低, 需要频繁增长,不仅操作效率比较低,而且频繁的向操作系统申请内存容易造成过多的内存碎片,所以这个时候需要合理使用resize()和reserve()方法提高效率减少内存碎片的,

vector拷贝使用总结(深复制、浅复制)

利用拷贝赋值操作符(深复制)

vector<int> array{3,5,2,6,4};
vector<int> outArray;
outArray = array;


利用拷贝构造(深复制)

vector<int> array{3,5,2,6,4};
vector<int> outArray(array);

利用swap()函数(交换两个vector)

会清空原vector数组

vector<int> array{3,5,2,6,4};
vector<int> outArray;//设为空
outArray.swap(array);//清空array数组

vector<int> array{3, 5, 2, 6, 4};
vector<int> outArray{ 1,2,3 };
outArray.swap(array);//outArray={3,5,2,6,4};array={1,2,3}

利用assign()函数(清空并深复制)

vector<int> array{3, 5, 2, 6, 4};
vector<int> outArray{ 1,2,3 };
outArray.assign(array.begin(),array.end());//清空原数据,赋予新数据={3,5,2,6,4}
outArray.assign(5, 0);//5个0,清空原数据={0,0,0,0,0,}

 

resize()

  • resize方法被用来改变vector中元素的数量,我们可以说,resize方法改变了容器的大小,且创建了容器中的对象;
  • 如果resize中所指定的n小于vector中当前的元素数量,则会删除vector中多于n的元素,使vector得大小变为n;容量不会变(capacity
  • 如果所指定的n大于vector中当前的元素数量,小于capacity容量值,则会在vector当前的尾部插入适量的元素,使得vector的大小变为n,在这里,如果为resize方法指定了第二个参数,则会把第二个参数值初始化为该指定值,如果没有为resize指定第二个参数,则会初始化为默认的初始值 0;容量不会变(capacity)
  • 如果resize所指定的n不仅大于vector中当前的元素数量,还大于vector当前的capacity容量值时,则会自动为vector重新分配存储空间; 容量会变(capacity)

 当之前的size*2小于resize时,容量为resize的大小
 当之前的size*2大于resize时,容量为size*2

reserve()

  • reserve方法被用来重新分配vector的容量大小
  • 只有当所申请的容量大于vector的当前容量时才会重新为vector分配存储空间;重新分配后的容量即reserve大小size大小没有变,所以不会产生新的对象
  • 小于当前容量则没有影响,size、capacity都没有影响
  • reserve方法对于vector元素大小没有任何影响,不创建对象。

end()

 

clear()

https://blog.csdn.net/acoolgiser/article/details/81018296

tempObject obj1;
tempObject obj2;
vector<tempObject> tempVector;

tempVector.pushback(obj1);
tempVector.pushback(obj2);
tempVector.clear();

调用clear()函数只会调用tempObject的析构函数,从而释放掉obj1和obj2两个对象,不会释放vector所占用的内存。真正释放vector所占用的内存,要到vector对象离开作用域时,自动调用vector的析构函数释放内存。当然有一种强制释放内存的方法,比如针对上面的代码:

vector<tempObject>().swap(tempVector);

That will create an empty vector with no memory allocated and swap it with tempVector, effectively deallocating the memory.

 vector   中的内建有内存管理,当   vector   离开它的生存期的时候,它的析构函数会把   vector   中的元素销毁,并释放它们所占用的空间,所以用   vector   一般不用显式释放   ——   不过,如果你   vector   中存放的是指针,那么当   vector   销毁时,那些指针指向的对象不会被销毁,那些内存不会被释放。

总结:

    vector与deque不同,其内存占用空间只会增长,不会减小。比如你首先分配了10,000个字节,然后erase掉后面9,999个,则虽然有效元素只有一个,但是内存占用仍为10,000个。所有空间在vector析构时回收。

    empty()是用来检测容器是否为空的,clear()可以清空所有元素。但是即使clear(),所占用的内存空间依然如故。如果你需要空间动态缩小,可以考虑使用deque。如果非要用vector,这里有一个办法:

    在《effective STL》和其实很多C++文章中都有指明,用clear()无法保证内存回收。但是swap技法可以。具体方法如下所示:
    vector<int> nums;
    nums.push_back(1);nums.push_back(1);nums.push_back(2);nums.push_back(2);
    vector<int>().swap(nums); //或者nums.swap(vector<int>());

    vector<int>().swap(nums); 或者如下所示 加一对大括号都可以,意思一样的:  
    { 
     std::vector<int> tmp =   nums;   
     nums.swap(tmp); 
    }      
    加一对大括号是可以让tmp退出{}的时候自动析构

    swap技法就是通过交换函数swap(),使得vector离开其自身的作用域,从而强制释放vector所占的内存空间

1.vector.clear释放二元数组

作用:将会清空temp中的所有元素,包括temp开辟的空间(size),但是capacity会保留,即不可以以temp[1]这种形式赋初值,只能通过temp.push_back(value)的形式赋初值。

同样对于vector<vector<datatype> > temp1(50)这种类型的变量,使用temp1.clear()之后将会不能用temp1[1].push_back(value)进行赋初值,只能使用temp1.push_back(temp);的形式。

正常情况:

#include <iostream>
#include<vector>
using namespace std;
int main(){
vector<vector<int>> test(50);
vector<int> temp;
test[10].push_back(1);
cout<<test[10][0]<<endl;
test.clear();
for(int i=0;i<51;i++)
test.push_back(temp);
system("pause");
return 0;
}

越界错误:

#include <iostream>
#include<vector>
using namespace std;
int main(){
vector<vector<int>> test(50);
vector<int> temp;
test[10].push_back(1);
cout<<test[10][0]<<endl;
test.clear();
for(int i=0;i<50;i++)
test[i].push_back(1);
system("pause");
return 0;
}

不同拷贝时间与效率对比

https://blog.csdn.net/autophyte/article/details/3256096

vector中数据的随机存取效率很高

  • O(1)的时间的复杂度,但是在vector 中随机插入元素,需要移动的元素数量较多,效率比较低
#include <iostream>
#include <vector>
using namespace std;

class A
{
public:
    A()
    { 
    	cout << "construct" << endl; //构造函数
    }
    A(const A &a)
    { 
    	cout << "copy construct" << endl; //拷贝构造函数
    }
    ~A()
    { 
    	cout << "destruction" << endl; //析构函数
    }
};

int main(void)
{
    vector<A> p;
    cout <<"size: "<<p.size() << "  "<<"capacity: "<< p.capacity()<< endl;//size = 0 capacity=0
    A a;//调用构造函数
    A b;//调用构造函数
    A c;//调用构造函数
    p.push_back(a);//调用拷贝构函数放入p中
    cout <<"size: "<<p.size() << "  "<<"capacity: "<< p.capacity()<< endl;//size=1 capacity=1
    cout << "+++++++++++++++++++++++++++\n";
    p.push_back(b);//容量不够 扩容成2倍 将原来的a拷贝构造到新的位置 将b拷贝构造到新的位置 析构原来位置的a
    cout <<"size: "<<p.size() << "  "<<"capacity: "<< p.capacity()<< endl;//size=2 capacity=2
    p.push_back(c);//容量不够 扩容2倍 将a、b分别拷贝构造到新的位置 将c拷贝构造到新的位置 析构原来位置的a、b
    cout <<"size: "<<p.size() << "  "<<"capacity: "<< p.capacity()<< endl;//size=3 capacit=4
    cout << "+++++++++++++++++++++++++++\n";
	//析构a、b、c、以及析构向量中的三个对象
}

在这里插入图片描述

 

迭代器

1.原理:迭代器(iterator)是一中检查容器内元素并遍历元素的数据类型,STL设计的精髓在于,把容器(Containers)和算法(Algorithms)分开,而迭代器(iterator)就是这个桥梁。

这里写图片描述

   输入迭代器input_iterator: 只读,且只能一次读操作,支持操作:++p,p++,!=,==,=*p,p->;
   输出迭代器output_iterator: 只写,且只能一次写操作,支持操作:++p,p++;
   正向迭代器forward_iterator: 可多次读写,支持输入输出迭代器的所有操作;
   双向迭代器bidirectional_iterator: 支持正向迭代器的所有操作,且支持操作:--p,--p;
   随机访问迭代器random_access_iterator: 除了支持双向迭代器操作外,还支持:p[n],p+n,
    n+p,p-n,p+=n,p-=n,p1-p2,p1<p2,p1>p2,p1>=p2,p1<=p2;
 

迭代器类别            说明
输入   从容器中读取元素。输入迭代器只能一次读入一个元素向前移动,输入迭代器只支持一遍算法,同一  个输入迭代能两遍遍历一个序列
输出     向容器中写入元素。输出迭代器只能一次一个元素向前移动。输出迭代器只支持一遍算法,统一输出 迭代器不能两次遍历一
正向    组合输入迭代器和输出迭代器的功能,并保留在容器中的位置
双向     组合正向迭代器和逆向迭代器的功能,支持多遍算法
随机访问   组合双向迭代器的功能与直接访问容器中任何元素的功能,即可向前向后跳过任意个元素
随机访问   组合双向迭代器的功能与直接访问容器中任何元素的功能,即可向前向后跳过任意个元素

2 STL迭代器作用
迭代器的作用其实相当于一个智能指针,它指向容器内部的数据,可以通过operator *操作符来解指针获得数据的值,也可以通过operator ->操作符来获取数据的指针,还能够重载++,--等运算符来移动指针。

3 迭代器(iterator)延伸
网站:http://www.cplusplus.com/reference/iterator/

4.原理

https://blog.csdn.net/qq_34777600/category_9275150.html

https://blog.csdn.net/u010236550/category_1833151.html

5.迭代器失效

其实http://www.cplusplus.com/reference/list/list/erase/也有提醒:

Iterator validity(list)

Iterators, pointers and references referring to elements removed by the function are invalidated.
All other iterators, pointers and references keep their validity.

Iterator validity(vector)

Iterators, pointers and references pointing to position (or first) and beyond are invalidated, with all iterators, pointers and references to elements before position (or first) are guaranteed to keep referring to the same elements they were referring to before the call.

https://blog.csdn.net/Dawn_sf/article/details/77929225

https://blog.csdn.net/baidu_37964071/article/details/81409272

解决方法:

当使用一个容器的insert或者erase函数通过迭代器插入或删除元素"可能"会导致迭代器失效,因此我们为了避免危险,有两种方法:

1.应该获取insert或者erase返回的迭代器,以便用重新获取的新的有效的迭代器进行正确的操作 

iter=vec.insert(iter);
iter=vec.erase(iter);

2.递增当前迭代器(有意思,放外面循环就不行,传参时++就行)

m.erase(iter++);//先把iter传值到erase里面,然后iter自增,然后执行erase。

原因:

迭代器失效的类型:
  1.由于插入元素,使得容器元素整体“迁移”导致存放原容器元素的空间不再有效,从而使得指向原空间的迭代器失效。
  2.由于删除元素使得某些元素次序发生变化使得原本指向某元素的迭代器不再指向希望指向的元素。

Vector

1.当插入(push_back)一个元素后,end操作返回的迭代器肯定失效。

2.当插入(push_back)一个元素后,capacity返回值与没有插入元素之前相比有改变,则需要重新加载整个容器,此时first和end操作返回的迭代器都会失效。

3.当插入(insert)一个元素后,如果发生reallocation,则全部失效。否则只是指向插入点的迭代器全部失效;指向插入点后面的元素的迭代器也将全部失效。

4.当进行删除操作(erase,pop_back)后,指向删除点的迭代器全部失效;指向删除点后面的元素的迭代器也将全部失效。

所以对vector的任何操作,一旦引起控件重新配置(reallocation),指向原vector的所有迭代器就都失效了。(涉及vector扩容的知识点:翻一倍并消除旧的,放入新的)

但是erase方法可以返回下一个有效的iterator,这样删除后iter指向的元素后,返回的是下一个元素的迭代器,这个迭代器是vector内存调整过后新的有效的迭代器。

List

1.对于链表型数据结构,比如list,使用了不连续分配的内存,删除运算使指向删除位置的迭代器失效,但是不会失效其他迭代器。

2.插入操作(insert)和接合操作(splice)不会造成原有的list迭代器失效,这在vector中是不成立的,因为vector的插入操作可能造成记忆体重新配置,导致所有的迭代器全部失效。

解决办法有两种:
与vector类似,erase(*iter)会返回下一个有效迭代器的值,或者erase(iter++)。

对于链表型数据结构,比如list,使用了不连续分配的内存,删除运算使指向删除位置的迭代器失效,但是不会失效其他迭代器。

Deque  

1.在deque容器首部或者尾部插入元素不会使得任何迭代器失效。//通过vs2012测试不管前端插入还是后端插入,都会使迭代器 失效 
2.在其首部或尾部删除元素则只会使指向被删除元素的迭代器失效。 
3.在deque容器的任何其他位置的插入和删除操作将使指向该容器元素的所有迭代器失效。

Set/Map

1.对于关联容器式(如map, set,multimap,multiset),删除当前的iterator,仅仅会使当前的iterator失效(与list相同)

解决方法:
在erase时,返回值为void,所以我们需要递增当前iterator。
比如:

m.erase(iter++);//先把iter传值到erase里面,然后iter自增,然后执行erase。


原因:这是因为map之类的容器,使用了红黑树来实现,虽然删除了一个元素,整棵树也会调整,以符合红黑树或者二叉树的规范,但是单个节点在内存中的地址没有变化,变化的是各节点之间的指向关系

 

List

1.end()&back()&begin()&front()

begin函数:
函数原型:
iterator begin();
const_iterator begin();
功能:
返回一个当前vector容器中起始元素的迭代器。
 
end函数:
函数原型:
iterator end();
const_iterator end();
功能:
返回一个当前vector容器中末尾元素的迭代器。
 
front函数:
函数原型:
reference front();
const_reference front();
功能:
返回当前vector容器中起始元素的引用。
 
back函数:
函数原型:
reference back();
const_reference back();
功能:
返回当前vector容器中末尾元素的引用。
 

标准库函数的list数据结构中,一旦list建立,end()函数的地址就是固定的,无论向list中push_back()还是erase()。

而相应改变的是存放数据的地址。

// list::back
#include <iostream>
#include <list>
using namespace std;

int main ()
{
  std::list<int> mylist;

  mylist.push_back(10);
  cout<<"end() addr:"<<&(*(mylist.end()))<<endl;
  while (mylist.back() != 0)
  {
    mylist.push_back ( mylist.back() -1 );
  }
  int& temp = mylist.back();
  temp = 9;
  cout<<"back() addr:"<<&temp<<endl;
  std::cout << "mylist contains:"<<endl;
  cout<<"end() addr:"<<&*(mylist.end())<<",didn't change!"<<endl;
  for (std::list<int>::iterator it=mylist.begin(); it!=mylist.end() ; ++it)
  std::cout <<"addr:"<<&*(it)<< " value: " << *it<<endl;

  std::cout << '\n';

  return 0;
}

由输出我们可以看出,end()函数所返回的地址不是list中最后项的地址。list的最后一项保存在end()函数返回地址的下一个低位地址上

※获取迭代器最后一个元素

-----解决方案--------------------
C/C++ code

if (iter == --ilst.end())

------解决方案--------------------

C/C++ code

iter1 = iter2 = list.begin();
if (++iter2 != list.end())
   ++ iter1;

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值