指针和数组的区别 (从下标运算符[ ]看)

数组和指针的区别令人困惑,很多人常常把数组名和指针看作是相同的——毕竟它们都是个地址。而且好像都可以进行算数操作、利用下标运算符运算。
例如如下代码,当把a的地址拷贝给p后,可以像把p当作a的引用一样使用:

#include <stdio.h>
int main(){
    int *p;
    int a[5] = {0, 1, 2, 3, 4};
    p = a;
    for(int i = 0; i != 5; i++)
        printf("p[%d] is %d \n", i, p[i]);
    return 0;
}

得到的结果是:
pic1
这就给人一个感觉,好像数组和指针是一样的。但在《C专家编程》一书中,作者明确指出数组名与指针不同。最大的一点理由在于,[ ]运算符作用于指针和数组名时,行为是不一样的。具体地说:
[ ]作用于指针时(例如p[i]),有三步操作:
1. 解地址。读入指针所指向的地址。
2. 进行指针算数运算。得到地址 E = V + i*s,s表示步长,是由指针类型决定的。例如,若p是由int *p声明的,则t = sizeof(int) = 4
3. 读地址。 得到地址E中所存的值,此即为p[i]的值。

[ ]作用于数组名时(例如a[i]),有两步操作:
1. 进行指针算数运算。 得到地址 E = a + i*s
2. 读地址。 得到地址E中所存的值,此即为a[i]的值。

因此,[ ]作用于数组名时,实际上操作是少一步的。如果不注意到这一点,可能会带来匪夷所思的错误。
例如,在 p_def.c 文件中放入全局变量 p 的定义

int p[5] = {0, 1, 2, 3, 4};

在 main.c 文件中引用全局变量 p 。但是,若认为指针和数组名是一样的,声明一个指针变量来接外部的数组,则会带来错误。

#include <stdio.h>
extern int *p;
int main(){
    for(int i = 0; i != 5; i++)
        printf("p[%d] is %d \n", i, p[i]);
    return 0;
}

此时,使用 gcc -o test main.c p_def.c 编译并运行,会有运行时错误:
这里写图片描述
若把 main.c 代码的第三行改为
extern int p[];
并编译运行,可以得到正常结果。
这里写图片描述

不同表现的原因是,当p作为指针和作为数组名时,[ ]的行为不同。在报错的情况中,把一个数组外部变量声明成指针,在进行下标运算时,[ ]按指针的规矩进行运算,先读入p处存放的地址,这时这个地址其实时数组里的值 p[0] = 0, 再进行第二步,对这个值解地址,自然会引发错误。 改正程序后,用数组来声明,[ ]的操作是符合逻辑的。

以上内容是《C专家编程》作者的主张,我在此转述。如果没讲明白请大家参考原书。但是,为什么本文第一个例子中,程序运行结果是合理的呢? 并且, 第6行代码的 p = a,不也隐含地在说,数组和指针是一个类型(或至少可以隐式转换)吗?
我的看法是,其实数组本质上仍是指针,更具体地说,是一个其存放地址等于自身地址的顶层const指针,即对于数组名a,a = &a。可以用代码验证:

#include <stdio.h>
int main(){
    int a[5];
    printf("%d %d \n", a, &a);
    return 0;
}

结果为:
这里写图片描述
可见,确实 a = &a。
而[ ]操作符对指针和数组名的操作是相同的,都是按照对指针操作的三步进行的。不过,由于数组名自身地址等于其内部存放的地址,故第一步操作其实没有带来改变。这就解释了[ ]操作符对数组和指针的行为。
本文只是个人的一些理解,欢迎各位指正。

猜你喜欢

转载自blog.csdn.net/u011508640/article/details/70180830