C语言经验分享:二维指针与二维数组的两种错误用法

点击蓝字

eda66e2355e22a8ef7cc91d5420c4703.png

关注我们

来源于网络,侵删

引子

首先看一段代码:

void test(int *p)
{}int main()
{int arr[]= {30, 450,14,5};test(arr);return 0;
}

毫无疑问,上面这段代码是运行OK的。因为C语言标准中有以下规则:在函数参数的声明中,数组名被编译器当作指向该数组第一个元素的指针。

第一种错误

那下面这段代码,会正确运行吗:

#include <stdlib.h>void test(int **p)
{}
int main()
{int arr[]={30,450,14,5};test(&arr);return 0;
}

可能有的同学认为这段代码是正确的,因为数组作为函数参数时退化成一个指针,那么我对数组进行取址,这样&arr就是一个二维指针了,所以可以作为函数test的入参。

这个理由貌似有道理,但很遗憾,这段代码会报编译错误:

main.c:3:17: note: expected 'int **' but argument is of type 'int (*)[4]'void test(int **p)~~~~~~^

这个错误是说,test函数期望的入参类型是int **。但是实际传入的参数类型是 int (*)[4],即实际传入的类型是指向数组的指针。

C语言标准中是定义了:在函数参数的声明中,数组名被编译器当作指向该数组第一个元素的指针。但是你不能因为数组在函数参数中当成一个指针,你对数组名取地址&arr就认为它的类型就是指向指针的指针(int **),这样以为是错的,因为不具备这样的传递性。C语言规范中只规定了数组名作为函数的入参时会被当做一个指针,但是并没有规定对数组取地址会被当成指向指针的指针。

根据上面代码的报错信息提示,将不能编译通过的代码片段作一个小的修改,就会编译通过了:

#include <stdlib.h>
#include <stdio.h>void test(int (*p)[4])
{(*p)[2] = 10;
}int main()
{int arr[] = {30, 450, 14, 5};test(&arr);printf("%d\n", arr[2]);return 0;
}

输出: 10

但其实上面的这种用法非常的怪。至少我在平时的工作中没遇到过这样的写法。这个函数的意图是改变数组的某个元素的内容。那下面的这种写法更加清晰和常见:

void test(int *p)
{p[2] = 10;
}int main()
{int arr[] = {30, 450, 14, 5};test(arr);printf("%d\n", arr[2]);return 0;
}

第二种错误

这种错误就是我之前在工作中犯的,从而导致出现异常的。

假如有一段代码:

#include <stdlib.h>
#include <stdio.h>
void foo(int **arr, int m, int n)
{int i,j;for (i = 0; i < m; i++) {for (j = 0; j < n; j++) {printf("%d ", arr[i][j]);}printf("\n");}
}int **alloc_2d(int m, int n)
{int **arr = malloc(m * sizeof(*arr));int i;for (i = 0; i < m; i++) {arr[i] = malloc(n * sizeof(**arr));}return arr;
}int main()
{int **joe = alloc_2d(2, 3);joe[0][0] = 1;joe[0][1] = 2;joe[0][2] = 3;joe[1][0] = 4;joe[1][1] = 5;joe[1][2] = 6;return 0;
}

上面的代码至此还是能正常工作的。

现在,我想用函数foo来打印一个二维的数组,那我能用下面的这段代码吗?

int moe[2][3];
moe[0][0] = 1;
moe[0][1] = 2;
moe[0][2] = 3;
moe[1][0] = 4;
moe[1][1] = 5;
moe[1][2] = 6;foo(moe, 2, 3);

很遗憾,这段代码同样会报编译错误:

ain.c:42:9: warning: passing argument 1 of 'foo' from incompatible pointer type [-Wincompatible-pointer-types]foo(moe, 2, 3);^~~
main.c:3:16: note: expected 'int **' but argument is of type 'int (*)[3]'void foo(int **arr, int m, int n)

将二维数组作为入参传入foo函数的原因可能是错误的认为二维数组就是二维指针。为什么会这么想呢?我以前的一个思考过程是这样的:一维数组作为函数参数的时候,编译器会把数组名当作指向该数组第一个元素的指针。所以我想当然的以为:既然一维数组和一维指针在函数参数中等价,那二维数组应该就等价于二维指针。

但是很遗憾,二维数组作为函数参数并等价于二维指针。因为数组作为函数参数时转换为指针没有传递性。也就是说你不能认为一维数组和一维指针作为函数参数等价,就认为二维数组和二维指针就等价了。在C语言中没有这样的传递性。

其实仔细想想,也是很容易明白的。二维数组其实就是一个数组的数组(即它是一个一维数组,只不过它的每个元素又都是一个一维数组)。当二维数组作为函数入参时,比如 int a[3][4]; 它的第一维数组会被转换成指针,相当于是传入了一个指向数组的指针。即作为函数参数, int a[3][4]和 int (*p)[4]等价。那 int (*p)[4]和 int **pp等价吗?肯定不等价, p指针指向类型是 int [4],而pp指向的类型是int *。

