c++ 踩坑大法好 复合数据类型------vector
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的更多相关文章
- c++ 踩坑大法好 枚举
1,枚举是个啥? c++允许程序员创建自己的数据类型,枚举数据类型是程序员自定义的一种数据类型,其值是一组命名整数常量. ,wed,thu,fri,sat,sun}; //定义一个叫day的数据类型, ...
- c++踩坑大法好 typedef和模板
1,typedef字面意思,自定义一种数据类型 语法:typedef 类型名称 类型标识符; 基本用法: 1) 为基本数据类型定义新的类型名. 2) 为自定义数据类型(结构体.公用体和枚举类型)定义简 ...
- c++踩坑大法好 数组
1,c++遍历数组 int数组和char数组不同哦,int占4位,char占1未,同理double也不同.基本遍历方法: ] = { ,,, }; ]); printf("len of my ...
- c++踩坑大法好 赋值和指针的区别
1,先说结论: 两个指针指向同一个结构,一个改了结构,另一个也会改掉. 两个指针指向同一个结构,修改了其中一个的指向,并且改了其中的内容,另一个不为所动. 2,看例子 main.cpp #includ ...
- c++ 踩坑大法好 char字符,char数组,char*
1,基本语法 1,定义一个char字符: char hehe='a'; //单引号 2,定义一个由char字符组成的数组: char daqing[] = "abcd"; char ...
- c++踩坑大法好 宏定义 头文件
1,c++宏定义是干啥的?防止重复引用,如何防止重复引用? //a.h //声明一个类,和其他声明 #include <iostream> class A{ public: static ...
- Java 开发中如何正确踩坑
为什么说一个好的员工能顶 100 个普通员工 我们的做法是,要用最好的人.我一直都认为研发本身是很有创造性的,如果人不放松,或不够聪明,都很难做得好.你要找到最好的人,一个好的工程师不是顶10个,是顶 ...
- Spark 1.6升级2.x防踩坑指南
原创文章,谢绝转载 Spark 2.x自2.0.0发布到目前的2.2.0已经有一年多的时间了,2.x宣称有诸多的性能改进,相信不少使用Spark的同学还停留在1.6.x或者更低的版本上,没有升级到2. ...
- 踩坑系列の Oracle dbms_job简单使用
二话不说先上代码 --创建存储过程 create or replace procedure job_truncateState is begin --此处就是要定时执行的sql execute imm ...
随机推荐
- java代码生成器 快速开发平台 二次开发 外包项目利器 springmvc SSM后台框架源码
. 权限管理:点开二级菜单进入三级菜单显示 角色(基础权限)和按钮权限 角色(基础权限): 分角色组和角色,独立分配菜单权限和增删改查权限. 按钮权限: 给角色分配按钮权限.2 ...
- 吴裕雄--天生自然 PYTHON数据分析:人类发展报告——HDI, GDI,健康,全球人口数据数据分析
import pandas as pd # Data analysis import numpy as np #Data analysis import seaborn as sns # Data v ...
- 生成指定规模大小的redis cluster对关系
需求: 指定一批ip列表,生成指定规模大小的redis cluster主从对应关系. ip_list=(1.1.1.1 2.2.2.2 3.3.3.3 4.4.4.4 5.5.5.5) port=70 ...
- 使用pem连接服务器
后台同学甩给你一个pem文件,username@IP后如何链接服务器 准备:ssh客户端 例子xshell 文件->新建->主机(连接界面主机输入框输入IP)->点击用户身份-> ...
- adworld easy_RSA | RSA算法
题目描述: 解答出来了上一个题目的你现在可是春风得意,你们走向了下一个题目所处的地方 你一看这个题目傻眼了,这明明是一个数学题啊!!!可是你的数学并不好.扭头看向小鱼,小鱼哈哈一笑 ,让你在学校里面不 ...
- 从接口自动化测试框架设计到开发(二)操作json文件、重构json工具类
用例模板里的请求数据多,看起来很乱,所以可以通过访问另外一个文件的方式获取请求数据 把请求数据都放在一个json文件中 取出login的内容: import json fp = open('G:/un ...
- Java 【instanceof使用】
一.instanceof使用 public class demo{ public static void main(String[] args){ String name = “hello”; boo ...
- 如何在IDEA中使用GitHub
第一步:在GitHub网站中注册自己的账号 地址:https://github.com/ 第二步:下载Git客户端 地址:https://git-scm.com/ 第三步:在GitBash中配置用户名 ...
- 数据类型(8种)和运算符——Java
一.什么是标识符,它有什么作用(重点掌握) 1. 标识符指的是 标识符是用户编程时使用的名字,用于给变量.常量.函数.语句块等命名,以建立起名称与使用之间的关系.标识符可由任何字母数字字符串形成. 2 ...
- 轻量级RPC设计与实现第三版
在前两个版本中,每次发起请求一次就新建一个netty的channel连接,如果在高并发情况下就会造成资源的浪费,这时实现异步请求就十分重要,当有多个请求线程时,需要设计一个线程池来进行管理.除此之外, ...