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\n", vi[i]);
    }
    //普通方法遍历队列输出内容

    vector<int>::iterator it;    //声明一个迭代器
    for (it = vi.begin(); it != vi.end(); it++) {
        printf("iterator  value is %d \n", *it);
    }
    //利用迭代器遍历队列

    for (auto itt : vi)
    {
        printf("%d\n", itt);
    }
    //c++11的新遍历方法,利用auto

    sort(vi.begin(), vi.end());    //sort 需要头文件 #include <algorithm>
    //把队列按照从小到大的顺序排序
    for (int i = 0; i < vi.size(); i++) {
        printf("%d\n", vi[i]);
    }
    reverse(vi.begin(), vi.end());
    //把队列按照从大到小的顺序排序
    for (int i = 0; i < vi.size(); i++) {
        printf("%d\n", 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\n", itt.hehe);
    }
    return 0;
}
A::A(int i) {
    hehe = i;
    //printf("%d\n", hehe);
};

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

#include <vector>

using namespace std;
//此用法可以用于把整块图片数据读取到一个vector中
int main() {
    unsigned char *hehe = NULL;
    hehe = (unsigned char *)malloc(10);
    printf("查看指针指向的内存的大小%d\n",_msize(hehe));
    //先去申请一块10字节的内存,申请成功以后返回的是指向该内存的指针,否则返回null
    vector<unsigned char> vi(hehe,hehe+10);
    //vector的传入参数分别是某块内存的开始地址和结束地址,
    printf("%d \n",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 \n", apple.color);
    printf("%p \n", "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 \n", 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 \n",vi[0].color);
    printf("%p \n", "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 \n", apple.color);
    printf("%p \n", color1);
    //问题一,以上这两个地址打印出来为什么不一样啊喂?
    //  0000008065AFF730
    //    0000008065AFF6C0
    string color2 = "yellow";
    fruit pear; 
    pear.color = &color2;
    cout << &apple << "value: " << *apple.color;
    printf("address:%p \n", apple.color);
    cout << &pear << "value:  " << *pear.color;
    printf("address:%p \n", 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 \n", 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 \n",vi[0].color);
    //printf("%p \n", "red");
    cout << &vi[1] << "using value: " << *vi[1].color ;
    printf(" address:%p \n", 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

原文链接: https://www.cnblogs.com/0-lingdu/p/12274278.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    c++ 踩坑大法好 复合数据类型------vector

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/327597

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年3月1日 下午4:20
下一篇 2023年3月1日 下午4:20

相关推荐