小猪猪C++笔记基础篇(四)数组、指针、vector、迭代器

时间:2021-09-29 17:21:35

小猪猪C++笔记基础篇(四)

关键词:数组,Vector。

一、数组与指针

数组相信大家学过C语言或者其他的语言都不陌生,简单的就是同一个变量类型的一组数据。例如:int a[10],意思就是从a开始有10个连续的int大小的空间。我们还是从初始化说起。

我们以数据类型int为例,当然也可由有很多的数据类型,可以是像int,double这种,也可以是自定义的类,一般的初始化方法有:

int a[10];

int a[10]={0};

int a[3]={0,1,2};

在前面的文章中,有的朋友提到了指针的运算,指针的运算在数组中是非常常见的。我们可以把a[10]看做10个连续的存储空间。那么a其实就是一个int型的指针,存放数组开始的位置,指向a[0]这个元素。那么容易知道a+1就是指向a[1],a+5就是指向a[5]了。于是根据指针的概念我们可以知道:

*a和a[0]是等价的,*(a+5)和a[5]等价的,注意*(a+5)的括号位置,如果你写成*a+5那么结果等价于a[0]+5。写一小段代码测试一下:

int a[6]={1,3,5,7,9,10};

cout<<"a[0]: "<<a[0]<<"*a "<<*a<<endl;

cout<<"a[5]: "<<a[5]<<" *(a+5)"<<*(a+5)<<"*a+5 "<<*a+5<<endl;

小猪猪C++笔记基础篇(四)数组、指针、vector、迭代器

结果就是这样的!

那么前面文章中有朋友也提到了,a+1其实加的是步长,并且说a+1等价于a+sizeof(int)那么到底是不是这么回事呢。跟上上面这个初始化过的a,我们再做一个实验:

cout<<sizeof(int)<<endl;

cout<<a<<endl;

cout<<a+1<<endl;

cout<<a+sizeof(int)<<endl;

int b[3]={0};

int c[3]={0};

cout<<b<<" "<<c<<" "<<b-c<<endl;

小猪猪C++笔记基础篇(四)数组、指针、vector、迭代器

从结果可以看到,sizeof(int)=4,一个B有8个bit,就是说我们这里的int 是32位。

第二行可以看到a[0]的地址是001FFDF0,a[1]的地址是0001FFDF4,两个之间确实差4个也就是sizeof(int)这么大。

这个比较容易理解,当你把计算机每一位都理解成一个小盒子,一个int要占32个小盒子,那么下一个int的位置就是这么多了。

那么实际上a+1和a+sizeof(int)是不是一样的呢。可以看到a+sizeof(int)=0001FFE00,比a的值大了16,也就是说多了4*sizeof(int)这么大,所以说a+1和a+sizeof(int)是不等价的,其实这也好解释,电脑是这么计算的,sizeof(int)=4,a+4就等于这么多了。

也就是说a的步长确实有sizeof(int)这么大,但是可不能写成a+sizeof(int)。

那我突然就很好奇,两个int *相减会怎么样,我就试了一下。可以发现b放的地址是001FFDDC,c的地址是0001FFDC8。相减的结果是5,也就是5*sizeof(int)。这样就更加好解释为嘛a+1和a+sizeof(int)是不一样的。我们想想啊,(a+1)-a=1,肯定不等于4。

这东西似乎并没有什么卵用,但是明确一下还是好的!

接下来,既然我们知道了数组跟指针是有密切的联系,在实际开发中很多情况我们并知道到底需要用多大的数组,很多情况下我们都喜欢这么干:

Int *a=new int[n];

这实际上跟a[n]并没有什么区别,并且你在调用的时候依然可以用a[1],a[2]这么读取相应地址的元素。我们可以这么说,在数组中间的下标值跟指针后面加个数差不多的意思。所以就不能解释这种变态的问题了p[-2]是个神马东西!

第一次碰到这个问题是在研究生复试的时候一个老师幽幽的问我,p是个数组,那p[-2]是多少?!这不是坑人么,长这么大突然发现数组的下标居然会是负数!!!但是老师果然就是吃的盐比我吃的饭要多,确实是这么回事,我们再写一下段代码来解释:

int a[6]={1,3,5,7,9,10};

int *p=&a[3];

cout<<a[1]<<endl;

