zoukankan      html  css  js  c++  java
  • c++ 踩坑大法好 复合数据类型------vector

    1,vector是啥?

    是具有动态大小的数组,具有顺序。能够存放各种类型的对象。相比于固定长度的数组,运行效率稍微低一些,不过很方便。

    2,咋用?

    声明:

    vector <int> vi;
    //vector<类型>标识符
    vector <int> vii(10);
    //Vector<类型>标识符(容量),这句话的意思是声明一个vector对象名字叫vii,初始大小是10

    常用方法:

    #include "pch.h"
    #include <algorithm>
    using namespace std;
    
    int main() {
        vector<int>vi;
        vi.push_back(1);
        vi.push_back(2);
        //向队列的最后添加数据,1和2
    
        vi.pop_back();
        //去掉队列的最后一个数据
    
        int vilen = vi.size();
        //队列的实际长度
    
        vi.clear();
        //清除队列中所有的数据
    
        vi.push_back(1);
        vi.push_back(2);
        vi.push_back(3);
        vi.push_back(4);
        //加点数据
    
        for (int i = 0; i < vilen; i++) {
            printf("%d
    ", vi[i]);
        }
        //普通方法遍历队列输出内容
    
        vector<int>::iterator it;    //声明一个迭代器
        for (it = vi.begin(); it != vi.end(); it++) {
            printf("iterator  value is %d 
    ", *it);
        }
        //利用迭代器遍历队列
    
        for (auto itt : vi)
        {
            printf("%d
    ", itt);
        }
        //c++11的新遍历方法,利用auto
    
        sort(vi.begin(), vi.end());    //sort 需要头文件 #include <algorithm>
        //把队列按照从小到大的顺序排序
        for (int i = 0; i < vi.size(); i++) {
            printf("%d
    ", vi[i]);
        }
        reverse(vi.begin(), vi.end());
        //把队列按照从大到小的顺序排序
        for (int i = 0; i < vi.size(); i++) {
            printf("%d
    ", vi[i]);
        }
    
        vector<vector<int> > obj;
        //定义一个二维数组,约等于python中的:[[1,2],[1,2],[1,2]]
    
        vector<vector<int> > obj(5, vector<int>(6));
        //这样也是可以的,语法不同而已,
    
        return 0;
    }

    3,队列支持的用法查询

    1.push_back 在数组的最后添加一个数据

    2.pop_back 去掉数组的最后一个数据

    3.at 得到编号位置的数据

    4.begin 得到数组头的指针

    5.end 得到数组的最后一个单元+1的指针

    6.front 得到数组头的引用

    7.back 得到数组的最后一个单元的引用

    8.max_size 得到vector最大可以是多大

    9.capacity 当前vector分配的大小

    10.size 当前使用数据的大小

    11.resize 改变当前使用数据的大小,如果它比当前使用的大,者填充默认值

    12.reserve 改变当前vecotr所分配空间的大小

    13.erase 删除指针指向的数据项

    14.clear 清空当前的vector

    15.rbegin 将vector反转后的开始指针返回(其实就是原来的end-1)

    16.rend 将vector反转构的结束指针返回(其实就是原来的begin-1)

    17.empty 判断vector是否为空

    18.swap 与另一个vector交换数据

    4,特殊声明一个用法

    C++11中,针对顺序容器(如vector、deque、list),新标准引入了三个新成员:emplace_front、emplace和emplace_back,这些操作构造而不是拷贝元素。当调用push或insert成员函数时,我们将元素类型的对象传递给它们,这些对象被拷贝到容器中。而当我们调用一个emplace成员函数时,则是将参数传递给元素类型的构造函数。emplace成员使用这些参数在容器管理的内存空间中直接构造元素。应该是代码执行会变得更快。看例子:

    #include <iostream>
    #include <algorithm>
    #include <vector>
    using namespace std;
    class A
    {
    public:
        int hehe;
        A(int i);
    };
    
    int main() {
        A a(1);
        A b(2);
        vector<A>vi = { a,b };
        //创建个队列
        vi.emplace_back(101);
        //直接用101构造一个实例塞到队列中
        vi.push_back(5);
        //先生成一个实例,然后拷贝到队列中。
        for (auto itt : vi)
        {
            printf("%d
    ", itt.hehe);
        }
        return 0;
    }
    A::A(int i) {
        hehe = i;
        //printf("%d
    ", hehe);
    };

     5,vector高级用法(这个厉害了,能够整块内存转存为vector)

    #include <vector>
    
    using namespace std;
    //此用法可以用于把整块图片数据读取到一个vector中
    int main() {
        unsigned char *hehe = NULL;
        hehe = (unsigned char *)malloc(10);
        printf("查看指针指向的内存的大小%d
    ",_msize(hehe));
        //先去申请一块10字节的内存,申请成功以后返回的是指向该内存的指针,否则返回null
        vector<unsigned char> vi(hehe,hehe+10);
        //vector的传入参数分别是某块内存的开始地址和结束地址,
        printf("%d 
    ",vi.size());
        free(hehe);
        //malloc获取的内存记得释放呦
        return 0;
    }

     6,vector中存放指针 vs vector中存放数据 vs vector中存放智能指针 

    最近遇到了一个问题,业务需求新建一个全局队列,一个线程向全局队列中添加数据,另一个线程从队列中取数据,简称,生产者消费者模型。那么问题来了,我是向vector中直接存放局部变量的值呢?还是直接存放指针呢?来吧,写个代码测试一下。

    1)把指向局部变量的指针添加到vector中,实践证明这种方法不可取。

    #include "pch.h"
    #include <iostream>
    #include <string>
    #include <vector>
    #include <memory>
    using namespace std;
    using namespace cv;
    //以下是局部变量的普通指针添加到数组中
    
    int prodoucer1(vector<string *> &xc);
    int prodoucer1(vector<string *> &xc) {
        string str = "1234";
        string str1 = "abc";
        //新建俩局部变量
        string *str3 = &str;
        string *str4 = &str1;
        //新建指向局部变量的指针
        cout << str3 << "修改前str3  " << *str3 << endl;
        cout << str4 << "修改前str4  " << *str4 << endl;
    
        xc.push_back(str3);
        xc.push_back(str4);
        //把指针添加到队列中
        
        str3 = &str1;
        //改变str3指针的指向
        cout << str3 << "修改中str3  " << *str3 << endl;
        return 0;
    
    }
    
    int main(int argc, char *argv[]){
        vector<string *> vi;
        int rlt = prodoucer1(vi);
        for (auto i:vi) {
            cout <<i<<"修改后 "<< *i << endl;
        }
        //修改以后i的地址可以拿到,但是i的值已经拿不到了。因为指针指向的内容是局部变量,已经回收掉了
        return 0;
    }

    在此,得出结论,如果你要使用vector存放指针,请保证指针指向的内容不会被自动回收。

    2)vector中存放数据,实践证明push_back这是值拷贝

    #include "pch.h"
    #include <iostream>
    #include <string>
    #include <vector>
    #include <memory>
    using namespace std;
    using namespace cv;
    
    //以下是局部变量的string添加到队列中的function
    int prodoucer(vector<string> &xc);
    int prodoucer(vector<string> &xc){
        string str1 ="1234";
        string str2 = "abcd";
        string str3 = "xyz";
        cout << &str1 << "before str1 " << str1 << endl;
        cout << &str2 << "before str2 " << str2 << endl;
        cout << &str3 << "before str3 " << str3 << endl;
        //输出结果:
        //00000027BDFDFB18before str1 1234
        //00000027BDFDFAF8before str2 abcd
        //00000027BDFDFAD8before str3 xyz
        xc.push_back(str1);
        xc.push_back(str2);
        xc.push_back(str3);
        str3= "hehehe";
        cout << &str3 << "changeing str3  " << str3 << endl;
        //00000027BDFDFAD8changeing str3  hehehe
        return 0;
    }
    
    int main(int argc, char *argv[]){
        vector<string> vi;
        int rlt = prodoucer(vi);
        cout << " out side the fun" << endl;
        cout <<&vi[0]<<"using "<< vi[0] << endl;
        cout << &vi[1] << "using " << vi[1] << endl;
        cout << &vi[2] << "using " << vi[2] << endl;
    
        //打印出来的是:
        //000001F43005DFB0using 1234
        //000001F43005DFD0using abcd
        //000001F43005DFF0using xyz
        return 0;
    }

    str1在局部变量中的内存地址原本是fb18,添加到vector中以后,再取出来地址就变成了dfb0,但是前后值没变,所以我认为这属于值拷贝

    3)vector中存放智能指针,没有问题,而且智能指针的指向的数据的地址没有改变,

    #include "pch.h"
    #include <iostream>
    #include <string>
    #include <vector>
    #include <memory>
    using namespace std;
    using namespace cv;
    //以下是局部变量的智能指针添加到队列中的function
    int prodoucer(vector<shared_ptr<string>> &xc);
    int prodoucer(vector<shared_ptr<string>> &xc){
        shared_ptr<string> str1 = make_shared<string>("1234");
        shared_ptr<string> str2 = make_shared<string>("abcd");
        shared_ptr<string> str3 = make_shared<string>("xyz");
        cout << str1 << "before str1 " << *str1 << endl;
        cout << str2 << "before str2  " << *str2 << endl;
        cout << str3 << "before str3  " << *str3 << endl;
        //三个变量的地址分别是:e0,a0,60,值就是上面写的这些
        xc.push_back(str1);
        xc.push_back(str2);
        xc.push_back(str3);
        str3= make_shared<string>("hehehe");
        cout << str3 << "changeing str3  " << *str3 << endl;
        //把地址60上的内容变为“hehehe”
        return 0;
    }
    int main(int argc, char *argv[]){
        vector<shared_ptr<string>> vi;
        int rlt = prodoucer(vi);
        cout << " out side the fun" << endl;
        for (auto i:vi) {
            cout <<i<<"using "<< *i << endl;
        }
        //循环中能打印出来的是:e0,1234    a0,abcd   60,xyz
        //很明显局部的智能指针放到队列中以后,地址没变,数据也没变,所以push_back的操作相当于把智能指针指向的数据块的引用增加了,而且作用域提升到了全局变量
        //push局部变量到vector的操作相当于是对实例本身进行值拷贝,但是更加科学的是,局部变量指向的数据块并没有真正地被复制了一遍,而是生命周期变得和vector一样长了
        return 0;
    }

     原本我不明白,现在我明白了。

    首先要明白shared_ptr是个啥?是个类,我创建:shared_ptr<string> hehe;hehe是一个类实例,这个类实例采用的创建模板是string,使用sizeof函数查看,你就会发现所有智能指针的大小都是16字节,所有的string大小都是32字节。

    那么问题来了,为什么智能指针只有16字节,却能够‘放’很多数据呢?大概流程是这样的:创建实例 --------> new 一块内存存放数据(模板传递的是string就开辟32字节以上,模板是int就开辟4字节以上)---------->实例中相关的属性存好(这其中包括但是不仅限:new出来的内存的地址,值得一提的是这个实例有个很牛的方法,把自己装得很像一个指针,)

    如何装得自己很像指针?第一,只要你打印实例hehe,我就把我存的源数据的地址给你打印出来。第二,你如果对我使用取值符号(比如:*hehe),我就把源数据的内容给你。但是这只是伪装出来的,为什么这么说?因为你可以打印一下&hehe,这样你就能得到这个实例的实际存储位置了。不信你看:

    int prodoucer(vector<shared_ptr<string>> &xc);
    int prodoucer(vector<shared_ptr<string>> &xc) {
        shared_ptr<string> str1 = make_shared<string>("1234");
        shared_ptr<string> str2 = make_shared<string>("abcd");
        shared_ptr<string> str3 = make_shared<string>("xyz");
        cout << &str1 << "before str1 " << *str1 << endl;
        cout << &str2 << "before str2  " << *str2 << endl;
        cout << &str3 << "before str3  " << *str3 << endl;
        //内容是这样的:
        //000000458013FC10before str1 1234
        //000000458013FC00before str2  abcd
        //000000458013FBF0before str3  xyz
        xc.push_back(str2);
        xc.push_back(str3);
        str3 = make_shared<string>("hehehe");
        cout << &str3 << "changeing str3  " << *str3 << endl;
        //000000458013FBF0changeing str3  hehehe
        return 0;
    }
    int main(int argc, char *argv[]) {
        vector<shared_ptr<string>> vi;
        int rlt = prodoucer(vi);
        cout << " out side the fun" << endl;
        for (auto i : vi) {
            cout << &i << "using " << *i << endl;
        }
        //循环中能打印出来的是:
        //000000458013FC70using 1234
        //000000458013FC70using abcd
        //000000458013FC70using xyz
        return 0;
    }

    所以,你看到了,整个流程是这样的:创建智能指针(这其中包括开辟了一块自带引用计数的内存存储源数据,然后新建了一个智能指针的实例指向存数据的内存),当push_back的时候,先值拷贝了一个实例(新实例仍旧指向原来的那块源数据,数据被引用次数加1,现在是2),然后局部function走完了,回收了局部变量(源数据块上引用数量减1,现在是1),全局变量的vector中仍旧保存了智能指针的实例,所以源数据的引用不会归0,不会被释放。

    4,将一个局部的带指针属性的实例添加到队列中,那会发生什么?

    #include <iostream>
    #include <string>
    #include <vector>
    #include <memory>
    using namespace std;
    
    class fruit {
        public:
            char *color ;
    };
    int prodoucer(vector<fruit> &xc);
    int prodoucer(vector<fruit> &xc) {
        fruit apple; apple.color = "red";
        fruit pear; pear.color = "yellow";
        cout << &apple<< "before str1 " << apple.color << endl;
        printf("%p 
    ", apple.color);
        printf("%p 
    ", "red");
        cout << &pear << "before str2  " << pear.color<< endl;
        //内容是这样的:
        //  0000009BC7AFFA00before str1 red
        //    00007FF764D03358
        //    00007FF764D03358
        //    0000009BC7AFFA08before str2  yellow
        xc.push_back(apple);
        xc.push_back(pear);
        apple.color = "green";
        printf("%p 
    ", apple.color);
        //00007FF764D03390
        return 0;
    }
    int main(int argc, char *argv[]) {
        vector<fruit> vi;
        int rlt = prodoucer(vi);
        cout << " out side the fun" << endl;
        cout << &vi[0] << "using " << vi[0].color << endl;
        printf("%p 
    ",vi[0].color);
        printf("%p 
    ", "red");
        cout << &vi[1] << "using " << vi[1].color << endl;
        //循环中能打印出来的是:
        //  0000028205C1F110using red
        //    00007FF764D03358
        //    00007FF764D03358
        //    0000028205C1F118using yellow
        return 0;
    }

    说明一下:

    第一个问题:为什么“red"这个字符串不管在局部还是在全局,在实例内还是单独打出来地址永远都是58呢?个人怀疑是因为它在静态区,或者是因为双引号的锅,但是目前不能确定。

    第二个问题,实例的地址前后改变了,这从侧面佐证了push_vector确实是值拷贝,但是实例中如果带指针,指向的数据究竟能不能带到全局变量中呢?这个实验看不出来,我们换一个

    #include <iostream>
    #include <string>
    #include <vector>
    #include <memory>
    using namespace std;
    //using namespace cv;
    class fruit {
        public:
            string *color ;
    };
    int prodoucer(vector<fruit> &xc);
    int prodoucer(vector<fruit> &xc) {
        fruit apple; 
        string color1 = "red";
        apple.color = &color1;
        printf("%p 
    ", apple.color);
        printf("%p 
    ", color1);
        //问题一,以上这两个地址打印出来为什么不一样啊喂?
        //  0000008065AFF730
        //    0000008065AFF6C0
        string color2 = "yellow";
        fruit pear; 
        pear.color = &color2;
        cout << &apple << "value: " << *apple.color;
        printf("address:%p 
    ", apple.color);
        cout << &pear << "value:  " << *pear.color;
        printf("address:%p 
    ", pear.color);
    
        //内容是这样的:
        //  0000008065AFF6E0value: redaddress:0000008065AFF730
        //    0000008065AFF6E8value:  yellowaddress:0000008065AFF710
        xc.push_back(apple);
        xc.push_back(pear);
        string color3 = "green";
        apple.color = &color3;
        cout << &apple << " changing value: " << *apple.color;
        printf("address:%p 
    ", apple.color);
        //0000005364EFF8E0 changing value: greenaddress:0000005364EFF8F0
        return 0;
    }
    int main(int argc, char *argv[]) {
        vector<fruit> vi;
        int rlt = prodoucer(vi);
        cout << " out side the fun" << endl;
        cout << &vi[0] << "using value:" << *vi[0].color ;
        printf(" address:%p 
    ",vi[0].color);
        //printf("%p 
    ", "red");
        cout << &vi[1] << "using value: " << *vi[1].color ;
        printf(" address:%p 
    ", vi[1].color);
        //循环中能打印出来的是:
        //  000001905C540290using value : address:0000005364EFF930
        //    000001905C540298using value : address:0000005364EFF910
        return 0;
    }

    以上这个例子,基本证明了,即使是当作类属性,在值拷贝的时候指针指向的内容也是不会被拷贝的。所以终极结论是:

    当进行值拷贝的时候,指向局部变量的普通指针是不可靠的。智能指针的可靠的。

    然后我就又有一个问题了,opencv中有个重要的类叫mat,mat占96字节,mat保存了一个属性是这样的:uchar *data;看起来是个普通指针,因此是否可以把局部的mat push到vector中呢?the truth is it does ok .but why?据说在堆上,但是在堆上的数据为啥不能被回收???namen

  • 相关阅读:
    javascript 解析json数据
    解析 对象列表的JSON数据 []、[{}] 中括号
    # 指针
    # 栈内存和堆内存
    # Linux学习笔记
    # jsp及servlet学习笔记
    # Git学习笔记
    # Excel批量处理数据
    # 数学建模算法
    # VsCode 配置C++调试运行
  • 原文地址:https://www.cnblogs.com/0-lingdu/p/12274278.html
Copyright © 2011-2022 走看看