任何指针都是指向某种类型的变量
看一段简单的指针代码
int i;
int *p;
p = &i;
*p=1;
看过指针的人都应该很清楚的知道这段代码表达的意思。我们声明了一个指针p,并且让它指向一个整形变量i.
因此之后,任何对于*p
的赋值就是对变量i的改变。
用一张简单的图可以表示
对于其他类型,指针表达的意思也和这个类似,记住指针是指向某一个类型的变量,它有一个确定的指向类型, 这一点很重要。
在C语言中,只有一维数组,并且数组的大小必须是一个常数,不可以是变量。当然在C99标准中可以让一个数组的大小
是一个变量,但是在我们正常写程序的时候完全没有必要这样做。
对于一个数组的元素,可以是任何一种(这从数组的声明中可以看出来,不可能有多种类型的元素在一个数组中)
类型的对象,
当然可以是数组。因此,我们就很容易模仿出一个二维数组。
对于一个数组,本质上就是在内存中的一段地址空间。这也和为什么数组大小是常数对应,因为编译器在编译的时候会根据数组大小分配一定的内存空间。很多书上会说,一个数组就是内存中的一段连续空间。我不知道是不是对所有都是这样。但是若是我们死死的认定数组就是内存中一段连续的内存空间的话,对于我们理解其他复杂类型的数组的时候会有很大的阻碍。而且这些都是编译器的事,我们只要知道有这样一段空间留着给我们用就是了。另一方面,数组可以也仅仅呈现给我们两个信息:
数组的大小
数组下标为0的元素的指针
而其它任何有关数组的操作都是基于指针的运算。
int a[3];
int *p;
p = a;
*p=1;
*(p+1)=2;
这段代码第一行声明了一个包含3个整形元素的数组。数组的名称为a。这也恰恰是一个数组所有的信息。然后我们 声明了一个指向整形类型的指针,在这里,数组的名称就是指向该数组元素下标为0元素的指针。注意: 在这里很多人就会犯迷糊了,包括我自己在开始接触的时候,苦了很久。
疑问:
既然数组的名称就是指向该数组下标为0元素的该类型的指针,那么它和一个单纯的指针有什么区别呢?也就是 说和上面那段代码中的p有什么区别?
这个问题曾困扰我很久,后来才明白,是自己概念没有理清楚。记得我们刚才谈到了一个主要的概念; 就是数组呈现给我们的两个信息:
数组的大小
数组下标为0的元素的指针
也就是说还有一个信息:就是数组的大小。而在实际编程中,仅仅
sizeof(a);
执行这个函数的时候,才会返回数组的大小。在其他任何时候,数组的名称都表示指向该数组下标为0的元素的指针。
有了这句话说明,数组和指针的关系就算清了,下面我们说说指针和数组下标表示之间的关系
上面代码中,如果p是一个指向数组中一个元素的指针,那么通过将指针加一就可以得到指向数组下一个元素的指针, 同样我们也可以使用减一得到指向数组前一个元素的指针。在这里我就曾经有这样错误的想法:
一个数组就是内存中的一段连续的地址,指针又刚好表示一个元素的地址,那么对指针加一,减一不就是在那段连续的内存地址中移动吗。刚好与数组元素对应嘛
这种理解方式,自认为很完美。其实有一个很明显的漏洞,那就是:数组是有类型的,比如int是4个字节, char是一个字节,如果仅仅是指针在实际的物理内存地址中加一减一,怎么知道每次加减应该移动多少呢? 其实这样错误的理解原因就是把指针和实际物理内存地址对应的缘故。而实际上指针的加减一个整数, 仅仅表示指向数组中后一个或者前一个该类型的元素。而不是指针二进制值的加减。这两者有本质区别。 这也是我开头为什么说不要认为数组是内存中一段连续的地址的原因。
在上面代码中我可以知道,p和a[0]表示的内容是一样的,同样(p+1)和a[1]表示的内容是一样的,即*(p+i) 是数组a中下标为i元素的引用。实际上数组对元素的表示正是用指针相加减来表示的。只是在很多时候, 使用下标表示方法比使用指针的运算表示方法要方便明了很多。因此就记为a[i]。在这里我们会发现一个有非常有趣 的现象
#include"stdio.h"
int main()
{
int a[3];
int i;
for (i=0;i<3;i++)
{
i[a]=i;
}
for(i=0;i<3;i++)
{
printf("%d\n",a[i]);
}
for(i=0;i<3;i++)
{
printf("%d\n",i[a]);
}
return 0;
}
读者可以在linux环境下,用gcc编译,并且运行它,看看输出结果。
- 解释: 因为我们知道(a+i)表示的就是a[i],而p+i和i+p实际意义是一样的。所以(p+i)和*(i+p)表示的元素也是一样的。 而下标表示法仅仅是根据指针进行运算的,所以a[i]和i[a]的效果是一模一样的。 不过尽量不要使用这种表示方法,这对于写代码没有任何好处。