指针进阶:函数指针 && 回调函数(真正理解qsort)

目录

一、函数指针

二、函数指针的应用(解剖qsort)

三、函数指针数组

四、函数指针数组应用: 转移表

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

六、函数指针笔试题

①        🐒   ->  offer

②        🐒   ->  offer

①        🐒   ->  offer

 仅自己理解,欢迎指正!!!


先看两段代码,有助于你接下来的学习:

int *arr1[10];
int (*arr2)[10];

arr1先与[]结合,说明是一个数组,数组里存放的int*类型的数据,,所以arr1是指针数组

arr2与*结合,arr2是指针,指针指向的是一个int型的数组,所以arr2是数组指针

再来看几段可能让你晕的代码;

//	//指向指针数组的指针
int* (*ppstr)[4] = &pstr;
//	//函数指针
int (*pfun)(int, int) = Add;
//	//函数指针数组
int (*pfun[4])(int, int) = { Add };
//	//指向函数指针数组的指针
int (* (*ppfun)[4])(int, int) = &pfun;

正文开始学习 ->

一、函数指针

函数指针对标数组指针,只不过它指向的是一个函数,也就是存放的是函数的地址。

下面是函数指针初始化的语法

int Add(int x , int y){
    return x+y;
}
int (*pf)(int, int) = &Add;

//(*pf)是一个指针变量

//   剩下的int  (int , int)int是函数的返回值,(int , int)是函数的参数类型

通过指针调用该函数。

int ans = (*pf)(1, 2);

因为()的优先级高于*,所以一定要给指针加(),那先跟()结合会发生什么呢???

VS直接编不过去,我们去掉*在运行发现ans等于3,咦!!,难道不用解引用就能调用函数,如果真是这样的话,那*3的确会报错,也就是说&函数名 == 函数名,我们打印它两的地址看一下是不是这样。

一摸一样,得出结论函数名 == &函数名。

拓展的想一下:我们以前是这样调用函数的Add(1,2),也就是说直接用地址加(),所以我们使用指针函数时也可以这样pf(1,2),不用加*。

二、函数指针的应用(解剖qsort)

举个实际的例子,qsort这个函数是用于排序的库函数,它可以排任意类型的,并且可以规定升序还是降序,很神奇吧!!!这里就用到了函数指针

想象一下,如果你想对一个整形数组升序排序,假如我们使用冒泡排序算法,你会写这么一个函数假如又要降序排序呢,你是不是每次都要重新写一遍这个排序算法,但是你只是改变了这个代码的以小部分。

//升序
void bubble_sort(int arr[], int n) {
    int i, j, temp;
    for (i = 0; i < n - 1; i++) {
        for (j = 0; j < n - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}
//降序
void bubble_sort(int arr[], int n) {
    int i, j, temp;
    for (i = 0; i < n - 1; i++) {
        for (j = 0; j < n - 1 - i; j++) {
            if (arr[j] < arr[j + 1]) {
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

 

我们来解析一下qsort的函数原型


qsort的参数

void* base                ->                待排序的起始地址

size_t num                ->                待排序元素个数

size_t size                ->                每个元素大小

int (*compar)(const void*,const void*)        ->                函数指针(升序还是降序)


接下来我们手动模拟实现qsort来感受下函数指针的应用

void test()
{
	int arr[] = { 2,7,34,14,87,4,23 };
	int size = sizeof(arr) / sizeof(arr[0]);
	my_bubble_sort(arr, size, sizeof(arr[0]), cmp);
	for (int i = 0; i < size; i++) {
		printf("%d ", arr[i]);
	}
}
int main()
{
	test();
	return 0;
}

这我想对arr这个数组进行冒泡排序,排序完打印看看

void my_bubble_sort(void* base, size_t num, size_t size, int (*cmp)(const void*, const void*)) {
	//num个数比较num-1趟
	for (int i = 0; i < num - 1; i++) {
		//第i趟比较num-1-i次
		for (int j = 0; j < num - 1 - i; j++) {
			//每次那j与j+1比较
			if ( cmp( (char*)base + size * j, (char*)base + size * (j + 1) )>0 ) {
				Swap((char*)base + size * j, (char*)base + size * (j + 1), size);
			}
		}
	}
}

函数的设计模仿了qsort,我们发现它跟一般冒泡排序不一样的地方就在这一段

 

这段代码就等价于上面的如果base[j] > base[j+1],交换两个元素,区别就在于这个是以1个字节为单位交换 ,内存图可能更方便理解。

 

你如果是让两个元素交换是这样int tmp = a; a = b; b = tmp;那就对应一次交换4个字节

如果这个交换的对象是结构体,大小为10个字节,那你就要重写你的排序代码了。

想一下如果每次交换一个字节,交换10次为一组,那是不是就实现了两个结构体变量的交换

对应上面代码

Swap函数

void Swap(void* a, void* b, size_t width) {
	for (int i = 0; i < width; i++) {
		char tmp = ((char*)a)[i];
		((char*)a)[i] = ((char*)b)[i];
		((char*)b)[i] = tmp;
	}
}

最后我们再看一下cmp函数的实现

int cmp(const void* a, const void* b) {
	return *(int*)a - *(int*) b;
}

当一个函数 funa 的参数是一个函数的地址 funb ,funa通过funb的地址调用 funb,那funa就叫做回调函数。

上面qsort就是一个回调函数,它传入了cmp函数的地址,在qsort里面调用了cmp函数,而cmp函数的作用是决定升序还是降序,所以我们就可以自己决定升降序而不用修改全部的代码了。

三、函数指针数组

parr3[10]说明是一个数组,数组里存放的类型是函数指针类型,没毛病。但编译器直接报错了呀

意思上没问题,但语法是这样的

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

四、函数指针数组应用: 转移表

函数指针数组可以用于构建转移表,即根据输入的参数值选择不同的函数进行调用。这种技术通常用于状态机、解析器、编译器等需要根据不同的输入进行不同操作的应用程序中。

例如:计算器

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 (*Calc[4])() = { Add, Sub, Mul, Div };
	int Add_ans = Calc[0](1, 2);
	printf("Add_ans=%d\n", Add_ans);
	int Sub_ans = Calc[1](1, 2);
	printf("Add_ans=%d\n", Sub_ans);
	return 0;
}

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

int (* (*ppfarr)[10] )();

了解即可,很少使用

六、函数指针笔试题

①        🐒   ->  offer

//解释下面代码含义
(*(void (*)())0)();

分析:

(*      void (*)()   )0     )();

void (*)()         这是个函数指针类型

( void (*)() ) 0    将 0 地址强制转化为了函数指针类型  地址就是一个数嘛

(* ----)()           调用该函数

所以这是调用0地址处的函数

②        🐒   ->  offer

//解释下面代码含义
void (*signal(int , void(*)(int)))(int);

分析:

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

signal(  int , void(*)(int)  )         函数声明        声明了函数名和参数

void (* ----)(int)                         函数指针        去掉函数名和参数就是返回值嘛

故这是signal的函数声明,该函数返回值为函数指针,参数为int和void(*)(int)

这里可以利用typedef简化代码

typedef void (*pf_t)(int);    //把void (*)(int)重命名为pf_t
//简化后的代码
pf_t *signal(int, pf_t); 

①        🐒   ->  offer

程序输出结果:

void fun(char  ps[]) {
	ps = "bbb";
}
int main() {
	char s[] = { "aaa" };
	fun(s);
	printf(s);
	return 0;
}

 仅自己理解,欢迎指正!!!

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

不会就选C.

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

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

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

打赏作者

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

抵扣说明:

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

余额充值