指针数组和二维数组在传参时的差异

本文详细介绍了C语言中二维数组和指针数组的概念,以及它们在qsort函数排序中的使用。通过示例代码展示了如何对二维数组和指针数组进行初始化、赋值、输出,并解释了在调用qsort函数进行排序时的关键参数设置和cmp函数的编写。文章强调了两者在类型、内存分配和访问方式上的差异,并提供了对指针和数组本质的理解。
摘要由CSDN通过智能技术生成

刚学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的类型是个数组,而不是一个指针!它只是经常被转化为指针罢了!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值