那些年与指针的情仇(二)---二级指针指针与数组的那点事函数指针

在这里插入图片描述关注小庄 顿顿解馋(。・∀・)ノ゙

欢迎回到我们的大型纪录片《那些年与指针的爱恨情仇》,在本篇博客中我们将继续了解指针的小秘密:二级指针,指针与数组的关系以及函数指针。请放心食用!


文章目录

  • 一. 二级指针
  • 二. 数组与指针的那点事儿
    • 1.🏠 数组名的理解
      • 1.1 数组名本质理解
      • 1.2 sizeof数组名和取地址数组名
    • 2. 🏠 指针数组
    • 3.🏠 字符串常量
    • 4.🏠 数组指针
    • 5.🏠 数组传参
  • 三. 函数指针

一. 二级指针

前面我们讲到了指针变量是个存储指针(地址)的变量,我们知道变量在创建的时候操作系统会给他分配内存空间同时给他编号(地址),那么指针变量的指针(地址)能否被存储呢?这里就引入我们二级指针的概念了

二级指针:存储指针变量地址的指针

地址变量
0x0012ffaaa
0x00134455*p = &a
0x0012aaff**pp=&p

*在上面表格中pp变量其实就是对应的二级指针,我们可以通过双重解引用pp来找到a,*pp = p, *(*pp)=a

注:二级指针类型定义时的*理解逻辑可以类比我们之前理解指针变量类型的定义,二级指针变量前的*说明这是个指针(存储地址的小子),而前面的int*说明它指向的对象是int *类型的,他的变量类型是int **

二. 数组与指针的那点事儿

1.🏠 数组名的理解

1.1 数组名本质理解

int arr[10] = {1,2,3,4,6,7,8,9,10};
printf("&arr[0] = %p\n", &arr[0]);
printf("arr = %p\n", arr);

输出结果
&arr[0] = 004F9CC
arr = 004F9CC

这里我们可以得出数组名本质是首元素的地址,明白这个后我们以后访问数组就可以用指针的方式le

int arr[10]={1,23,4,5,6,7,8,9,10};
int* p = arr;
for(int i = 0;i < 10;i++)
{
   printf("%d ",*(p+i));
}//p+i指针加减法顺藤摸瓜到下个元素
//

延伸:*此时p与arr等价->arr[i] = *(p+i)= *(arr+i) *

那是否所有情况下都是这个样子呢?嘿嘿,我们看下面的代码

1.2 sizeof数组名和取地址数组名

Sizeof数组名

int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", sizeof(arr));

输出结果 : 40

看到这里有同学会疑惑,数组名本质不是首元素地址吗?

小庄如是说:没错,但是这是一种特殊情况也就是特例,sizeof中单独放数组名,这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩,单位是字节

&数组名

//代码1
int arr[10] = {1,2,3,4,5,6,78,9,10};
printf("%p\n",&arr[0]);
printf("%p\n",&arr);
printf("%p\n",arr);
//代码2
printf("&arr[0]+1 = %p\n", &arr[0]+1);
printf("arr+1 = %p\n", arr+1);
printf("&arr+1 = %p\n", &arr+1);

代码1输出结果:
0x004FC550 //&arr[0]
0x004FC550 //&arr
0x004FC550 //arr
代码2输出结果:
0077F824 //&arr[0] +1
0077F824 //arr+1
0077F848 //&arr+1

我们可以发现代码1输出结果相同,代码2就出现不同了其中&arr[0]和arr的结果相同这就再次验证了数组名的本质是首元素的地址,但对于&数组名+1,&arr和&arr+1相差40个字节,这就是因为&arr是数组的地址,+1操作是跳过整个数组的!

因此我们可以得出&数组名,这里的数组名表示整个数组,取出的是整个数组的地址,只不过数组都是从首个元素开始的。相当于你拿一箱水果只能一个一个搬,而比你壮的可以整箱搬起来

总结:对于数组名的运用,sizeof+数组名和&+数组名是特例表示整个数组地址,其他地方都是首元素地址

2. 🏠 指针数组

抛砖引玉:前面我们学习了数组的相关知识,整形数组是存储整型数据的数组,字符数组是存储字符型数据的数组… 那指针数组呢?

指针数组:存储指针的数组,数组每个元素都是来存储指针(地址)的,主体是数组.

  • 语法形式

Datatype * arr[size]
这里的Datatype*指的是数组存储元素的类型,也就是arr是一个存储整型指针的数组,大小为size。

  • 指针数组模拟二维数组
