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