我记不住的那些C语言的二维数组的函数传参

背景: 最近在复习数据结构和算法,顺带刷刷题,虽然很长时间不刷题了但还是原来熟悉的味道,每一次重学都是加深了上一次的理解。本次我们看一下如何将C语言的二维数组进行函数传参,C语言实现。

其实这个比较简单,但复习了一下,有几个地方需要记录下来,以防忘记。

问题1:  假设你有一个二维数组,需要将这个二维数组传入某个函数进行计算,你如何传入,传入的方式方法有哪些?

肯定是需要传输指针,而不是 复制这个二维数组然后传值进去,这样对内存消耗较大。

关于二维数组的一些基本定义,可以参考之前的文章我记不住的那些编程语言的语法(数组)-1

第一种:  将二维数组的指针 转换为一维数组的指针,然后再将元素总数量传值即可

void average(float* p, int n){
    float* end;
    float sum=0,aver;
    end = p+n-1;
    for(;p<=end;p++){
        sum+= (*p);
    }
    aver = sum/n;
    printf("average=%5.2f\n",aver);
}


float score[3][4] = {65,67,70,60,80,87,90,81,90,99,100,98};
//  *score 等价于 *(score+0) 也就是 *(score+0) + 0
//  *score 就是 &score[0][0] 
average(*score,12);

// 也就是说,等价于
average(&score[0][0],12);

第二种: 将二维数组的指针 转换为一维数组的指针,然后再将矩阵的行数和列数传值即可

void average2(float* p, int m, int n){
    float sum=0,aver;
    for(int i =0;i<m;i++){
        for(int j=0;j<n;j++){
            sum+= *(p+i*n+j);
        }
    }
    aver = sum/(m*n);
    printf("average=%5.2f\n",aver);
}

float score[3][4] = {65,67,70,60,80,87,90,81,90,99,100,98};
//  *score 这个传值和 第一种是一个道理
average2(*score,3,4);

// 也就是说,等价于
average2(&score[0][0],3,4);

第三种: 将二维数组的指针作为 行指针进行传参,然后获取某行的数据

//  这里的 float (*p)[4] 等价于  float p[][4]
//  代表的是一个二维数组,但只知道是4列的二维数组,此时行数是一个变值
//  这种方式其实 就是二维数组 传给 二维数组,属于同一个类型的传参

void search(float (*p)[4],int k){
    printf("the score of No. %d are: \n",k);
    for(int i=0;i<4;i++){
        printf("%5.2f ",*(*(p+k)+i));
        printf("%5.2f ",p[k][i]);
    }
}

void search2(float p[][4],int k){
    printf("the score of No. %d are: \n",k);
    for(int i=0;i<4;i++){
        printf("%5.2f ",*(*(p+k)+i));
        printf("%5.2f ",p[k][i]);
    }
}

float score[3][4] = {65,67,70,60,80,87,90,81,90,99,100,98};

// 这里传 score 意思是 score代表是一个 行指针数组
// score 与 *score 不同的是 offset,
// score  offset是一行数据
// *score offset是一个数据,  *score 等效于 *(score+0)+0 ,其实就是 score[0][0]
// 可参考文章 我记不住的那些编程语言的语法(数组)-1
​
search(score,2);
search2(score,2);

第四种: 将二维数组的指针作为 行指针进行传参,C99兼容, 然后获取某行的数据

// 4.
// C99 VLA (variable-length array) 变长数组
// 变长数组作为参数,变量n一定要在变量数组前面,这样运行时才能确定数组的大小

//  这里的 float (*p)[n] 等价于  float p[][n]
//  代表的是一个二维数组,但只知道是n列的二维数组,此时行数是一个变值
//  这种方式其实 就是二维数组 传给 二维数组,属于同一个类型的传参

void searchVLA(int n, float (*p)[n], int k){
    printf("the score of No. %d are: \n",k);
    for(int i=0;i<n;i++){
        printf("%5.2f ",*(*(p+k)+i));
        printf("%5.2f ",p[k][i]);
    }
}

void searchVLA2(int n, float p[][n], int k){
    printf("the score of No. %d are: \n",k);
    for(int i=0;i<n;i++){
        printf("%5.2f ",*(*(p+k)+i));
        printf("%5.2f ",p[k][i]);
    }
} 

float score[3][4] = {65,67,70,60,80,87,90,81,90,99,100,98};
// 这个和上一种类似,只不过是用了 变长数组,将列数通过参数进行传递    
searchVLA(4,score,2);
searchVLA2(4,score,2);

第五种: 将二维数组的指针转为 双重指针(指针的指针),

这种一般是动态分配,

缺点是: 行与行之间是割裂开的,也就是说地址是不连续的,而各行内部的元素的地址是连续的,所以和原始的二维数组是有区别的。

优点是: 相比于原始二维数组定义,这种动态分配可以充分利用内存空间,而不用找较大的一块连续空间盛放数据,利用率较高

double** arr 代表的不是一个二维数组,而是指针的指针,如果出现这种情况的函数形参的传值,那说明 调用的函数例如main函数里面一定是 使用malloc等动态分配数组的方式进行的,而不是原始的二维数组的定义,例如 int a[3][4]

