为什么数组名a是指向a[0]的指针如何指向二维数组?

  • 在很多用到数组名字的地方,编译器都会自动地将其替换为一个指向该数组首元素的指针。
  • 所以,在大多数表达式中,使用数组名其实是在使用一个指向该数组首元素的指针。


  1. 从局部变量表可以看出,数组a和指针p的构成是很相似的。它们实际存的都是一个地址,都会指向一个对象(或多个对象的第一个对象)。所以说数组名其实是种特殊的指针。
  2. 但它俩自身又有不同,指针本身是一个对象,在内存中是为其分配了空间的;但数组名在内存空间中没有分配到空间(这将导致&a操作的效果可能和预想的不大一样)。
  1. a既然是种特殊的指针,那么其打印时就会是存的地址。
  2. &a的类型是int(*)[5](读法从小括号里往外,首先是指针,然后是大小为5的数组,然后数组元素类型是int),从局部变量中看到其类型也可写成int[5] *:即指向大小为5的int数组的指针。由于数组名没有内存分配空间,所以&a取地址还是取到的是数组首元素的地址。
  3. &a[0]就是取一个int对象的地址,这个int对象是数组首元素。综上所述,就造成了a &a &a[0]三者打印出来的地址是一样的。
  4. p,指向数组首元素的指针。
  5. a + 1,p + 1都是按照元素大小的字节数(4字节),增加4。
  6. &a + 1,前面说了 &a的类型是指向大小为5的int数组的指针,大小为5的int数组所占字节数为20,所以&a + 1就应该增加20。

对了,你也可以int *p = a,也是指向数组首元素的指针。

int k = p[-2],0索引的元素。这里是内置的取下标操作,所以可以用有符号数。(标准库类型取下标的话,则必须使用无符号数)
但一定注意这里的p都必须指向数组中的元素。

关于C语言,数组和指针的关系是密不可分的,如果我们没能理解其中的关系或者一些细节,就可能在以后的学习中遇到大麻烦。

  1. C语言中只有一维数组,定义一个数组的时候必须要指明大小或者初始化。可能这是会有人问这个int a[2][3]怎么解释?因为在C语言中,数组的元素类型可以是int,double,struct等等。换言之,数组的元素类型可以是任意类型,当然也可以是另一个数组。所以,int a[2][3]表示该数组有2个元素,每个元素都是一个拥有2个整形元素的数组。因此,定义出一个二维数组就是一件轻而易举的事。
  2. 对于一个数组,我们只能做两件事:确定数组的大小以及获取指向该数组的第一个元素(下标为0)的指针。对于其它的有关数组的操作,哪怕他看上去像是在操作下标,其实实际是在对指针进行操作,因为它们呈现的是一种一一对应的关系。

要理解C语言中数组的运算机制,首先我们要能学会如何声明一个数组,以及如何理解它,例如:int a[5]; 这个语句声明了变量a是一个有5个整型元素的数组。double b[2];  这个语句声明了变量b是一个有2个双精度浮点型元素的数组。

声明了 Stu是拥有15个元素的数组,每个元素都是一个结构体,该结构体包括了一个拥有10个字符型元素的数组(名字为name)和一个整形变量(名字为age)。

这个语句声明了c是一个数组,该数组拥有10个数组类型的元素,每个元素都是一个拥有15个整型元素的数组(而不是一个组拥有15个数组类型的元素,每个元素都是一个拥有10个整型元素的数组).因此sizeof(c)的值是150(15*10)和sizeof(int)的乘积。这里,如果c不是sizeof的操作符,而是用于其他场景,c将被转化成为一个指向该数组首元素指针。如果要很好的理解这句话,我们必须对指针也要有一定的了解。