int arr1[] = {1,2,3,4,5};
int arr2[] = {6,7,8,9,10};
int arr3[] = {11,12,13,14,15};
int* arr[3] = {arr1,arr2,arr3};
int i,j;
for(i=0;i<3;i++)
{
   for(j=0;j<5;j++)
   {
   printf("%d ",arr[i][j]);
   }
   printf("\n");
}
//我们取每个整形数组首元素地址存储到指针数组中,就可以依靠这个首元素地址顺藤摸瓜到整个数组
//从而打印出每行实现二维数组的效果

延伸:arr[i][j] = * (*(arr+i)+j) ,arr[i]相当于是数组名,以每行为一个元素。

3.🏠 字符串常量

字符串我们学过下面两种表达式

1  char arr1[5] = "good";
2  char arr2[5] = {'g','o','o','d','\0'};

还有另外一种

const char* p = "good";

这里const修饰在*的前面说明p指向内容不可变,我们这时称这个字符串为常量字符串,这里是否表示把整个字符串放进p指针呢?不是的,这里意思是把字符串首元素地址放进p里,我们来段代码测试下。

char * p = "hello"
printf("%s",p);
printf("%c",*p);

输出结果为hello h,也就是说字符串打印是通过首地址来找到位置打印的跟字符数组类似

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");
else
printf("str1 and str2 are not same\n");
if(str3 ==str4)
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n"

输出结果:
str1 and str2 are not same
str1 and str2 are same

结论:1.c语言会把常量字符串存储到单独的一个内存区域,几个指针指向同一个字符串时指向的是同一块内存空间2.用他们初始化不同数组会开辟不同相独立的内存空间。

4.🏠 数组指针

抛砖引玉:整形指针是指向整形数据的指针,字符型指针是指向字符型的指针…那数组指针呢?

数组指针:指向对象为数组的指针,也就是存储的数组的地址

  • 数组指针变量
1 int (*p)[10];

解释:这里的先与p结合表示p是个指针变量(存地址的小子),p指向的是一个大小为10存储元素为整形数据的数组,指针类型为int()[10]
注:【】的优先级高于*,所以要加上()来让*与p先结合表示他是个指针变量

  • 数组指针变量初始化
int(*p)[10] = &arr; //取地址数组名取的是整个数组地址

5.🏠 数组传参

  • 一维数组传参
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;
}

输出结果 sz1 = 10 sz2 = 1;

这里我们可以看到在函数内部和外部求数组长度得出的结果是不同的,原因就是数组传参时传的是数组名,也就是说数组传参本质上传的是首元素地址,此时函数内部sizeof(arr) == sizeof(arr[0]),因此我们不能在函数内部求数组长度

明白一维数组传参本质我们可以这样传数组

void test(int* p);

总结:一维数组传参可以写成数组的形式也可以写成指针的形式

  • 二维数组传参

类比一维数组传参的本质 二维数组传参传的也是首元素地址,那二维数组的首元素是什么?

我们知道二维数组是一维数组的数组,也就是说二维数组的第一行元素就是它的首元素!那第一行元素地址怎么表示呢?这就与我们之前的数组指针串联起来了,也就是int(*p)[size];

void test(int arr[3][5]);
void test(int(*p)[5]);

总结:二维数组传参传的是第一行一维数组的地址,传参既可以写成二维数组的形式也可以写成数组指针的形式。

三. 函数指针

  • 函数指针变量

经过前面的类比我们不难得出:
函数指针变量:存储函数地址的指针变量

函数是否有地址呢?我们写代码测试下

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

输出结果
test: 005913CA
&test: 005913CA

所以函数是有地址的,函数名就是函数的地址,当然也可以通过 &函数名的方式获得函数的地址
延伸:函数指针调用(*p)(x,y),由于函数名等价于&函数名,故函数指针调用也可以p(x,y)

  • 函数指针变量的创建
int (*pf3) (int x, int y)//函数指针类型为 int(*)(int x ,int y)

解释:这里的*表示pf3是个指针变量用来存储指针,前面的int表示指向函数的返回值是int,后面的(int x,int y)表示的是指向函数参数的类型和个数(参数变量名可写可不写),可以类型数组指针变量的定义

  • 函数指针数组

类比指针数组,把几个函数的地址存到一个数组,那这个数组就叫函数指针数组

那函数指针数组如何定义呢?

int (*parr1[3])();

数组特征是【】在函数指针变量基础上加上【】让parr1先与【】结合就是数组了,数组存储元素的类型为int(*)()

  • typedef重新定义
typedef int(*parr_t)[5]; //新的类型名必须在*的右边
typedef void(*pfun_t)(int);//新的类型名必须在*的右边
  • 转移表

知道函数指针数组后我们可以实现转移表

