使用C语言就必然会使用到指针和数组。看下面的代码:
int main(int argc, char** argv){ int a[4] = {1,3,5,7}; int *p = a; int i; for (i=0; i<4;i++){ printf("i=%d, p[i]=%d, *(p+i)=%d, a[i]=%d, *(a+i)=%d\n", i, p[i], *(p+i), a[i], *(a+i)); } return 0; }
似乎二者的用法完全相同,但其实指针和数组没有任何关系。这里先总结下指针和数组的区别,后面再详细区分下两者作为参数的情形及其用途。
指针和数组的区别
指针 | 数组 |
---|---|
保存数据的地址,任何存入指针变量 p 的数据都会被当作地址来处理。p 本身的地址由编译器另外存储,存储在哪里,我们并不知道。 | 保存数据,数组名 a 代表的是数组首元素的首地址而不是数组的首地址。&a 才是整个数组的首地址。a 本身的地址由编译器另外存储,存储在哪里,我们并不知道。 |
间接访问数据,首先取得指针变量 p 的内容,把它作为地址,然后从这个地址提取数据或向这个地址写入数据。指针可以作为左值,也可以作为右值。 | 直接访问数据,数组名 a 是整个数组的名字, 数组内每个元素并没有名字。只能通过“具 名 +匿名”的方式来访问其某个元素,不能把 数组当一个整体来进行读写操作。即数组名不能作为左值。 |
访问的实质是先取 p 的内容然后加上i* sizeof(类型)个 byte 作为数据的真正地址。 | 访问的实质都是 a 所代表的数组首元素的首地址加上 i*sizeof(类型 )个 byte 作为数据的真正地址。 |
通常用于动态数据结构 | 通常用于存储固定数目且数据类型相同的元素。 |
相关的函数为 malloc 和 free。 | 隐式分配和删除 |
通常指向匿名数据(当然也可指向具名数据) | 自身即为数组名 |
一维数组参数
先看下面的示例代码:
void foo(int a[10]){ printf("%d",a[0]); } int main(int argc, char** argv){ int a[2] = {1,2}; foo(a[10]); return 0; }
上面的代码至少有两个问题:1)a[10]是肯定不存在的,但编译器并不实际计算地址,因此不能发现这样的错误。2)函数参数实际上需要一个int *指针,但我们传递的是一个int类型的数据。这时候把a[10]里面的数据当成指针地址访问,肯定也会出问题。此外,函数的声明很容易让读者误认为只能传递长度为10的数组。
这里有一个规则:C语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。这么做主要是从不浪费大量时间去拷贝数组的角度考虑的。基于这一点,实际传递的数组大小与函数形参指定的数组大小并没有关系。
一级指针参数
要时刻注意:函数中使用的参数变量是实参的一份拷贝。看下面的代码:
#include <stdio.h> #include <stdlib.h> #include <string.h> void allocateMemory(char *p, int size){ p = (char *)malloc(size * sizeof(char)); } int main(int argc, char** argv){ char *p = NULL; allocateMemory(p, 30); strcpy(p, "Hello"); //这里会出现错误 free(p); //并没有效果 return 0; }
上面的代码在运行到strcpy时p的值仍然为NULL,我们试图分配的空间被赋给allocate函数里的拷贝变量去了,发生了内存泄漏和空指针。而这个拷贝变量我们根本没法处理,它是编译器自动分配回收的。所以使用一级指针参数一定要注意这个问题。要继续使用一级指针并解决这个问题,可以给函数加上返回值:
char * allocateMemory(char *p, int size){ p = (char *)malloc(size * sizeof(char)); return p; }
多级指针参数
还有一种办法是使用多级指针:
void allocateMemory(char **p, int size){ *p = (char *)malloc(size * sizeof(char)); } int main(int argc, char** argv){ char *p = NULL; allocateMemory(&p, 30); strcpy(p, "Hello"); free(p); return 0; }
注意,这里使用了二级指针,在使用的时候参数要用&p, 而不是p。这样传递过去的是p的地址,被调函数内部拷贝一个地址并不影响p值的使用,也就达到了我们的目的。
多维数组参数
数组有一维的,也有二维以上的。由于内存是线性的,我们把高维数组理解为数组的数组。但有一条规则却不能如此递归:C语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。也就是说只有一维数组才是如此,当数组超过一维时,将第一维改写为指向数组首元素首地址的指针之后,后面的维再也不可改写。比如: a[3][4][5]作为参数时可以被改写为(*p)[4][5]。在声明多维数组为参数的时候,也只能省略第一维的长度。例如下面的声明:
void funa(int a[1][3]){} //正确 void funb(int a[][3]){} //正确 void func(int a[][]){} //错误
C语言中还有一个复杂的指针叫做函数指针,这将在下一篇笔记里记录。本文先到这里。