数组与指针
在大部分情况下,数组和指针是可以互换的。书中的第4章主要讲解了数组和指针的不同之处,而在第9章则着重讨论两者可以互换的场景。
不同之处
对于两者的不同之处,主要在于访问两者时有一些不同。
编译器会为每个变量分配一个地址,每个符号的地址在编译时可知(如数组的名字)。那么在访问数组中的数据时,可以通过符号表直接得出数组的起始地址,加上偏移量即可访问对应的数据。
而对于指针,必须先在运行时取得它当前存储的值,再对它解引用来获取(数组的)起始值,最后才能加上偏移量来访问数据。
其他区别
指针 | 数组 |
---|---|
保存数据的地址 | 保存数据 |
间接访问数据,首先取得指针的内容,把它作为地址,然后从这个地址提取数据。 如果指针有一个下标[i],就把指针的内容加上i作为地址,从中提取数据 |
直接访问数据,a[i]只是简单的a+i为地址取得数据 |
通常用于动态数据结构 | 通常用于存储固定数目且数据类型相同的元素 |
相关的函数为malloc(),free() | 隐式分配和删除 |
通常指向匿名数据 | 自身即为数据名 |
相同之处
在书中,主要从“声明”和“使用”两种情况下进行讨论,直接看图:
在C语言标准中,有若干条规则对两者间的联系进行了说明,本书作者对其进行了进一步的说明:
- “表达式中的数组名”就是指针
- C语言把数组下标作为指针的偏移量
- “作为函数参数的数组名”等同于指针
编程挑战
#include <stdio.h>
#define PR(fmt,val) printf(#val " = %" #fmt " ", (val))
#define NL putchar('
')
#define PRINT1(f,x1) PR(f,x1), NL
#define PRINT3(f,x1,x2,x3) PR(f,x1), PRINT2(f,x2,x3)
static void func1(char ca[]) { PRINT3(#x, &ca, &(ca[0]), &(ca[1])); }
static void func2(char* pa)
{
PRINT3(#x, &pa, &(pa[0]), &(pa[1]));
PRINT1(#x, ++pa);
}
char ga[] = "abcdefg";
int main()
{
PRINT3(#x, &ga, &(ga[0]), &(ga[1]));
func1(ga);
func2(ga);
return 0;
}
运行结果如下:
&ga = 0x37c3b010 &(ga[0]) = 0x37c3b010 &(ga[1]) = 0x37c3b011
&ca = 0x7f38c918 &(ca[0]) = 0x37c3b010 &(ca[1]) = 0x37c3b011
&pa = 0x7f38c918 &(pa[0]) = 0x37c3b010 &(pa[1]) = 0x37c3b011
++pa = 0x37c3b011
可以看出作为参数,数组的地址和数组的第一个元素的地址是不同的。