【C语言】—— 指针三 : 参透数组传参的本质

【C语言】—— 指针三 : 参透数组传参的本质

    • 一、数组名的理解
    • 二、使用指针访问数组
      • 2.1、指针访问数组
      • 2.2、[ ] 的深入理解
      • 2.3、数组与指针的区别
    • 三、一维数组的传参本质
    • 四、数组指针变量
      • 4.1、数组指针变量是什么
      • 4.2、 数组指针的初始化
    • 五、二维数组传参的本质

一、数组名的理解

  
  如果我们想用指针来访问数组,我们可以怎么做呢?
  

int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = &arr[0];

  
  之前我们都是通过 & a r r arr arr[0] 的方式得到数组首元素的地址,因为数组地址在内存中是连续存放的,得到首元素的地址就能访问整个数组。
  
  但其实,数组名本身就是一个地址,是数组首元素的地址,不信?我们可以通过代码来验证一下。
  

#include<stdio.h>int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("&arr[0]      = %p\n", &arr[0]);printf("&arr[0] + 1  = %p\n", &arr[0] + 1);printf("arr          = %p\n", arr);printf("arr + 1      = %p\n", arr + 1);return 0;
}

  
输出结果:
在这里插入图片描述

  
  我们发现,他们的输出结果是一样的,进行指针运算,同样是跳过了四个字节,所以数组名就是数组首元素的地址
  
  这时,有小伙伴就要问了,那我之前用 s i z e o f sizeof sizeof 为什么能求出整个数组的长度呢
  

#include<stdio.h>int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("%d\n", sizeof(arr));return 0;
}

  
  输出的结果是 40,如果是地址的话,输出的结果应该是 4/8,为什么呢?难道前面说错了?
  
  其实数组名是地址这本身并没有错,但是有两个例外:
  

  • s i z e o f (数组名) sizeof(数组名) sizeof(数组名) s i z e o f sizeof sizeof单独放数组名,这里的数组名就表示整个数组,计算的是整个数组的大小,单位是字节
  • &数组名:这里的数组名表示整个数组,即取出整个数组的地址(整个数组的地址和数组首元素的地址在数值上相等的,但还是有区别的,下面详解)

  除此之外,任何地方数组名就是数组首元素的地址

  下面,我们可以通过代码来比较一下
  

#include<stdio.h>int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("&arr[0]   = %p\n", &arr[0]);printf("&arr[0]+1 = %p\n", &arr[0]);printf("arr       = %p\n", arr);printf("arr+1     = %p\n", arr);printf("&arr      = %p\n", &arr);printf("&arr+1    = %p\n", &arr);return 0;
}

  
输出结果:
在这里插入图片描述

  我们可以看到,&arr[0]&arr[0]+1 相差 4 个字节,同样 a r r arr arr a r r + 1 arr+1 arr+1 也是相差 4 个字节
  
  & a r r arr arr& a r r arr arr[0] a r r arr arr 在数值上相等,但 & a r r arr arr& a r r + 1 arr+1 arr+1 相差 40 个字节,这是因为 & a r r arr arr 取出的是整个数组的地址,+1 跳过的是整个数组,而该数组的大小为 40 个字节
  
  看到这,相信大家对数组名有了更深层次的理解了吧。
  
  

二、使用指针访问数组

2.1、指针访问数组

  
  学了前面的知识,我们就可以很方便地使用指针访问数组了:

#include<stdio.h>int main()
{int arr[10] = { 0 };int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);//输入int* p = arr;for (i = 0; i < sz; i++){scanf("%d", p + i);//scanf("%d", arr + i);}//for (i = 0; i < sz; i++);{printf("%d ", *(p + i));}return 0;
}

  
  上述代码弄明白后,我们不妨想,数组名是首元素的地址,可以赋值给 p p p,这里 p p p a r r arr arr 是等价的,我们可以用 a r r arr arr [ i i i ] 来访问数组,那可不可以用 p p p [ i i i ] 访问数组呢?
  