那有什么办法能让上面的foo函数编译通过呢?

最直接的方式是将二维数组作为函数的参数传入:

void foo(int arr[2][3], int m, int n)
{
}

事实上,上面的第一维的参数可以去掉:

void foo(int arr[][3], int m, int n)
{
}

上面也分析了,二维数组作为函数入参和 int (*p)[3]等价,所以也可以写成下面的形式:

void foo(int (*p)[3], int m, int n)
{
}

总结

上面说了2维数组作为函数参数的情况,那3维数组呢?

和二维一样:首先将类似三维数组arr[2][3][4]传入到 int ***类型的函数参数是错误的。但是可以将这个三维数组传入到参数类型为 int arr[][3][4]或者 int (*arr)[3][4]的函数中。

"数组名被改写成一个指针参数"规则并不是递归定义的(没有传递性)。数组的数组会被改写为"数组的指针",而不是"指针的指针"。

aadaf7f79a9c471b86e83cee5ce82dab.png

你之所以能在main()函数中看到char **argv这样的参数,是因为argv是个指针数组(即 char *argv[])。这个表达式被编译器改写为指向数组第一个元素的指针,也就是一个指向指针的指针。如果argv参数事实上被声明为一个数组的数组(也就是char argv[10][5]), 它将被编译器改写为 char (*argv)[15],也就是一个字符数组的指针,而不是char **argv。

0ea92629cf630c9640c173e99ab5223d.gif

如果你年满18周岁以上,又觉得学【C语言】太难?想尝试其他编程语言,那么我推荐你学Python,现有价值499元Python零基础课程限时免费领取,限10个名额!
▲扫描二维码-免费领取

c0e415ab55930c32797e9acc8ae6964c.gif

戳“阅读原文”我们一起进步

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

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

相关文章

camel 使用_使用Camel从WildFly 8向WebLogic 12发送JMS消息

camel 使用系统集成是一个很好的挑战。 特别是当您在寻找通信标准和可靠的解决方案时。 在当今的微服务世界中&#xff0c;每个人都在谈论REST服务和基于http的协议。 实际上&#xff0c;对于大多数通常具有更复杂的需求集的大多数企业项目来说&#xff0c;这是远远不够的。 合…

C++异常处理控制流下的OLLVM混淆

点击蓝字关注我们来源于网络&#xff0c;侵删Inflated!!!C异常化处理OLLVM-控制流平坦化Two PuzzlesException一般碰到C异常逆向&#xff0c;确定了异常分发、处理部分&#xff0c;直接把call throw改为jmp catch块&#xff0c;再F5即可。PS: 多个catch块根据rdx来当为异常处理…

【微服务】springboot整合kafka-stream使用详解

目录 一、前言 二、kafka stream概述 2.1 什么是kafka stream 2.2 为什么需要kafka stream 2.2.1 对接成本低 2.2.2 节省资源 2.2.3 使用简单 2.3 kafka stream特点 2.4 kafka stream中的一些概念 2.5 Kafka Stream应用场景 三、环境准备 3.1 搭建zk 3.1.1 自定义d…

C语言知识总结一:C语言的基本知识汇总

点击蓝字关注我们来源于网络&#xff0c;侵删C语言是一种计算机程序设计语言。它既有高级语言的特点&#xff0c;又具有汇编语言的特点。它可以作 为系统设计语言&#xff0c;编写工作系统应用程序&#xff0c;也可以作为应用程序设计语言&#xff0c;编写不依赖计算机 硬件的应…

jboss8日志级别设置_罐中研讨会:设置JBoss BPM Suite全日研讨会

jboss8日志级别设置是否在寻找一种简单的方法来宣传&#xff0c;展示或演示JBoss业务流程管理套件&#xff08;BPM Suite&#xff09;产品的入门难度&#xff1f; 别无所求&#xff0c;因为我们已经召集了这个研讨会&#xff0c;因此您可以围绕JBoss BPM Suite构建一个晚上&a…

yapi 接口文档_1分钟docker部署顶尖 API 文档管理系统

YApi 是高效、易用、功能强大的 api 管理平台&#xff0c;旨在为开发、产品、测试人员提供更优雅的接口管理服务。可以帮助开发者轻松创建、发布、维护 API&#xff0c;YApi 还为用户提供了优秀的交互体验&#xff0c;开发人员只需利用平台提供的接口数据写入工具以及简单的点击…

微软CTO建议业界弃用C/C++采用Rust,遭C++之父回怼

点击蓝字关注我们来源于网络&#xff0c;侵删近日&#xff0c;Microsoft Azure CTO、Sysinternals 的主要开发者 Mark Russinovich 在其社交账号上发布动态称&#xff0c;开发人员是时候停止使用 C/C 来启动新项目&#xff0c;并建议可在需要使用 non-GC 语言的场景中使用 Rust…

红帽 jboss_红帽峰会2015所需的JBoss BPM内容指南

