【C语言】进阶指针(二)—>函数指针与回调函数

简介: 【C语言】进阶指针(二)—>函数指针与回调函数

前言:

今天我们继续学习指针的进阶,本篇内容主要围绕函数指针与回调函数进行,并且会模拟实现库函数qsort来深入理解回调函数。

你可以解释下面的代码么?

//代码1

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

//代码2

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

一、函数指针

函数指针是指针,本质是指针,比如:

void (*pfun1)();
void* pfun2();

我们知道函数指针本质是指针,因此pfun应首先与*结合变为指针

明显pfun1为函数指针,其指向的函数没有参数,返回值类型为void,而pfun2是返回值类型为void*的函数。

了解了函数指针的概念我们看文章开头的两端复杂代码。

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

代码1分析:

1、将0强制类型转换为void(*)()类型的函数指针。

2、调用0地址处的函数。

代码2分析:

signal是一个参数类型为整型int,函数指针类型void(*)(int),返回类型为函数指针类型void(*)(int)的函数。

但是这样的写法太过复杂,我们可以利用typedef重定义。如下:

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

二、函数指针数组

函数指针数组是数组,本质是数组。

因为[]的优先级高于*,那么只需要让变量(数组名)先与[]结合变成数组,其数组内容为函数指针即可。

int (*parr[10])();//数组中元素的数据类型为int(*)()

下面给出一个函数指针应用实例:

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

在应用时,利用函数指针运行函数时,函数指针前的解引用操作符*可省略,此处主要是方便读者理解,对于编译器而言,此处的解引用符号无意义。

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

指向函数指针数组的指针是指针,本质是指针,它指向一个数组,数组中的元素都是函数指针。

为了更好的区分,我把函数指针,函数指针数组和指向函数指针数组的指针在这里统一在进行区分讲解。

int (*pf)(int, int);             //函数指针
int (*pfArr[])(int, int);        //函数指针数组
int (*(*p)[])(int, int) = &pfArr;//指向函数指针数组的指针

其实我们只需要知道我们要定义的是指针还是数组这一基本原则就可以很好的区分,搞清楚变量是先与*结合还是先与[]结合

对于函数指针来说,pf与*结合就决定了他是指针,指针类型就是去掉pf,即int(*)(int ,int),指针指向的就去掉*p,是int (int,int)一个参数为int,int,返回值为int的函数。

对于函数指针数组来说,pfArr先与[]结合就决定了他是数组,数组中元素的数据类型为去掉pfArr[],即int (*)(int, int)。

对于指向函数指针数组的指针来说,p先与*结合就决定了他是指针,指针类型就是去掉p,即int (*(*)[])(int, int),指针指向的就去掉*p,是int (*[])(int, int)函数指针数组。