#include<stdio.h>int main()
{int arr[10] = { 0 };int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);//输入int* p = arr;for (i = 0; i < sz; i++){scanf("%d", p + i);//scanf("%d", arr + i);}//for (i = 0; i < sz; i++);{printf("%d ", p[i]);}return 0;
}

  可以看到,是可行的,所以,本质上 p p p [ i i i ] 等价于 ∗ ( p + i ) *(p+i) p+i,同理, a r r arr arr [ i i i ] 等价于 ∗ ( a r r + 1 ) *(arr+1) arr+1
  

2.2、[ ] 的深入理解

  
  这里,我们对下标引用操作符进行更深层次的理解
  
   ∗ ( a r r + 1 ) *(arr+1) arr+1 由加法交换律,应该与 ∗ ( i + a r r ) *(i+arr) i+arr 等价,事实上也确实如此。这时,我们不妨做一个大胆的猜想:

  • 既然 ∗ ( a r r + i ) *(arr+i) arr+i等价于 a r r arr arr [ i i i ],那 ∗ ( i + a r r ) *(i+arr) i+arr 是否等价于 i i i [ a r r arr arr ] 呢?
  • 那这样的话, a r r arr arr [ i i i ] 是不是与 i i i [ a r r arr arr ] 相等呢 ?那他们四种表示是不是相等呢?
      

在这里插入图片描述

  
实践出真知,我们直接上代码试试:

#include<stdio.h>int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int sz = sizeof(arr) / sizeof(arr[0]);int i = 0;//arr[i]打印for (i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\n");//*(arr + i)打印for (i = 0; i < sz; i++){printf("%d ", *(arr + i));}printf("\n");//*(i + arr)打印for (i = 0; i < sz; i++){printf("%d ", *(i + arr));}printf("\n");//i[arr]打印for (i = 0; i < sz; i++){printf("%d ", i[arr]);}printf("\n");return 0;
}

运行结果:
在这里插入图片描述

  

在这里插入图片描述

  
  可以看到,猜想是 正确 的,其实下标引用操作符 [ ] 并没有那么神秘,它仅仅只是一个操作符而已,就像 + 操作符一样,它的两个操作数可以互换位置,[] 也是如此,事实上, a r r arr arr [ i i i ] 在程序运行时,编译器都会以 ∗ ( a r r + i ) *(arr+i) arr+i 的形式执行。当然,还是尽量别写成 i i i [ a r r arr arr ] 的形式,这样代码可读性不高
  

2.3、数组与指针的区别

  
  讲了这么多,大家可能对数组与指针的区别有点糊涂了吧,这里我们来理一下

  • 数组就是数组,是一块连续的空间。数组的大小和数组元素类型和数组大小都有关系。
  • 指针(变量)就是指针(变量),大小 4/8 字节。
  • 联系:数组名是地址,是首元素地址,可使用指针访问数组

  
  

三、一维数组的传参本质

  
  在【C语言】—— 指针二 : 初识指针(下)一文中,我曾经提到,将变量名传递给函数,是传值调用形参仅仅是实参的一份拷贝,无法通过函数改变实参的值。
  
  而将数组名传给函数,却可以通过函数改变数组的值。这是为什么呢,想到之前学过的传址调用(详情请看【C语言】—— 指针二 : 初识指针(下)),再结合刚刚所学到的知识:数组名是函数首元素的地址
  
  答案就不言而喻了:数组传参,传递的是数组名本质上传递的是数组首元素的地址
  
  同时,形参中创建的数组不会再单独创建数组空间的,所以形参是可以省略数组大小

  这时,我们不妨想一个问题:之前我们都是在函数外面将数组的元素个数求好,再传递给函数,我们可以把数组传递给函数后,在函数内部求数组的元素个数吗
  

