关键词:数组, 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;
结果就是这样的!
那么前面文章中有朋友也提到了, 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;
从结果可以看到, 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;
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;
这里都是把 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:改了名字,据说写逆袭博士容易遭喷,这其实也是给自己个动力,每天看看书,想想坚持个几年就可以毕业了。不过,还是改名字了。