指针!!C语言(第三篇)

目录

一. 二维数组传参的本质

二. 函数指针变量和函数指针数组

三. typedef关键字

四. 转移表

五. 回调函数以及qsort使用举例


一. 二维数组传参的本质

🍟首先我们先回顾一下二维数组是怎样传参的?我们需要传入数组名以及行数和列数,这样才能将一个二维数组传入一个函数中,除了这样我们还能怎么办?首先认识一下二维数组传参的本质,我们知道二维数组其实是一维数组组成的,所以二维数组的数组名也是数组首元素的地址二维数组的首元素的地址就是它的第一行

所以,根据这个规则我们的传参就可以有另一种方式,如下:
//二维数组传参的本质
void test1(int(*p)[5], int r, int c)
{for (int i = 0; i < r; i++){for (int 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} };test1(arr, 3, 5);return 0;
}

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

二. 函数指针变量和函数指针数组

函数指针变量

🍔什么是函数指针变量呢? 根据前面学习整型指针,数组指针的时候,我们的类比关系,我们不难得出结论: 函数指针变量应该是用来存放函数地址的,未来通过地址能够调用函数的。 那么函数是否有地址呢?毋庸置疑肯定是有的。那么函数地址有什么不一样的地方呢?

首先是函数指针变量的创建:假如现在有一个简单的函数:int Add(int x,int y);{return x+y;},那么它的函数指针变量就是:int (*p) (int,int)=Add,变量int后面的x和y可写可不写:

另外对于函数指针而言,&函数名和函数名都是函数的地址,并无区别。

下面给大家一段代码演示:


int add(int x, int y)
{return x + y;
}int main()
{int(*pf)(int, int) = &add;printf("%d ", (*pf)(2, 3));printf("%d ", (*pf)(4, 5));return 0;
}

函数指针数组: 

数组是一个存放相同类型数据的存储空间,所以要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?首先我们要定义一个数组,也就说假如这个变量名是pf,pf要先和[ ]结合,来证明pf是数组,像这样int (*pf[3])();数组的内容是什么呢?是 int (*)() 类型的函数指针。eg: int (*pf[4])(int,int)={    };大括号里存放的是数组。

三. 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 );→ 新的类型名必须在*的右边
这些就是typedef关键字的重命名用法。

四. 转移表

转移表其实就是函数指针数组的用途之一,那么我们要如何理解呢?我们通过实现一个计算机来说明,在我们没有学习函数指针数组的时候我们的一般实现如下:

#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;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);switch (input){case 1:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = add(x, y);printf("ret = %d\n", ret);break;case 2:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = sub(x, y);printf("ret = %d\n", ret);break;case 3:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = mul(x, y);printf("ret = %d\n", ret);break;case 4:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = div(x, y);printf("ret = %d\n", ret);break;case 0:printf("退出程序\n");break;default:printf("选择错误\n");break;}} while (input);return 0;
}

 我们可以看到在一般实现中我们分别要创建不同的函数然后再分别调用不同的函数来满足我们不同的计算需求,但是其实我们又会发现在我们的运算中,我们的程序看起来有点相似,但又不完全一样,看起来有些许繁琐,所以我们有没有什么办法让代码更加简单一点呢?这时候就要用到我们的函数数组指针了。如下:

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;
}int main()
{int x = 0;int y = 0;int input = 0;int ret = 0;int(*pf[5])(int, int) = { 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 = (*pf[input])(x, y);printf("ret = %d\n", ret);}else if (input == 0){printf("退出计算器\n");}else{printf("输⼊有误\n");}} while (input);return 0;
}

int(*pf[5])(int, int) = { 0,add,sub,mul,div }; 我们将四个不同的运算放在一个函数指针数组中,然后在实际的选择中,当我们需要哪一个运算的时候我们使用解引用*pf[input](x,y),当我们input选择1的时候,就是调用下标为1的元素,即add,也就说现在我们直接调用了add这个函数,又传入了两个参数x和y,这样就避免了大量的重复代码,使我们的代码更加高效。而且这也是函数指针数组的一个很好的例子。

五. 回调函数以及qsort使用举例

学到现在我们今天的重点就来了,也就说我们马上要讲的和回调函数以及对qsort的认识和使用举例,首先来认识一下什么是回调函数,回调函数就是一个通过函数指针调用的函数

如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数
时,被调用的函数就是回调函数 。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
我们可以把调用的函数的地址以参数的式传递过去,使用函数指针接收,函数指针指向什么函数就调用什么函数,这里其实使用的就是回调函数的功能。

