指针的进阶(2)

作者 :ふり

专栏 :C语言进阶

格言 : 知行并进


😮 函数指针

函数指针 --> 指向函数的指针

数组指针 --> 指向数组的指针

函数指针的用法 :

#include <stdio.h>

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int arr[5] = { 0 };
	//&数组名 - 取出的数组的地址

	int(*p)[5] = &arr; //数组指针

	//&函数名 - 取出的是函数的地址
	//pf 就是函数的地址
	//对于函数来说 , &函数名和函数名都是函数的地址

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

	return 0;
}

函数指针的另一个用法 :

#include <stdio.h>
int Add(int x, int y)
{
	return x + y;
}
void calc(int(*pf)(int, int))
{
	int a = 3;
	int b = 5;
	int ret = pf(a, b);
	printf("%d\n", ret);
}
int main()
{
	calc(Add);
	return 0;
}

阅读两段有趣的代码:

#include <stdio.h>

int main()
{

	//代码1 
	(*(void (*)())0)();
    //代码2
	void (*signal(int, void(*)(int)))(int);
	return 0;
}

解析:
在这里插入图片描述


🔥 简单的计算器

我们用简单的计算器来告诉大家函数指针的用处到底在哪里,该怎么去使用,基于整型类型模拟实现计算器的功能

#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;
	int x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		printf("请选择2个操作数:>");
		scanf("%d %d", &x, &y);
		switch (input)
		{
		case 1:
			ret = Add(x, y);
			printf("%d\n", ret);
			break;
		case 2:
			ret = Sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			ret = Mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			ret = Div(x, y);
			printf("%d\n", ret);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}

在这里插入图片描述


经过一点的小改动


#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;
	int x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请选择2个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Add(x, y);
			printf("%d\n", ret);
			break;
		case 2:
			printf("请选择2个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			printf("请选择2个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			printf("请选择2个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Div(x, y);
			printf("%d\n", ret);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}

在这里插入图片描述


这时候的代码已经实现运行的较为完美了,但是大家此刻发现没有, switch里面的操作会不会显得过分冗余了!!!

那我们接下来就要动用 函数指针 来解决这个问题了


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

void calc(int* (pf)(int, int))
{
	int x, y, ret = 0;
	printf("请输入2个操作数 :>");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);
	printf("%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;
}

  1. 由此我们可以发现,函数指针可以解决代码的冗余问题。
  2. 通过函数地址传递给函数参数,进入函数内部,去调用函数,这就是回调函数。 后面会讲到。

😇 函数指针数组

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

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

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

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

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


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

如果要添加实现x&y,x^y,x>>y,x<<y的功能,此时就是添加4,5,6,7,8之类的选项,case的选项越来越多以此类推,代码会变得越来越长,这时候,把代码写得整洁一些:把switch语句去掉,创建一个函数指针数组存放函数,通过输入的选择作为下标去调用即可


#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;
	int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
	while (input)
	{
		printf("*************************\n");
		printf("*******1:add 2:sub*******\n");
		printf("*******3:mul 4:div*******\n");
		printf("*************************\n");
		printf("请选择:>");
		scanf("%d", &input);
		if ((input <= 4 && input >= 1))
		{
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = (*p[input])(x, y);
		}
		else
			printf("输入有误\n");
		printf("ret = %d\n", ret);
	}
	return 0;
}

这样修改的好处在于,以后想添加新的功能,只需要把函数的地址放在数组里面即可,改变范围即可,稍微调整一下代码即可。
通过函数指针数组便于以后修改代码。
通过这个简单的例子,演示了函数指针数组的作用。


😮 指向函数指针数组的指针

指向函数指针数组的指针是一个 指针

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

如何定义?

#include <stdio.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;
}

后面你就可以无限追加下去


😮 回调函数

计算器这边就使用了回调函数

在这里插入图片描述

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

我们接下来用 qsort 来深入理解

👉 qsort函数的使用

qsort实现冒泡排序

在这里插入图片描述

使用快速排序的思想实现的一个排序函数

下面,我们来简单理解一下qsort函数的参数的意思:

在这里插入图片描述

void* 指针
void是无具体类型的指针,这种指针可以接收任意类型的地址。
void
是无具体类型的指针,所以不能解引用操作,也不能±整数操作。
这里的参数是void的原因是因为不知道传过来的类型的指针是什么,所以定义为void


qsort的简单应用

#include <stdio.h>
#include<stdlib.h>
//qosrt函数的使用者得实现一个比较函数
int int_cmp(const void* e1, const void* e2)
{
	return (*(int*)e1 - *(int*)e2);
}
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;
}

我们只要通过改变e1和e2相减的位置即可实现。使之逻辑相反。这个就是回调函数来实现qsort的功能!
这里只是qsort的基本使用。

用qosort排序结构体名字

#include <stdio.h>
#include<stdlib.h>
#include <string.h>
struct Stu
{
	char name[20];
	int age;
};
//qosrt函数的使用者得实现一个比较函数
int int_cmp(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
int main()
{
	struct Stu s[] = { { "zhangsan",1 },{ "lisi",2 }, { "wangwu" , 3 } };
	int i = 0;
	qsort(s, sizeof(s) / sizeof(s[0]), sizeof(s[0]), int_cmp);
	for (i = 0; i < sizeof(s) / sizeof(s[0]); i++)
	{
		printf("%s\n", s[i].name);
	}
	printf("\n");
	return 0;
}

用qosort排序结构体年龄

#include <stdio.h>
#include<stdlib.h>
struct Stu
{
	char name[20];
	int age;
};
//qosrt函数的使用者得实现一个比较函数
int int_cmp(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age, ((struct Stu*)e2)->age;
}
int main()
{
	struct Stu s[] = { { "zhangsan",1 },{ "lisi",2 }, { "wangwu" , 3 } };
	int i = 0;
	qsort(s, sizeof(s) / sizeof(s[0]), sizeof(s[0]), int_cmp);
	for (i = 0; i < sizeof(s) / sizeof(s[0]); i++)
	{
		printf("%d\n", s[i].age);
	}
	printf("\n");
	return 0;
}

下面我们把冒泡排序改造qsort

#include <stdio.h>
#include<stdlib.h>
void Swap(char* a, char* b, int width)
{
    int i = 0;
    for (i = 0; i < width; i++)
    {
        char tmp = *a;
        *a = *b;
        *b = tmp;
        a++;
        b++;
    }
}
int cmp_int(const void* e1, const void* e2)
{
    return *(int*)e2 - *(int*)e1;
}
void bubble_sort(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
    int i = 0;
    for (i = 0; i < sz - 1; i++)
    {
        int flag = 1;
        //用来判断数组是否有序,提高效率
        int j = 0;
        for (j = 0; j < sz - 1 - i; j++)
        {
            //通过利用强转base为(char*)乘以宽度的多少来进行比较
            if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
            {
                Swap((char*)base + j * width, ((char*)base + (j + 1) * width), width);
                flag = 0;
            }
        }
        if (flag == 1)
        {
            break;
        }
    }
}
int main()
{
    int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
}

💥 结语

 好了,通过本篇博客我们模拟实现简单计算器,认识到了什么是函数指针数组,以及使用的例子,还略微提及了指向函数指针数组的指针,以及最后的回调函数,以及后续通过回调函数而展开冒泡排序与qsort函数的实现!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值