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

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

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

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

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

相关文章
|
4月前
|
C语言
指针进阶(C语言终)
指针进阶(C语言终)
|
13天前
|
C++
指针中的回调函数与qsort的深度理解与模拟
本文详细介绍了回调函数的概念及其在计算器简化中的应用,以及C++标准库函数qsort的原理和使用示例,包括冒泡排序的模拟实现。
11 1
|
19天前
|
算法 搜索推荐 C语言
【C语言篇】深入理解指针4(模拟实现qsort函数)
【C语言篇】深入理解指针4(模拟实现qsort函数)
18 2
|
11天前
魔法指针 之 函数指针 回调函数
魔法指针 之 函数指针 回调函数
8 0
|
13天前
|
存储
一篇文章了解区分指针数组,数组指针,函数指针,链表。
一篇文章了解区分指针数组,数组指针,函数指针,链表。
15 0
|
2月前
|
存储 程序员 C语言
指针的高级应用:指针数组、数组指针、函数指针等。
指针的高级应用:指针数组、数组指针、函数指针等。
93 0
|
4月前
|
Java 程序员 Linux
探索C语言宝库:从基础到进阶的干货知识(类型变量+条件循环+函数模块+指针+内存+文件)
探索C语言宝库:从基础到进阶的干货知识(类型变量+条件循环+函数模块+指针+内存+文件)
43 0
|
4月前
|
C语言
C语言中的函数指针、指针函数与函数回调
C语言中的函数指针、指针函数与函数回调
|
13天前
|
C语言
无头链表二级指针方式实现(C语言描述)
本文介绍了如何在C语言中使用二级指针实现无头链表,并提供了创建节点、插入、删除、查找、销毁链表等操作的函数实现,以及一个示例程序来演示这些操作。
15 0
|
1月前
|
存储 人工智能 C语言
C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。