我们还是以计算机的实现来说明这个问题,首先还是拿来我们的一般实现:

#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;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);switch (input){case 1:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = add(x, y);printf("ret = %d\n", ret);break;case 2:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = sub(x, y);printf("ret = %d\n", ret);break;case 3:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = mul(x, y);printf("ret = %d\n", ret);break;case 4:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = div(x, y);printf("ret = %d\n", ret);break;case 0:printf("退出程序\n");break;default:printf("选择错误\n");break;}} while (input);return 0;
}

下面是我们使用回调函数的方法:

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 calc(int(*pf)(int, int))
{int ret = 0;int x, y;printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = pf(x, y);printf("ret = %d\n", ret);
}int main()
{int input = 0;int ret = 0;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);switch (input){case 1:calc(add);break;case 2:calc(sub);break;case 3:calc(mul);break;case 4:calc(div);break;case 0:printf("退出程序\n");break;default:printf("选择错误\n");break;}} while (input);return 0;
}

从代码中我们可以看出来我们创建了一个calc函数里面放入了函数指针,在后面的使用中我们使用calc这个函数来调用我们要使用的函数,比如当我们输入1的时候,是calc(add),也就说现在指针pf指向的是add这个函数,就实现了加法。

🍳qsort使用举例:

我们来认识一个新的函数叫做qsort函数,在之前我们讲过使用冒泡排序来实现对于数值的升序或降序排列,但是都有就局限性,比如只能对整型数值进行排列,现在我们使用qsort函数可以对任意类型的数据进行排序

下面我们就使用qsort函数来实现一些排列其他类型的数据,首先可以排列整型数据

#include <stdio.h>
//qosrt函数的使⽤者得实现⼀个⽐较函数
int int_cmp(const void * p1, const void * p2)
{return (*( int *)p1 - *(int *) p2);
}
int main()
{int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };int i = 0;qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++){printf( "%d ", arr[i]);}printf("\n");return 0;
}

 int int_cmp(const void *p1, const void *p2);
 其返回值具有以下含义:
- 如果返回值小于 0( <0 ),那么 p1 所指向元素会被排在 p2 所指向元素的前面。
- 如果返回值等于 0( =0 ),那么 p1 所指向元素与 p2 所指向元素的顺序不确定。
- 如果返回值大于 0( >0 ),那么 p1 所指向元素会被排在 p2 所指向元素的后面
所以qsort函数是默认升序排列的。另外还有一点要提醒大家就是使用函数指针时候要注意我们使用的是void类型的指针,但是void类型是不能进行比较和计算的,所以我们要进行强制类型转换之后再进行计算

🍜使用qsort排序结构数据

除了整型之外,我们还可以排列其他的类型数据,比如比较字符串,结构题体等,让我们实现一下代码:

//比较结构体
//创建一个结构体
#include <stdlib.h>
struct Stu //学⽣
{char name[20];//名字int age;//年龄
};int cmp_name(const void* p1, const void* p2)
{return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}void test1()
{struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };qsort(s, sizeof(s) / sizeof(s[0]), sizeof(s[0]), cmp_name);for (int i = 0; i < 3; i++){printf("%s ", s[i]);}
}int main()
{test1();return 0;
}

 

这里说明一下,strcmp函数是用来比较字符串的,它的头文件是#include <string.h>,比较的是ASCII码值,还有就是结构体比较除了使用操作符(.)之外,还可以直接使用箭头(→)的方式

🍢qsort函数的模拟实现:

那么qsort函数究竟是怎样实现的呢?我们可以使用回调函数,模拟实现qsort(采用冒泡的方式)。

int int_cmp(const void* p1, const void* p2)
{return (*(int*)p1 - *(int*)p2);
}
void _swap(void* p1, void* p2, int size)
{int i = 0;for (i = 0; i < size; i++){char tmp = *((char*)p1 + i);*((char*)p1 + i) = *((char*)p2 + i);*((char*)p2 + i) = tmp;}
}
void bubble(void* base, int count, int size, int(*cmp)(void*, void*))
{int i = 0;int j = 0;for (i = 0; i < count - 1; i++){for (j = 0; j < count - i - 1; j++){if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0){_swap((char*)base + j * size, (char*)base + (j + 1) * size,size);}}}
}int main()
{int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };int i = 0;bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++){printf("%d ", arr[i]);}printf("\n");return 0;

这个函数 int_cmp  用于比较两个 void*  类型的指针所指向的整数。通过将指针强制转换为 int*  类型,然后计算它们所指向的值的差值来确定大小关系。返回值大于 0 表示 p1  所指的值大于 p2  所指的值,小于 0 表示 p1  所指的值小于 p2  所指的值,等于 0 表示两者相等。 

