函数指针(function pointer)and 回调函数(callback)

翻译自:https://www.cprogramming.com/tutorial/function-pointers.html

函数指针是一个变量,用于存储稍后可以通过该函数指针调用的函数的地址。这很有用,因为函数封装了行为。例如,每次你需要一个特定的行为,比如绘制一条线,而不是写出一堆代码,你需要做的就是调用该函数。但有时你想在不同的时间在基本相同的代码片段中选择不同的行为。继续阅读具体的例子。

#include 
#define GET_MAX 0
#define GET_MIN 1

int get_max(int i ,int j)
{
    return i>j?i:j ;
}
int get_min(int i ,int j)
{
    return i>j?j:i ;
}

int compare(int i,int j,int flag)
{
    int ret;
    int (*p)(int,int);     //这里定义了一个函数指针,可以根据传入的flag,灵活决定
    if(flag==GET_MAX)    //决定调用哪个函数
        p=get_max;
    else 
        p=get_min;
    ret = p(i,j);
    return ret;
}

int main()
{
    ....
}

函数指针的示例使用

作为其他函数的参数的函数

如果您要编写排序例程,您可能希望允许函数的调用者选择数据排序的顺序; 一些程序员可能需要按升序对数据进行排序,其他程序员可能更喜欢降序排列,而其他程序员可能需要类似但不完全喜欢其中一种选择的程序。让你的用户指定做什么的一种方法是提供一个标志作为函数的参数,但这是不灵活的; sort函数只允许一组固定的比较类型(例如,升序和降序)。 

允许用户选择如何对数据进行排序的更好方法就是让用户将函数传递给sort函数。此函数可能需要两个数据并对它们进行比较。我们稍后会看一下这个的语法。

回调函数

函数指针的另一个用途是设置在特定事件发生时调用的“监听器”或“回调”函数。调用该函数,这会通知您的代码发生了一些感兴趣的事情。 

为什么要用回调函数编写代码?在使用某人的库编写代码时,您经常会看到它。一个例子是当你为图形用户界面(GUI)编写代码时。大多数情况下,用户将与允许鼠标指针移动并重绘界面的循环进行交互。但是,有时用户会单击按钮或在字段中输入文本。这些操作是“事件”,可能需要您的程序需要处理的响应。你的代码怎么知道发生了什么?使用回调函数!用户的单击应该使接口调用您编写的用于处理事件的函数。 

要了解何时可以执行此操作,请考虑如果使用具有“create_button”函数的GUI库可能会发生什么。它可能需要按钮应出现在屏幕上的位置,按钮的文本以及单击按钮时要调用的功能。假设目前C(和C ++)有一个名为function的泛型“函数指针”类型,这可能如下所示:

void create_button(int x,int y,const char * text,function callback_func);

只要单击该按钮,就会调用callback_func。究竟callback_func的作用取决于按钮; 这就是为什么允许create_button函数获取函数指针是有用的。

函数指针语法

声明函数指针的语法起初可能看起来很混乱,但在大多数情况下,一旦你理解了正在发生的事情,它就非常简单。

定义形式:

        类型 (*指针变量名)(参数列表);

我们来看一个简单的例子:

void(* foo)(int);

void func()->void func(int)->void *func(int)->void (*func)(int)
上面的图示能看明白吧?func是一个函数指针,它的返回类型为空,它所指向的函数接收一个int型的参数。若是写成void *func(int)则变成了:func是一个函数,它的返回类型是空指针,它接受一个int型参数。

所以 void (*signal( int sinno,void(*func)(int)) ) (int)  意思是:
signal是一个函数指针,它的返回类型是void,它接收一个int类型的参数;不过这个指针是另一个函数的返回值,它接收2个参数,第一个是int,第二个已经解释过了。

为函数指针编写声明的关键是你只是写出声明一个函数,但有(* func_name),你通常只是把func_name。 
 

读取函数指针声明

当有更多的星星投入时,人们有时会感到困惑:

void *(* foo)(int *);

在这里,关键是要从里到外阅读; 注意表达式的最内层元素是* foo,否则它看起来像一个普通的函数声明。* foo应该引用一个返回void *并接受int *的函数。因此,foo是指向这样一个函数的指针。

初始化功能指针

要初始化函数指针,必须在程序中为其指定函数的地址。语法与任何其他变量一样:

#include 
void my_int_func(int x)
{
    printf( "%d\n", x );
}

int main()
{
    void (*foo)(int);
    /* the ampersand is actually optional */
    foo = &my_int_func;

    return 0;
}

(注意:所有示例都编写为与C和C ++兼容。)

使用功能指针

要调用函数指针指向的函数,可以将函数指针视为要调用的函数的名称。调用它的行为执行取消引用; 没有必要自己做:

#include 
void my_int_func(int x)
{
    printf(“%d \ n”,x);
}


int main()
{
    void(* foo)(int);
    foo =&my_int_func;

    / *调用my_int_func(注意你不需要写(* foo)(2))* /
    foo(2);
    / *但如果你愿意,你可以* /
    (* foo)(2);

    return 0;
}

注意函数指针语法是灵活的; 它可以看起来像指针的大多数其他用途,使用&和*,或者您可以省略语法的这一部分。这类似于处理数组的方式,其中裸数组衰减到指针,但您也可以在数组前加上&以请求其地址。

野指针(Uninitialized pointers are known as wild pointers)

让我们回到排序示例,我建议使用函数指针来编写泛型排序例程,其中可以由调用排序函数的程序员指定确切的顺序。事实证明,C函数qsort就是这样做的。 

在Linux手册页中,我们有qsort的以下声明(来自stdlib.h):

void qsort(void * base,size_t nmemb,size_t size,int(* compar)(const void *,const void *));

注意使用void * s允许qsort对任何类型的数据进行操作(在C ++中,你通常使用模板来完成这个任务,但是C ++也允许使用void *指针)因为void *指针可以指向任何东西。因为我们不知道void *数组中单个元素的大小,所以除了给出长度,大小的标准要求之外,我们必须给qsort要排序的数组的元素数量nmemb,base。 ,输入。 

但我们真正感兴趣的是qsort的compar参数:它是一个函数指针,它接受两个void * s并返回一个int。这允许任何人指定如何对数组的元素进行排序,而无需编写专门的排序算法。另请注意,compar返回一个int; 如果第一个参数小于第二个参数,则指向的函数应返回-1;如果它们相等则返回0;如果第二个参数小于第一个参数,则返回1。 

例如,要按升序对数字数组进行排序,我们可以编写如下代码:

#include 

int int_sorter( const void *first_arg, const void *second_arg )
{
    int first = *(int*)first_arg;
    int second = *(int*)second_arg;
    if ( first < second )
    {
        return -1;
    }
    else if ( first == second )
    {
        return 0;
    }
    else
    {
        return 1;
    }
}

int main()
{
    int array[10];
    int i;
    /* fill array */
    for ( i = 0; i < 10; ++i )
    {
        array[ i ] = 10 - i;
    }
    qsort( array, 10 , sizeof( int ), int_sorter );
    for ( i = 0; i < 10; ++i )
    {
        printf ( "%d\n" ,array[ i ] );
    }

}

使用多态和虚函数代替函数指针(C ++)

您通常可以通过使用虚函数来避免显式函数指针的需要。例如,您可以编写一个排序例程,该例程接受一个提供名为compare的虚函数的类的指针:

class Sorter
{
    public:
    virtual int compare (const void *first, const void *second);
};

// cpp_qsort, a qsort using C++ features like virtual functions
void cpp_qsort(void *base, size_t nmemb, size_t size, Sorter *compar);

在cpp_qsort内部,只要需要进行比较,就应该调用compar-> compare。对于覆盖此虚函数的类,sort例程将获取该函数的新行为。例如:

class AscendSorter : public Sorter
{

    virtual int compare (const void*, const void*)
    {
        int first = *(int*)first_arg;
        int second = *(int*)second_arg;
        if ( first < second )
        {
            return -1;
        }
        else if ( first == second )
        {
            return 0;
        }
        else
        {
            return 1;
        }
    }
};

然后你可以将指向AscendSorter实例的指针传递给cpp_qsort,以按升序对整数进行排序。

但你真的不使用功能指针吗?

虚函数是在幕后使用函数指针实现的,所以你真的在使用函数指针 - 只是编译器让你的工作更轻松。使用多态可以是一种合适的策略(例如,它被Java使用),但它确实导致必须创建对象而不是简单地传入函数指针的开销。

功能指针摘要

句法

声明

声明一个函数指针,就像你声明一个函数一样,除了名字像* foo而不只是foo:

void(* foo)(int);

初始化

您可以通过命名来获取函数的地址:

void foo();
func_pointer = foo;

或者通过在&符号前面添加函数名称:

void foo();
func_pointer =&foo;

调用

调用指向的函数,就像调用函数一样。

func_pointer(arg1,arg2);

或者你可以选择在调用它指向的函数之前取消引用函数指针:

(* func_pointer)(arg1,arg2);

功能指针的好处

  • 函数指针提供了一种传递指令的方法,用于执行某些操作
  • 您可以编写灵活的函数和库,允许程序员通过将函数指针作为参数传递来选择行为
  • 通过使用具有虚函数的类也可以实现这种灵活性

你可能感兴趣的:(C/C++)