#include<stdio.h>void test(int arr[])
{int sz2 = sizeof(arr) / sizeof(arr[0]);printf("sz2 = %d\n", sz2);
}
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int sz1 = sizeof(arr) / sizeof(arr[0]);printf("sz1 = %d\n", sz1);test(arr);return 0;
}

  
输出结果:
在这里插入图片描述

  可见,函数内部并没有正确求出数组的元素个数
  
  为什么呢?因为数组传参时,传递的是数组首元素的指针,虽然函数中 s i z e o f ( a r r ) sizeof(arr) sizeofarr a r r arr arr 是单独放在 s i z e o f sizeof sizeof 中的,但前面函数传参时, a r r arr arr 为指针,函数将 a r r arr arr 当做指针变量来接收,所以这里的 a r r arr arr 也被认为是指针,大小为 4/8,所以最终结果为 1/2。
  
  所以在函数内部是无法求出数组元素个数的
  

#include<stdio.h>void test(int arr[])//参数写成数组形式,本质上还是指针
{printf("%d\n", sizeof(arr));
}void test(int* arr)//参数写成指针形式
{printf("%d\n", sizeof(arr));//计算一个指针变量的大小
}int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };test(arr);return 0;
}

总结:一维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式,当然,数组的形式本质上还是指针
  
  

四、数组指针变量

  

4.1、数组指针变量是什么

  
  数组指针变量是什么,之前我们学过指针数组(详情请看【C语言】—— 指针二 : 初识指针(下)),指针数组是一种数组,存放的是指针变量。
  
  那么数组指针呢?是数组还是变量?
  
  前面,我们学习过:整形指针变量 i n t int int ∗ * ,存放的是整形变量的地址,能够指向整形数据的指针字符指针变量 c h a r char char ∗ * ,存放字符形变量的地址,能够指向字符型数据的指针。
  
所以,数组指针变量应该是: 存放的是数组的地址,能够指向数组的指针变量
  
下面代码哪个是数组指针变量:

int* p1[10];
int (*p2)[10];

  分析之前,我们先要知道: [ ] 的优先级比 ∗ *

  第一个, p p p 先与 [ ] 结合,表示是一个数组,剩下的 i n t int int ∗ * 表示数组存放的元素是 i n t int int ∗ * ,为指针数组

  第二个,由于有(), p p p 先与 ∗ * 结合,表示是一个指针变量,然后指针指向的是大小为 10 个整型的数组。 p p p 是一个指针,指向一个数组,为数组指针变量
  

4.2、 数组指针的初始化


  数组指针变量是用来存放数组的地址的,那怎么获得数组的地址呢,这时就需要用到前面学的 &数组名
  

int arr[10] = { 10 };
&arr;//得到的是数组的地址

  
  如果要存放数组的地址,就得存放在数组指针变量中
  

int(*p)[10] = &arr;

  
在这里插入图片描述

注:下标引用操作符 [ ] 中 10 不能省略,同时,要匹配得上。
  
  
数组指针类型解析:

在这里插入图片描述

  
  

五、二维数组传参的本质

  
  有了前面对数组指针的了解,我们就可以了解二维数组传参的本质了。

  过去,我们想传递二维数组时,可以这样写
  

#include<stdio.h>void test(int a[3][5], int r, int c)
{int i = 0;int j = 0;for (i = 0; i < r; i++){for (j = 0; j < c; j++){printf("%d ", a[i][j]);}printf("\n");}
}
int main()
{int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };test(arr, 3, 5);return 0;
}

  这里,实参是二维数组,形参也是二维数组的形式,那还有什么其他方法吗。
  
  我们可以类比一下一维数组。对一维数组来说,除了可以传递一维数组的形式,还可以传递数组名,即首元素地址,那二维数组可不可以也传递数组名呢?
  
  答案是 肯定 的,二维数组的数组名也是首元素的地址,但这之前,我们需再次理解二维数组:
  
  二维数组可以看做每个元素是一维数组的数组,即二维数组每个元素是一维数组,那么二维数组的首元素就是第一行,即第一个一维数组。
  
  所以,根据数组名就是数组首元素的地址这个规则,二维数组数组名是第一个一维数组的地址,其类型为 i n t [ 5 ] int[5] int[5] 。这样,在函数接收参数时,接收的是数组地址,所以形参应该为数组指针变量,类型为 i n t ( ∗ ) [ 5 ] int(*)[5] int[5].
  
  二维数组传参本质上也是传递地址,传递的是第一行这个一维数组的地址
  