_swap  函数用于交换两个指针所指向的内存区域的值。它通过一个循环,逐个字节地交换两个区域的内容。循环的次数由 size  参数决定,以确保交换指定大小的数据。

bubble  函数实现了冒泡排序的逻辑。
 -  void* base  表示要排序的数组的起始地址。
-  int count  是数组中的元素个数。
-  int size  是每个元素的大小。
-  int(*cmp)(void*, void*)  是一个函数指针,用于指定比较元素大小的规则。
函数通过两层循环来比较相邻的元素。如果相邻元素的顺序不符合比较规则(即  cmp  函数返回值大于 0 ),就调用  _swap  函数交换它们的位置。

 

 

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

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

相关文章

Spring Boot|如何实现 Spring Boot 的优雅停机

文章目录 概述优雅停机的重要性实现优雅停机的方法1. 使用Spring Boot Actuator配置使用 2. 自定义Shutdown Hook示例代码 3. 使用Spring Boot 2.3及更高版本的优雅停机特性配置 4. 通过Spring Cloud Gateway和Ribbon示例配置 5. 定制化停机逻辑示例代码 最佳实践结论 概述 在…

Dhtmlx Gantt教程:创建交互式甘特图的完整指南

在现代的项目管理中&#xff0c;时间是一种宝贵的资源&#xff0c;而甘特图则是一把解锁项目进度的魔法钥匙&#xff0c;想象一下&#xff0c;您可以在一个直观而动态的时间轴上&#xff0c;清晰地看到项目的每一个任务如何交织在一起&#xff0c;如何随着时间的推移展开&#…

【优质精选】12节大模型系列教学课程之二:RAG 原理与应用

课程二&#xff1a;RAG 原理与应用 12节大模型系列教学课程之二&#xff1a;RAG 原理与应用 课程详细内容RAG 技术的基础知识RAG 的工作原理RAG 提高生成质量和准确性的原理RAG 在问答系统中的应用RAG 在文本创作中的应用RAG 在其他领域的应用探索RAG 技术的挑战与应对策略RAG …

代码随想录算法训练营【动态规划篇】

动态规划 注&#xff1a;本文代码来自于代码随想录 509. 斐波那契数 力扣509 Python 动态规划&#xff08;版本一&#xff09; class Solution:def fib(self, n: int) -> int:# 排除 Corner Caseif n 0:return 0# 创建 dp table dp [0] * (n 1)# 初始化 dp 数组dp[0]…

LangChain4j-RAG高级-检索增强器

Retrieval Augmentor 检索增强器 RetrievalAugmentor 是 RAG 管道的入口点。它负责使用从各种来源检索的相关 Content 来扩充 ChatMessage 。 可以在创建 AiService 期间指定 RetrievalAugmentor 的实例&#xff1a; Assistant assistant AiServices.builder(Assistant.cla…

探索大型语言模型LLama 2:原理揭秘与代码实践

一、引言 1.1 大型语言模型的重要性 大型语言模型作为人工智能领域的重要研究方向&#xff0c;近年来取得了显著的成果。这些模型在自然语言处理、机器翻译、对话系统、文本生成等领域展现了强大的能力&#xff0c;为人类带来了诸多便利。大型语言模型的出现&#xff0c;使得…

初识git工具~~上传代码到gitee仓库的方法

目录 1.背景~~其安装 2.gitee介绍 2.1新建仓库 2.2进行相关配置 3.拉取仓库 4.服务器操作 4.1克隆操作 4.2查看本地仓库 4.3代码拖到本地仓库 4.4关于git三板斧介绍 4.4.1add操作 4.4.2commit操作 4.4.3push操作 5.一些其他说明 5.1.ignore说明 5.2git log命令 …

ElementPlus轮播图-Vue3

注意&#xff1a;安装时建议使用手机热点&#xff0c;wifi不稳定&#xff0c;会出现执行不成功的现象。 安装 使用包管理器 # 选择一个你喜欢的包管理器# NPM npm install element-plus --save# Yarn yarn add element-plus# pnpm pnpm install element-plus引入配置 完整引…

视频主题Qinmei 3.0视频站源码_WordPress影视视频主题/附详细安装教程

Qinmei 3.0主题主要是将 wordpress 改造成纯 api 的站点&#xff0c;以便实现前后端分离的技术栈&#xff0c;目前的进度已经大致完成&#xff0c;唯一的问题就是需要安装 JWT token 插件。 功能介绍&#xff1a; 支持豆瓣以及 bangumi 的一键获取信息, 豆瓣 api 目前使用的是…

