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

news2024/11/27 0:25:04

背景: 最近在复习数据结构和算法,顺带刷刷题,虽然很长时间不刷题了但还是原来熟悉的味道,每一次重学都是加深了上一次的理解。本次我们看一下如何将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<4;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维数组也是同理,所以无论如何变化,都是这三种的变形而已。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/643402.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

springboot项目使用proguard配置代码混淆

springboot项目使用proguard配置代码混淆 代码混淆是一些软件开发过程中必不可少的步骤。 常用的代码混淆技术有 proguard maven plugin , yguard maven plugin, procyon maven plugin, dex maven plugin . 这些代码混淆技术大同小异&#xff0c;都是对maven打包生成class时进…

补充知识点

这里写目录标题 进制转换Java内置的进制转换介绍具体代码 有符号数据表示法整数强制转换之数据溢出浮点数进制转换浮点数储存 进制转换 Java内置的进制转换 介绍 也就是常用API里Integer的静态方法 具体代码 注意 最后一个方法&#xff0c;返回的是基于第二个参数为基数的第…

大模型部署实战(三)——ChatGLM-6B

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

SSR渲染-初识Nuxt-01

SSR服务端渲染 SSR服务端渲染&#xff1a;在后端将html页面处理好&#xff0c;前端直接展示&#xff08;可以解决为后端给你传了一个html脚本&#xff0c;全段渲染&#xff09; 为什么要有SSR服务端渲染&#xff1f; 可以解决单页面首屏加载慢的问题&#xff0c;同时有利于用…

在线分享怎么多接口批量查询快递信息

做物流和电商行业的小伙伴应该都知道&#xff0c;大量快递集中发出后&#xff0c;我们要做的就是及时查询和跟踪快递单号&#xff0c;这样能够有效避免快递发错或快递丢失等情况出现&#xff0c;小编今天给大家安利一款全自动批量查询跟踪快递单号的辅助工具&#xff0c;它支持…

【C++/嵌入式笔试面试八股】二、21.分层模型 | HTTP

分层模型 01.画出OSI和TCP/IP协议栈的对应关系 对应关系记忆2113 02.什么是OSI七层模型?每层列举2个协议。❤️ OSI七层模型及其包含的协议如下 物理层: 传输单位为bit 功能:通过物理媒介透明的传输比特流,确定机械及电气规范 主要包括的协议为:IEE802.3 CLOCK RJ45 数据链…

[学习笔记] [机器学习] 10. 支持向量机 SVM(SVM 算法原理、SVM API介绍、SVM 损失函数、SVM 回归、手写数字识别)

视频链接数据集下载地址&#xff1a;无需下载 学习目标&#xff1a; 了解什么是 SVM 算法掌握 SVM 算法的原理知道 SVM 算法的损失函数知道 SVM 算法的核函数了解 SVM 算法在回归问题中的使用应用 SVM 算法实现手写数字识别器 1. SVM 算法简介 学习目标&#xff1a; 了解 …

路径规划算法:基于阿基米德优化优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于阿基米德优化优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于阿基米德优化优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用…

Spring Bean-生命周期

三连支持 一起鼓励 一起进步 Bean生命周期 文章目录 一、生命周期1.Bean中配置生命周期2.实现InitializingBean和DisposableBean接口3.PostConstruct & PreDestroy4.BeanPostProcessor接口 二、执行过程三、源码中使用的BeanPostProcessor1.以ApplicationContextAwareProce…

【Flutter】Flutter 如何实现主题 Theme 切换

文章目录 一、引言二、Flutter 中的主题&#xff08;Theme&#xff09;和主题数据&#xff08;ThemeData&#xff09;三、如何在 Flutter 中创建自定义主题四、在 Flutter 中实现主题切换五、完整的代码示例六、总结 一、引言 大家好&#xff0c;欢迎阅读这篇文章。今天我们要…

Android——发送和接收广播

实验名称&#xff1a; 发送和接收广播 实验目的&#xff1a; &#xff08;1&#xff09;能创建广播接收者&#xff0c;实现广播的注册 &#xff08;2&#xff09;能自定义广播&#xff0c;发送和接收广播 实验内容及原理&a…

uni-app 使用axios发请求 运行到微信开发者工具报错 Adapter “http‘ is not available in the build

场景 最近在使用uni-app开发H5移动端&#xff0c;跟往常一样使用axios发请求&#xff0c;做一些全局的请求拦截响应拦截操作 uni-app数据存储&#xff0c;uni-ui组件开发&#xff0c;配置axios&#xff0c;vuex。配置了vue.config.js文件做跨域操作 运行到谷歌浏览器一切正常…

[n00bzCTF 2023] CPR 最后还是差一个

Crypto AES 给了java的加密原码&#xff0c;AES加密&#xff0c;有key import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import java.n…

【论文导读】- Variational Graph Recurrent Neural Networks(VGRNN)

文章目录 文章信息摘要BackgroundGraph convolutional recurrent networks (GCRN)Semi-implicit variational inference (SIVI) Variational graph recurrent neural network (VGRNN)VGRNN modelSemi-implicit VGRNN (SI-VGRNN) 文章信息 Variational Graph Recurrent Neural …

1.OpenCV 运行环境配置(Python)

一、安装Python 1.在Python官网下载Python。Download Python | Python.org 下载有点慢&#xff0c;需耐心等一等。&#xff08;用迅雷下载挺快&#xff09; 2.下载完后&#xff0c;一步一步的安装即可。我本地安装在 D:\Python\&#xff0c;路径可以自定义。安装时勾选了添加…

​selenium+python做web端自动化测试框架与实例详解教程

最近受到万点暴击&#xff0c;由于公司业务出现问题&#xff0c;工作任务没那么繁重&#xff0c;有时间摸索seleniumpython自动化测试&#xff0c;结合网上查到的资料自己编写出适合web自动化测试的框架&#xff0c;由于本人也是刚刚开始学习python&#xff0c;这套自动化框架目…

Linux操作系统的启动流程

一、&#xff08;通常&#xff09;操作系统的启动流程步骤 【关于BIOS的介绍&#xff0c;如果是操作系统小白可以参考一下百度百科的解释&#xff1a;】 通常操作系统启动的流程一般包括以下步骤&#xff1a; BIOS自检&#xff1a;计算机开机后&#xff0c;会进入Power On Se…

CMOS组合逻辑(二)

在前面介绍了静态互补CMOS逻辑&#xff0c;这里主要说明有比逻辑和动态CMOS逻辑。 CMOS组合逻辑_vtc曲线_沧海一升的博客-CSDN博客介绍了静态互补CMOS逻辑https://blog.csdn.net/qq_21842097/article/details/107456036 一、有比逻辑 1、伪NMOS 因为互补CMOS优点是全轨输出&…

WinForm——软件加载读条界面卡死问题

WinForm——软件加载读条界面卡死问题 前言一、问题现象二、测试部分代码1.Loading窗体2.加载代码Program处 三、分析原因四、解决方案代码1.Loading窗体2.加载代码Program处 前言 在制作软件开启界面&#xff0c;读条加载时&#xff0c;在Program中new了个Loading窗体&#x…

02 表达客观事物的术语

文章目录 02 表达客观事物的术语类与对象&#xff08;1&#xff09;定义与表示&#xff08;2&#xff09;类名(类的标识)&#xff08;3&#xff09;属性(attribute)属性的作用范围&#xff1a;定义属性的格式为&#xff1a; (4)操作(operation)表达操作的完整语法格式 &#xf…