四、回调函数(模拟实现库函数qsort

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

通俗的讲回调函数是将自己的地址作为参数传递给另一个函数,由这个函数调用使用的,回调函数不由该函数实现方直接调用,而是间接的需要另外一个函数调用使用。

接下来我会通过对库函数qsort的模拟实现讲解回调函数。

(一)void*类型指针的作用

在模拟实现库函数qsort之前,我们先来讲解以下void*的作用。

void*类型的指针不能直接进行解引用操作,也不能直接进行指针运算。

但是void*类型的指针可以接收任意类型的地址,所以它广泛应用于函数参数。

比如:

int main()
{
  int a = 10;
  int* pa = &a;
  char* pc = &a;//err
  void* pd = &a;//ok
  pd++;         //err
  *pd;          //err
}

(二)模拟实现库函数qsort()

模拟实现qsort的目的是为了更好的理解回调函数,所以这里模拟的qsort排序方法我们使用冒泡排序。

首先要实现qsort我们需要了解qsort的返回值,参数以及功能等信息,我们进入cplusplus.com - The C++ Resources Network查询qsort函数。

根据查询的内容,我们了解到,该函数有四个参数,分别为base、num、size、compar,大致的意思我已经标在图中,这里我着重说一下compar,以及回调函数为何要利用qsort来讲解。

qsort的优点在于,它可以排序任意数据类型的数据,可以是整型也可以是字符型还可以是结构体等等,而它如此灵活的关键就在与它利用的回调函数,它将排序依据交给使用者,利用函数指针compar传参,并利用其余三个参数的灵活配合就能在不知道待排序元素数据类型的情况下实现排序功能。

完整代码如下:

int int_cmp(const void* p1, const void* p2)//排序依据
{
    return (*(int*)p1 - *(int*)p2);
}
void _swap(void* p1, void* p2, int size)//交换元素
{
    int i = 0;
    for (i = 0; i < size; i++)
    {
        char tmp = *((char*)p1 + i);
        *((char*)p1 + i) = *((char*)p2 + i);
        *((char*)p2 + i) = tmp;
    }
}
void bubble(void* base, int count, int size, int(*cmp)(void*, void*))//冒泡函数主体
{
    int i = 0;
    int j = 0;
    for (i = 0; i < count - 1; i++)
    {
        for (j = 0; j < count - i - 1; j++)
        {
            if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
            {
                _swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
            }
        }
    }
}
int main()
{
    int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
    //char *arr[] = {"aaaa","dddd","cccc","bbbb"};
    int i = 0;
    bubble(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;
}

在这里再理解一下回调函数的概念:

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

通俗的讲回调函数是将自己的地址作为参数传递给另一个函数,由这个函数调用使用的,回调函数不由该函数实现方直接调用,而是间接的需要另外一个函数调用使用。

需要注意的是我们需要将base强制转换为char*,令该指针加减整数的步长设为1,这是qsort可以不考虑数据类型排序的原理。


第二部分进阶指针就讲到这,下一篇内容我会引入笔试题实战为大家带来更加优质的内容,关注博主不迷路🔥🔥🔥

目录
相关文章
|
2月前
|
C语言
【c语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
51 0
|
5天前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
2月前
|
C语言
【c语言】指针就该这么学(3)
本文介绍了C语言中的函数指针、typedef关键字及函数指针数组的概念与应用。首先讲解了函数指针的创建与使用,接着通过typedef简化复杂类型定义,最后探讨了函数指针数组及其在转移表中的应用,通过实例展示了如何利用这些特性实现更简洁高效的代码。
20 2
|
2月前
|
C语言
如何避免 C 语言中的野指针问题?
在C语言中,野指针是指向未知内存地址的指针,可能引发程序崩溃或数据损坏。避免野指针的方法包括:初始化指针为NULL、使用完毕后将指针置为NULL、检查指针是否为空以及合理管理动态分配的内存。
|
2月前
|
C语言
C语言:哪些情况下会出现野指针
C语言中,野指针是指指向未知地址的指针,通常由以下情况产生:1) 指针被声明但未初始化;2) 指针指向的内存已被释放或重新分配;3) 指针指向局部变量,而该变量已超出作用域。使用野指针可能导致程序崩溃或不可预测的行为。
|
2月前
|
存储 算法 C语言
C语言:什么是指针数组,它有什么用
指针数组是C语言中一种特殊的数据结构,每个元素都是一个指针。它用于存储多个内存地址,方便对多个变量或数组进行操作,常用于字符串处理、动态内存分配等场景。
|
2月前
|
编译器 C语言
【c语言】指针就该这么学(2)
本文详细介绍了指针与数组的关系,包括指针访问数组、一维数组传参、二级指针、指针数组和数组指针等内容。通过具体代码示例,解释了数组名作为首元素地址的用法,以及如何使用指针数组模拟二维数组和传递二维数组。文章还强调了数组指针与指针数组的区别,并通过调试窗口展示了不同类型指针的差异。最后,总结了指针在数组操作中的重要性和应用场景。
20 0
|
C语言 C++ Unix
深入浅出剖析C语言函数指针与回调函数(三)
前面两篇文章: http://blog.csdn.net/morixinguan/article/details/65494239 http://blog.csdn.net/morixinguan/article/details/65938128 在UNix多线程编程中,我们会使用到以下函数:...
1202 0
|
Linux C语言
深入浅出剖析C语言函数指针与回调函数(二)
上一篇博文的地址: http://blog.csdn.net/morixinguan/article/details/65494239 这节,我们来看看函数指针与回调函数在Linux内核中的应用。
1043 0
|
C语言 开发者 Linux
深入浅出剖析C语言函数指针与回调函数(一)
关于静态库和动态库的使用和制作方法。 http://blog.csdn.net/morixinguan/article/details/52451612 今天我们要搞明白的一个概念叫回调函数。 什么是回调函数? 百度的权威解释如下: 回调函数就是一个通过函数指针调用的函数。
1497 0