拿捏指针(二)

个人主页:秋邱'博客

所属栏目:C语言

(感谢您的光临,您的光临蓬荜生辉)

目录

前言 

数组与指针

数组名的理解

指针数组与数组指针

指针数组

 数组指针

数组传参

一维数组传参的本质

二维数组传参的本质

二维数组模拟 

二级指针

字符指针变量

函数指针变量


前言 

前面我们已经讲了,C语言的第一篇《拿捏指针(一)》,接下里我们继续深入的来了解指针。

1.0 数组与指针

1.1 数组名的理解

我们之前学习了,数组知道了数组arr就是首元素的地址,但却不理解&arr和&arr[0]的区别,脑子还是有点乱,今天我们一次给它讲明白。

int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("&arr[0] = %p\n", &arr[0]);//&arr[0]表示首元素地址printf("&arr    = %p\n", arr);//&arrb表示整个数组的地址printf("arr     = %p\n", arr);//arr表示一维数组数组名return 0;
}

 输出结果:

&arr[0] = 00AFF754
&arr     = 00AFF754
arr        = 00AFF754

从输出的结果看,&arr[0],&arr和arr这三的地址都是一样的,那么就能得出结论,数组名就是首元素的地址。

那么我们再来看看一下的代码

int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("&arr[0] = %zd字节\n", sizeof(&arr[0]));printf("&arr    = %zd字节\n", sizeof(&arr));printf("arr     = %zd字节\n", sizeof(arr));return 0;
}

输出结果:

&arr[0] = 4字节
&arr     = 4字节
arr       = 40字节

 我们能看出来,&arr[0],&arr和arr尽管都是打印首元素的地址,但还是有所区别的

如果arr是数组⾸元素的地址,那输出应该的应该是4/8才对,为什么打印的确实40?

其实数组名就是数组⾸元素(第⼀个元素)的地址是对的,但是有两个例外:

  • sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩, 单位是字节
  •  &数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素 的地址是有区别的)

除此之外,任何地⽅使⽤数组名,数组名都表⽰⾸元素的地。 

1.2 指针数组与数组指针

相信看到指针数组和数字指针每个人都很头疼,不知道哪个是数组指针,哪个是指针数字。我带大家来看看吧。

1.2.1 指针数组

指针数组:存放指针的数组。

指针数组是指针还是数组,我们来类比一下,就知道。

 整形数组和字符串数组

整形数组是数组,字符串数组是数组,那么指针数组自然就是指针了。指针数组的每个元素都是⽤来存放地址(指针)的。

 1.2.2 数组指针

数组指针是数组还是指针呢?

int(*arr)[10];

 p先和*结合,说明p是⼀个指针变量变量,然后指着指向的是⼀个⼤⼩为10个整型的数组。所以 p是⼀个指针,指向⼀个数组,叫数组指针

值得注意的是,[]的优先级大于*,所以必须得加括号。

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

所以我们知道了,int(*)[10] = &arr这两个的类型其实是相同的。

数组指针类型解析:

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

intp     p指向的数组的元素类型

(*p)p是数组指向变量名

[10]     p指向数组的元素个数


2.0 数组传参

2.1 一维数组传参的本质

一维数组arr表示首元素的地址,那么一维数组传参传的是地址还是一个数值呢?

void  Print(int * arr)
{int sz = sizeof(arr) / sizeof(arr[0]);for (int i = 0; i < sz; i++){printf("%d ", arr[i]);}
}
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };Print(arr);return 0;
}

arr是首元素地址,既然是地址我们就能用指针接收,也可以用 int arr[10]

打印结果:

这里我们预期的打印是1 2 3 4  5 6 7 8 9 10,可是输出的确实1,这就印证了我们的猜想,数组传参传的也是首元素的地址。 

所以arr的元素个数我们还是要在main函数里初始化,在Print函数里是一个指针4个字节(32位下,如果是64位下是8个字节),当然sz=1。

2.2 二维数组传参的本质

我们知道了一维数组传参传的是首元素地址,那么二维数组也是同理的。

void  Print(int(*arr)[5])
{for(int j = 0; j < 3;j++){for (int i = 0; i < 5; i++){printf("%d ", arr[j][i]);}printf("\n");}
}
int main()
{int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7,} };Print(arr);return 0;
}

 arr[3][5]我们也可以用数组的形式接收,int arr[3][]列数可以省略,而行数却不可以

 输出结果

1 2 3 4 5
2 3 4 5 6
3 4 5 6 7

 这⾥实参是⼆维数组,形参也写成⼆维数组的形式,那还有什么其他的写法吗