// 5. 动态分配,dynamically allocated,这种方式使用较多
// 这种方式是因为动态分配,所以各行的元素并不是连续的
// 所以和二维数组是有区别的。
void assign(float** arr,int m,int n,float* p,int size){
    for(int i =0;i<m;i++){
        for(int j=0;j<n;j++){
            //arr[i][j] = *(p++);
            *(*(arr+i)+j) = *(p++);
        }
    }
}
void printarray( float **array, int m,int n ){
    int i;
    int j;

    for( j = 0; j < m; j++ ){
        for( i = 0; i < n; i ++){
            printf( "%.2f ", array[j][i] );
        }
        printf( "\n" );
    }
}

float score[3][4] = {65,67,70,60,80,87,90,81,90,99,100,98};
float** arr = (float**)malloc(3* sizeof(float*));
for(int i = 0;i<3;i++){
   arr[i] = (float*)malloc(4*sizeof(float));
}
assign(arr,3,4,*score,12);

全部代码可参考上传代码包。

和矩阵相关的几道题

LeetCode 74题:给定一个有序的二维数组,查找某个元素是否存在于这个二维数组中。

分析:这道题很简单,其实我认为就是二维数组,将其转换为一维数组,且是有序的,那么我直接二分查找即可, 使用的是C语言写一下,这种情况需要将 mid转换为 i 和 j 坐标。

bool searchMatrix(int** matrix, int matrixSize, int* matrixColSize, int target){
    int rows = matrixSize;
    int columns = *matrixColSize;
    int size = rows * columns;
    int start = 0;
    int end = size -1;
    while(start<=end ){
      int mid = start+ (end-start)/2;
      int i=mid/columns;
      int j=mid%columns;
      //   *(*(matrix+i)+j) 也是 OK的
      if(matrix[i][j] == target){
        return true;
      }else if(matrix[i][j] < target){
        start = mid+1;
      }else {
        end = mid-1;
      }
    }
    return false;
}

这种方法有点性能一般,是因为你计算了mid,还得计算数组的i和j,  为什么我们要计算i和j,我们直接将这个二维数组按照一维数组来做,直接使用mid做判断,是否可行? 能否通过AC呢?

这个问题我们留到最后讨论。

还有另外一个解法更高效,其实就是二叉搜索树 Binary Tree,把这个二维数组通过逆时针旋转,想象成为一个二叉搜索树,每走一步将选择 左子树或右子树,这样每一步缩小一行或一列的范围。

bool searchMatrix(int** matrix, int matrixSize, int* matrixColSize, int target){
    int rows = matrixSize;
    int columns = *matrixColSize;
    int row = 0;
    int column = columns - 1;
    while(row < rows && column>=0 ){
      //  *(*(matrix+row)+column) 这种表达也可以
      if(matrix[row][column] == target){
        return true;
      }else if(matrix[row][column] < target){
        row++;
      }else {
        column--;
      }
    }
    return false;
}

这个74题的难点在于 给定的函数的形参,一开始我有点懵逼,如下面所示

bool searchMatrix(int** matrix, int matrixSize, int* matrixColSize, int target)

问题1: matrix可以理解为是数据,是一个双重指针,指针的指针,它和 二维数组什么关系?

这种双重指针 并不能代表真正的二维数组,而是对二维数组的一种模拟,区别在于真实的二维数组行与行之间的地址是连续的,而双重指针则不连续,但是这种方式提高了内存的使用率,这种方式也能使用  matrix[i][j]  和  *(*(matrix+i)+j) 进行访问。

问题2:matrixSize是 行数,  matrixColSize是列数

为什么行数和列数传参又有int整型,又有int指针,这是为什么?  直接传 两个int整型不就行了,为什么还要传指针,

说实话,这个我没有找到答案,不知道为啥传指针又传int整型

问题3: 上面我们提到的,可否使用  mid来做判断,不用再将其转换i和j了

答:从形参 int** matrix 可知,这个函数的调用者Caller使用malloc进行分配的内存空间,而不是常规的定义的二维数组,所以 这个 二重指针的matrix的各行之间的地址不是连续的,也就是说有 matrixSize行的数据但每一行数据之间是不连续的,所以我们无法将其转换为一维数组再使用指针去进行二分查找操作。  二分查找操作应该满足 有序且连续 这两个条件。

总结:

1. 你有一个二维数组,如何传给其他函数呢?  

     答: 要看这个二维数组是怎么创建的,是常规的定义,还是malloc方式定义。

目前针对有三种方式:

     第一种方式是 函数形参也是二维数组,即 二维传给二维;

     例如: 上面的 第3和第4

void search(float (*p)[4],int k)
void search2(float p[][4],int k)
void searchVLA(int n, float (*p)[n], int k)
void searchVLA2(int n, float p[][n], int k)

     第二种方式是 函数形参是一维数组,即 二维 转一维;

     例如:上面的 第1和第2

void average(float* p, int n)
void average2(float* p, int m, int n)

     第三种方式是 函数形参是 双重指针

     例如: 上面的 第5

void assign(float** arr,int m,int n,float* p,int size)
void printarray( float **array, int m,int n )

    从二维数组推广到n维数组也是同理,所以无论如何变化,都是这三种的变形而已。

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Penguinbupt

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值