二维数组传参问题

先给出问题:
像下面这样的数组,在函数中如何传参?也就是说如何保证虚参与实参类型一致。

char str_arr[3][10] = {"yes","no","uncertain"};
char *str_array[] = {"yes","no","unsure"};

函数原型:
               void func1( char (*a)[10] )
               void func2( char **a )

调用:
              func1( str_arr );
              func2( str_array);
如果向func2()中传入str_arr会怎么样呢?编译器会警告:传递参数 1 (属于 ‘func2’)时在不兼容的指针类型间转换。即虚参与实参类型不一致。

同理,也不能向func1()中传入str_array。

我们给出完整的测试程序:

/********二维数组传参测试程序***************/
#include <stdio.h>
void func1( char (*a)[10])
{
        int i;
        for(i=0;i<3;i++)
                printf("%s/n",a[i]);
}

void func2( char **a )
{
        int i;
        for(i=0;i<3;i++)
                printf("%s/n",*(a+i));
}

int main()
{
        char str_arr[3][10] = {"yes","no","uncertain"};
        char *str_array[] = {"yes","no","unsure"};
        char *str[3] = {"a","b","c"};/*这两种表达效果一样*/
        func1(str_arr);
        func2(str_array);   
        return 0;
}
/******************end*******************/
运行结果:
[root@localhost ansi_c]# gcc test.c
[root@localhost ansi_c]# ./a.out
yes
no
uncertain
yes
no
unsure
[root@localhost ansi_c]#
如果将
        func1(str_arr);
        func2(str_array);
改成:
        func1(str_array);
        func2(str_arr);
会怎么呢?
[root@localhost ansi_c]# gcc test.c
test.c: 在函数 ‘main’ 中:
test.c:22: 警告:传递参数 1 (属于 ‘func1’)时在不兼容的指针类型间转换
test.c:23: 警告:传递参数 1 (属于 ‘func2’)时在不兼容的指针类型间转换

这两种数组的正确赋值应该如下:
        char str_arr[3][10] = {"yes","no","uncertain"};
        char *str_array[] = {"yes","no","unsure"};
        char (*pa)[10] = str_arr;
        char **p = str_array;
pa和p才是和他们相一致的类型。
       



当然,如果不是传参的话,在main()函数中就不会发生这么多烦恼了。
/*************非传参时的情况************************/
#include <stdio.h>
int main()
{
        char str_arr[3][10] = {"yes","no","uncertain"};
        char *str_array[] = {"yes","no","unsure"};
        char *str[3] = {"a","b","c"};
        int i;
        for(i=0;i<3;i++)
              printf("%s/n",str_arr[i]);
        for(i=0;i<3;i++)
              printf("%s/n",str_array[i]);
         return 0;
}
/*************************************/
运行结果:
[root@localhost ansi_c]# gcc test1.c
[root@localhost ansi_c]# ./a.out
yes
no
uncertain
yes
no
unsure
[root@localhost ansi_c]#

这说明了一点,在没传参之前,main()函数清楚它们都是二维数组。对于上面给出的两种函数原型:
函数原型:
               void func1( char (*a)[10] )
               void func2( char **a )
这两种传参方法有什么不同呢?这们对实参有什么要求呢?

上面只是抛出了一个问题,我在这里的主题是想搞清楚二维数组传参有什么奥秘,而非只针对这一个问题提出解决方法。
后面从基础的开始讨论。

我们先看看教材上怎么讲这一块的,
谭浩强的《C程序设计》二维数组作为参数传递,原文如下(略有改变,请原谅):

[原文开始]

    可以用二维数组名作为实参或者形参,在被调用函数中对形参数组定义时可以可以指定所有维数的大小,也可以省略第一维的大小说明,如:
    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 array[3][4],要传参的话,函数原型可以为下面三种的任一种:
      void func(int a[3][4])
     void func(int a[][4])
     void func(int (*a)[4])
调用时为:func(array);

    同时教材里也说了,如果在型参里不说明列数,则编译器无法决定应为多少行多少列。那么能不能把
int array[3][4]的数组名 array 传给 void func(int **a)呢?
看下面:
/**********************************/
#include <stdio.h>
int main()
{
int array[3][4];
int **p = array;
}
**********************************/
root@localhost ansi_c]# gcc test2.c
test2.c: 在函数 ‘main’ 中:
test2.c:5: 警告:从不兼容的指针类型初始化
[root@localhost ansi_c]#

虽然从本质上讲int array[3][4] 的数组名相当于二级指针,但它不等同于一般的二级指针,因为它还含有数组相关的信息,所以在main函数中:
char str_arr[3][10] = {"yes","no","uncertain"};

for(i=0;i<3;i++)
              printf( "%s/n",str_arr+i );

它可以通过下标,每次跳过10个字节来寻址。我们再看看编译器是怎样处理数组的:
对于数组 int p[m][n];
       如果要取p[i][j]的值(i>=0 && i<m && 0<=j && j < n),
编译器是这样寻址的:
       p + i*n + j;

我们再看一个例子:
/*********************二维数组传参*****************************/
#include <stdio.h>
void fun( int *a, int m, int n)
{
        int i,j;
        for( i=0; i<m; ++i)
        {
                for(j=0;j<n;++j)
                {
                        printf("%d ", *( a+i*n+j ) );
                }
                putchar('/n');
        }
}

