从头学C(45)指针与数组

0

指针和数组的关系密不可分,通过数组下标所能完成的任何操作都可以通过指针来实现,而且一般用指针编写的程序比用数组下标编写的程序执行速度快,虽然用指针实现的程序要更难理解一些……

第五章 指针与数组 >> 5.3 指针与数组

声明

int a[10];

定义了一个长度为 10 的数组 a,也就是定义了一个由 10 个对象组成的集合,并且这 10 个对象存在相邻的内存区域中,名字是 a[0]、a[1]、……、a[9],如下图:

C语言_数组存储单元

对应的 a[i]表示数组的第 i 个元素(数组的下标是从 0 开始算的)。

声明

int *pa;

说明 pa 是一个指向整型对象的指针,那么赋值语句

pa = &a[0];

是将指针 pa 指向数组 a 的第 0 个元素,示意图如下:

C语言_数组与指针

那么访问 *pa 便是访问数组元素 a[0] 的值,比如我们可以 x = *pa;将 a[0] 的值复制到变量 x 中。

如果 pa 指向数组的某个元素,那么根据指针运算的定义,我们得知:

  1. 指针 pa + 1 指向下一个元素,以此类推 pa + i 将指向 pa 所指向的数组元素之后的第 i 个元素;
  2. 指针 pa – 1 指向前一个元素,类似 pa – i 指向 pa 所指向的数组元素之前的第 i 个元素。

C语言_数组与指针2

因此,如果 pa 指向 a[0],那么 *(pa+1) 引用的是数组元素 a[1] 的值,同理 pa + i 是数组元素 a[i]的内存地址,*(pa + i) 是引用数组元素 a[i]的值。

事实上,无论数组 a 中元素的数据类型或数组长度是什么,上面的结论都成立,“指针加 1”就意味着 pa + 1 指向 pa 所指的对象的下一个对象。

由于数组名所代表的是该数组最开始的第0个元素的地址,所以:

pa = &a[0] 等价于 pa = a;

对数组元素 a[i]的引用也可以写成 *(a+i)或*(pa+i)这种形式。在访问数组元素 a[i]时,C语言实际上也是先将其转换为 *(a+i)的形式,再进行求值,所以我们也可以得出这样的结论:&a[i]和 a+i 的含义是相同的,pa[i]和 *(pa+i) 也是等价的。

简而言之,一个通过数组和下标实现的表达式可以等价地通过指针和偏移量实现。

但数组名和指针有一个不同的地方要注意是:指针是一个变量,因此语句 pa = a 和 pa++ 都是合法的,但数组名不是变量,类似 a = pa 以及 a ++ 是非法的语句。

当把数组名以参数形式传递给一个函数时,实际传递的是该数组的第 0 个元素的地址,在被调函数中,该参数是一个局部变量,因此数组名参数必须是一个指针。来看下面这个计算一个字符串长度的 strlen 函数:

/* strlen: retrun length of string s */
int strlen(char *s) 
{
        int n;

        for(n = 0; *s != '\0'; s++)
                n++;
        return n;
}

s 是一个指针,因此执行自增运算是合法的。s++ 操作并不会影响到主调函数传递过来的字符串,它仅仅是对改指针在 strlen 函数中的副本进行自增运算,所以类似:

strlen("hello, world"); /* string constant */
strlen(array);          /* char array[100] */
strlen(ptr);            /* char *ptr */

都可以正确被执行。

在函数定义中,形式参数

char s[] 等价于 char *s

我们更习惯于第二种方式(更直观的说明该参数是一个指针)。

除了把第一个元素的地址传递给函数,显然我们也可以把任意一个元素的地址传递给函数,比如

f(&a[2]) 等价于 f(a+2)

都是把 a[2] 的地址传递给函数 f,函数 f 的声明可以是:

f(int arr[]) { ... }

或者

f(int *arr) { ... }

对函数 f 来说,它并不关系所引用的是否只是一个更大数组的部分元素。

如果确信相应的元素存在,也可以通过下标访问数组第一个元素之前的元素。类似 arr[-1] 、 arr[-2] 这样的表达式在语法上是合法的。

但我们应该注意:引用数组边界以外的对象是非法的。

Leave A Reply