指针的奥秘(三):数组指针+函数指针(+typedef)+函数数组指针+转移表

指针

  • 一.数组指针
    • 1.数组指针变量是什么?
    • 2.指针数组和数组指针区别和口诀
    • 3.数组指针变量怎么初始化
    • 4.二维数组传参的本质
  • 二.函数指针
    • 1.函数指针变量的创建
    • 2.函数指针变量的使用
    • 3.两段有趣的代码
      • 1.( *( void ( * )( ) )0 ) ( );
      • 2.void( *signle(int, void( * )(int) ) ) (int)
      • 3.typedef
  • 三.函数指针数组
    • 1.函数指针数组的用途:转移表

一.数组指针

1.数组指针变量是什么?

  之前我们学习了指针数组,指针数组是⼀种数组,数组中存放的是地址(指针)。数组指针变量是指针变量?还是数组?答案是:指针变量

我们已经熟悉:

  • 整形指针变量: int * pi; 存放的是整形变量的地址,能够指向整形数据的指针。
  • 浮点型指针变量: float * pf; 存放浮点型变量的地址,能够指向浮点型数据的指针。
  • 那数组指针变量应该是:存放的应该是数组的地址,能够指向数组的指针变量。

2.指针数组和数组指针区别和口诀

思考:数组指针和指针数组该如何写。

int *p1[10];//指针数组

解释:p1先和 [10] 结合,说明p1是一个数组,且数组中有10个元素,元素的类型是整形指针(int*)。所以p2是一个数组,数组元素为指针,叫指针数组。

int (*p2)[10];//数组指针

解释:p2先和 * 结合,说明p2是⼀个指针变量,然后指针指向的是一个大小为10个整型的数组。所以p2是一个指针,指向一个数组,叫数组指针。

注意:

  • [] 的优先级要高于 * 号的,所以必须加上()来保证p先和 * 结合。

这里我总结了一个口诀

  • 指针数组是数组,数组元素是指针
  • 数组指针是指针,指针指向是数组

3.数组指针变量怎么初始化

  数组指针变量是用来存放数组地址的,那怎么获得数组的地址呢?就是我们之前学习的&数组名。如果要存放个数组的地址,就得存放在数组指针变量中,如下:

#include <stdio.h>
int main()
{int arr[10] = { 0 };int(*p)[10] = &arr;return 0;
}

在这里插入图片描述
我们调试也能看到 &arr 和 p 的类型是完全⼀致的。

int (*p) [10] = &arr;|    |   ||    |   ||    |   p指向数组的元素个数|    p是数组指针变量名p指向的数组的元素类型

4.二维数组传参的本质

  我们前面学了:一维数组传参,为了避免额外开辟数组,只需传入数组首元素的地址即可,通过地址可以找到之后的元素

过去我们有⼀个⼆维数组的需要传参给⼀个函数的时候,我们是这样写的:

#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;
}

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

重点:

  • 二维数组在内存中是连续存储的。
  • 二维数组可以理解为一维数组的数组,二维数组的每一行可以看作是一个一维数组。
  • 二维数组名也是首元素的地址,这里的首元素是指第一行数组,传过去的是第一行这个一维数组的地址,也就是arr[0]的地址。
  • 第一行的⼀维数组的类型就是 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));//等价于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;
}
  1. p:数组首元素的地址,也就是一维数组arr[0]的地址。
  2. p+i:跳过 i 个 int[5] 这样的数组(p的类型是数组指针),指向arr[i],p+i 就是一维数组 arr[i] 的地址。
  3. *(p+i):访问一维数组arr[i],等价于一维数组arr[i],而 arr[i] 是数组名,又是数组首元素的地址,也就是 arr[i][0] 的地址。
  4. *(p + i) + j:由于 *(p+i)是 arr[i][0] 的地址,所以 +j 跳过 j 个整形(指向整形),也就是 arr[i][j] 的地址。
  5. *( *(p + i) + j):由于 *(p + i) + j 是 arr[i][j] 的地址,进行解引用操作,就是找到 arr[i][j]。
  6. 最终:*( *(p + i) + j) 等价于 arr[i][j]