cout<<p[-2]<<endl;

小猪猪C++笔记基础篇(四)数组、指针、vector、迭代器

P是一个int型指针,位置从a的第四个元素开始,也就是说p[0]=7,p[1]=9,p[2]=10,这样。很容易理解,p=a+3;那么p[-2]=*(p-2)=*(a+3-2)=*(a+1)=a[1]。也就是说p[-2]=*(p-2)。所以如果p-2指向的地址初始化过,那么p[-2]就等于p-2地址指向的值。这种用法是极少的,但是理论就是这样子的,以后万一给我当了老师也可以这么忽悠学生了!

那么指针和数组这个问题我们也讲得差不多了,接下来还有一个很痛苦的问题:什么是指针数组和数组指针。

解决这个问题我们先从多维数组来说起,多维数组就是例如int a[3][4]这样子的东西,实际上我们很少用,因为很容易出错。

int a[3][4]意思就是有个大小为3的数组,每个元素是含有4个元素的数组。我们知道int a[3]里面这个a是一个指针int *,所以int a[3][4]的a可以看成int(*p)[4]这种类型,就是指向有四个元素的指针。那么 int (*p)[4]是指向4个元素的数组,就是我们说的数组指针。P指向一个int *的指针,然后*p指向一个int 型。那么int *p[4]就是一个指针数组,元素放的是指针。实际上这些东西并没有什么用,而且非常容易搞错。还有的人用int **p来表示二维数组,int***p来表示三维数组,虽然是对的,但是看起来怕怕的。

简单的总结一下:

int (*p)[4]是数组指针,指向一个整数数组。调用元素的方法是(*p)[i],值是整数。

int  *p[4]是一个指针数组,存放的是指针,调用元素的方法是*p[i],值是指针。那实际上我们是怎么干的呢。两个方法:

第一,做个别名:

typedef int int_arry[4];

int_arry a[3];

第二,算坐标,比如我们申请int a[3][4],实际上可以申请一个int b[3*4]这么大的空间。a[2][3]=b[2*3+3];后面这种方法在图像处理中十分常见。

二、vector和迭代器

按道理来说,这个应该放在后面来讲的。但是它跟数组太像了,我在这里和数组做一个对比。

Vector跟数组很像,我认为它和数组最主要的区别是vector并不需要事先知道要存的这么一串数据的大小,它的空间是动态分配的。我们做一个简单的对比。

int a[6];

vector<int>b;

for(int i=0;i<6;i++)

{

a[i]=i;

b.push_back(i);

}

for(int i=0;i<6;i++)

{

cout<<a[i]<<" ";

}

cout<<endl;

for(int i=0;i<6;i++)

{

cout<<b[i]<<" ";

}

cout<<endl;

小猪猪C++笔记基础篇(四)数组、指针、vector、迭代器

这里都是把0-5这些数字存进去然后再打印出来。可以看到,vector存的时候是用.push_back(i)来做的,而不是b[i],但是输出的时候跟数组是一样一样的操作。

那么你会问为什么vector不能像数组一样直接调用下标赋值呢。这个很容易解释,因为数组预先开辟了空间,但是vector并没有预先开辟空间。Push_back这个操作其实是先申请一个空间,再把数据放进去。如果我们给没有初始化过的vector元素赋值,会报错,跟数组越界差不多的意思。

但是如果vector的这个原始初始化过了,我们还是可以用b[i]=n,这样的跟数组一样的赋值。

所以调用方法除了初始化部分其他基本一样。当你不确定数组大小的时候可以用vector来解决。当然还有一种替代的方法就是用指针,然后每增加一个元素就动态的申请一空间。具体做法可以参见《数据结构》中的队列或者栈中初始化的操作。

然后我们再说一说迭代器。迭代器有点像指针,或者说指针就是迭代器的一种。当我们调用vec.begin(),vec.end()返回的都是一个迭代器,类似于指针,取值方法也是带星号*vec.begin()这样。基本上指针怎么使,迭代器就怎么使。

最后,再提一提那个负数下标的问题。在vector里面的下标只能值正整数,不能是负数,这是它跟指针的下标的区别。

PS:改了名字,据说写逆袭博士容易遭喷,这其实也是给自己个动力,每天看看书,想想坚持个几年就可以毕业了。不过,还是改名字了。