【C进阶】第十二篇——指针(二)(函数指针+函数指针数组+回调函数)

简介: 【C进阶】第十二篇——指针(二)(函数指针+函数指针数组+回调函数)

函数指针


函数指针的定义


整型指针是指向整形的指针,数组指针式指向数组的指针,其实函数指针就是指向函数的指针

和学习数组指针一样,学习函数指针我们也需要知道三点:

  • ()的优先级要高于*
  • 一个变量出去了变量名,便是它的变量类型
  • 一个指针变量除去了变量名和*,便是指针指向的内容的类型

举个例子:

#include<stdio.h>
int Add(int x, int y)
{
  return x + y;
}
int main()
{
  int(*p)(int, int) = &Add;//取出函数的地址放在函数指针p中
  return 0;
}

那么,函数指针p的类型我们是如何创建的呢?

首先,p是一个指针,所以必须先与 * 结合,而( )的优先级高于 * ,所以我们要把 * 和p用括号括起来,让它们先结合。

指针p指向的内容,即函数Add的类型是int (int,int),所以函数指针p就变成了int(*p)(int,int)。

去掉变量名p后,便是该函数指针的变量类型int( * )(int,int)。

image.png

函数指针的使用


知道了如何创建函数指针,那么函数指针应该如何使用呢?

1.函数指针的赋值

对于数组来说,数组名和&数组名它们代表的意义不同,数组名代表的是数组首元素地址,而&数组名代表的是整个数组的地址。

但是对于函数来说,函数名和&函数名它们代表的意义却是相同的,它们都代表函数的地址(毕竟你也没有听说过函数有首元素这个说法吧)。

所以,当我们对函数指针赋值时可以赋值为&函数名,也可以赋值为函数名。

1.  int(*p)(int, int) = &Add;
2.  int(*p)(int, int) = Add;

 2.通过函数指针调用函数

方法一:我们知道,函数指针存放的是函数的地址,那么我们将函数指针进行解引用操作,便能找到该函数了,于是就可以通过函数指针调用该函数。

#include<stdio.h>
int Add(int x, int y)
{
  return x + y;
}
int main()
{
  int a = 10;
  int b = 20;
  int(*p)(int, int) = &Add;
  int ret = (*p)(a, b);//解引用找到该函数
  printf("%d\n", ret);
  return 0;
}

我们可以理解为, * 和&是两个相反的操作符,像正号(+)和负号(-)一样,一个 * 操作符可以抵消一个&操作符。

image.png

方法二:我们在函数指针赋值中说到,函数名和&函数名都代表函数的地址,我们可以赋值时直接赋值函数名,那么通过函数指针调用函数的时候我们就可以不用解引用操作符就能找到函数了

#include<stdio.h>
int Add(int x, int y)
{
  return x + y;
}
int main()
{
  int a = 10;
  int b = 20;
  int(*p)(int, int) = Add;
  int ret = p(a, b);//不用解引用
  printf("%d\n", ret);
  return 0;
}

image.png

函数指针数组


函数指针数组的定义


我们知道,数组是一个存放相同类型数据的空间,我们已经认识了指针数组,比如:

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

那如果要将一系列相同类型的函数指针存放到一个数组中,那么这个数组就叫做函数指针数组,比如:

1.  int(*pArr[10])(int, int);
2.  //数组pArr有10个元素,每个元素的类型是int(*)(int,int)

函数指针数组的创建只需在函数指针创建的基础上加上[ ]即可。

比如,你要创建一个函数指针数组,这个数组中存放的函数指针的类型均为int(*)(int,int),如果你要创建一个函数指针为该类型,那么该函数指针的写法为int(*p)(int,int),现在你要创建一个存放该指针类型的数组,只需在变量名的后面加上[ ]即可,int(*pArr[10])(int,int)。

函数指针数组的使用-模拟计算器


函数指针数组一个很好的运用场景,就是计算机的模拟实现:

#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");
}//菜单
double Add(double x, double y)
{
  return x + y;
}//加法函数
double Sub(double x, double y)
{
  return x - y;
}//减法函数
double Mul(double x, double y)
{
  return x*y;
}//乘法函数
double Div(double x, double y)
{
  return x / y;
}//除法函数
int main()
{
  int input = 0;
  double x = 0;//第一个操作数
  double y = 0;//第二个操作数
  double ret = 0;//运算结果
  double(*pArr[])(double, double) = { 0, Add, Sub, Mul, Div };
  //函数指针数组-转移表
  int sz = sizeof(pArr) / sizeof(pArr[0]);//计算数组的大小
  do
  {
    menu();
    printf("请输入:>");
    scanf("%d", &input);
    if (input == 0)
      printf("退出程序\n");
    else if (input > 0 && input < sz)
    {
      printf("请输入两个操作数:>");
      scanf("%lf %lf", &x, &y);
      ret = pArr[input](x, y);
      printf("ret=%lf\n", ret);
    }
    else
      printf("选择错误,请重新选择!\n");
  } while (input);//当input不为0时循环继续
  return 0;
}

代码中,函数指针数组存放的是一系列参数和返回类型相同的函数名,即函数指针。将0放在该函数指针数组的第一位是为了让用户输入的数字input与对应的函数指针下标相对应。

