1,vector是啥?

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

2,咋用?

声明:

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

常用方法:

#include "pch.h"
#include <algorithm>
using namespace std; int main() {
vector<int>vi;
vi.push_back();
vi.push_back();
//向队列的最后添加数据,1和2 vi.pop_back();
//去掉队列的最后一个数据 int vilen = vi.size();
//队列的实际长度 vi.clear();
//清除队列中所有的数据 vi.push_back();
vi.push_back();
vi.push_back();
vi.push_back();
//加点数据 for (int i = ; 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 = ; i < vi.size(); i++) {
printf("%d\n", vi[i]);
}
reverse(vi.begin(), vi.end());
//把队列按照从大到小的顺序排序
for (int i = ; i < vi.size(); i++) {
printf("%d\n", vi[i]);
} vector<vector<int> > obj;
//定义一个二维数组,约等于python中的:[[1,2],[1,2],[1,2]] vector<vector<int> > obj(, vector<int>());
//这样也是可以的,语法不同而已, return ;
}

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();
A b();
vector<A>vi = { a,b };
//创建个队列
vi.emplace_back();
//直接用101构造一个实例塞到队列中
vi.push_back();
//先生成一个实例,然后拷贝到队列中。
for (auto itt : vi)
{
printf("%d\n", itt.hehe);
}
return ;
}
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();
printf("查看指针指向的内存的大小%d\n",_msize(hehe));
//先去申请一块10字节的内存,申请成功以后返回的是指向该内存的指针,否则返回null
vector<unsigned char> vi(hehe,hehe+);
//vector的传入参数分别是某块内存的开始地址和结束地址,
printf("%d \n",vi.size());
free(hehe);
//malloc获取的内存记得释放呦
return ;
}

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 = "";
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 ; } int main(int argc, char *argv[]){
vector<string *> vi;
int rlt = prodoucer1(vi);
for (auto i:vi) {
cout <<i<<"修改后 "<< *i << endl;
}
//修改以后i的地址可以拿到,但是i的值已经拿不到了。因为指针指向的内容是局部变量,已经回收掉了
return ;
}

在此,得出结论,如果你要使用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 ="";
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 ;
} int main(int argc, char *argv[]){
vector<string> vi;
int rlt = prodoucer(vi);
cout << " out side the fun" << endl;
cout <<&vi[]<<"using "<< vi[] << endl;
cout << &vi[] << "using " << vi[] << endl;
cout << &vi[] << "using " << vi[] << endl; //打印出来的是:
//000001F43005DFB0using 1234
//000001F43005DFD0using abcd
//000001F43005DFF0using xyz
return ;
}

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>("");
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 ;
}
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 ;
}

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

首先要明白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>("");
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 ;
}
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 ;
}

所以,你看到了,整个流程是这样的:创建智能指针(这其中包括开辟了一块自带引用计数的内存存储源数据,然后新建了一个智能指针的实例指向存数据的内存),当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 ;
}
int main(int argc, char *argv[]) {
vector<fruit> vi;
int rlt = prodoucer(vi);
cout << " out side the fun" << endl;
cout << &vi[] << "using " << vi[].color << endl;
printf("%p \n",vi[].color);
printf("%p \n", "red");
cout << &vi[] << "using " << vi[].color << endl;
//循环中能打印出来的是:
// 0000028205C1F110using red
// 00007FF764D03358
// 00007FF764D03358
// 0000028205C1F118using yellow
return ;
}

说明一下:

第一个问题:为什么“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 ;
}
int main(int argc, char *argv[]) {
vector<fruit> vi;
int rlt = prodoucer(vi);
cout << " out side the fun" << endl;
cout << &vi[] << "using value:" << *vi[].color ;
printf(" address:%p \n",vi[].color);
//printf("%p \n", "red");
cout << &vi[] << "using value: " << *vi[].color ;
printf(" address:%p \n", vi[].color);
//循环中能打印出来的是:
// 000001905C540290using value : address:0000005364EFF930
// 000001905C540298using value : address:0000005364EFF910
return ;
}

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

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

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

