这篇文章我们来一起讨论一下指针与数组。指针与数组密不可分,但是绝不能将二者混为一谈。
数组名用作指针
我们可以用数组的名字作为指向数组第一个元素的指针。因此通常情况下,a+i
等同于&a[i]
,*(a+i)
等同于a[i]
,其中a是数组名。于是我们可以这样操作:
int a[100];
*a = 7; // 数组第一个元素赋值7
*(a+2) = 4; // 数组第2个元素赋值4
指针用作数组名
既然我们可以用数组名作为指针,那么反过来也是可以的。
int a[9], *p = a;
p[3] = 5; // 数组a的第三个元素赋值5
通常情况下,p[i]
可以看作*(p+i)
。
用指针处理数组
了解了上面的用法后,我们可以很轻松地用指针来处理数组。下面我们来吃几个栗子。
- 第一个栗子:一维数组求和
#define N 10
...
int a[N], sum, *p;
...
sum = 0;
for (p = &a[0]; p < &a[N]; p++)
sum += *p;
上面的例子中,我们将p指向数组a的第一个元素,然后依次对指针p自增从而遍历整个数组。你可能会怀疑p < &a[N]
的用法,其实这样做是完全正确的。即使元素a[N]
不存在,但是对它使用取地址运算符也是合法的。
- 第二个栗子:二维数组的求和
#define R 9
#define C 8
int a[R][C];
int *p, sum = 0;
...
for (p = &a[0][0]; P <= &a[R-1][C-1]; p++)
sum += *p;
如果我们要对一个二维数组进行求和,通常我们会用两个嵌套的for
循环,但是如果我们有了指针的话,我们只用一个循环就可以搞定,只不过可读性变差了。
- 第三个栗子:二维数组的第i行求和
...
int a[R][C], *p, i;
...
for (p = &a[i][0]; p < &a[i][C]; p++)
sum += *p;
上面的代码很容易理解,但是我们可以进一步简化。&a[i][0]
指向第i行的第一个元素,而对于二维数组,a[i]
即为指向第i行的第一个元素的指针,因此我们完全可以用a[i]
代替&a[i][0]
,同理可以用a[i]+C
代替&a[i][C]
。
int a[R][C], *p, i;
...
for (p = a[i]; p < a[i] + C; p++)
sum += *p;
同样,第二个栗子的代码也可以简化成这样:
...
int a[R][C];
int *p, sum = 0;
...
for (p = a[0]; P < a[R]; p++)
sum += *p;
因为a[0]指向数组第i行的第一个元素,同样也是数组的第一个元素。
- 第四个栗子:二维数组的第i列求和
int a[R][C], (*p)[C], i, sum = 0;
...
for (p = &a[0]; p < &a[R]; p++)
sum += (*p)[i];
上面的代码有些晦涩。在声明中,我们把p
指向长度为C
的整型数组的指针,在(*p)[C]
中,*p
是需要使用括号的;如果没有括号,编译器将认为p
是指针数组,而不是指向数组的指针。表达式p++
把p
移动到下一行的开始位置。在表达式(*p)[i]
中*p
表示a
的一整行,因此(*p)[i]
选中了该行第i列的那个元素。如果你还是不懂的话,你可以联想一下一维数组的声明方法int a[C]
,再看看这里p
的声明(*p)[C]
,是不是可以对应上呢?(*p)
可以对应数组a的名字a
,而p
对应&a
。(*p)[i]
中的括号是必要的,因为编译器会将*p[i]
解释为*(p[i])
。
其实,上面的代码还可以进一步简化:
int a[R][C], (*p)[C], i, sum = 0;
...
for (p = a; p < a+R; p++)
sum += (*p)[i];
理解上面这个代码,必须首先要理解a
(记住此时数组为二维数组)的含义。a不是指向a[0][0]
的指针,而是指向a[0]
的指针。因此&a[0]
就可以简化为a
。
参考资料
- C语言程序设计现代方法, K.N.King, 人民邮电出版社