void func( int *a, int m, int n)
{
        int i,j;
        for( i=0;i<m*n;++i)
        {
                        printf("%d ",a[i]);
        }
        putchar('/n');
}

int main()
{
     int a[3][3] =
    {
      {1, 1, 1},
      {2, 2, 2},
      {3, 3, 3}
    };
        fun( (int *)a, 3,3);
        func( &a[0][0],3,3);
        func( (int *)a, 3,3);
        return 0;
}

********************end******************************/
[root@localhost ansi_c]# gcc test4.c
[root@localhost ansi_c]# ./a.out
1 1 1
2 2 2
3 3 3
1 1 1 2 2 2 3 3 3
1 1 1 2 2 2 3 3 3
[root@localhost ansi_c]#

我们来看其中的要点,
数组为:
int a[3][3] =
    {
      {1, 1, 1},
      {2, 2, 2},
      {3, 3, 3}
    };
函数原型和调用为:
原型:
void fun( int *a, int m, int n) 
{
.............
         printf("%d ", *( a+i*n+j ) );
.............
}   
调用:
    fun( (int *)a, 3,3);

另一个函数为:
原型:
void func( int *a, int m, int n)
{
.............
         printf("%d ",a[i]);
.............
}   
调用:
    func( &a[0][0],3,3);
    func( (int *)a, 3,3);
我们发现这两种方式都能正常执行,我们把一个二级指针,强制转换成了一级指针传了进去,并在函数中模仿编译器数组的寻址方式:*( a+i*n+j )。

我们再看看二维字符数组的例子:
/*******************二维字符数组*******************************/

#include <stdio.h>
void f( char **a, int n)
{
        int i;
        printf("%c/n",*( (char*)a+0 ) );
        printf("%c/n",((char * )a)[n] );
        puts("------------OK");

        for(i=0;i<3;i++)
                printf("%s/n",(char*)a+i*n );
        }
int main()
{
        char str_arr[3][10] = {"yes","no","uncertain"};

        f( (char **)str_arr, 10);
        return 0;
}
/****************end*************************/
运行结果:
[root@localhost ansi_c]# ./a.out
y
n
------------OK
yes
no
uncertain
[root@localhost ansi_c]#
这里也做了强制类型转换,转换成字符指针,
printf("%s/n",(char*)a+i*n ); 每个字符串的地址就是数组中字符'y'、'n'、'u'的地址,
printf("%c/n",*( (char*)a+0 ) );字符在数组中的排列是顺序的,可以用 *( (char*)a+i )或 ((char * )a)[i] 表示。
当然这个程序也可以改成这样,完全不用二级指针:
/*****************************************************************/
#include <stdio.h>
void f( char *a, int n)
{
        int i;
        printf("%c/n",*( a+0 ) );
        printf("%c/n",(a)[n] );
        puts("------------OK");

        for(i=0;i<3;i++)
                printf("%s/n",a+i*n );
        }
int main()
{
        char str_arr[3][10] = {"yes","no","uncertain"};

        f( (char *)str_arr, 10);
        return 0;
}
/*****************************************************************/
归根结底,还是把它转成一级指针来用。

下面做个小结:

数组传参
数组:
        int array[4][10];
函数原型:
         void func1( int a[][10] );
        void func2( int (*a)[10] );
        void func3( int *p, int col, int row );
函数调用:
        func1( array );
        func2( array );
         func3( (int *)array, 4, 10 );

容易出错的地方:
int arr[][10];
int **p = arr;
这种方式是错误的.
应该是
int (*p)[10] = arr;
同理,也不能将arr传给fun( int **p)

另外数组传参之后会降级为指针,如:
#include <stdio.h>
void Test(char a[][2])
{
     int size = sizeof( a );//4
}
int main(void)
{
      char a[3][2] = {'a','b','c','d','e','f'};
      int size =sizeof( a );//6
     Test( a );
}

函数原型:要求传入一个动态二维数组
void func1(int **p,int row, int column)
{
}
调用:
int main()
{
int m,n;
int **b;
cin >> m;
cin >> n;
b = new int *[m];
for(int i=0; i<m; i++)
{
  b[i] = new int[n];
};
func1(b,m,n);
return 0;
}

我习惯的做法是不用指针数组,定义一个大块(这种情况主要面向每行行数相同):比如现在有一个W*H的矩阵(H个长度为W的数组),你就直接定义一个float型指针:   float* pfBuffer;然后动态分配大小 pfBuffer = new float[W*H];这个buffer在用完之后要调用 delete pfBuffer;来释放.你传递这个float指针,传递行列数之后,你如果要访问y行x列的话,只要算一下它在哪儿, int addr = y*W+x;就是其"地址"了,你要访问它,直接使用pfBuffer[addr]就OK了,实际上我做图象处理的时候全部这样做,因为这样的地址访问很明了,不会给阅读带来不便,而且作为大部分的时候,我们用矩阵比较多,列数不等的情况很少。这只是个人见解。

猜你喜欢

转载自blog.csdn.net/weixin_41318405/article/details/80041660