c语言中二维数组及函数参数传递

本文详细解析了二维数组的内存布局及其与指针的关系,并介绍了如何通过指针操作及动态分配内存来灵活使用二维数组。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

先来回顾一下一维数组。一维数组的数组名即为指向该数组的指针,该指针值保存了数组存放在内 存中的一块连续区域的起始地址;数组的下标表示了这片内存区域的某存储区相对于起始地址的偏移量。简单来讲就是:指向一维数组的指针,指向数据存放区域的 起始位置。

事实上,计算机系统的多维数组其实最终还是以一维数组的形式实现的。就N x M的二维数组来讲,设其数组名为array。指针array指向一个数组,该数组存放的是一系列指针,这些指针分别指向相应的一维数组,而这些数组中存放 的才是我们的数据。

array -> [一维数组指针1] ->   [ 一维数组,M长]

    [一维数组指针2] ->   [ 一维数组,M长]

        ……           ……

    [一维数组指针N] ->   [ 一维数组,M长]

由此array是第i个指针变量地址,array[j]则表示相对于第i个指针变量偏移 j*sizeof(数组类型)。系统通过这种机制访问了该二维数组的第i行,第j列的内容。

有上述可知,指向二维数组的指针其实是指向“指针变量地址”的指针变量。所以在声明指向二维 数组的指针时,用 int ** array的形式。

    有以下两种方式来对二维数组分配内存:

///// 方法一

    #include <stdlib.h>   // 必须包含该头文件,里面定义了malloc的实现

    int ** array = malloc( N * sizeof(int *) );

    for (int k=0;k<N;k++)

      array[k] = malloc( M * sizeof(int) );  

///// 方法二

    #include <stdlib.h>

    int ** array = malloc( N * sizeof(int *) );

    array[0] = malloc( M * sizeof(int) );

    for (int k=1;k<N;k++)

      array[k] = array[0]+M*k;

    上述两种方法的区别在于:前者在内存中分配的区域有可能是不连续的;而后者则在内存中的一片连续区域为该数组分配空间。

    我们还可以通过一维数组模拟二维数组。在这中间要进行下标转换。如对于模拟的NxM数组,访问其第i行,第j列元素时,在一维数组中对应的位置是 i*M+j。当然为了更简捷,我们可以把这个数组下标转换过程定义为一个宏,交由编译系统来处理。

    #define Arr2 ( array_name, row,col )   array_name[row*M+col]

    定义该宏后,访问Arr2( array, i, j)等价于访问 array[i*M+j]。


     可以用二维数组名作为实参或者形参,在被调用函数中对形参数组定义时可以可以指

定所有维数的大小,也可以省略第一维的大小说明,如:
    void Func(int array[3][10]);
    void Func(int array[][10]);
    二者都是合法而且等价,但是不能把第二维或者更高维的大小省略,如下面的定义是

不合法的:
    void Func(int array[][]);
    因为从实参传递来的是数组的起始地址,在内存中按数组排列规则存放(按行存放),

而并不区分行和列,如果在形参中不说明列数,则系统无法决定应为多少行多少列,不能

只指定一维而不指定第二维,下面写法是错误的:
    void Func(int array[3][]);实参数组维数可以大于形参数组,例如实参数组定义为


    void Func(int array[3][10]);
    而形参数组定义为:
    int array[5][10];
    这时形参数组只取实参数组的一部分,其余部分不起作用。


  大家可以看到,将二维数组当作参数的时候,必须指明所有维数大小或者省略第一维的

,但是不能省略第二维或者更高维的大小,这是由编译器原理限制的。大家在学编译原理

这么课程的时候知道编译器是这样处理数组的:

  对于数组 int p[m][n];
  如果要取p[i][j]的值(i>=0 && i<m && 0<=j && j < n),编译器是这样寻址的,它的

地址为:
  p + i*n + j;

  从以上可以看出,如果我们省略了第二维或者更高维的大小,编译器将不知道如何正确

的寻址。但是我们在编写程序的时候却需要用到各个维数都不固定的二维数组作为参数,

这就难办了,编译器不能识别阿,怎么办呢?不要着急,编译器虽然不能识别,但是我们

完全可以不把它当作一个二维数组,而是把它当作一个普通的指针,再另外加上两个参数

指明各个维数,然后我们为二维数组手工寻址,这样就达到了将二维数组作为函数的参数

传递的目的,根据这个思想,我们可以把维数固定的参数变为维数随即的参数,例如:

    void Func(int array[3][10]); 
    void Func(int array[][10]);
  变为:
    void Func(int **array, int m, int n);

  在转变后的函数中,array[i][j]这样的式子是不对的(不信,大家可以试一下),因为

编译器不能正确的为它寻址,所以我们需要模仿编译器的行为把array[i][j]这样的式子

手工转变为

    *((int*)array + n*i + j);

    在调用这样的函数的时候,需要注意一下,如下面的例子:
    int a[3][3] = 
    {
      {1, 1, 1},
      {2, 2, 2},
      {3, 3, 3}
    };
    Func(a, 3, 3);

  根据不同编译器不同的设置,可能出现warning 或者error,可以进行强制转换如下调用

:  
    Func((int**)a, 3, 3);

  其实多维数组和二维数组原理是一样的,大家可以自己扩充的多维数组,这里不再赘述





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值