指针进阶(2)

2023-11-07

6.函数指针数组

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

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

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

int (*parr1[10])();
int *parr2[10]();
int (*)() parr3[10];

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

如果我们想写一个加法函数,然后我们把这个函数的地址存放到pf1里面去,再写一个减法函数,将这个函数的地址存放到pf2里面去,但是我们如果写更多同类型的函数,都这样存放地址的话,就要重复这样的操作,所以我们可以创建一个函数指针数组,存放同类型函数指针。

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int main()
{
	//int (*pf1)(int,int) = &Add;
	//int (*pf2)(int,int) = &Sub;
    int (*pfarr[2])(int,int)={&Add,&Sub};
	return 0;
}

函数指针数组的用途:转移表.
例子:(计算器)

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void menu()
{
	printf("********************************");
	printf("****1.Add   2.Sub***************");
	printf("****3.Mul   4.Div***************");
	printf("**** 0.exit  *******************");
}
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 x = 0;
	int y = 0;
	int ret = 0;
	printf("请输入2个操作数:");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);
	printf("ret = %d\n", ret);
}
int main()
{
	int input = 0;
    menu();
	printf("请选择:>");
	scanf("%d", &input);
	do
	{
		case 1:
			printf("请输入2个操作数:");
			scanf("%d %d", &x, &y);
			ret = Add(x, y);
			printf("ret = %d\n", ret);
			break;
		case 2:
			printf("请输入2个操作数:");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("ret = %d\n", ret);
			break;
		case 3:
			printf("请输入2个操作数:");
			scanf("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("ret = %d\n", ret);
			break;
        case 4:
			printf("请输入2个操作数:");
			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 input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		//函数指针数组 - 转移表
		int (*pfArr[])(int, int) = {NULL, Add, Sub, Mul, Div};
		//                          0     1     2   3    4
		if (0 == input)
		{
			printf("退出计算器\n");
		}
		else if (input >= 1 && input <= 4)
		{
			printf("请输入2个操作数:");
			scanf("%d %d", &x, &y);
			ret = pfArr[input](x, y);
			printf("ret = %d\n", ret);
		}
		else
		{
			printf("选择错误,重新选择!\n");
		}
	} while (input);

	return 0;
}

 未来如果想要在这个功能的基础上加上其他的功能,先把菜单改变一下,再把该功能的代码写出来,再把这个函数的地址放在函数指针数组里面去就可以了,这样还不会增加switch的长度,这种写法非常的巧妙,但同时也是有缺陷的,有约束。就是这些函数的返回类型和参数必须都是int,必须保持一模一样。

7. 指向函数指针数组的指针

指向函数指针数组的指针是一个 指针
指针指向一个 数组 ,数组的元素都是 函数指针 ;
如何定义?

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void test(const char* str)
{
	printf("%s\n", str);
}
int main()
{
	//函数指针pfun
	void (*pfun)(const char*) = test;
	//函数指针的数组pfunArr
	void (*pfunArr[5])(const char* str);
	pfunArr[0] = test;
	//指向函数指针数组pfunArr的指针ppfunArr
	void (*(*ppfunArr)[5])(const char*) = &pfunArr;
	return 0;
}

8. 回调函数

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

 回调函数是一个非常重要的知识点,回调函数依赖函数指针,有了函数指针才能实现回调函数。

 我们可以使用回调函数简化上面的计算器。

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<string.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 calc(int (*pf)(int,int))
{
	int x = 0;
	int y = 0;
	int ret = 0;
	printf("请输入2个操作数:");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);
	printf("ret = %d\n", ret);
}
int main()
{
	int input = 0;

	do
	{
		menu();
		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;
}

 通过回调函数可以使得函数变得通用,拥有多种功能。

首先演示一下qsort函数的使用:
qsort是一个库函数,底层使用的是快速排序的方式,对数据进行排序的。这个函数可以直接使用,可以用来排序任意类型的数据。

首先我们来回忆一下冒泡排序,冒泡排序的核心思想就是相邻的两个元素进行比较。

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

void bubble_sort(int arr[], int sz)
{
	//趟数
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		//每一趟冒泡排序的过程
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}
int main()
{
	//数据
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	print_arr(arr, sz);
	bubble_sort(arr, sz);//冒泡排序
	print_arr(arr, sz);
	return 0;
}

 这个函数有个不好的地方就是只能排序整型数组,所以并不通用,但是qsort函数可以排序任意类型的数据。

void qsort(void* base, //待排序数组的第一个元素的地址
           size_t num, //待排序数组的元素个数
	       size_t size,//待排序数组中一个元素的大小
	       int (* cmp)(const void* e1, const void* e2)//函数指针-cmp指向了一个函数,这个函数是用来比较两个元素的
         //e1和e2中存放的是需要比较的两个元素的地址
          );

如果我们需要排序整型数组的话,我们就要自己写一个比较函数。

void* 类型的指针 - 不能进行解引用操作符,也不能进行+-整数的操作。
void* 类型的指针是用来存放任意类型数据的地址。
void* 无具体类型的指针。

写成void*指针的好处是在进行调用这个函数的时候可以根据自己的需求进行转换。

所以我们需要将e1和e2进行强制类型转换成int*,将他们做差,将结果返回。

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}
void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
//测试qsort排序整型数据
void test1()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	print_arr(arr, sz);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	print_arr(arr, sz);
}
int main()
{
	//数据
	test1();
	//test2();
	//test3();
	return 0;
}

