【进阶指针】函数指针、数组以及回调函数详解

在这里插入图片描述


一、函数指针

函数指针变量,用来存放函数的地址

1. 函数指针的地址

首先看一段代码:

#include <stdio.h>
void test()
{
 printf("hehe\n");
}

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

✨运行结果✨
在这里插入图片描述
区分:
数组名——数组首元素的地址
&数组名——数组的地址

函数名——表示函数的地址
&函数名——表示的也是函数的地址

2. 函数指针地址的存放

void test()
{
 printf("hehe\n");
}
//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (*pfun1)();
void *pfun2();

首先,能给存储地址,就要求pfun1或者pfun2是指针,那哪个是指针?

答案是:
pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。

🌸练习:

#include <stdio.h>
int Add(int x, int y)
{
	return x + y;
}

int main()
{
	printf("%p\n", &Add);
	printf("%p\n", Add);

	int a = 10;//类型为int

	int arr[10] = { 0 };//数组类型为int [10]

	//数组指针类型为int(*)[10]
	int(*parr)[10] = &arr;//parr就是数组指针变量

	//函数指针类型为int(*)(int, int)
	int (*pf)(int, int) = &Add;
	//pf是用来存放函数的地址 - pf就是函数指针变量
	return 0;
}

数组类型为int [10]
数组指针类型为int( * )[10]
函数指针类型为int( * )(int, int)

3. 函数指针的使用

💡函数解引用操作

#include <stdio.h>
int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int (*pf)(int, int) = &Add;

	int ret = Add(2, 3);
	printf("%d\n", ret);

	ret = (*pf)(4, 5);
	printf("%d\n", ret);
	return 0;
}

✨运行结果✨
在这里插入图片描述
💡

#include <stdio.h>
int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int (*pf)(int, int) = Add;//此时的pf==Add

	int ret = pf(4, 5);
	//ret = (*pf)(4, 5);这里的*可以不写,也可以写很多个
	//此时的*在语法上没有任何意义,只是摆设
	printf("%d\n", ret);
	return 0;
}

✨运行结果✨
在这里插入图片描述

4. 两段有趣的代码

//代码1
(*( void (*)() ) 0)();

代码是一次函数调用

解析:
(1) void (* )()为函数指针,指向的函数无需传参,且返回类型为void,即无需返回值。
(2) 代码中把 0 强制类型转换为为 void (*)() ,即一个函数的地址。
(3) 解引用地址,调用此地址处的函数,被调用的函数是无参的。

//代码2
void (*signal(int , void(*)(int)))(int);

这个代码是一次函数声明

解析:
(1)声明的函数名signal
(2)signal函数有 2 个参数,第一个是 int 类型,第二个是void(* )(int)的函数指针类型。
(3)signal函数的返回类型依然是:void( * )(int)的函数指针类型。

如何简化代码2:

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

关键字 typedef(typedef 顾名思义是类型定义,这里理解为类型重命名。)

二、函数指针数组

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

int *arr[10];
//arr为整型指针的数组,数组的每个元素是int*

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

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

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

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

#include <stdio.h>

int Add(int x, int y)//int (*)(int, int)
{
	return x + y;
}

int Sub(int x, int y)//int (*)(int, int)
{
	return x - y;
}

int Mul(int x, int y)//int (*)(int, int)
{
	return x * y;
}

int Div(int x, int y)//int (*)(int, int)
{
	return x / y;
}

int main()
{
	//int (*pf1)(int, int) = Add;
	//int (*pf2)(int, int) = Sub;
	//int (*pf3)(int, int) = Mul;
	//int (*pf4)(int, int) = Div;

	//函数指针数组 - 存放函数指针的数组
	//pfArr就是一个函数指针的数组
	int (* pfArr[4])(int, int) = {Add, Sub, Mul, Div};

	return 0;
}

1. 函数指针数组的应用

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

例子:(计算器)

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void menu()
{
	printf("**********************************\n");
	printf("******* 1.add     2. sub    ******\n");
	printf("******* 3.mul     4. div    ******\n");
	printf("******* 0.exit              ******\n");
	printf("**********************************\n");
}

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;
	do
	{
		int x = 0;
		int y = 0;
		int ret = 0;
		menu();
		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;
}

使用函数指针数组的实现:
方法一:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void menu()
{
	printf("**********************************\n");
	printf("******* 1.add     2. sub    ******\n");
	printf("******* 3.mul     4. div    ******\n");
	printf("******* 0.exit              ******\n");
	printf("**********************************\n");
	//x&y x|y x>>y x<<y x^y
}

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;
	do
	{
		int x = 0;
		int y = 0;
		int ret = 0;
		menu();
		printf("请选择:>");
		scanf("%d", &input);

		//转移表 - 《C和指针》
		int (*pfArr[5])(int, int) = {0, Add, Sub, Mul, Div};
		                            //0   1   2    3    4
		if (input == 0)
		{
			printf("退出计算器\n");
		}
		else if(input>=1 && input<=4)
		{
			printf("请输入2个操作数:>");
			scanf("%d %d", &x, &y);
			ret = pfArr[input](x, y);
			printf("%d\n", ret);
		}
		else
		{
			printf("选择错误\n");
		}
	} while (input);
	return 0;
}

