函数指针数组指针数组传参的本质字符指针
🚀 作者:阿辉不一般
🚀 你说呢:不服输的你,他们拿什么赢
🚀 专栏:爱上C语言
🚀作图工具:draw.io(免费开源的作图网站)
如果觉得文章对你有帮助的话,还请点赞,关注,收藏支持博主,如有不足还请指点,博主及时改正,感谢大家支持!!!
文章目录
- 🚀前言
- 🚀字符指针变量
- 🚀数组指针变量
- 🚀数组传参的本质
- ✈️一维数组传参的本质
- ✈️二维数组传参的本质
- 总结
- 🚀函数指针变量
- 🚀指针变量是什么?
🚀前言
在阿辉上一篇博客指针的基础篇中我们了解到指针的一些基础知识
- 指针变量是用来存放地址的变量,通过指针可以找到所存地址指向的空间
- 指针变量的大小与平台有关,64位/32位平台大小为8字节/4个字节
- 指针变量的类型决定了指针变量所指向的内存空间的类型和大小以及指针加减整数时移动的字节数
- 指针的运算
- 多级指针
有了以上对于指针的基础了解,那么今天阿辉将为大家介绍C语言的指针部分,包括字符指针、数组指针、数组传参的本质以及函数指针,关注阿辉不迷路哦 😘 ,内容干货满满😋,接下来就跟着阿辉一起学习吧👊
🚀字符指针变量
在指针的类型中我们知道有⼀种指针类型为字符指针 char*
一般我们这么用:
int main()
{char ch = 'w';char *pc = &ch;*pc = 'w';return 0;
}
其实它还可以这么用:
int main()
{char* pstr = "hi bro";printf("%s\n", pstr);return 0;
}
可能大家认为ptr里面存的是hi bro,实际上ptr里面存的是字符串的首元素地址也就是h的地址,这里大家有没有发现其实这和字符数组是一样的,比如char arr[] = “hi bro”数组名arr 也是首元素h的地址,我们不妨把字符指针理解成字符数组,但真的这么简单吗?他们还是有两点不同,我们接着看👇
第一点不同:

我们可以看到当我们去改hi bro的内容时编译器直接报错,这是因为hi bro是常量字符串,而常量字符串被存储在程序的只读数据段(.rodata)中,这个数据段是只读的,意味着其中的数据在程序执行期间是不可修改的,而字符数组是可以修改的
第二点不同:
int main()
{char* str1 = "hi bro";char* str2 = "hi bro";char arr1[] = "hi bro";char arr2[] = "hi bro";if (str1 == str2)printf("str1与str2空间相同\n");elseprintf("str1与str2空间不相同\n");if(arr1 == arr2)printf("arr1与arr2空间相同\n");elseprintf("arr1与arr2空间不相同\n");return 0;
}
输出:
str1与str2空间相同
arr1与arr2空间不相同
其实很简单,这里str1和str2指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以arr1和arr2不同,str1和str2相同
🚀数组指针变量
在之前阿辉的数组篇中讲到关于数组名的理解,数组名是数组首元素的地址,而取地址数组名是整个数组的地址,那取地址数组名既然是地址那它应该存在什么类型的指针中呢?没错,就是数组指针
例如:
int arr[5] = { 0,1,2,3,4 };int(*p)[5] = &arr;p是变量名int(*)[5]是p变量的类型*指的是p是指针int[5]表示p指向的空间的类型是5个int类型变量的数组[]的优先级高于*,所以要把*p用括号()括起来表明p是指针我们对p解引用,*p就是arr也就是首元素地址我们对*p再次解引用,就访问到数组第一个元素**p等价于arr[0]通过*(*p + i)我们就可以遍历数组arr[5]
那数组指针有何用?别急我们接着看👊
🚀数组传参的本质
✈️一维数组传参的本质
我们知道一维数组的数组名是首元素地址,当我们传一个数组给函数时,我们不会去在开辟一块同样的空间,而是通过首元素地址访问数组,这样有效的避免了空间的浪费
函数接收一维数组有三种方式,例如:
void test(int a[]);
void test(int a[5]);
void test(int* a);
写法确实有三种,不过前两种人模狗样的是数组(毕竟传数组用数组接受很容易理解)本质上还是指针,包括[5]这个5屁用没有
注意
我个人的一点总结:当一维数组名作为首元素地址传给函数后,函数内部接收地址的形参不再具有数组名的性质,仅仅是一个普通的指针
怎么理解呢,我们接着看👇
void test(int a[])
{&a;sizeof(a);
}
int main()
{int a[5] = { 1,2,3,4,5 };test(a);return 0;
}