⾸先我们再次理解⼀下⼆维数组,⼆维数组起始可以看做是每个元素是⼀维数组的数组,也就是⼆维 数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏,是个⼀维数组。

 三行五列,第一行相当于arr[0],第二行相当于arr[2],第三行相当于arr[3];,第⼀⾏的地址是⼀维数组的类型就是数组指针类型 int [5] ,所以第⼀⾏的地址的类 int(*)[5] 那就意味着⼆维数组传参本质上也是传递了地址,传递的是第⼀ ⾏这个⼀维数组的地址。

总结:

  • 数组传参的本质是传递了数组首元素的地址,所以形参访问的数组和实参的数组是同一个数组的。
  • 形参的数组是不会单独再创建数组空间的,所以形参的数组是可以省略掉数组大小的。

实际上还可以用指针的方式打印出来 

void test(int(*p)[5])
{int i = 0;int j = 0;for (i = 0; i < 3; i++){for (j = 0; j < 5; j++){printf("%d ", *(*(p + i) + j));}printf("\n");}
}

 因为二维数组在内存中是连续存放的,且arr是一个数组arr[0]里面有五个元素{1,2,3,4,5}; 我们可以把它想像成一维数组。

(arr+1)是首元素的地址,(arr+1)+1是首元素第一个的地址。

2.2.1 二维数组模拟 
int main()
{int arr1[5] = { 1,2,3,4,5 };int arr2[5] = { 2,3,4,5,6 };int arr3[5] = { 3,4,5,6,7 };int* p[3] = {arr1,arr2,arr3};//数组名是数组⾸元素的地址,类型是int*的,就可以存放在p数组中for (int i = 0; i < 3; i++){for (int j = 0; j < 5; j++){printf("%d ", p[i][j]);}printf("\n");}return 0;
}

输出结果:

1 2 3 4 5
2 3 4 5 6
3 4 5 6 7 

