指针数组就是Iliffe向量
可以通过声明一个一维数组,其中每个指针指向一个字符串来取得类似二维字符数组的效果。
这种形式的声明如下:
char *pea[4];
这里我们进行了简化---指针实际上是声明为单个字符的,但是如果定义为指向字符的指针,就存在一种可能性,就是其他字符可能紧邻着它存储,隐式地形成了一个字符串。像下面这样的声明:
char (*rhubarb[4])[7];
才是真正声明了一个指向字符串的指针数组。在实际代码中从未曾使用过这种形式,因为它不必要限制了所指向的数组的长度(只能恰好为7)。
下标方括号的优先级比指针的星号高。
用来实现多维数组的指针数组有多种名字,如“Iliffe向量”“display”或“dope向量”。display在英国也用来表示一个指针向量,用于激活一个在词法上封闭的过程的活动记录(作为“一个静态链接后面跟一个链表”的替代方案)。这种形式的指针数组是一种强大的编程技巧,在C语言之外取得了广泛的应用。
这种数组必须用这样一种指针进行初始化,即指针指向字符串分配的内存。既可以在编译时用一个常量初始值,也可以在运行时用下面的代码进行初始化:
for (j = 0; j < 4; j++) {
pea[j] = malloc(6);
}
另一种方法是一次性地用malloc分配这个x * y个数据的数组:
malloc(row_size * column_size * sizeof(char));
然后使用一个循环,用指针指向这块内存的各个区域。整个数组保证能够存储在连续的内存中,即C分配静态数组的次序存储。它减少了malloc的维护性开销,但缺点是当处理完一个字符串时无法单独将其释放。
注意声明的语法
注意char *turnip[23]把turnip声明为一个具有23个元素的数组,每个元素的类型是一个指向字符的指针(或者一个字符串---单纯从声明中无法区别两者)。可以假想它两边加上了括号---(char *)turnip[23]。这跟从左至右读时看上去一样(一个指向“具有23个字符类型元素的数组”的指针)不一样。这是因为下标括号的优先级比指针的星号高。
当你看见squash[i][j]这样的形式时,你不知道它是怎样被声明的!
两个下标的二维数组和一维指针数组所存在的一个问题是:当你看到squash[i][j]这样的引用形式时,你并不知道squash是声明为:
int squash[23][12]; /*int类型的二维数组*/
或是
int *squash[23]; /*23个int类型指针的Iliffe向量*/
或是
int **squash; /*int类型的指针的指针*/
甚至是
int (*squash)[12]; /*类型为int数组(长度为12)的指针*/
这有点类似于在函数内部无法分辨传递给函数的实参究竟是一个数组或者一个指针。当然,处于同样的理由:
作为左值的数组名被编译器当做是指针。
在上面几种定义中,都可以使用squash[i][j]这样的形式,尽管在不同的情况中访问的实际类型并不相同。
与数组的数组一样,一个Iliffe向量中的单个字符也是使用两个下标来引用数组中的元素(如pea[i][j])。
指针下标引用的规则告诉我们pea[i][j]被编译器解释为:
*(*(pea + i) + j)
它和一个多维数组引用的分解形式完全一样,在许多C语言图书中就是这样解释的。然而,这里存在一个很大的问题。尽管这两种下标形式在源代码里看上去一样,而且被编译器解释为同一个指针表达式,但它们在各自的情况下所引用的实际类型并不相同。
一个数组的数组char a[4][6]
char a[4][6]---一个数组的数组
在编译器符号表中,a的地址为9980
运行时步骤1:取i的值,把它的长度调整为一行的长度(这里是6),然后加到9980上。
运行时步骤2:取j的值,把它的长度调整为一个元素的宽度(这里是1),然后加到前面所得 出的结果上。
运行时步骤3:从地址(9980 + i * scale - factor1 + j * scale - factor2)取出内容
char a[4][6]的定义表示a是一个包含4个元素的数组,每个元素是一个char类型的数组(长度为6)。所以查找到第4个数组的第i个元素(前进i*6字节),然后找到数组中的第j的元素。
字符串指针数组中的char *p[4]
char *p[4]---一个字符串指针数组
在编译器的符号表中,p的地址为4624
运行时步骤1:取i的值,乘以指针的宽度(4字节),并把结果加到4624上。
运行时步骤2:从地址(4624 + 4 * i)取出内容,为5081。
运行时步骤3:取j的值,乘以元素的宽度(这里是1字节),并把结果加到5081上。
运行时步骤4:从地址(5081 + j * 1)取出内容p[i][j]
char *p[4]的定义表示p是一个包含4个元素的数组,每个元素为一个指向char的指针。所以除非指针已经指向字符(或字符数组),否则查找过程无法完成。假定每个指针都给定了一个值,那么查询过程先找到数组的第i个元素(每个元素均为指针),取出指针的值,加上偏移量j,以此为地址,取出地址的内容。
这个过程之所以能行,是因为第9章的规则2:一个下标始终相当于指针的偏移量。因此,turnip[i]选择一个元素,也就是一个指针,然后使用下标[j]引用指针产生*(指针+j),它所指向的是一个字符。这仅仅是a[2]和p[2]的一种扩展,它们的结果都是一个字符,正如我们在前一章所见到的那样。