上图是x86环境下的调试结果,我们看到&a类型是int * *是二级指针而并非数组指针,sizeof(a)的值是4,如果a还具有数组名的特性,我们知道&a将是int(*)[5]类型的数组指针,而sizeof(a)的值将是20。这恰恰说明了当一维数组名作为首元素地址传给函数后,函数内部接收地址的形参不再具有数组名的性质,仅仅是一个普通的指针
✈️二维数组传参的本质
void test(int arr[][4]);
void test(int arr[3][4]);
行可以省略,列不能省略
那函数接收二维数组还有没有其他的方式?
二维数组的数组名怎么理解呢?二维数组可以理解为数组的数组,二维数组的每一行理解为二维数组的一个元素,二维数组的数组名同样是首元素地址,只不过二维数组的首元素是第一行,比如下图中二维数组arr[3][4]首元素是绿色的那一行一维数组,所以二维数组的数组名arr表示一个int(*)[4]类型的数组指针

那么二维数组的传参就可以这么写void test(int (*arr)[4])
例子:
void test(int (*arr)[4])
{int i = 0;int j = 0;for (i = 0; i < 3; i++){for (j = 0; j < 4; j++){printf("%d ", *(*(arr + i) + j));}printf("\n");}
}
int main()
{int arr[3][4] = { {1,2,3,4},{2,3,4,5},{3,4,5,6} };test(arr);return 0;
}

总结
- 一维数组的数组名是存放该数组首元素的一个指针,二维数组的数组名是存放该数组首元素的一个数组指针
- 一个数组int arr[5]={1,2,3,4,5},你可以用arr[1]和*(arr + 1)两种方式访问数组的了第二个元素,甚至可以1[arr]访问了解一下不建议使用。 *(arr+i)等价于arr[i]
🚀函数指针变量
函数指针变量顾名思义是存放函数地址的指针,函数也有地址吗?
没错函数也有地址,函数的函数名就是函数的地址
我们来看一段代码👇
#include <stdio.h>
void test()
{printf("hehe\n");
}
int main()
{printf("test: %p\n", test);printf("&test: %p\n", &test);return 0;
}
输出:
test:0xff44f68a
&test:0xff44f68a
我们看到test与&test打印出来的地址是一样的,不仅打印出来的是一样的,它俩本质也是一样的,这俩等价,这个&多少有点多余 😆
那函数指针如何创建呢?其实与数组指针类似
int add(int x, int y)
{return x + y;
}int main()
{int(*p)(int, int) = add;p是函数指针变量名*表示p是一个指针int(*)(int,int)是p的类型int(int,int)表示p所指向的空间是函数,函数的返回类型是int而且有两个int类型的形参return 0;
}
那函数指针变量有什么用呢?
我们可以通过函数指针变量来调用函数,用上面的函数演示
int ret = (*p)(3,5);
*P要用括号括起来,因为函数调用操作符的优先级更高
其实p、*p、add以及&add这四个等价
所以调用add这个函数(*p)(3,5)和p(3,5)这俩都可以,取地址和解引用都挺多余😆
🚀指针变量是什么?
大家看到这个标题或许很懵,指针变量不就是用来存放地址的变量嘛,讲这么多了还问?
int add(int x, int y)
{return x + y;
}int main()
{int(*padd)(int, int) = add;int a = 0;int* pa = &a;int arr[5] = { 1,2,3,4,5 };int(*parr)[5] = &arr;return 0;
}
上面这段代码中padd是int( * )(in,int)类型的函数指针变量,pa是int类型的指针变量,parr是int( * )[5]类型的数组指针变量,其实add也是 int( * )(in,int)类型的函数指针变量,&a也是int类型的指针变量,&arr也是int( * )[5]类型的数组指针变量
地址是类似于
0xff40688a这样的玩意,add、&a、&arr里面存的不也是地址嘛,SO他们也是指针变量
到这里,阿辉今天对于C语言中一些特殊类型的指针的分享就结束了,希望这篇博客能让大家有所收获, 如果觉得阿辉写得不错的话,记得给个赞呗,你们的支持是我创作的最大动力🌹