指针是一个标识,就像地址一样,用这个地址就可以找到一个确切的地点。所以指针可以指向任何类型的变量,不过前提得是两者的类型必须保持一致。例如,我们声明一个指针变量int* p,此时指针p并没有被初始化,因此p里面放的就是一个随机的地址,所以我们不能对这个指针进行任何解引用操作,否则编译器会发出警告或者报错,甚至有可能带来毁灭性的后果。再如,我们声明 int i = 0;那么我们就可以把i的地址赋给指针p:p = &i;我们也可以通过指针p将i的值改为10:*p =10。但是,如果你要把一个双精度浮点类型变量的地址赋给指针p,例如:double j = 0; p = &j;这种操作是非法的,因为它们的类似不匹配。

如果我们定义一个数组:int week[7] = {1, 2, 3, 4, 5, 6, 7}; 众所周知,数组名是一个地址(指针),但是它到底是数组首元素的地址还是整个数组的地址,大部分人都很混淆,以为这两者是相同的概念,实则不然。此时,我们就可以直接利用一段简单的代码来告诉我们真正的答案:

看到这里,大部分人都会认为数组名就是整个数组的地址,也就等价于数组首元素的地址,这样的理解其实是错误的,我们再来看下一段代码

 而这段代码的结果却是:

不难发现,对数组名进行取地址操作,再加1,使它指向了数组的最后一个元素的后一个地址。数组名加1,使它指向了数组中第2个元素的地址,两者相差了24个字节,这就证实了数组名是首元素的地址。此时,我们就可以定义一个指针变量,用来存放数组week的地址了,例如: int*p = week;这样的含义就是把数组week中下标为0的元素的地址赋给p。可是,我们并没有写成:int* p = &week;这种写法是非法的,因为&week是一个指向数组的指针,而p是一个指向整型变量的指针,它们的类型不匹配。

这使我们联想到给指针加上一个整数或者减去一个整数。例如:int a[5]={1,2,3,4,5}; int* p = a+1;    同样的也可以让两个指向同一个数组中的元素的地址相减,这样做是有意的,例如:int* q = p+i;    那么我们就可以通过q-p得到i的值,通俗的讲,i就是q和p之间的元素个数。值得我们注意的是:

         2.如果p和q指向的是不同的数组中元素的地址,即使它们所指向的地址在内存中正好间隔一个数组元素的整数倍,所得的结果也是一种耦合,无法保证其中的正确性。更有甚者,p和q指向的甚至是不同类型元素的数组,这样的结果更难预料。

arr;此时,我们可以通过数组下标来访问数组的每一个元素:arr[0],arr[1],以此类推。因为p等同于arr,我们也可以这样:p[0],p[1],以此类推。因为p是一个指针,指向的是数组arr中的第一个元素,也就是下标为0的元素,因此我们可以这样:*p,*p+1,以此类推。同样的,我们也可以*arr,*arr+1,以此类推。

事实告诉我们是相等的。实际上,因为i+p和p+i的含义相同,因此p[i]和i[p]也具有相同的意义,但是我们推荐p[i]这种写法,因为这种写法让我们明确知道这是在对下标为i的元素进行引用。

现在,我们来讨论二维数组。首先我们先声明这样一个二维数组:int calendar[12][31];然后声明一个变量int i;最后声明一个指针int* = calendar[4];这个语句表明了将calendar这个数组的首元素的地址赋给p,总的看来,就相当于把calendar[4][0]这个元素的地址赋给p。因为calendar[4]是一个数组,我们可以通过下标的方式将这个数组的第6个元素赋值给i:i = calendar[4][5]。其次就是,我们也可以通过指针的方式:i =

我们再看:p = calendar;前面的例子已经告诉我们这种写法是错误的了,因为calendar是一个指向数组的指针,而p是一个指向整型变量的指针,类型不匹配。如果我们要将calendar这个指针赋给另一个指针,我们就必须重新再声明一个数组指针,例如:int (*pp)[31] ;这个语句告诉我们这样一个信息:pp是一个指针,该指针指向了一个拥有10个整型元素的数组。所以,这样的写法才是正确的:pp = calendar;

对于编程人员,我们应该深入理解数组和指针的关系,有着融会贯通的能力,这样才能在学习和工作中少走弯路,写出优秀的代码,不至于错误连篇。

我要回帖

更多关于 指针如何指向二维数组 的文章

 

随机推荐