如图:
在这里插入图片描述
了解清楚⼆维数组在内存中的布局,有利于我们后期使用指针来访问数组的学习。

二.函数指针

1.函数指针变量的创建

  什么是函数指针变量呢?根据前面学习整型指针,数组指针的时候,我们的类比关系,我们不难得出结论:函数指针变量应该是用来存放函数地址的,未来通过地址能够调用函数的。那么函数是否有地址呢?我们做个测试:

#include <stdio.h>
void test()
{printf("hehe\n");
}
int main()
{printf("test:  %p\n", test);printf("&test: %p\n", &test);return 0;
}

在这里插入图片描述
确实打印出来了地址,所以函数是有地址的,函数名就是函数的地址,当然也可以通过 &函数名 的方式获得函数的地址。

2.函数指针变量的使用

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

#include<stdio.h>
int Add(int x, int y)
{return x + y;
}
int main()
{//int a = 10;//int* pa = &a;//整型指针变量//int arr[5] = {0};//int (*parr)[5] = &arr;//parr 是数组指针变量//arr:数组首元素的地址   &arr:数组的地址//&函数名和函数名都是函数的地址,没有区别//printf("%p\n", &Add);//printf("%p\n", Add);//int(*pf3)(int, int) = Add;//int(*pf3)(int x, int y) = &Add;//x和y写上或者省略都是可以的 //int (*pf)(int,int) = &Add;//pf 函数指针变量,()不能省略int (*pf)(int, int) = Add;//pf 函数指针变量int ret1 = (*pf)(4, 5);int ret2 = pf(4, 5);//pf等价于Addprintf("%d\n", ret1);printf("%d\n", ret2);int ret = Add(4, 5);printf("%d\n", ret);//int (*pf)(int x, int y) = &Add;//pf 函数指针变量//int (*)(int,int) 函数指针类型return 0;
}
  1. int (*pf)(int, int) = Add,*pf外的 () 不能省略。
  2. pf == (*pf) == Add == &Add。

函数指针类型解析:

int (*pf) (int x, int y)|    |     ||    |     ||    |     pf指向函数的参数类型和个数的交代|    函数指针变量名为pfpf指向函数的返回类型int (*) (int x, int y) //pf函数指针变量的类型 

3.两段有趣的代码

1.( *( void ( * )( ) )0 ) ( );

在这里插入图片描述

在这里插入图片描述

2.void( *signle(int, void( * )(int) ) ) (int)

在这里插入图片描述

3.typedef

  typedef是同来类型重命名的,可以将复杂的类型,简单化。
比如,你觉得 unsigned int 写起来不方便,如果能写成 uint 就方便多了,那么我们可以使用:

typedef unsigned int uint;
//将unsigned int 重命名为uint 

如果是指针类型,能否重命名呢?其实也是可以的,比如,将 int* 重命名为 ptr_t ,这样写:

typedef int* ptr_t;

但是对于数组指针和函数指针稍微有点区别:
比如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ,那可以这样写:

typedef int(*parr_t)[5]; //新的类型名必须在*的右边 

函数指针类型的重命名也是⼀样的,比如,将 void(*)(int) 类型重命名为 pf_t ,就可以这样写:

typedef void(*pfun_t)(int);//新的类型名必须在*的右边 

那么要简化代码2,可以这样写:

typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);

三.函数指针数组

数组是⼀个存放相同数据类型的存储空间,我们已经学习了指针数组,比如:

int* arr[10];
//数组的每个元素是int* 

那要把函数的地址存到⼀个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?

int (*parr1[3])();//right
int *parr2[3]();//err
int (*)() parr3[3];//err