#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a*b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0:exit \n");
printf("*************************\n");
printf( "请选择:" );
scanf("%d", &input);
if ((input <= 4 && input >= 1))
{
printf( "输⼊操作数:" );
scanf( "%d %d", &x, &y);
ret = (*p[input])(x, y);
printf( "ret = %d\n", ret);
}
else if(input == 0)
{
printf("退出计算器\n");
}
else
{
printf( "输⼊有误\n" );
}
}while (input);
return 0;
}

这里将函数数组作为一个跳板将函数们放进一个表中方便使用,这里的函数指针数组就叫转移表


本次分享到这就结束了,喜欢的话给小庄三连吧!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

c# textbox 提示文字

1. 定义提示文字内容 private readonly string RemarkText "最多输入100字"; // 提示文字 2. 添加textbox 焦点事件&#xff0c; 初始化textbox提示文字和字体颜色 public UserControl(){InitializeComponent();tb_Remark.Text RemarkText;tb_Remark.ForeColor…

Windows系统本地安装Wnmp服务并结合内网穿透公网远程访问

目录 前言 1.Wnmp下载安装 2.Wnmp设置 3.安装cpolar内网穿透 3.1 注册账号 3.2 下载cpolar客户端 3.3 登录cpolar web ui管理界面 3.4 创建公网地址 4.固定公网地址访问 结语 作者简介&#xff1a; 懒大王敲代码&#xff0c;计算机专业应届生 今天给大家聊聊Windows…

【极数系列】Flink集成DataSource读取文件数据(08)

文章目录 01 引言02 简介概述03 基于文件读取数据3.1 readTextFile(path)3.2 readFile(fileInputFormat, path)3.3 readFile(fileInputFormat, path, watchType, interval, pathFilter, typeInfo)3.4 实现原理3.5 注意事项3.6 支持读取的文件形式 04 源码实战demo4.1 pom.xml依…

ssl证书更换步骤及更换后有效期没有更新问题

因公司ssl证书到期&#xff0c;在阿里云申请免费证书更换后&#xff0c;查看证书有效期&#xff0c;发现有效期没有更新。 ssl证书更换步骤&#xff1a; 1.下载nginx证书文件 2.服务器上替换原有ssl证书&#xff08;操作前记得备份&#xff09; 3.更改nginx.conf文件中证书路径…

CES 2024:AI赋能机器人,国产机器人更亮眼

原创 | 文 BFT机器人 一年一度的国际消费电子展(CES)又与我们见面了。作为全球消费电子和科技创新的盛会&#xff0c;CES每年都吸引着无数目光。今年&#xff0c;AI赋能机器人成为展会的一大亮点&#xff0c;而国产机器人更是凭借其创新技术和实用功能&#xff0c;成为全场焦点…

ES Serverless让日志检索更加便捷

前言 在项目中,或者开发过程中,出现bug或者其他线上问题,开发人员可以通过查看日志记录来定位问题。通过日志定位 bug 是一种常见的软件开发和运维技巧,只有观察日志才能追踪到具体代码。在软件开发过程中,开发人员会在代码中添加日志记录,以记录程序的运行情况和异常信…

1.Mybatis入门

目录 前言 1入门 1.1 入门程序实现 1.2 数据准备 ​编辑 1.3 配置Mybatis 1.4 编写SQL语句 1.5 单元测试 1.6 解决SQL警告与提示 2. JDBC介绍(了解) 2.1 介绍 2.2 代码 2.3 问题分析 2.4 技术对比 3. 数据库连接池 3.1 介绍 3.2 产品 4. lombok 4.1 介绍 4.…

07-Nacos-接入Mysql实现持久化

1、默认内嵌的数据库 Derby 存于/data目录 2、扩展仅支持Mysql 5.6.5 执行Nacos中的SQL脚本&#xff0c;该脚本是Nacos-server文件夹中的nacos-mysql.sql 详见 01-Nacos源码打包、部署-CSDN博客 3、修改配置文件 Nacos-server中的conf目录下&#xff0c;application.proper…

ChatGPT更新了Mention功能,集结若干GPTs作战,AI智能体的心智入口;向量数据库的挑战和未来

&#x1f989; AI新闻 &#x1f680; ChatGPT更新了Mention功能&#xff0c;集结若干GPTs作战&#xff0c;AI智能体的心智入口 摘要&#xff1a;OpenAI在ChatGPT中引入了一个新功能&#xff0c;允许用户在聊天时任意一个GPTs&#xff08;即ChatGPT最新推出的AI Agent 智能应用…

2023量子科技十大用例 | 光子盒年度系列