c++ 踩坑大法好 复合数据类型------vector的更多相关文章

  1. c++ 踩坑大法好 枚举

    1,枚举是个啥? c++允许程序员创建自己的数据类型,枚举数据类型是程序员自定义的一种数据类型,其值是一组命名整数常量. ,wed,thu,fri,sat,sun}; //定义一个叫day的数据类型, ...

  2. c++踩坑大法好 typedef和模板

    1,typedef字面意思,自定义一种数据类型 语法:typedef 类型名称 类型标识符; 基本用法: 1) 为基本数据类型定义新的类型名. 2) 为自定义数据类型(结构体.公用体和枚举类型)定义简 ...

  3. c++踩坑大法好 数组

    1,c++遍历数组 int数组和char数组不同哦,int占4位,char占1未,同理double也不同.基本遍历方法: ] = { ,,, }; ]); printf("len of my ...

  4. c++踩坑大法好 赋值和指针的区别

    1,先说结论: 两个指针指向同一个结构,一个改了结构,另一个也会改掉. 两个指针指向同一个结构,修改了其中一个的指向,并且改了其中的内容,另一个不为所动. 2,看例子 main.cpp #includ ...

  5. c++ 踩坑大法好 char字符,char数组,char*

    1,基本语法 1,定义一个char字符: char hehe='a'; //单引号 2,定义一个由char字符组成的数组: char daqing[] = "abcd"; char ...

  6. c++踩坑大法好 宏定义 头文件

    1,c++宏定义是干啥的?防止重复引用,如何防止重复引用? //a.h //声明一个类,和其他声明 #include <iostream> class A{ public: static ...

  7. Java 开发中如何正确踩坑

    为什么说一个好的员工能顶 100 个普通员工 我们的做法是,要用最好的人.我一直都认为研发本身是很有创造性的,如果人不放松,或不够聪明,都很难做得好.你要找到最好的人,一个好的工程师不是顶10个,是顶 ...

  8. Spark 1.6升级2.x防踩坑指南

    原创文章,谢绝转载 Spark 2.x自2.0.0发布到目前的2.2.0已经有一年多的时间了,2.x宣称有诸多的性能改进,相信不少使用Spark的同学还停留在1.6.x或者更低的版本上,没有升级到2. ...

  9. 踩坑系列の Oracle dbms_job简单使用

    二话不说先上代码 --创建存储过程 create or replace procedure job_truncateState is begin --此处就是要定时执行的sql execute imm ...

随机推荐

  1. include 和require 区别

    include和require的区别  1.include() 包含文件 2.include_once(filename)如果已经包含,则不再执行include_once 3.requirerequi ...

  2. JS笔记之第二天

    一元运算符:++  -- 分为前++和后++ and 前--和后-- 如果++在后面,如:num++ +10参与运算,先参与运算,自身再加1 如果++在前面,如:++num+10参与运算,先自身加1, ...

  3. uniapp后台api设计(微信user表)

    MySQL 创建数据库: CREATE  DATABASE [IF NOT EXISTS] <数据库名> [[DEFAULT] CHARACTER SET <字符集名>] [[ ...

  4. linux环境安装mysql

    安装虚拟机:  RedHat+CentOS 我说略会不会被打,哈哈,这个虚拟机的安装之后我再补上吧~~ 首先查看已经安装的mysql相关的包 rpm -qa | grep mysql 执行之后会出现以 ...

  5. 离线部署ArcGIS Runtime for Android100.5.0

    环境 系统:window 7 JDK:1.8.0_151 Maven:3.6.1 Android Studio:2.3 ArcGIS Runtime SDK for Android:100.5.0 1 ...

  6. 前端开发神器 VSCode 使用总结

    VSCode 是微软出品的,基于 Electron 和 TypeScript 的,集成了 git 版本管理和命令行终端,而且开源稳定,插件丰富,再搭配一款 Chrome 浏览器,可以说是前端开发神器了 ...

  7. bootstrap234的ie兼容选择

    如果你需要兼容IE8甚至是IE7和IE6,那么只能选择Bootstrap2,虽然它自身在IE6的效果也并不完美.如果需要兼容IE678的话用2.如果需要高版本的浏览器,并且移动端优先的话,那么用boo ...

  8. 第一个12PB的项目--2017年

    今年第一个上PB级的项目终于落地.准备了几年,期盼了几年的项目,终于有了市场.内心还是有成就感的. 可是年终沟通,就像是一瓢冷水,从头往下倒.拔凉拔凉的.什么是心寒.什么是希望.想了很多. 我还本想着 ...

  9. jquery-进度条

    function process_bar(s_date, e_date, data) { $('.modal-body').text('你选择的时期范围是:' + s_date + '到' + e_d ...

  10. ES6基础与解构赋值(高颜值弹框小案例!)

    let只作用在当前块级作用域内使用let或者const声明的变量,不能再被重新声明let不存在`变量提升` console.log(dad); var dad = '我是爸爸!';//预定义undef ...