#include<stdio.h>void test(int(*p)[5], int r, int c)
{int i = 0;int j = 0;for (i = 0; i < r; i++){for (j = 0; j < c; j++){printf("%d ", *(*(p + i) + j));}printf("\n");}
}
int main()
{int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };test(arr, 3, 5);return 0;
}

  总结:二维数组传参,形参部分可写成数组形式,也可以写成指针形式。

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

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

相关文章

spring整合Sentinel

安装sentinel&#xff1a; 执行命令; java -jar sentinel-dashboard-1.8.6.jar 注:sentinel的默认端口为8080&#xff0c;容易出现tomcat的冲突。 当端口冲突&#xff0c;可以使用该指令修改sentinel的端口 默认账号和密码都为sentinel Springcloud整合sentinel&#xff1a;…

深入理解Java中的TCP连接:三次握手和四次挥手

欢迎来到我的博客&#xff01;今天我们将一起探索网络通信的奥秘。在Java编程中&#xff0c;我们经常会涉及到网络通信&#xff0c;而TCP协议是实现可靠数据传输的重要协议之一。在建立TCP连接和断开连接的过程中&#xff0c;三次握手和四次挥手是至关重要的步骤。本文将深入探…

人工智能数据分析Python常用库 01 time、random、collections、itertools库

文章目录 一、time库1、获取现在时间2、时间戳与计时器3、格式化4、睡眠 二、random库1、随机种子2、产生随机整数3、产生随机浮点数4、随机序列5、概率分布——以高斯分布为例 三、collections库——容器数据类型1、namedtuple——具名元组&#xff08;1&#xff09;定义方法&…

YOLO_you only look once

前言 计算机图形学的课程即将结束&#xff0c;我需要提交一份关于YOLO模型的学习报告。在这段时间里&#xff0c;我对YOLO进行了深入的学习和研究&#xff0c;并记录下了我的学习过程和心得体会。本文将详细介绍YOLO模型的原理、优缺点以及应用领域&#xff0c;希望能够为后续…

Day11:栈 LeedCode 20.有效的括号 1047. 删除字符串中的所有相邻重复项 150. 逆波兰表达式求值

20.有效的括号 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合。每个右括号都有一个对应的…

使用stream流合并多个List(根据实体类特定属性合并)

开发情景 现有多个List集合,其中都是一样的实体类,这里我想根据实体类的特定属性将它们合并在一起,形成一个最终的List集合。 这里主要用到了Stream流的flatMap方法与reduce方法。 flatMap:可以将多个Stream流合并在一起,形成一个Stream流。 reduce:可以将Stram流中的元…

基于Java+Springmvc+vue+element实现高校心理健康系统详细设计和实现

基于JavaSpringmvcvueelement实现高校心理健康系统详细设计和实现 博主介绍&#xff1a;多年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐…

Ubuntu Flask 运行 gunicorn+Nginx 部署

linux Ubuntu 下运行python 程序出现killed 原因&#xff1a;CPU或内存限制&#xff1a;在华为云上&#xff0c;你可能有CPU或内存使用的限制。例如&#xff0c;如果你使用的是一个固定大小的实例&#xff0c;那么超过该实例的CPU或内存限制可能会导致进程被杀死。 参考&am…

【Sql Server】通过Sql语句批量处理数据,使用变量且遍历数据进行逻辑处理

欢迎来到《小5讲堂》&#xff0c;大家好&#xff0c;我是全栈小5。 这是《Sql Server》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对…

55、服务攻防——数据库安全RedisHadoopMysql未授权访问RCE

文章目录 常见服务应用的安全测试&#xff1a; 配置不当——未授权访问安全机制——特定安全漏洞安全机制——弱口令爆破攻击 应用服务安全测试流程&#xff1a; 判断服务开放情况——端口扫描&组合猜解等 端口扫描&#xff1a;服务开放&#xff0c;绑定端口没开放&#…

