数组与指针的区别总是一个老生长谈的话题,总的来说是C语言中设计的时候一定程度上混淆了两者之间的关系,在语法上使两者能混用,但本质上是有区别的,举个简单的例子:
int a[10];
int *p = malloc(10*sizeof(int));
我们可以说a表示数组的首地址,p也表示类似数组的首地址。在使用的时候,你可以用p[1]
的方式,等同于*(p+1)
;数组方面,数组名可以当做指针用,你可以用*(a+1)
等同于a[1]
。这种混用形式让多数人不假思索的认为指针和数组是等价的,而实际上不过是C语言提供的语法糖而已。
数组的本质是线性表,C语言实现的时候用指针的方式来表示了,但是注意这儿有两个巨大的区别:
- 数组名a在程序中没有分配空间用于存储,它的位置是由编译器在编译阶段决定的,你用
printf("a:%p",a);
打印的地址和printf("&a:%p",&a)
是一样的。而p作为一个指针变量,在构建程序的时候,编译其要为其分配地址空间的。所以printf("p:%p",p);
打印的地址和printf("&p:%p",&p)
是不一样的。 - 存储空间不一样,数组根据全局和局部的区别,存储在.bss段或者函数栈(stack)上,而指针本身也根据全局和局部的区别分别存储在.bss段或函数栈(stack)上,但是指针指向的空间由系统从堆(heap)上分配。
举个二维矩阵的例子,你就很难像声明一个二维数组一样用指针获取一个二维矩阵,而不付出额外的代价:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int **alloc_matrix(int size)
{
int i;
int **pp = (int **)malloc(size * sizeof(int *));
if (!pp)
return NULL;
int *p = (int *)malloc(size*size*sizeof(int));
if (!p){
free(pp);
return NULL;
}
for(i = 0; i < size; i++)
pp[i] = (int *)(p+i*size);
return pp;
}
void init_matrix(int **matrix, int size)
{
int i,j;
for(i = 0; i < size; i++)
for(j = 0; j < size; j++)
matrix[i][j] = i+j;
}
int main(void)
{
int a[10][10];
int **pp = alloc_matrix(10);
init_matrix(pp,10);
int i,j;
for(i = 0; i < 10; i++)
for(j = 0; j < 10; j++)
a[i][j] = i+j;
for(i = 0; i < 10; i++)
printf("pp:%p, pp[%d]:%p, &pp[%d]:%p, *pp[%d]:%d, pp[%d][0]:%d, &pp[%d][0]:%p\n",pp, i,pp[i], i,&pp[i], i,*pp[i], i,pp[i][0], i,&pp[i][0]);
printf("\n");
for(i = 0; i < 10; i++)
printf("a:%p, a[%d]:%p, &a[%d]:%p, *a[%d]:%d, a[%d][0]:%d, &a[%d][0]:%p\n",a, i,a[i], i,&a[i], i,*a[i], i,a[i][0], i,&a[i][0]);
return 0;
}
编译执行的结果:
pp:0x17fdb010, pp[0]:0x17fdb070, &pp[0]:0x17fdb010, *pp[0]:0, pp[0][0]:0, &pp[0][0]:0x17fdb070
pp:0x17fdb010, pp[1]:0x17fdb098, &pp[1]:0x17fdb018, *pp[1]:1, pp[1][0]:1, &pp[1][0]:0x17fdb098
pp:0x17fdb010, pp[2]:0x17fdb0c0, &pp[2]:0x17fdb020, *pp[2]:2, pp[2][0]:2, &pp[2][0]:0x17fdb0c0
pp:0x17fdb010, pp[3]:0x17fdb0e8, &pp[3]:0x17fdb028, *pp[3]:3, pp[3][0]:3, &pp[3][0]:0x17fdb0e8
pp:0x17fdb010, pp[4]:0x17fdb110, &pp[4]:0x17fdb030, *pp[4]:4, pp[4][0]:4, &pp[4][0]:0x17fdb110
pp:0x17fdb010, pp[5]:0x17fdb138, &pp[5]:0x17fdb038, *pp[5]:5, pp[5][0]:5, &pp[5][0]:0x17fdb138
pp:0x17fdb010, pp[6]:0x17fdb160, &pp[6]:0x17fdb040, *pp[6]:6, pp[6][0]:6, &pp[6][0]:0x17fdb160
pp:0x17fdb010, pp[7]:0x17fdb188, &pp[7]:0x17fdb048, *pp[7]:7, pp[7][0]:7, &pp[7][0]:0x17fdb188
pp:0x17fdb010, pp[8]:0x17fdb1b0, &pp[8]:0x17fdb050, *pp[8]:8, pp[8][0]:8, &pp[8][0]:0x17fdb1b0
pp:0x17fdb010, pp[9]:0x17fdb1d8, &pp[9]:0x17fdb058, *pp[9]:9, pp[9][0]:9, &pp[9][0]:0x17fdb1d8
a:0x7fff16931fe0, a[0]:0x7fff16931fe0, &a[0]:0x7fff16931fe0, *a[0]:0, a[0][0]:0, &a[0][0]:0x7fff16931fe0
a:0x7fff16931fe0, a[1]:0x7fff16932008, &a[1]:0x7fff16932008, *a[1]:1, a[1][0]:1, &a[1][0]:0x7fff16932008
a:0x7fff16931fe0, a[2]:0x7fff16932030, &a[2]:0x7fff16932030, *a[2]:2, a[2][0]:2, &a[2][0]:0x7fff16932030
a:0x7fff16931fe0, a[3]:0x7fff16932058, &a[3]:0x7fff16932058, *a[3]:3, a[3][0]:3, &a[3][0]:0x7fff16932058
a:0x7fff16931fe0, a[4]:0x7fff16932080, &a[4]:0x7fff16932080, *a[4]:4, a[4][0]:4, &a[4][0]:0x7fff16932080
a:0x7fff16931fe0, a[5]:0x7fff169320a8, &a[5]:0x7fff169320a8, *a[5]:5, a[5][0]:5, &a[5][0]:0x7fff169320a8
a:0x7fff16931fe0, a[6]:0x7fff169320d0, &a[6]:0x7fff169320d0, *a[6]:6, a[6][0]:6, &a[6][0]:0x7fff169320d0
a:0x7fff16931fe0, a[7]:0x7fff169320f8, &a[7]:0x7fff169320f8, *a[7]:7, a[7][0]:7, &a[7][0]:0x7fff169320f8
a:0x7fff16931fe0, a[8]:0x7fff16932120, &a[8]:0x7fff16932120, *a[8]:8, a[8][0]:8, &a[8][0]:0x7fff16932120
a:0x7fff16931fe0, a[9]:0x7fff16932148, &a[9]:0x7fff16932148, *a[9]:9, a[9][0]:9, &a[9][0]:0x7fff16932148
可以看到,对于pp,我们可以像二维数组那样直接使用,但是为了实现这样的二维数组,我们要借助中间变量:一阶指针pp[i]
,这些变量都需要分配内存空间的。所以pp[i]
的值不等于&pp[i]
的值。而二维数组中a[i]
等于&a[i]
。
用指针的方式可以灵活一点,但是代价是中间数组指针的使用,另外注意的是如果要free的时候,要:
for(i = 0; i < 10; i++)
free(pp[i]);
free(pp);
通过这样的分析,希望大家能对数组和指针有本质的认识。