如果我们想排序结构体数组,那我们就写一个test2和test3,test2比较年龄,test3比较名字。

所以这里我们要重新写一个比较函数,比较名字的话我们就先强制类型转换成struct Stu*,然后用->来访问,再进行作差。

test3也需要写一个比较函数,所以我们也强制类型转换成struct Stu*,再用strcmp来比较,strcmp的返回值刚好是0,>0或者<0。

struct Stu
{
	char name[20];
	int age;
};
//结构体数据怎么比较呢?
//1. 按照年龄比较
//2. 按照名字比较
int cmp_stu_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
void test2()
{
	struct Stu arr[] = { {"zhanhsan", 20}, {"lisi", 30}, {"wangwu", 12} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
}
int cmp_stu_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
void test3()
{
	struct Stu arr[] = { {"zhanhsan", 20}, {"lisi", 30}, {"wangwu", 12} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}

今天的分享到这里就结束啦!谢谢老铁们的阅读,让我们下期再见。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

指针进阶(2) 的相关文章

随机推荐

  • 用opencv3写的超详细注释的车牌检测

    include iostream include opencv2 opencv hpp include opencv2 highgui highgui hpp include opencv2 core core hpp include op
  • uniapp打包app,调用相机功能时在真机调试可以,打包成app之后不行

    在打包成app之后报如图所示错误 解决 在app模块配置勾选上相机这一项 如图 这是主要针对上面这个报错的 当然还有一下情况比较类似 就是相机功能在真机调试下可以 打包之后就不行了 我总结了如下几点 1 代码中的sourceType要配置为
  • 【满分】【华为OD机试真题2023 JS】不爱施肥的小布

    华为OD机试真题 2023年度机试题库全覆盖 刷题指南点这里 不爱施肥的小布 知识点二分查找 时间限制 1s 空间限制 256MB 限定语言 不限 题目描述 某农场主管理了一大片果园 fields i 表示不同果林的面积 单位 m 2 现在
  • mybatis批量更新list对象

    最重要的一点 mybatis要想批量更新 首先我们数据库需要支持批量更新操作 需要在连接数据库时 添加配置 url jdbc mysql 192 168 6 11 3306 equipment im dev serverTimezone A
  • 内网渗透测试 MSF搭建socks代理

    环境搭建 最终效果为如此 web服务器可以ping通外网和内网两台服务器 外网打点 信息收集 发现端口 80 http 3306 mysql 敏感目录 phpMyadmin 数据库弱口令 root root 通过日志功能getshell 现
  • 在Unbuntu 18.04上docker安装MindSpore

    作者 张小白 文章来源 https bbs huaweicloud com blogs 241465 摘要 本文介绍了如何在ubuntu 18 04上使用docker安装python 3 7 5和mindspore 1 1 1 docker
  • RecyclerView设置Item的边距

    一 通过继承RecyclerView ItemDecoration RecyclerView有三种布局即 LinearLayoutManager 线性布局 StaggeredGridLayoutManager瀑布流布局 GridLayout
  • Java判断两个ListMap值是否相等(包括数据长度、存储的值)超实用工具类(亲测)

    一 功能描述 比较两个list中存储的map数据 比较的list的数据类型需一致 就能比较出存储的数据是否一致 二 实现步骤 1 比较两个list的长度大小 2 统计出每个list中存储的map值的个数及重复的 进行比较 3 具体比较值 三
  • React Hooks(useState、useEffect)

    本文目录 一 react hooks useState 1 1 useState使用 1 2 hooks案例 useState 二 react hooks useEffect 2 1 useEffect使用 2 2 useEffect模拟销
  • 设置地图背景图片

    设置地图背景图片 代码如下 设置地图框div的background 设置map ground surfaceColor 0 0 0 0 设置sceneview的 environment background color 0 0 0 0 di
  • xss绕过尖括号和双括号_XSS绕过filter高级技术part1

    在如今的web时代 XSS攻击十分常见 针对xss攻击的防御也有不少 Filter就是一种用来防御xss攻击的最常见的手段 filter通常是采用黑名单的形式或者基于正则表达式来过滤 尽管如此 依然有很多技术可以用来绕过Filter 基本变
  • VUE 子组件内输入框,父组件获取输入框的值

    父组件
  • 26. Remove Duplicates from Sorted Array

    题目 Given a sorted array nums remove the duplicates in place such that each element appear only once and return the new l
  • 01-TomCat和HTTP协议

    一 介绍 Tomcat 服务器是一个免费的开放源代码的Web应用服务器 Tomcat是Apache 软件基金会 Apache Software Foundation 的Jakarta 项目中的一个核心项目 由Apache Sun 和其他一些
  • [UE4][C++]用Socket传输图片并生成UTexture2D的一些坑

    首先 我规定好数据传输的格式 char 数组 RGBARGBARGBA 主要是将RGBA四个通道的数据按一定格式和顺序写好 然后发过来接收并解析就行 然后 就是数据的接收与解析 先讲一点很少有人注意到的细节 Socket的recv函数并不是
  • 华为OD机试真题-简单的压缩算法/栈【2023Q1】

    题目描述 现需要实现一种算法 能将一组压缩字符串还原成原始字符串 还原规则如下 1 字符后面加数字N 表示重复字符N次 例如 压缩内容为A3 表示原始字符串为AAA 2 花括号中的字符串加数字N 表示花括号中的字符重复N次 例如压缩内容为
  • 区块链电子签名技术及方案

    数字签名技术是电子签名的底层核心技术 由非对称密钥加密技术和数字摘要技术构成 首先发送方利用Hash函数对文件内容进行加密并生成数字摘要 然后利用私钥对数字摘要加密形成数字签名 接收方利用发送方提供的公钥对数字签名进行解密 若解密后的数字摘
  • [编程题] 不要二

    二货小易有一个W H的网格盒子 网格的行编号为0 H 1 网格的列编号为0 W 1 每个格子至多可以放一块蛋糕 任意两块蛋糕的欧几里得距离不能等于2 对于两个格子坐标 x1 y1 x2 y2 的欧几里得距离为 x1 x2 x1 x2 y1
  • Docker简介

    1 Docker是什么 Docker 是一个开源的应用容器引擎 Docker是一个基于轻量级虚拟化技术的容器 整个项目基于Go语言开发 并采用了Apache 2 0协议 Docker可以将我们的应用程序打包封装到一个容器中 该容器包含了应用
  • 指针进阶(2)

    6 函数指针数组 数组是一个存放相同类型数据的存储空间 那我们已经学习了指针数组 比如 int arr 10 数组的每个元素是int 那要把函数的地址存到一个数组中 那这个数组就叫函数指针数组 那函数指针的数组如何定义呢 int parr1