随着量子科技的不断突破&#xff0c;量子计算、量子通信、量子测量等应用场景逐渐向纵深拓展&#xff0c;量子产业呈现出较好的发展势头。 量子计算的发展比以往任何时候都更加迅速&#xff0c;这提醒我们&#xff0c;这项看似‘高冷’的前沿科技&#xff0c;已悄然应用于不少领…

vue3 + antd 封装动态表单组件(三)

传送带&#xff1a; vue3 antd 封装动态表单组件&#xff08;一&#xff09; vue3 antd 封装动态表单组件&#xff08;二&#xff09; 前置条件&#xff1a; vue版本 v3.3.11 ant-design-vue版本 v4.1.1 我们发现ant-design-vue Input组件和FormItem组件某些属性支持slot插…

【RT-DETR有效改进】2024.1最新MFDS-DETR的HS-FPN改进特征融合层(降低100W参数,全网独家首发)

👑欢迎大家订阅本专栏,一起学习RT-DETR👑 一、本文介绍 本文给大家带来的改进机制是最近这几天最新发布的改进机制MFDS-DETR提出的一种HS-FPN结构,其是一种为白细胞检测设计的网络结构,主要用于解决白细胞数据集中的多尺度挑战。它的基本原理包括两个关键部分:特征…

[Linux]:软硬连接(什么是软硬链接,怎么创建软硬链接,以及对应的例子)

目录 软连接&#xff1a; 什么是软连接&#xff1a; 怎么创建软连接&#xff1a; 例子&#xff1a; 硬链接&#xff1a; 什么是硬链接&#xff1a; 怎么创建硬链接&#xff1a; 例子&#xff1a; 软连接&#xff1a; 什么是软连接&#xff1a; 软连接文件是一个独立的…

如何在群晖NAS部署office服务实现多人远程协同办公编辑文档

文章目录 本教程解决的问题是&#xff1a;1. 本地环境配置2. 制作本地分享链接3. 制作公网访问链接4. 公网ip地址访问您的分享相册5. 制作固定公网访问链接 本教程解决的问题是&#xff1a; 1.Word&#xff0c;PPT&#xff0c;Excel等重要文件存在本地环境&#xff0c;如何在编…

Adobe Photoshop 2024 v25.4.0 - 专业的图片设计软件

Adobe Photoshop 2024 v25.4.0更新了&#xff0c;从照片编辑和合成到数字绘画、动画和图形设计&#xff0c;任何您能想象到的内容都能通过PS2024轻松实现。 利用人工智能技术进行快速编辑。学习新技能并与社区分享您的工作。借助我们的最新版本&#xff0c;做令人惊叹的事情从未…

2023年全国职业院校技能大赛(高职组)“云计算应用”赛项赛卷9

某企业根据自身业务需求&#xff0c;实施数字化转型&#xff0c;规划和建设数字化平台&#xff0c;平台聚焦“DevOps开发运维一体化”和“数据驱动产品开发”&#xff0c;拟采用开源OpenStack搭建企业内部私有云平台&#xff0c;开源Kubernetes搭建云原生服务平台&#xff0c;选…

生物信息学高质量刊物

个人觉得生信期刊的水平排名&#xff08;只谈计算方法&#xff09; 1&#xff0c;Nature Biotechnology (Article) 生信人心中的梦之刊&#xff0c;很少有纯计算能上的&#xff0c;一般都需要一定的湿实验验证&#xff0c;在计算领域某些场合认可度甚至高于正刊。 2&#xf…

【pdf密码】怎么打印加密的PDF文件?

PDF文件是可以打开查看的&#xff0c;但是现在不能编辑、不能打印&#xff0c;功能栏中的功能都是灰色的&#xff0c;这种设置了加密的PDF文件该如何加密&#xff1f; 如果PDF中的大多数功能按钮以及打印按钮都是灰色的状态&#xff0c;那就证明是文件的问题导致不能打印的。 …

FC-135 / FC-135 TYPE 贴片晶振

描述 FC135是一种被广泛采用的32.768 kHz晶体单元&#xff0c;自2002年发布以来已在全球范围内使用。 理想的单片机子时钟和模块&#xff0c;从消费设备到工业设备的应用。如果温度范围至105.C&#xff0c;请与我们联系。 爱普生是千赫波段晶体单元的领先供应商&#xff0c;…

力扣hot100 柱状图中最大的矩形 单调栈

Problem: 84. 柱状图中最大的矩形 文章目录 思路复杂度Code 思路 &#x1f468;‍&#x1f3eb; 参考地址 复杂度 时间复杂度: O ( n ) O(n) O(n) 空间复杂度: O ( n ) O(n) O(n) Code class Solution {public static int largestRectangleArea(int[] height){Stack&l…