|
指针与数组等价? 在C语言中对数组和指针的困惑多数都来自这句话。说数组和指针“等价”不表示它们相同, 甚至也不能互换。它的意思是说数组和指针的算法定义可以用指针方便的访问数组或者模拟数组。
特别地, 等价的基础来自这个关键定义:
一个T的数组类型的左值如果出现在表达式中会蜕变为一个指向数组第一个成员的指针(除了三种例外情况); 结果指针的类型是T的指针。
这就是说, 一旦数组出现在表达式中, 编译器会隐式地生成一个指向数组第一个成员地指针, 就像程序员写出了&a[0]一样。例外的情况是, 数组为sizeof或&操作符的操作数, 或者为字符数组的字符串初始值。
作为这个这个定义的后果, 编译器并那么不严格区分数组下标操作符和指针。在形如a的表达式中, 根据上边的规则, 数组蜕化为指针然后按照指针变量的方式如p那样寻址, 如问题6.2所述, 尽管最终的内存访问并不一样。如果你把数组地址赋给指针:
p = a;
那么p[3]和a[3]将会访问同样的成员。
指针与数组的区别
数组自动分配空间, 但是不能重分配或改变大小。指针必须明确赋值以指向分配的空间(可能使用malloc),但是可以随意重新赋值(即, 指向不同的对象),同时除了表示一个内存块的基址之外, 还有许多其它的用途。
由于数组和指针所谓的等价性,数组和指针经常看起来可以互换, 而事实上指向malloc分配的内存块的指针通常被看作一个真正的数组(也可以用[ ] 引用)。但是, 要小心sizeof。
既然数组引用会蜕化为指针, 如果arr是数组, 那么arr和&arr又有什么区别呢?
区别在于类型。
在标准C中, &arr 生成一个“T型数组”的指针, 指向整个数组。在ANSI之前的C中, &arr中的&通常会引起一个警告, 它通常被忽略。在所有的C编译器中, 对数组的简单引用(不包括&操作符)生成一个T的指针类型的指针, 指向数组的第一成员。
为什么作为函数形参的数组和指针申明可以互换?
这是一种便利。
由于数组会马上蜕变为指针, 数组事实上从来没有传入过函数。允许指针参数声明为数组只不过是为让它看起来好像传入了数组, 因为该参数可能在函数内当作数组使用。特别地, 任何声明“看起来象”数组的参数, 例如
void f(char a[])
{ ... }
在编译器里都被当作指针来处理, 因为在传入数组的时候,那正是函数接收到的.
void f(char *a)
{ ... }
这种转换仅限于函数形参的声明, 别的地方并不适用。如果这种转换令你困惑, 请避免它; 很多程序员得出结论, 让形参声明“看上去象”调用或函数内的用法所带来的困惑远远大于它所提供的方便。
当向一个接受指针的指针的函数传入二维数组的时候, 编译器报错了
数组蜕化为指针的规则不能递归应用。数组的数组(即C语言中的二维数组)蜕化为数组的指针, 而不是指针的指针。数组指针常常令人困惑, 需要小心对待;
如果向函数传递二位数组:
int array[NROWS][NCOLUMNS];
f(array);
那么函数的声明必须匹配:
void f(int a[][NCOLUMNS])
{ ... }
或者
void f(int (*ap)[NCOLUMNS]) /* ap 是个数组指针*/
{ ... }
在第一个声明中, 编译器进行了通常的从“数组的数组”到“数组的指针”的隐式转换(参见问题6.3和6.4); 第二种形式中的指针定义显而易见。因为被调函数并不为数组分配地址, 所以它并不需要知道总的大小, 所以行数NROWS可以省略。但数组的宽度依然重要, 所以列维度NCOLUMNS (对于三维或多维数组, 相关的维度)必须保留。
如果一个函数已经定义为接受指针的指针, 那么几乎可以肯定直接向它传入二维数组毫无意义。
|
|