相关文章:
函数指针数组指针数组传参的本质字符指针
🚀 作者:阿辉不一般 🚀 你说呢:不服输的你,他们拿什么赢 🚀 专栏:爱上C语言 🚀作图工具:draw.io(免费开源的作图网站) 如果觉得文章对你有帮助的话,还请点赞…...
Linux swapon命令教程:如何在Linux中启用和禁用交换空间(附实例教程和注意事项)
Linux swapon命令介绍 Linux的swapon命令用于启用指定设备和文件的交换。当物理内存(RAM)达到其最大容量时,Linux使用交换空间。如果系统需要更多的内存,而RAM不足,内存中的非活动页面将被移动到交换空间。交换空间是…...
云计算领域的第三代浪潮!
根据IDC不久前公布的数据,2023年上半年中国公有云服务整体市场规模(IaaS/PaaS/SaaS)为190.1亿美元,阿里云IaaS、PaaS市场份额分别为29.9%和27.9%,都远超第二名,是无可置疑的行业领头羊。 随着人工智能(AI)…...
面试题目总结(一)
1. 谈谈数据库的乐观锁和悲观锁 乐观锁和悲观锁是数据库并发控制中常用的两种策略,用于处理多个事务同时访问和修改同一个数据时的并发冲突问题。 数据库的乐观锁是指在读取数据时,不对数据进行加锁,而是在更新数据时检查数据版本是否发生变…...
建造者设计模式
3. 建造者设计模式 3.1 原理 Builder 模式,中文翻译为建造者模式或者构建者模式,也有人叫它生成器模式。 建造者模式是用来创建一种类型的复杂对象,通过设置不同的可选参数,“定制化”地创建不同的对象。 创建者模式主要包含以…...
YOLO目标检测——垃圾检测数据集下载分享【含对应voc、coco和yolo三种格式标签】
实际项目应用:智能化垃圾分类系统、垃圾回收和处理领域的优化管理等方面数据集说明:垃圾分类检测数据集,真实场景的高质量图片数据,数据场景丰富,含报纸、蛋壳、矿泉水瓶、电池、拉链顶罐、塑料餐盒、纸质药盒、香蕉皮…...
Vue CLI的介绍【vue利器之一】
文章目录 前言Vue CLI 介绍CLICLI 服务CLI 插件后言 前言 hello world欢迎来到前端的新世界 😜当前文章系列专栏:vue.js 🐱👓博主在前端领域还有很多知识和技术需要掌握,正在不断努力填补技术短板。(如果出现错误&am…...
【学习笔记】插值之拉格朗日插值(Lagrange)
0 插值介绍 插值法是广泛应用于理论研究和工程实际的重要数值方法。用提供的部分离散的函数值来进行理论分析和设计都是极不方便的,因此希望能够用一个既能反映原函数特征,又便于计算的简单函数去近似原函数。 1 低次拉格朗日插值 定理:设…...
无人机电力巡检系统运行流程全解读
随着电力行业体系不断完善,保障电网运营的安全成为至关重要的任务。传统的人工巡检方式在面对电力设备广泛分布和复杂工况时显得效率低下,为了解决这一难题,无人机电力巡检系统应运而生,以智能化的运行流程,为电网安全…...
有关全局变量和sizeof的题
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int i; int main() {i--;if (i > sizeof(i)){printf(">");}else{printf("<");}return 0; } 这道题结果是 > 首先对于一个全局变量,当没有对其初始化时,它…...
vue简述
vue为渐进式框架:vmmv 1.易用 有html、css、javascript基础,即可学习vue框架 2.高效、开发前端页面 非常高效 1.vue的体积小、压缩完只需要20k的大小 2.超快的虚拟dom操作js中非常多的dom操作,vue设计虚拟dom非常快 3.设计时vue底层深度优化 …...
YOLOv8 训练自己的分割数据集
之前写过一篇 使用YOLOv8训练自己的【目标检测】数据集-【收集数据集】-【标注数据集】-【划分数据集】-【配置训练环境】-【训练模型】-【评估模型】-【导出模型】,里面带大家整个流程走过一遍了, 这篇文章我们来介绍如何使用 YOLOv8 训练分割数据集&a…...
Python实现DDos攻击实例详解
文章目录 SYN 泛洪攻击Scapy3k 基本用法代码实现DDos 实现思路argparse 模块socket 模块代码实现Client 端程序测试后记关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Python学习书籍四、Python工具包项目源码合集①Python工具包②Python实战案…...
微信小程序实现【点击 滑动 评分 评星(5星)】功能
wxml文件: <view class"wxpl_xing"><view class"manyidu">{{scoreContent}}</view><view><block wx:for{{scoreArray}} wx:for-item"item"><view classstarLen bindtapchangeScore data-sy"{{…...
堡垒机的用途
堡垒机的用途 堡垒机,即在一个特定的网络环境下,为了保障网络和数据不受来自外部和内部用户的入侵和破坏,而运用各种技术手段监控和记录运维人员对网络内的服务器、网络设备、安全设备、数据库等设备的操作行为,以便集中报警、及时…...
超全超实用行业解决方案合集,覆盖十大行业数据应用需求
现代企业面对复杂的业务需求,对数据分析的需求日益增加。 从实时销售到市场趋势,从客户行为到产品优化,每个环节都依赖于数据支持。然而,传统的数据分析平台常分散在不同系统和团队中,形成数据孤岛,降低了…...
一盏茶的时间,入门 Node.js
一、.什么是 Node.js? Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时,用于构建高性能、可伸缩的网络应用。 它采用事件驱动、非阻塞 I/O 模型,使其在处理并发请求时表现出色。 二、安装 Node.js 首先,让我们从 Node.…...
关于Java多线程的一些随笔
Synchronized与ReentrantLock有哪些相同点和不同点? 在Java中,synchronized关键字和ReentrantLock类都用于管理线程间的同步,但它们在实现方式、功能和灵活性方面存在一些差异。以下是它们的相同点和不同点: 相同点 互斥性&…...
Answering difficult questions in other way
I’m not (too) sure Q:Do you think computers make life easier? A:I’m not (too) sure, to be honest, but I reckon they do make life easier because… I can’t say for sure, but … Q:Do you think computers make lif…...
RabbitMQ教程:Linux下安装、基本命令与Spring Boot集成
RabbitMQ教程:Linux下安装、基本命令与Spring Boot集成 1. RabbitMQ简介 RabbitMQ是一个开源的消息代理和队列服务器,用于通过轻量级消息传递协议(AMQP)在分布式系统中传递消息。它支持多种编程语言,包括Java、Pytho…...
Spring Boot 实现流式响应(兼容 2.7.x)
在实际开发中,我们可能会遇到一些流式数据处理的场景,比如接收来自上游接口的 Server-Sent Events(SSE) 或 流式 JSON 内容,并将其原样中转给前端页面或客户端。这种情况下,传统的 RestTemplate 缓存机制会…...
Oracle查询表空间大小
1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...
Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...
select、poll、epoll 与 Reactor 模式
在高并发网络编程领域,高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表,以及基于它们实现的 Reactor 模式,为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。 一、I…...
Rapidio门铃消息FIFO溢出机制
关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系,以下是深入解析: 门铃FIFO溢出的本质 在RapidIO系统中,门铃消息FIFO是硬件控制器内部的缓冲区,用于临时存储接收到的门铃消息(Doorbell Message)。…...
R 语言科研绘图第 55 期 --- 网络图-聚类
在发表科研论文的过程中,科研绘图是必不可少的,一张好看的图形会是文章很大的加分项。 为了便于使用,本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中,获取方式: R 语言科研绘图模板 --- sciRplothttps://mp.…...
MySQL:分区的基本使用
目录 一、什么是分区二、有什么作用三、分类四、创建分区五、删除分区 一、什么是分区 MySQL 分区(Partitioning)是一种将单张表的数据逻辑上拆分成多个物理部分的技术。这些物理部分(分区)可以独立存储、管理和优化,…...
jdbc查询mysql数据库时,出现id顺序错误的情况
我在repository中的查询语句如下所示,即传入一个List<intager>的数据,返回这些id的问题列表。但是由于数据库查询时ID列表的顺序与预期不一致,会导致返回的id是从小到大排列的,但我不希望这样。 Query("SELECT NEW com…...