方法二:借用回调函数

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void menu()
{
	printf("**********************************\n");
	printf("******* 1.add     2. sub    ******\n");
	printf("******* 3.mul     4. div    ******\n");
	printf("******* 0.exit              ******\n");
	printf("**********************************\n");
	//x&y x|y x>>y x<<y x^y
}

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

在这里插入图片描述

三、指向函数指针数组的指针

指向函数指针数组的指针是一个指针
指针指向一个数组 ,数组的元素都是函数指针

在这里插入图片描述
在这里插入图片描述

四、回调函数

回调函数就是一个通过函数指针调用的函数。

  • 如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
  • 回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
    例如:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int Add(int x, int y)
{
	return x + y;
}

int Sub(int x, int y)
{
	return x - y;
}

void Calc(int(*pf)(int, int))
{
	int ret = pf(3, 5);
	printf("%p\n", pf);

	printf("%d\n", ret);
}

int main()
{
	//Calc(Add);
	Calc(Sub);
	printf("%p\n", Sub);
	return 0;
}

✨运行结果✨
在这里插入图片描述

4.1 qsort(快速排序)函数

qsort 函数为库函数,可实现多种类型元素的排序。

在这里插入图片描述
在这里插入图片描述

void qsort(void* base, 
	       size_t num, //待排序的元素个数
	       size_t width, //一个元素的大小,单位是字节
	       int(* cmp)(const void* e1, const void* e2) 
	       //cmp指向的是:排序时,用来比较2个元素的函数) 
);

(1)注意一

	int a = 10;
	float f = 5.5f;

	char* p = &a;//int*
	//错误代码
	char* p2 = &f;//float*
	//错误代码

	void* p3 = &a;//int*
	void* p4 = &f;//float*

错误原因:
在这里插入图片描述

void* ——无具体类型的指针,能够接收任意类型的地址

(2)注意二

	int a = 10;
	float f = 5.5f;

	int* p1 = &a;
	p1++;//正确
	p1 = p1 + 1;//正确

	void* p3 = &a;//int*
	p3++;//错误代码
	p3 = p3 + 1;//错误代码
	*p3;//错误代码,不能解引用

void* ——无具体类型的指针
能够接收任意类型的地址
缺点:不能进行运算,不能±整数,不能解引用

注意三
在这里插入图片描述
不同类型数据的比较,需要用指针。

💡排序整型数组

#include<stdio.h>
#include<stdlib.h>

//比较2个整型函数
int cmp_int(const void* e1, const void*e2)
{
	return *(int*)e1 - *(int*)e2;//升序,若为降序,只需将e1和e2位置互换
}

void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}

void test1()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	//打印
	print_arr(arr, sz);
}

int main()
{
	//测试排序整型数组
	test1();
	return 0;
}

💡 排序结构体数据

#include<stdio.h>
#include<stdlib.h>
#include <string.h>

struct Stu
{
	char name[20];
	int age;
};

int cmp_by_name(const void*e1, const void*e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
	//strcmp是比较两个字符串大小的库函数
}

int cmp_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age -((struct Stu*)e2)->age;
}

void test2()
{
	struct Stu s[3] = { {"zhangsan", 15}, {"lisi", 30},{"wangwu", 10}};

	int sz = sizeof(s) / sizeof(s[0]);

	//按照名字排序
	qsort(s, sz, sizeof(s[0]), cmp_by_name);

	//按照年龄来排序
	//qsort(s, sz, sizeof(s[0]), cmp_by_age);
}

int main()
{
	//排序结构体数据
	test2();
	return 0;
}

在这里插入图片描述
💡 设计一个自定义的冒泡排序函数,使得其作用和 qsort 一样,可以排序不同类型的数据

#include<stdio.h>

//比较2个整型函数
int cmp_int(const void* e1, const void*e2)
{
	return *(int*)e1 - *(int*)e2;
}

void Swap(char* buf1, char* buf2, int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

//使用回调函数实现一个通用的冒泡排序函数
void BubbleSort(void* base, size_t num, size_t width, int (*cmp)(const void*, const void*))
{
	size_t i = 0;//使得比较时num和i都是无符号整型unsigned int
	//趟数
	for (i = 0; i < num - 1; i++)
	{
		//比较的对数
		size_t j = 0;
		for (j = 0; j < num - 1 - i; j++)
		{
			//base[j] ==> *(base+j)
			if (cmp((char*)base+j*width, (char*)base+(j+1)*width)>0)
			{
				//交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

 void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
 
//测试自定义的BubbleSort();
void test3()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	BubbleSort(arr, sz, sizeof(arr[0]), cmp_int);
	//打印
	print_arr(arr, sz);
}

int main()
{
	//测试自定义的BubbleSort();
	test3();
	return 0;
}

无符号和有符号数据类型:
在这里插入图片描述


如有问题,欢迎批评指正呀!

  • 13
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一颗小小粒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值