刚学c语言的时候,我总是混淆指针数组和二维数组。可能是因为访问、改写两个数组中存储的某个值都可以使用a[m][n]这个表达式,或者说用*(*(a+m)+n)这个表达式。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int i,j;
//a数组是二维数组
int a[10][5];
for (i=0;i<10;i++)
{
for (j=0;j<5;j++)
{
a[i][j]=10*i+j;
}
}
//b数组是指针数组
int **b=NULL;
b=(int**)malloc(10*sizeof(int*));
for (i=0;i<10;i++)
{
b[i]=(int*)malloc(5*sizeof(int));
//等价于*(b+i)=(int*)malloc(5*sizeof(int));
for (j=0;j<5;j++)
{
b[i][j]=10*i+j;
//等价于*(*(b+i)+j)=rand()%100;
}
}
for (i=0;i<10;i++)
{
for (j=0;j<5;j++)
{
printf("%3d%3d%3d%3d\n",a[i][j],b[i][j],*(*(a+i)+j),*(*(b+i)+j));
//分别按照两种不同方式输出
}
}
}
如上一段代码所示,二维数组a和指针数组b按照相同的方法写入和输出的结果是一样的!
也许就是因为这个,初学c语言的我才感到迷惘。但后来我才发现,相同的访问方式,可能是这两种数组为数不多的共同点了。
一、对qsort函数和cmp函数的说明
void qsort( void *base, size_t num, size_t wid, int (*cmp)(const void *e1, const void *e2) );
第一个参数base的类型是void*,令它指向待排数组的第一个元素即可。如果待排数组是int类型数组a,那么这里输入a即可。
第二个参数num的类型是size_t,输入待排的元素个数即可
第三个参数wid的类型是size_t,记录待排元素的字节数(byte)。
第四个参数cmp的类型是int(*)(const void*,const void*),也即一个指向函数的指针,在使用函数时直接输入比较函数名即可。
cmp函数的原型为int function_name(const void* a,const void* b);其中const void*类型的形参a、b分别是指向被比较元素的指针。它的类型是未定义的,你还需要对它进行强制类型转换。
二、二维数组进行qsort是怎么样的?
下面请看这一个程序,随机生成十组数,每组数都包含5个0到100的随机数,然后按照每组数的第二个元素的大小将这十组数升序排序,组合输出。
我们先用二维数组去实现这个程序,注意看关键表达式1、2、3。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int cmp_a(const void* a,const void* b);
int main(void)
{
int i,j;
srand(time(0));
//a数组是二维数组
int a[10][5];
for (i=0;i<10;i++)
{
for (j=0;j<5;j++)
{
a[i][j]=rand()%100;
}
}
//关键表达式1
qsort(a,10,sizeof(int[5]),cmp_a);
//打印排序结果
for (i=0;i<10;i++)
{
for (j=0;j<5;j++)
{
printf("%4d ",a[i][j]);
}
putchar('\n');
}
}
int cmp_a(const void* a,const void* b)
{
//关键表达式2,注意是int*
int* p1=(int*)a;
int* p2=(int*)b;
//关键表达式3,注意是p1[1]
if (p1[1]>p2[1])
{
return 1;
}
else if(p1[1]==p2[1])
{
return 0;
}
else
{
return -1;
}
}
我们知道qsort的第三个参数是被排序元素的长度,所以对于关键表达式1,我们输入的参数是sizeof(int[5])。当然,写sizeof(a[0])也是可以的,不过这就忽略了被排序元素的类型了。你可以尝试,如果使用sizeof(int*),程序的功能就无法实现了!
我们知道cmp函数的参数a,b应当是指向被qsort比较的单个元素的指针,但这个指针是未被定义的void*类型指针,需要我们将其强制转化为期望的指针类型。所以对于关键表达式2,我们将指针定义为int*类型。
对于关键表达式3,我们写的是p1[1],而不是*p1[1],也不是(*p1)[1]。这是需要特别关照的!
三、指针数组进行qsort是怎么样的?
我们用指针数组去实现同样的程序,注意看关键表达式1、2、3。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int cmp_b(const void* a,const void* b);
int main(void)
{
int i,j;
srand(time(0));
//b数组是指针数组
int **b=NULL;
b=(int**)malloc(10*sizeof(int*));
for (i=0;i<10;i++)
{
*(b+i)=(int*)malloc(5*sizeof(int));
//等价于b[i]=(int*)malloc(5*sizeof(int));
for (j=0;j<5;j++)
{
*(*(b+i)+j)=rand()%100;
//等价于b[i][j]= rand()%100;
}
}
//关键表达式1
qsort(b,10,sizeof(int*),cmp_b);
for (i=0;i<10;i++)
{
for (j=0;j<5;j++)
{
printf("%4d ",b[i][j]);
}
putchar('\n');
}
}
int cmp_b(const void* a,const void* b)
{
//关键表达式2
int** p1=(int**)a;
int** p2=(int**)b;
//关键表达式3
if ((*p1)[1]>(*p2)[1])
{
return 1;
}
else if((*p1)[1]==(*p2)[1])
{
return 0;
}
else
{
return -1;
}
}
关键表达式1,我们给qsort传入的第三个参数是sizeof(int*)。当然,又可以简单地写sizeof(b[0]),但这也同样忽略了对被排序元素类型的关照。
对于关键表达式2,我们将指针定义为int**。
对于关键表达式3,我们写的是(*p1)[1]。
四、解释关键表达式1
二维数组a的类型是int[10][5],可以理解把它为一个高为10,宽为5的数组类型。这就是一种类型,与int,float等类型一样,只不过它是一种聚合的类型。你可以用sizeof算出它的长度!如下代码段所示
(同样的,含有10个int变量的一维数组c的类型是int[10],也可以直接用sizeof算长度)
#include <stdio.h>
int main(void)
{
printf("%d",sizeof(int[10][5]));
//输出200!
}
那么,二维数组中的元素,a[0],a[1],a[2]······分别是什么类型呢?答案是一维数组类型!因为每一行有5个int型变量,所以类型是int[5]。我们给qsort函数传入第三个参数为sizeof(int[5]),使程序得到期望的运行结果。
指针数组b的类型是int*[10]。在程序中我是用两次malloc定义的,事实上标准的定义方法是用表达式int* b[10],然后分别为指针数组各个元素申请空间。但无论如何,指针数组里各个元素的类型均为int*,也就是指向int的指针。所以,我们给qsort函数传入的第三个参数为sizeof(int*)。
五、解释关键表达式2
cmp的参数a和b应该被强制转化为指向被比较元素的指针。前面提到,二维数组被比较的元素是一维数组,那么a和b就是指向一维数组的指针。那么按照标准,这个表达式应该如下写
int(*)[10] p1=(int(*)[10])a;
int(*)[10] p2=(int(*)[10])b;
很可惜,这个表达式是不可以通过编译的。c语言不允许直接定义一个指向一维数组的指针(至少古老的c语言编译器Dev c++ 5.11不允许这么做)。但是,我们知道,一个指向一维int型数组的指针可以被隐式转化为一个指向int的指针。而且既是上面的表达式通过编译,它也往往被转化为一个指向int类型的指针进行运算。所以,下面这样写可以使程序正常运行。
int* p1=(int*)a;
int* p2=(int*)b;
而指针数组被比较的元素是一个指向一维int型数组的指针,也就是一个可以被转化为int*类型的指针。a和b指向被比较元素,所以它们的类型是int**!因此,我们如下输入表达式
int** p1=(int**)a;
int** p2=(int**)b;
六、解释关键表达式3
第一个程序中,p1[1],等价于*(p1+1)。p1原本指向一维数组的第一个元素,加一后指向一维数组的第二个元素,解引用后的值就是一维数组第二个元素的值。
第二个程序中,p1,是一个指向一维数组的指针,这个一维数组是由malloc函数为一个int*类型指针申请空间而得到 的,所以指向这个一维数组的指针也就是指向int*的指针。因此,这个指针需要首先解引用为(*p1),变为一个int*类型指针,再进行加一和第二次解引用,就可以访问一维数组的第二个元素了。那么,表达式写为*((*p1)+1),也就是(*p1)[1]。
需要注意的是,由于[]的优先级高于*,所以不能写成*p1[1],必须加括号写为(*p1)[1]。
七、指针和数组
对于一个一维数组a,它的类型就是一个数组,可能是有2个、3个、4个······int类型数据的数组,但绝对不是一个指针。这一点你可以通过如下程序验证
#include <stdio.h>
int main (void)
{
int a[10];
printf("%d",sizeof(a));
//如果a是指针,那对于64位系统,结果应当是8,但结果不是8,所以a不是指针
}
但之所以a常常被当作一个int*类型的指针,是因为它再很多很多操作中,都被隐式转化为一个int*类型的指针。比如,a[1]=3这个表达式就将a先转化为int*指针,等价于*(a+1)。
在函数传参过程中,数组类型a也被转化为相应的指针后传入。所以,下面的程序才有期望的输出结果。
#include <stdio.h>
void function(int a[])
{
printf("%d\n",sizeof(a));
//输出8
a[0]=1;
}
int main (void)
{
int a[10]={0};
function(a);
printf("%d\n",a[0]);
//输出1
}
这就是为什么函数内部可以操作函数外部的变量,这就是为什么函数形参时int a[],而不是int a[10]。这就是为什么sizeof(a)的结果时8。都是因为a被转化为了指针。
但无论如何,a的类型是个数组,而不是一个指针!它只是经常被转化为指针罢了!