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. 6.python设置代理和添加镜像源介绍

    为什么要修改镜像源? 一般使用python安装库,会用到pip install xxx 指令或者conda install xxx指令,因为pip和conda默认国外镜像源,这时会在Python的官方 ...

  2. 内网学习之MySQL服务提权

    利用MySQL提权原理: 1.具有mysql的root权限,且mysql以system权限运行. 2.具有执行sql语句的权限,webshell或者外连皆可 UDF提权 UDF(user define ...

  3. Django基础一Web框架的本质

    我们可以这样理解:所有的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端,基于请求做出响应,客户都先请求,服务端做出对应的响应,按照http协议的请求协议发送请求, ...

  4. Bash脚本编程学习笔记04:测试命令test、状态返回值、位置参数和特殊变量

    我自己接触Linux主要是大学学习的Turbolinux --> 根据<鸟哥的Linux私房菜:基础篇>(第三版) --> 马哥的就业班课程.给我的感觉是这些课程对于bash的 ...

  5. 【sklearn】Toy datasets上的分类/回归问题 (XGBoost实践)

    分类问题 1. 手写数字识别问题 from sklearn.datasets import load_digits digits = load_digits() # 加载手写字符识别数据集 X = d ...

  6. 【笔记】机器学习 - 李宏毅 - 7 - Deep Learning

    深度学习发展历史: 感知机和逻辑回归很像,只是没有\(sigmoid\)激活函数. 深度学习训练的三个步骤: Step1:神经网络(Neural network) Step2:模型评估(Goodnes ...

  7. Mybatis的延迟加载和立即加载

    Mybatis的延迟加载和立即加载 示例:在一对多中,当我们有一个用户,他有100个帐户 问题1:在查询用户时,要不要把关联的账户查出来? 问题2:在查询账户时,要不要把关联的用户信息查出来? 问题1 ...

  8. Api跨域设置

    跨域设置:(服务端) webconfig文件中,system.webServer节点下添加 <!--跨域请求:三个配置信息--> <httpProtocol> <cust ...

  9. pandas学习笔记之删除指定列

    删除指定 def df["列名"] del df4["韩国地震影响"] 直接删除,df4中不在含有"韩国地震影响"这一列了 drop 不改变 ...

  10. PAT (Advanced Level) Practice 1036 Boys vs Girls (25 分)

    This time you are asked to tell the difference between the lowest grade of all the male students and ...