该代码若不使用函数指针数组,而选择使用一系列的switch分支语句当然也能达到想要的效果,但会使代码出现许多重复内容,而且当以后需要增加该计算机功能时又需要增加一个case语句,而使用函数指针数组,当你想要增加计算机功能时只需在数组中加入一个函数名即可。

指向函数指针数组的指针


既然存在函数指针数组,那么必然存在指向函数指针数组的指针。

  int(*p)(int, int);
  //函数指针
  int(*pArr[5])(int, int);
  //函数指针数组
  int(*(*pa)[5])(int, int) = &pArr;
  //指向函数指针数组的指针

那指向函数指针数组的指针的类型是如何写的呢?

image.png

回调函数


回调函数的定义


回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

举个简单的例子:

#include<stdio.h>
void test1()
{
  printf("hello\n");
}
void test2(void(*p)())
{
  p(); //指针p被用来调用其所指向的函数
}
int main()
{
  test2(test1);//将test1函数的地址传递给test2
  return 0;
}

在该代码中test1函数不是由该函数的实现方直接调用,而是将其地址传递给test2函数,在test2函数中通过函数指针间接调用了test1函数,那么函数test1就被称为回调函数。

回调函数的使用-qsort函数


其实回调函数并不是很难见到,在用于快速排序的库函数qsort中便运用了回调函数。

void qsort(void*base,size_t num,size_t width,int(*compare)(const void*e1,const void*e2));

qsort函数的第一个参数是待排序的内容的起始位置;第二个参数是从起始位置开始,待排序的元素个数;第三个参数是待排序的每个元素的大小,单位是字节;第四个参数是一个函数指针。qsort函数的返回类型为void。

qsort函数的第四个参数是一个函数指针,该函数指针指向的函数的两个参数的参数类型均为const void*,返回类型为int。当参数e1小于参数e2时返回小于0的数;当参数e1大于参数e2时返回大于0的数;当参数e1等于参数e2时返回0。


列如,我们要排一个整型数组:

#include<stdio.h>
int compare(const void* e1, const void* e2)
{
  return *((int*)e1) - *((int*)e2);
}//自定义的比较函数
int main()
{
  int arr[] = { 2, 5, 1, 8, 6, 10, 9, 3, 5, 4 };
  int sz = sizeof(arr) / sizeof(arr[0]);//元素个数
  qsort(arr, sz, 4, compare);//用qsort函数将arr数组排序
  return 0;
}

最终arr数组将被排为升序。


注意:qsort函数默认将待排序的内容排为升序,如果我们要排为降序可将自定义的比较函数的两个形参的位置互换一下即可。


在qsort函数中我们传入了一个函数指针,最终qsort函数会在其内部通过该函数指针调用该函数,那么我们的这个自定义比较函数就被称为回调函数。

相关文章
|
1月前
使用指针访问数组元素
【10月更文挑战第30天】使用指针访问数组元素。
35 3
|
17天前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
21天前
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。
|
21天前
|
容器
在使用指针数组进行动态内存分配时,如何避免内存泄漏
在使用指针数组进行动态内存分配时,避免内存泄漏的关键在于确保每个分配的内存块都能被正确释放。具体做法包括:1. 分配后立即检查是否成功;2. 使用完成后及时释放内存;3. 避免重复释放同一内存地址;4. 尽量使用智能指针或容器类管理内存。
|
21天前
|
存储 NoSQL 编译器
C 语言中指针数组与数组指针的辨析与应用
在C语言中,指针数组和数组指针是两个容易混淆但用途不同的概念。指针数组是一个数组,其元素是指针类型;而数组指针是指向数组的指针。两者在声明、使用及内存布局上各有特点,正确理解它们有助于更高效地编程。
|
25天前
|
存储 人工智能 算法
数据结构实验之C 语言的函数数组指针结构体知识
本实验旨在复习C语言中的函数、数组、指针、结构体与共用体等核心概念,并通过具体编程任务加深理解。任务包括输出100以内所有素数、逆序排列一维数组、查找二维数组中的鞍点、利用指针输出二维数组元素,以及使用结构体和共用体处理教师与学生信息。每个任务不仅强化了基本语法的应用,还涉及到了算法逻辑的设计与优化。实验结果显示,学生能够有效掌握并运用这些知识完成指定任务。
44 4
|
1月前
使用指针访问数组元素
【10月更文挑战第31天】使用指针访问数组元素。
40 2
|
1月前
|
算法 索引
单链表题+数组题(快慢指针和左右指针)
单链表题+数组题(快慢指针和左右指针)
39 1
|
2月前
|
存储
如何使用指针数组来实现动态二维数组
指针数组可以用来实现动态二维数组。首先,定义一个指向指针的指针变量,并使用 `malloc` 为它分配内存,然后为每个子数组分配内存。通过这种方式,可以灵活地创建和管理不同大小的二维数组。
|
2月前
|
存储
如何通过指针数组来实现二维数组?
介绍了二维数组和指针数组的概念及其区别,详细讲解了如何使用指针数组模拟二维数组,包括定义与分配内存、访问和赋值元素、以及正确释放内存的步骤,适用于需要动态处理二维数据的场景。