p[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型⼀维数组,parr[i][j]就是整型⼀维数 组中的元素。 上述的代码模拟出⼆维数组的效果,实际上并⾮完全是⼆维数组,因为每⼀⾏并⾮是连续的。


3.0 二级指针

二级指针跟一级指针的原理很相似

	int a = 10;int* pa = &a;

一级指针是将变量的地址放入一级指针变量里面,二级就是将一级的指针变量的地址放入二级的指针变量。

	int a = 10;int* pa = &a;//一级指针int** ppa = &pa;//二级指针

这就是一个二级指针,我们画一个图了解。

当我们解引用的时候,*ppa访问达到的指针变量pa的内容(a的地址),*pa继续解引用访问到的是a的内容

这就是二级指针,依次类推三级指针,四级指针,五级指针都是这样的。

4.0 字符指针变量

//代码1
int main()
{char ch = 'w';char* pc = &ch;*pc = 'w';return 0;
}//代码2
int main()
{char* pc = "hello world";return 0;
}

代码2,常常被人误解,以为 是将“hello world”一整串的地址放入了pc变量中,其实只是将"h"的地址放入了pc变量,这样的字符串我们称为常量字符串。

我们来看你一段代码


int main()
{char str1[] = "hello bit.";char str2[] = "hello bit.";const char* str3 = "hello bit.";const char* str4 = "hello bit.";if (str1 == str2)printf("str1 and str2 are same\n");elseprintf("str1 and str2 are not same\n");if (str3 == str4)printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");return 0;
}

  打印结果:

str1 and str2 are not same
str3 and str4 are same

 为什么会出现这样的结果呢?

这是因为str3和str4指向的是⼀个同⼀个常量字符串。C/C++会把常量字符串存储到单独的⼀个内存区域, 当⼏个指针指向同⼀个字符串的时候,他们实际会指向同⼀块内存。但是⽤相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。

5.0 函数指针变量

上面我们已经学习了数组指针变量,字符串指针变量,同样函数指针变量也是类似的。

函数指针变量应该是⽤来存放函数地址的,未来通过地址能够调⽤函数的

void test()
{printf("hehe\n");
}
int main()
{printf("test:  %p\n", test);printf("&test: %p\n", &test);return 0;
}

输出结果:

test:  00DE13CA
&test: 00DE13CA

 这里函数的取地址与数组是相似的,都是首元素的地址,&函数名的方法获得函数的地址。

如果我们需要将函数的地址存放起来,,就得创建函数指针变量咯,函数指针变量的写法其实和数组指针 ⾮常类似。

void test()
{printf("hehe\n");
}
void (*pf1)() = &test;
void (*pf2)() = test;
int Add(int x, int y)
{return x + y;
}
int(*pf3)(int, int) = Add;
int(*pf3)(int x, int y) = &Add;//x和y写上或者省略都是可以的

调佣指针指向的函数

int add(int x,int y)
{return x + y;
}
int main()
{int a = 0;int b = 0;int(*pf)(int, int) = add;//存add的地址printf("%d\n", (*pf)(2, 3));printf("%d\n", pf(5, 5));return 0;
}

输出结果:

5

10 

这就是函数变量的用途。

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

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

相关文章

Github Gitlab SSH 密钥配置

1. 操作流程 生成密钥&#xff08;非对称加密&#xff09; ssh-keygen -t rsa -C "your_emailexample.com"注意&#xff0c;这里的 -C 表示注释&#xff0c;没有什么用&#xff0c;主要是标记密钥避免自己遗忘。 查看密钥&#xff08;公钥&#xff09; //进入所属…

Go 日期时间包装器:15条更便捷的时间处理

在Go编程中&#xff0c;处理日期和时间是一项常见任务&#xff0c;涉及到精确性和灵活性。尽管Go的标准库提供了时间包&#xff08;time&#xff09;用于处理时间相关操作&#xff0c;但在某些情况下&#xff0c;我们需要额外的实用函数来简化这些任务。本文将介绍一系列实用函…

RIPGeo参文31—36(关于对比学习):鼓励对同一数据点进行各种增强(视图),以学习更健壮的表示

RIPGeo中有: —干扰参数。在内部最大化中,我们提出了步骤,以增加损失的方向更新。我们的方法不是用简单的一步方案最大化内部部分,而是在每次迭代结束时将扰动投影到球面空间上(第2-7行),这允许模型产生更微妙但有价值的扰动[31]。 [31] A. Kurakin, I. J. Goodfellow…

代码随想录算法随想录day22 | 235. 二叉搜索树的最近公共祖先、701.二叉搜索树中的插入操作、450.删除二叉搜索树中的节点

目录 二叉搜索树的最近公共祖先思路解题方法复杂度Code 二叉搜索树中的插入操作思路解题方法复杂度Code 删除二叉搜索树中的节点思路解题方法复杂度Code 总结 二叉搜索树的最近公共祖先 链接: 二叉搜索树的最近公共祖先 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共…

【智能硬件、大模型、LLM 智能音箱】MBO:基于树莓派、ChatGPT 的桌面机器人

MAKER:David Packman/译:趣无尽(转载请注明出处) 这是国外 Maker David Packman 制作的基于树莓派机器人 MBO,该机器人的外观设计灵感来自动漫 Adventure Time 中的机器人 MBO。它具有强大的交互功能,可实现脱机唤醒词检测、调用 ChatGPT 3.5 进行聊天、机器视觉对图像进…

京东云主机+京美建站SaaS版

京美建站SaaS版 京美建站搭建企业网站、小程序、3000精美模板 链接:https://daili.jd.com/s?linkNo57UBX34BZMWGNFYTOCPVUE7SN36CCIPKLTFLPCUCPYBKSYYBIPS2BJ57GP7RACLDHU66X526ZOULMIXL2VN7DT7IHU 京东云主机&#xff0c;安全稳定&#xff0c;性能强劲&#xff0c;新客下单…

(网络安全)一款强大的逆向分析工具,开源!

工具介绍 Ghidra 是由美国国家安全局&#xff08;NSA&#xff09;研究部门开发的软件逆向工程&#xff08;SRE&#xff09;套件&#xff0c;用于支持网络安全任务。包括一套功能齐全的高端软件分析工具&#xff0c;使用户能够在各种平台(Windows、Mac OS和Linux)分析编译后的代…

如何成为一名CCAA审核员?报名复习考试注册实习指南

一、管理体系审核员的注册领域 管理体系审核员包括质量管理体系&#xff08;QMS&#xff09;、环境管理体系&#xff08;EMS&#xff09;、职业健康安全管理体系&#xff08;OHSMS&#xff09;、食品安全管理体系&#xff08;FSMS&#xff09;、危害分析与关键控制点&#xff0…

一文带你了解神经网络是如何学习预测的

文章目录 1、GPT与神经网络的关系 2、什么是神经网络 3、神经网络是如何计算的 数据是如何输入到神经网络中的 神经网络是如何进行预测的 神经网络是如何进行学习的 4、小结 1、GPT与神经网络的关系 GPT想必大家已经耳熟能详&#xff0c;当我们与它进行对话时&#xff0c;通常…

Spring 被打暴了! vs Javalin vs Solon

测试仅供参考。不同的环境、场景&#xff0c;效果会不同。 测试记录&#xff1a; 项目SpringBoot2SpringBoot3JavalinSolon运行时java 17java 17java 17java 17测试前状态/内存101.1Mb112.9Mb66.1Mb45.6Mb测试后状态/内存996.3Mb326.9Mb457.3Mb369.2Mb测试后状态/并发2万2.6万…

桌面待办,电脑桌面怎么设置待办事项

在忙碌的工作生活中&#xff0c;我们经常会有许多事情需要处理&#xff0c;为了提高工作效率和管理时间&#xff0c;很多人都有一套自己的桌面待办事项管理方法。那么&#xff0c;如何利用电脑桌面待办事项来提高工作效率&#xff0c;电脑桌面怎么设置待办事项呢&#xff1f; …

【Unity】persistentDataPath、streamingAssetsPath和dataPath

介绍 我们在用Unity进行开发时&#xff0c;资源路径是我们最常用到的&#xff0c;下面我就来简单介绍一下几种常用的路径。 1.dataPath dataPath是包含游戏数据文件夹的路径&#xff0c;是app程序包安装路径 Windows: xxx /Assets &#xff08;如下图&#xff09; Mac: xxx…

C语言初学12:强制类型转换

一、强制数据类型转换举例 1.1 double赋值给int #include<stdio.h> int main() {double sum 18, count 5;int mean;mean sum / count;printf("Value of mean : %d\n", mean);} 执行结果&#xff1a; double赋值给int&#xff0c;小数部分会删除&#xff…

Linux 学习笔记(16)

十六、 计划任务 在很多时候为了自动化管理系统&#xff0c;我们都会用到计划任务&#xff0c;比如关机&#xff0c;管理&#xff0c;备份之类的操作&#xff0c;我 们都可以使用计划任务来完成&#xff0c;这样可以是管理员的工作量大大降低&#xff0c;而且可靠度更好。 l…

vue3模块化引用组件和引用ts,调用ts中的接口

以简单的登录功能为例子 1.在util中创建loginValidators.ts import { ref, reactive } from vueinterface User{email: string;password: string; }export const loginUserreactive<User>({email: ,password: })interface Rules{email: {required: boolean;message: …

P8752 [蓝桥杯 2021 省 B2] 特殊年份 Python

[蓝桥杯 2021 省 B2] 特殊年份 题目描述 今年是 2021 年&#xff0c;2021 这个数字非常特殊, 它的千位和十位相等, 个位比百位大 1 1 1&#xff0c;我们称满足这样条件的年份为特殊年份。 输入 5 5 5 个年份&#xff0c;请计算这里面有多少个特殊年份。 输入格式 输入 …

可视化展示与交互编辑:探索3D Web轻量化平台HOOPS WEB Platform在BIM中的新可能性

随着数字技术的飞速发展&#xff0c;建筑行业也在不断迈向数字化转型的道路。在这个过程中&#xff0c;BIM&#xff08;Building Information Modeling&#xff0c;建筑信息模型&#xff09;技术已经成为建筑设计、施工和管理领域中的一项重要工具。 而在BIM的应用中&#xff…

金融知识分享系列之:MACD指标精讲

金融知识分享系列之&#xff1a;MACD指标精讲 一、MACD指标二、指标原理三、MACD指标参考用法四、MACD计算步骤五、MACD分析要素六、根据快线DIF位置判断趋势七、金叉死叉作为多空信号八、快线位置交叉信号九、指标背离判断行情反转十、差离值的正负十一、差离值的变化十二、指…

蓝桥杯2023年-平均(小根堆)

题目描述 有一个长度为 n 的数组&#xff08;n 是 10 的倍数&#xff09;&#xff0c;每个数 ai 都是区间 [0, 9] 中的整数。小明发现数组里每种数出现的次数不太平均&#xff0c;而更改第 i 个数的代价为bi&#xff0c;他想更改若干个数的值使得这 10 种数出现的次数相等&…

执行任务赚积分C卷(JavaPythonC++Node.jsC语言)

现有N个任务需要处理,同一时间只能处理一个任务,处理每个任务所需要的时间固定为1。 每个任务都有最晚处理时间限制和积分值,在最晚处理时间点之前处理完成任务才可获得对应的积分奖励。 可用于处理任务的时间有限,请问在有限的时间内,可获得的最多积分。 输入描述 第一…