hot100 -- 矩阵

&#x1f442; Peter Pan - kidult. - 单曲 - 网易云音乐 &#x1f442; Bibliothque&#xff08;图书馆&#xff09; - Jasing Rye - 单曲 - 网易云音乐 目录 &#x1f33c;前言 &#x1f33c;二分模板 &#x1f382;矩阵置零 AC 标记数组 AC 标记变量 &#x1f6a9;…

Java使用Selenium实现自动化测试以及全功能爬虫

前言 工作中需要抓取一下某音频网站的音频&#xff0c;我就用了两个小时学习弄了一下&#xff0c;竟然弄出来&#xff0c;这里分享记录一下。 springboot项目 Selenium Java使用Selenium实现自动化测试以及全功能爬虫 前言1 自动化测试2 java中集成Selenium3 添加浏览器驱动4…

Word2vec 学习笔记

word2vec 学习笔记 0. 引言1. Word2vec 简介1-1. CBOW1-2. SG 2. 实战 0. 引言 最近研究向量检索&#xff0c;看到有同事使用 MeCab、Doc2Vec&#xff0c;所以把 Word2vec 这块知识学习一下。 1. Word2vec 简介 Word2vec 即 word to vector&#xff0c;顾名思义&#xff0c;…

【老话常谈之Java自学】自学Java应该怎么规划学习内容?

如果你学Java的目的是为了找到一份好工作 问这个问题之前,首先你确保自己了解了这些: IT行业都有哪些技能适合转行学习?每个技术都是做什么的?每个技术行业发展是怎样的?薪资怎么样?根据自己的情况和兴趣,选择到感兴趣的1-2个技术深入了解最终确定你就要学Java,并且有…

如何在代理的IP被封后立刻换下一个IP继续任务

目录 前言 1. IP池准备 2. 使用代理IP进行网络请求 3. 处理IP被封的情况 4. 完整代码示例 总结 前言 当进行某些网络操作时&#xff0c;使用代理服务器可以帮助我们隐藏真实IP地址以保护隐私&#xff0c;或者绕过一些限制。然而&#xff0c;经常遇到的问题是代理的IP可能…

Django框架的全面指南:从入门到高级【第128篇—Django框架】

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 Django框架的全面指南&#xff1a;从入门到高级 Django是一个高效、功能强大的Python Web框…

JDK21虚拟线程

目录 虚拟线程 话题 什么是平台线程&#xff1f; 什么是虚拟线程&#xff1f; 为什么要使用虚拟线程&#xff1f; 创建和运行虚拟线程 使用线程类和线程创建虚拟线程。生成器界面 使用Executor.newVirtualThreadPerTaskExecutor&#xff08;&#xff09;方法创建和运行…

针对BSV区块链新推出的网络访问规则NAR和警报系统AS的解释与问答

​​发表时间&#xff1a;2024年2月22日 BSV区块链社区团队最近开设了一个Twitter&#xff08;X&#xff09;话题空间&#xff0c;讨论BSV区块链协会最新推出的网络访问规则和警报系统的相关问题。 本次讨论由BSV区块链社区负责人Brett Banfe主持&#xff0c;以便社区成员更好…

刷题DAY29 | LeetCode 491-递增子序列 46-全排列 47-全排列 II

491 递增子序列&#xff08;medium&#xff09; 给你一个整数数组 nums &#xff0c;找出并返回所有该数组中不同的递增子序列&#xff0c;递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。 数组中可能含有重复元素&#xff0c;如出现两个整数相等&#xff0c;也…

综合练习(python)

前言 有了前面的知识积累&#xff0c;我们这里做两个小练习&#xff0c;都要灵活运用前面的知识。 First 需求 根据美国/英国各自YouTube的数据&#xff0c;绘制出各自的评论数量的直方图 第一版 import numpy as np from matplotlib import pyplot as plt import matplo…