【Node.js基础05】包的理解与使用

一&#xff1a;包的理解与简介 1 什么是包 包是一个将模块、代码、以及其他资料聚合成的文件夹 2 包的分类 项目包&#xff1a;编写项目代码的文件夹 软件包&#xff1a;封装工具和方法供开发者使用 3 为什么要在软件包中编写package.json文件 记录包的清单信息 二&…

NSIS打包脚本第二篇

NSIS打包脚本 NSIS打包脚本第一篇 字符串提取过滤 WordFind 使用方法 ${WordFind} "[string]" "[delimiter]" "[E][options]" $varoption解读 +1和+01一样,代表分割后的第一个字符串 +表示从左往右,-表示从右向左 +2}} 表示从左到右,前2…

Matlab arrayfun 与 bsxfun——提高编程效率的利器!

许多人知道 MATLAB 向量化编程&#xff0c;少用 for 循环 可以提高代码运行效率&#xff0c;但关于代码紧凑化编程&#xff0c; arrayfun 与 bsxfun 两个重要函数却鲜有人能够用好&#xff0c;今天针对这两个函数举例说明其威力。 Matlab arrayfun 概述 arrayfun 是 Matlab …

第五十天 第十一章:图论part01 图论理论基础 深搜理论基础 98. 所有可达路径 广搜理论基础

图论理论基础 了解邻接矩阵(*)&#xff0c;度&#xff0c;邻接表&#xff08;数组链表&#xff09;等 遍历顺序&#xff1a;深搜加广搜 深搜理论基础 dfs是可一个方向去搜&#xff0c;不到黄河不回头&#xff0c;直到遇到绝境了&#xff0c;搜不下去了&#xff0c…

力扣SQL 最后一个能进入巴士的人 自连接

Problem: 1204. 最后一个能进入巴士的人 文章目录 思路复杂度Code 思路 &#x1f468;‍&#x1f3eb; 参考题解 复杂度 时间复杂度: O ( ∗ ) O(*) O(∗)空间复杂度: O ( ∗ ) O(*) O(∗) Code select a.person_name from queue a,queue b where a.turn > b.turn -…

Java代理模式详细

Java代理模式详细 一、引言 代理模式&#xff08;Proxy Pattern&#xff09;是一种常用的设计模式&#xff0c;它为其他对象提供一种代理以控制对这个对象的访问。在Java中&#xff0c;代理模式可以分为静态代理和动态代理两种。本文将详细介绍代理模式的概念、实现方式以及应…

[极客大挑战 2019]PHP1

打开靶机 提示有备份&#xff0c;可以用工具扫描&#xff0c;我还没有配置好环境&#xff0c;搜了一下其他师傅的&#xff1a;备份的地址在这&#xff1a; /www.zip 下载后得到这几个文件&#xff1a; index.php就是上面打开的网页&#xff0c;其中有一段php代码&#xff1a;…

谷粒商城实战笔记-72-商品服务-API-属性分组-获取分类属性分组

文章目录 一&#xff0c;后端接口开发Controller层修改接口接口测试 二&#xff0c;前端开发 这一节的内容是开发获取分类属性分组的接口。 一&#xff0c;后端接口开发 Controller层修改接口 修改AttrGroupController接口。 RequestMapping("/list/{catelogId}")p…

ROS getting started

文章目录 前言一、认识ROS提供的命令行工具nodestopicsservicesparametersactionsrqt_console, rqt_graph批量启动多个节点recorde and playc基础pub-sub 1.5 ROS2和fastdds1 改变订阅模式2 xml配置3 指定xml位置4 talker/listener通过发现服务器发送topic5 ros2 检视6 远程fas…

Redis zset 共享对象

前言 本文介绍 Redis 中 skiplist 编码的 zset 对象是如何共享对象的。 skiplist 编码的 zset 对象为了同时支持高效的点查询和范围查询&#xff0c;内部使用了跳表和哈希表。倘若将每个插入的元素都拷贝两份&#xff0c;分别插入跳表和哈希表&#xff0c;将浪费大量的内存&a…

Unity 骨骼动画(Skinned Mesh Renderer): 角色动画的高级渲染

在Unity中&#xff0c;骨骼动画(Skinned Mesh Renderer)是一种用于高级角色动画渲染的组件。它允许开发者将复杂的3D模型和动画应用到游戏角色上&#xff0c;实现逼真的视觉效果。本文将探讨Skinned Mesh Renderer的基本概念、使用方法以及如何优化性能。 Skinned Mesh Render…