红帽 jboss明年再见&#xff1f; 今年在Red Hat Summit上&#xff0c;我们在JBoss BRMS和JBoss BPM Suite演讲中获得了很多乐趣。 在DevNation周围也有一些社区会议&#xff0c;重点介绍了使我们的产品成为可能的项目。 您可以在他们的博客上找到此演讲的概述&#xff0c;并…

Python、C、Java 和 C++ 四足鼎立,其他已无胜算? | TIOBE 10 月编程语言排行榜

点击蓝字关注我们来源于网络&#xff0c;侵删技术的千变万化&#xff0c;都是有迹可循的&#xff0c;最新的 TIOBE 十月编程语言榜单重磅发布&#xff0c;快来看看有哪些值得关注的变化吧&#xff01;四大编程语言不断增强其主导地位曾几何时&#xff0c;编程语言界中 Java、C、…

C语言 #define 和 typedef 区别

点击蓝字关注我们来源于网络&#xff0c;侵删在C语言编程中&#xff0c;typedef 和 #define是最常用语句&#xff0c;可能很多工作过几年的工程师都没有去深究过它们的一些用法和区别。typedef的用法在C/C语言中&#xff0c;typedef常用来定义一个标识符及关键字的别名&#xf…

Spring Batch –用JavaConfig替换XML作业配置

最近&#xff0c;我协助一个客户启动并运行了Spring Batch实现。 该团队决定继续使用针对批处理作业的基于JavaConfig的配置&#xff0c;而不是传统的基于XML的配置。 随着这越来越成为配置Java应用程序的一种常用方法&#xff0c;我觉得是时候更新Keyhole的Spring Batch系列了…

sql limit 子句_Java 8流中的常见SQL子句及其等效项

sql limit 子句功能编程允许使用通用语言进行准声明性编程 。 通过使用功能强大的流畅API&#xff08;例如Java 8的Stream API &#xff09;或jOOλ的顺序Stream扩展Seq或更复杂的库&#xff08;例如javaslang或functionaljava&#xff09; &#xff0c;我们可以以一种非常简洁…

C++编程中的核心知识点!

点击蓝字关注我们来源于网络&#xff0c;侵删尊重函数接口&#xff0c;尽量不作内部改动C代码语句分为&#xff1a;内置类型&#xff0c;名字&#xff0c;变量&#xff0c;操作符&#xff0c;标量&#xff0c;字符串&#xff0c;预处理指示&#xff08;如#include&#xff09;等…

C++ 语言的单元测试与代码覆盖率

点击蓝字关注我们来源于网络&#xff0c;侵删前言测试是软件开发过程中一个必须的环节&#xff0c;测试确保软件的质量符合预期。对于工程师自己来说&#xff0c;单元测试也是提升自信心的一种方式。直接交付没有经过测试的代码是不太好的&#xff0c;因为这很可能会浪费整个团…

C++ 模板(Template)总结,长点小知识

点击蓝字关注我们因公众号更改推送规则&#xff0c;请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络&#xff0c;侵删前言&#xff1a;大家好&#xff0c;今天给大家分享一篇关于 C 模板总结概述.模板&#xff08;Template&#xff09;指 C 程序设计设计语言中采用…

归并排序 java_归并排序(Java实现)

package primary_01;/** 归并排序* 1.算法实现* 2.验证算法的正确性* 3.分析算法的复杂度*/public class Merge_sort {public static void main(String[] args) {int arr [] {2,1,6,5,9,8,2020,199};sort(arr, 0, arr.length-1);for (int i : arr) {System.out.print(i" &…

C++17 常用新特性:带初始化的 if 和 switch 语句

点击蓝字关注我们因公众号更改推送规则&#xff0c;请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络&#xff0c;侵删早在2016-03-14&#xff0c;Thomas Kppe 在https://wg21.link/p0305r0中就提出了在if和switch使用初始化语句&#xff0c;使用时的代码示例如下&…

四大语言加强统治地位,Rust威胁C/C++

点击蓝字关注我们因公众号更改推送规则&#xff0c;请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络&#xff0c;侵删TIOBE 公布了 2022 年 10 月的编程语言排行榜。截至今日&#xff0c;Python、Java、C 和 C 语言已经在 TIOBE 榜单的前 4 位盘踞了相当长的一段时…

sql计算留存_SQL无所不能:DBA宝妈宝爸系列分享

原文链接&#xff1a;https://www.modb.pro/db/22315 目前程序从功能上其实已经完全满足客户(当然我这里的客户都是指媳妇儿)需求&#xff0c;具体可参考&#xff1a;使用SQL计算宝宝每次吃奶的时间间隔 - Part1&#xff1a;分析函数的使用使用SQL计算宝宝每次吃奶的时间间隔 -…

对 int 变量赋值的操作是原子的吗?为什么?

点击蓝字关注我们因公众号更改推送规则&#xff0c;请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络&#xff0c;侵删这个是在面试的时候遇到的问题&#xff0c;当时没有答出来。回到家以后查了查&#xff0c;整理记录下来。原问题&#xff1a;什么指令集支持原子…