• STL常见容器的理解


    理论的东西必须要实践才能真的变成自己的

    转载:https://www.cnblogs.com/sea520/p/12711565.html 

    https://www.cnblogs.com/renboyu/p/13150264.html  -》很详细

    一.背景

       电脑主板的检测:有一张主板的图片,需要对上面的各个器件进行检测,每个器件调用不同的算法。

       逻辑设计:算法的参数保存在一个容器中,一个线程(线程A)向容器push参数,另一个线程(线程B)一直从容器取参数,并开启线程调用算法

    参数结构体:

    //小ROI的检测信息
    struct TestParam
    {
        bool NeedSavePic;
        bool OnlySaveResultPic;
        int msg;
        int configVecIndex;
        int GroupIndex; //不为1则为secSource
        string SmlroiType;//检测点的器件类型(DIP_AOI使用)
        string strROIID; //小矩形ID
        string BigROIID; //大矩形ID
        string SaveImgPath;//存小图路径
        string dllName;
        string funcName;
        vector<Mat> vecMat;
        vector<string> vecString;
        vector<int> vecInt;
        vector<Rect> vecRect;
        //固定信息
        vector<ConfigMsg> g_ConfigVec;//检测点的大ROI的类型   如:SSD , HDD
        SmallROITestParam()
        {
            msg = 0;
        }
    };
    
    

    二.保存参数结构体容器的选择

       C++下面编程,首先就是想到使用STL提供的vector来保存TestParam。问题由此产生。

       A线程一直向vector push参数,

       B线程循环从vector中取参数:

    //B线程
    int index = 0while ( testSmallROIEndFlag )
    {
       std::thread aa = std::thread(Func, ref(TestParam.at(i)));
       aa.detach();
       index++;
    }

    问题:运行过程中随机出现crash。

      log分析显示,调用算法crash。单独把算法和参数拿出来调试又没有问题。在参数push进vector前和传进算法前都数出来做对比,发现参数在传进算法的时候已经变成了一堆随机的值,并且有时还没有输出参数就已经crash。在这个过程中也没有对参数的读写动作。

    卡了很久。。。

    寻找同事帮助:  1.会crash  2.参数自己被改变。   两个问题产生的原因 : 第一个原因多数都是内存的非法访问导致,第二个原因是访问未初始化的内存。为什么会访问非法的内存???中间就是去读取了vector而已,怎么可能会访问非法内存。难道是vector传进B线程

    就访问不了?vector地址失效?反正就是各种猜测。最后就是突然想到vector自动扩容的实现方式,找到了问题点。

    划重点:

    vector的内存结构和自动扩容的实现:

    内存结构:一块连续的内存,对其中元素的访问可以通过下标的方式。也就是基地址+偏移地址就能直接访问到对应的元素。

    自动扩容:一开始定义一个空的vector的时候,预先分配的大小为0(可以通过capacity()查看),后面每次push元素的时候,如果vector当前的容量已达到最大,就会自动申请一块 1.5-2倍于之前内存大小的连续内存,然后把元素移动到新内存上,释放掉之前的内存。

    2个问题产生的原因:就在于vector的自动扩容机制。在push参数的时候,会发生内存重新分配,但是算法当中使用的还是之前的vector地址,造成了非法内存访问以及随机内存数据。

    解决方法:1.使用链表或者队列 

                      2.vector有提供reserve() 函数。在定义的时候,可以预先分配一个最小指定容量大小的连续内存,在vector的容量超过设定值之前,都不会重新分配内存。

    学习:理论和实践要结合。

    对vector的实现都有了解,但是使用过程中都不会去思考到这些问题。

    三.常见容器的初步了解

    STL容器主要分为两大类:1.顺序容器 2.关联容器

    顺序容器主要有:string vector  list  deque stack...

    关联容器主要有:map set...

    1.vector

      vector是一段连续的内存地址,

      我写代码过程中选择vector的原因:

      1.支持随机访问:想访问哪一个元素,就访问哪一个元素(最大的优点)

      2.不用像数组一样事先分配好一块固定大小的内存,而且不用管当前vector的内存是否够用,因为vector内部有实现自动扩容的机制:如果当前vector内剩余的空间已经使用完,再向vector中push元素时,会自动申请一块当前

    内存1.5-2倍大小的新的连续内存,然后把所有元素迁移到新内存,释放之前的地址。以此完成自动扩容。

      3.vector中元素的访问快,因为是一段连续的内存空间,所以可以直接通过 基地址+偏移地址 的方式访问。vec[1] 直接到 begin+1 的内存位置取一个元素  vec.at(1)会先判断是否越界,然后再取第一个元素

      缺点:

      1.因为每次是固定分配前一次1.5-2倍大小的内存,所以当数据量大时,存在浪费内存空间的问题。(不过因为工作中没遇到过需要处理大量数据的情况,所以从来没考虑过这个问题)

      2.每次vector扩容时,花费的时间长以及会产生开篇的那个问题。

      3.插入和删除涉及到其它位置数据的移动

    //1.定义和初始化
        vector<int> vec1;    //默认初始化,vec1为空
        vector<int> vec2(vec1);  //使用vec1初始化vec2
        vector<int> vec3(vec1.begin(),vec1.end());//使用vec1初始化vec2
        vector<int> vec4(10);    //10个值为0的元素
        vector<int> vec5(10,4);  //10个值为4的元素
        
        //2.常用操作方法
        vec1.reserve(10);                    //预设大小为10
        vec1.push_back(100);               //尾部添加元素
        int totalSize = vec1.capacity();   //容器大小       
        int size = vec1.size();           //元素个数  size<=totalSize  
        bool isEmpty = vec1.empty();    //判断是否为空
        cout<<vec1[0]<<endl;           //取得第一个元素
        vec1.insert(vec1.end(),5,3);    //从vec1.back位置插入5个值为3的元素
        vec1.pop_back();              //删除末尾元素
        vec1.erase(vec1.begin(),vec1.begin()+2);//删除vec1[0]-vec1[2]之间的元素,不包括vec1[2]其他元素前移
        cout<<(vec1==vec2)?true:false;  //判断是否相等==、!=、>=、<=...
        vector<int>::iterator iter = vec1.begin();    //获取迭代器首地址
        vector<int>::const_iterator c_iter = vec1.begin();   //获取const类型迭代器
        vec1.clear();                 //清空元素
        vec1.shrink_to_fit();         //把capacity大小改为当前size大小
        
        //3.遍历 
        //下标法 int length = vec1.size(); 
        for ( int i=0; i<length; i++ ) 
        { 
            cout<<vec1[i]; 
        } 
        cout<<endl<<endl; 
        
        //迭代器法 vector<int>::iterator iter = vec1.begin(); 
        for ( ; iter != vec1.end(); iter++ )  
        { 
            cout<<*iter; 
        } 

    2.list

      双向链表,每个元素都有两个指针,一个指向前一个元素的地址,一个指向后一个元素的地址。头的前向指针为空,尾的后向指针为空。不是一块连续的内存,每个元素通过指针关联起来。

      优点:

      1.数据的插入和删除很简单(简单是指实现简单,对使用者来说:插入和删除都一样,就是调用接口)

      2.不会浪费内存,没有预分配内存的动作,当需要一块内存时,才会申请一块内存

      缺点:

      1.好像STL中没有提供随机访问的支持。

      2.如果实现随机访问,也会很慢,每次的随机访问都需要遍历list,然后找到对应的数据

      

    //1.定义和初始化
        list<int> lst1;          //创建空list
        list<int> lst2(3);       //创建含有三个元素的list
        list<int> lst3(3,2); //创建含有三个元素为2的list
        list<int> lst4(lst2);    //使用lst2初始化lst4
        list<int> lst5(lst2.begin(),lst2.end());  //同lst4
        
        //2.常用操作方法
        lst1.assign(lst2.begin(),lst2.end());  //分配值,3个值为0的元素
        lst1.push_back(10);                    //末尾添加值
        lst1.pop_back();                   //删除末尾值
        lst1.begin();                      //返回首值的迭代器
        lst1.end();                            //返回尾值的迭代器
        lst1.clear();                      //清空值
        bool isEmpty1 = lst1.empty();          //判断为空
        lst1.erase(lst1.begin(),lst1.end());                        //删除元素
        lst1.front();                      //返回第一个元素的引用
        lst1.back();                       //返回最后一个元素的引用
        lst1.insert(lst1.begin(),3,2);         //从指定位置插入个3个值为2的元素
        lst1.rbegin();                         //返回第一个元素的前向指针
        lst1.remove(2);                        //相同的元素全部删除
        lst1.reverse();                        //反转
        lst1.size();                       //含有元素个数
        lst1.sort();                       //排序
        lst1.unique();                         //删除相邻重复元素
        
        //3.遍历
        //迭代器法
        for ( list<int>::const_iterator iter = lst1.begin(); iter != lst1.end(); iter++ )
        {
            cout<<*iter;
        }

    3.deque

    双端队列:由一段一段等长的连续空间构成,各段空间之间并不一定是连续的,可以位于在内存的不同区域。

    deque 容器用数组(数组名假设为 map)存储着各个连续空间的首地址。也就是说,map 数组中存储的都是指针,指向那些真正用来存储数据的各个连续空间。

    通过建立 map 数组,deque 容器申请的这些分段的连续空间就能实现“整体连续”的效果。换句话说,当 deque 容器需要在头部或尾部增加存储空间时,它会申请一段新的连续空间,同时在 map 数组的开头或结尾添加指向该空间的指针,由此该空间就串接到了 deque 容器的头部或尾部。

    如果 map 数组满了,再申请一块更大的连续空间供 map 数组使用,将原有数据(很多指针)拷贝到新的 map 数组中,然后释放旧的空间。

    (所以deque的底层实现很复杂)

    //后面的使用过再说

    4.map

    容器提供一个键值对(key/value)容器

    5.set

  • 相关阅读:
    golang模板语法简明教程(后面有福利哦)
    C#实现jQuery的方法连缀
    静态构造函数
    MVC4 中使用 Area 和 注意的地方
    APS.NET MVC4生成解析二维码简单Demo
    net mvc 利用NPOI导入导出excel
    Ambari DataNode Start Failure
    Java的三种代理模式
    清理ambari安装的hadoop集群
    【小型系统】简单的刷票系统(突破IP限制进行投票)
  • 原文地址:https://www.cnblogs.com/linxisuo/p/14280523.html
Copyright © 2020-2023  润新知