答案是:parr1
parr1 先和 [] 结合,说明parr1是数组,数组的内容是什么呢?是 int (*)() 类型的函数指针。

1.函数指针数组的用途:转移表

举例:计算器的⼀般实现:

#include<stdio.h>
int Add(int x, int y)
{return x + y;
}
int Sub(int x, int y)
{return x - y;
}
int Mul(int x, int y)
{return x * y;
}
int Div(int x, int y)
{return x / y;
}
void menu()
{printf("*************************\n");printf("**1:add***********2:sub**\n");printf("**3:mul***********4:div**\n");printf("*********0:exit**********\n");printf("*************************\n");}
int main()
{int x = 0;int y = 0;int input = 0;int ret = 0;do{menu();printf("请输入:");scanf("%d", &input);switch (input){case 0:break;case 1:printf("请输入两个数:");scanf("%d %d", &x, &y);ret = Add(x, y);printf("%d+%d=%d\n", x, y, ret);break;case 2:printf("请输入两个数:");scanf("%d %d", &x, &y);ret = Sub(x, y);printf("%d-%d=%d\n", x, y, ret);break;case 3:printf("请输入两个数:");scanf("%d %d", &x, &y);ret = Mul(x, y);printf("%d*%d=%d\n", x, y, ret);break;case 4:printf("请输入两个数:");scanf("%d %d", &x, &y);ret = Div(x, y);printf("%d/%d=%d\n", x, y, ret);break;default:printf("输入错误,请重新输入\n");break;}} while (input);return 0;
}

这种代码有很多冗余的部分,那有没有什么代码可以优化呢?我们发现这些函数的返回值参数类型数目与类型都是相同的,这时我们就可以用到函数指针数组了,通过数组下标找到函数指针,可以调用不同的函数。如下代码:

#include<stdio.h>
int Add(int x, int y)
{return x + y;
}
int Sub(int x, int y)
{return x - y;
}
int Mul(int x, int y)
{return x * y;
}
int Div(int x, int y)
{return x / y;
}
void menu()
{printf("*************************\n");printf("**1:add***********2:sub**\n");printf("**3:mul***********4:div**\n");printf("*********0:exit**********\n");printf("*************************\n");}
int main()
{int x = 0;int y = 0;int input = 0;int ret = 0;do{menu();printf("请输入:");scanf("%d", &input);int(*arr[5])(int, int) = { 0,Add,Sub,Mul,Div };//转移表if (input == 0){break;}else if (input >= 1 && input <= 4){printf("请输入两个数:");scanf("%d %d", &x, &y);ret = arr[input](x, y);printf("ret=%d\n", ret);}else{printf("输入错误,请重新输入\n");}} while (input);return 0;
}

不仅仅可以实现加减乘除,还能实现按位与,或,异或,左移,右移等操作,只需在数组中追加函数地址即可,当然前提是将函数敲出来,这种就叫作转移表。

今天的内容到这就结束了,后续还有指针的奥秘(四)哦,不要走开,马上回来!!!
创作不易,如果能帮到你的话能赏个三连吗?感谢啦!!!
在这里插入图片描述

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

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

相关文章

TCP协议的确认应答机制

TCP&#xff08;Transmission Control Protocol&#xff09;是一种面向连接的、可靠的、基于字节流的传输层协议&#xff0c;它在网络通信中扮演着至关重要的角色。其中&#xff0c;确认应答机制是TCP协议中的一个核心概念&#xff0c;它确保了数据的可靠传输。本文将详细介绍J…

【QA】Java集合常用的函数

文章目录 前言Collection接口通用函数 | Collections工具类通用函数 | List接口 Set接口List接口ArrayListLinkedList Set接口TreeSetHashSetLinkedHashSet Map接口通用函数TreeMapHashMapLinkedHashMap 前言 本文介绍Java集合中常用的函数。 Collection接口 通用函数 | Co…

WP All Import Pro插件下载 - 一键导入,无限可能

在当今快节奏的数字时代&#xff0c;网站内容的更新和管理是每个网站管理员和开发者的日常工作。但是&#xff0c;传统的手动更新方法不仅耗时&#xff0c;而且容易出错。现在&#xff0c;有了WP All Import Pro&#xff0c;这一切都将改变。 WP All Import Pro 是一款专为Wor…

ABAP ZCL_EXCEL 实际用的案例 对账单

ABAP ZCL_EXCEL 实际用的案例 FORM GETITEMSANDOUTPUT USING PGS_HEAD TYPE GSHEAD.DATA:FILENAME TYPE STRING.DATA:LKUNNR TYPE KNA1-KUNNR.DATA:PROW TYPE I.DATA:LSDATE TYPE STRING.DATA:SUMXSJE TYPE DMBTR,ZYMYE TYPE DMBTR, "月末余额SUMBYXSYE TYPE DMBT…

全新神经网络架构KAN——本文用于学习与探索

论文地址&#xff1a;https://arxiv.org/pdf/2404.19756 Github&#xff1a;GitHub - KindXiaoming/pykan: Kolmogorov Arnold Networks 文档说明&#xff1a;Welcome to Kolmogorov Arnold Network (KAN) documentation! — Kolmogorov Arnold Network documentation 本文仅…

二手交易系统基于Uniapp+FastAdmin+ThinkPHP(源码搭建/上线/运营/售后/更新)

​一款基于UniappFastAdminThinkPHP开发的二手交易系统&#xff0c;卖家可以发布二手信息&#xff0c;买家可以在线询价&#xff0c;支持在线聊天(高级授权)&#xff0c;在线购买支付&#xff0c;支持发布高价回收(高级授权)信息。自带社交板块&#xff0c;用户可以发布帖子、加…

Prompt|Kimi高阶技巧,99%的人都不知道

大家好&#xff0c;我是无界生长。 今天分享一条咒语&#xff0c;轻松让Kimi帮你生成流程图&#xff0c;学会了的话&#xff0c;点赞收藏起来吧&#xff01; 效果展示 我们演示一下让kimi帮忙绘制 关注微信公众号“无界生长”的流程图&#xff0c;最终效果图如下所示 效果还不…

针对 % 号 | 引起的 不安全情况

把网站开放的课程都检索下来了 一、情况1 org.apache.tomcat.util.http.Parameters processParameters 信息: Character decoding failed. Parameter [Mac] with value [%%%] has been ignored. Note that the name and value quoted here may be corrupted due to the failed…

数组元素翻倍C++

编写一个 C 程序&#xff0c;实现一个功能&#xff0c;即将数组中的每个元素值翻倍。程序应定义一个函数 doubleArray&#xff0c;该函数接收一个整数数组的指针和数组的大小&#xff0c;然后将数组中的每个元素都翻倍。 代码 #include <iostream>void doubleArray(int…

红帽为 Red Hat OpenShift AI 扩大与 Elasticsearch 向量数据库的合作

作者&#xff1a;来自 Elastic Aditya Tripathi 红帽和 Elastic 今天宣布开展合作&#xff0c;以便在 Red Hat OpenShift AI 上集成 Elasticsearch 向量数据库。 Red Hat OpenShift 用户现在可以通过红帽生态系统目录实施 Elasticsearch 以进行向量搜索和检索增强生成 (RAG) 应…

商机来了你能抓住吗?2024新商机!2024创业新风口!2024创业项目小成本!2024创业干什么最赚钱?

19世纪末&#xff0c;美国加利福尼亚州发现了黄金&#xff0c;出现了淘金热。有一位17岁的少年来到加州&#xff0c;是想加入淘金者的队伍&#xff0c;可看到金子没那么好淘&#xff0c;淘金的人很野蛮&#xff0c;他很害怕。这时&#xff0c;他看到淘金人在炎热的天气下干活口…

C++笔试强训day16

目录 1.字符串替换 2.神奇数 3.DNA序列 1.字符串替换 链接 简单的遍历替换即可&#xff1a; class Solution { public:string formatString(string str, vector<char>& arg) {string ret;int k 0;for (int i 0; i < str.size(); i){if (str[i] %){ret arg…

Windows10搭建GPU版Darknet—yolov4—VS2022+CUDA+CUDNN(亲测有效)

1 VS2019安装 网址&#xff1a;Visual Studio: 面向软件开发人员和 Teams 的 IDE 和代码编辑器 下载完成之后双击.exe文件 步骤严格如下安装 默认语音包为中文&#xff08;简体&#xff09; 安装位置可以自行选择&#xff0c;完成以后就可以点击安装了。 安装完毕以后需要重启…

464. 我能赢吗

464. 我能赢吗 原题链接&#xff1a;完成情况&#xff1a;解题思路&#xff1a;参考代码&#xff1a;_464我能赢吗_记忆化dp 错误经验吸取 原题链接&#xff1a; 464. 我能赢吗 https://leetcode.cn/problems/can-i-win/description/ 完成情况&#xff1a; 解题思路&#x…

【Go】Go Swagger 生成和转 openapi 3.0.3

本文档主要描述在 gin 框架下用 gin-swagger 生成 swagger.json 的内容&#xff0c;中间猜的坑。以及&#xff0c;如何把 swagger 2.0 转成 openapi 3.0.3 下面操作均在项目根目录下执行 生成 swagger 2.0 import swagger go get -u github.com/swaggo/gin-swagger go get …

炫酷个人主页(源码免费)

炫酷个人主页 效果图部分代码领取源码下期更新预报 效果图 部分代码 <!DOCTYPE html> <!--哪怕是深爱之人 对我们的痛苦一无所知&#xff01;* ░░░░░░░░░░░░░░░░░░░░░░░░▄░░* ░░░░░░░░░▐█░░░░░░░░░░░▄▀▒▌░* ░…

Jenkins android 自动打包安卓 centos8.5 运维系列五

1 新建项目android #cat android.sh #!/bin/bash rm -rf /data/.jenkins/workspace/android/app/build/outputs/apk/debug/* rm -rf /data/.jenkins/workspace/android/app/build/outputs/apk/release/* cd /data/.jenkins/workspace/android/app source /etc/profile g…

一文读懂:架构图类型、设计方法(内附大量案例)

架构图是一种用于描述和展示软件系统或应用程序的结构和组成的图形表示。它通常包括系统的各个组件、模块、接口、数据流等元素&#xff0c;并显示它们之间的关系和交互。 一、架构图的类型 架构图有多种类型&#xff0c;常见的几种类型包括&#xff1a; 高层架构图&#xff0…

政安晨【零基础玩转各类开源AI项目】:基于Ubuntu系统本地部署使用GPT-SoVITS进行语音克隆与TTS语音生成

目录 介绍 什么是TTS 安装Miniconda 框架功能 测试通过的环境 开始 1. 安装好miniconda 2. 进入下载的GPT-SoVITS目录 3. 创建虚拟环境并执行脚本 4. 执行过程中可能会出错 5. 下载预训练模型 6. 训练过程中可能会报错 7. 使用过程中可能出错 8.以下是使用全过程…

JavaEE技术之MySql主从复制及mycat[了解,不讲]

文章目录 1. 主从复制1.1. 主从同步的原理1.2. 检查数据库远程访问权限1.3. 主从配置1.3.1. master配置1.3.2. slave配置1.3.3. 主库创建同步用户1.3.4. 从库配置主从关系1.3.5. 重置主从关系 1.4. 测试主从复制 2. Mycat2.1. Mycat简介2.2. MyCat读写分离原理2.3. 不废话&…