指针!!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,一经查实,立即删除!

相关文章

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

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

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命令 …

视频主题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文件 记录包的清单信息 二&…

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

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

力扣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 -…

[极客大挑战 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…

Docker容器的数据管理

转载说明&#xff1a;如果您喜欢这篇文章并打算转载它&#xff0c;请私信作者取得授权。感谢您喜爱本文&#xff0c;请文明转载&#xff0c;谢谢。 我们在使用Docker的过程中&#xff0c;往往需要能查看容器内应用产生的数据&#xff0c;或者需要把容器内的数据进行备份&#x…

信创终端操作系统上vmware的命令行操作

原文链接&#xff1a;信创终端操作系统上vmware的命令行操作 Hello&#xff0c;大家好啊&#xff01;今天给大家带来一篇关于在信创终端操作系统上使用命令行操作VMware的文章。通过命令行管理VMware虚拟机可以提高效率&#xff0c;特别是在需要批量操作或自动化管理时。本文将…

VS2022创建C C++ GTEST工程

原因 需要对带代码进行单元测试&#xff0c;选择在Visual studio 中使用GTEST 框架。 实施 创建一个常规的控制台可执行程序。然后使用NUGET安装包 安装GTEST 头文件和动态库&#xff0c;同时安装GTEST ADAPTER。 安装可能提示找不到包源&#xff0c;此时需要根据提示配置一…

如何使用API快速打造健康医疗系统?

在数字医疗市场&#xff0c;数据是人们经常谈及的一个话题。当前&#xff0c;消费者医疗和健康应用收集的数据越来越多&#xff0c;电子健康记录的实施也创造出了大量有关病人的电子信息。 API接口在智慧医院跨网、跨机构之间的业务协同和数据共享交换中得到数据共享。支撑了医…

Redis从入门到超神-(十二)Redis监听Key的过期事件

前言 试想一个业务场景&#xff0c;订单超过30分钟未支付需要做自动关单处理,修改订单状态&#xff0c;库存回退等&#xff0c;你怎么实现&#xff1f;方案一&#xff1a;可以使用定时任务扫表&#xff0c;通过支付状态和下单时间来判断是否支付过期。但是这样的方案是非常消耗…

C#使用Clipper2进行多边形合并、相交、相减、异或的示例

Clipper2库介绍 开源库介绍&#xff1a; Clipper2在Github上的地址&#xff1a;https://github.com/AngusJohnson/Clipper2 Clipper2库对简单和复杂多边形执行交集&#xff08;Intersection&#xff09;、并集&#xff08;Union&#xff09;、差分&#xff08;Difference&…

protobuf编译之后找不到isStringEmpty方法

原因: 与mysql的jar包冲突了 解决办法&#xff1a; 在MySQL中排除proto-java <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.22</version><scope>runtime</scope>&l…

Webpack 从入门到精通

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 一、Webpack 简介 二、Webpack 的核心概念 三、Webpack 的安装与配置 安装 Node.js 安装 Webpack 初始…

戴着苹果Vision Pro,如何吃花生米

6月底苹果Vision Pro国内开售&#xff0c;我早早到官网预订了一台。选择必要的配件&#xff0c;输入视力信息&#xff0c;定制符合自己视力的蔡司镜片。确实贵。把主要配件和镜片配齐&#xff0c;要3万6&#xff0c;比Pico、META的眼镜贵一个数量级。 Vision Pro出来后&#x…