El propósito de la función de devolución de llamada.

Por favor agregue la descripción de la imagen.
Por favor agregue la descripción de la imagen.

1. Puntero de función

Antes de hablar de funciones de devolución de llamada, debemos comprender los punteros de función.

Todos sabemos que el alma del lenguaje C son los punteros. A menudo utilizamos punteros de números enteros, punteros de cadenas, punteros de estructura, etc.

int *p1;
char *p2;
STRUCT *p3; //STRUCT为我们定义的结构体

Pero parece que rara vez usamos punteros a funciones, generalmente usamos llamadas a funciones directamente.

Echemos un vistazo al concepto y uso de los punteros de función.

1.1 Concepto

Un puntero de función es una variable de puntero que apunta a una función.

Por lo general, la variable de puntero de la que hablamos apunta a un número entero, un carácter o una variable de matriz, mientras que el puntero de función apunta a una función.

Los punteros de función se pueden utilizar para llamar funciones y pasar parámetros como funciones normales.

Los punteros de función se definen como:

Tipo de valor de retorno de función (* nombre de variable de puntero) (lista de parámetros de función);
"Tipo de valor de retorno de función" indica a qué tipo de valor de retorno puede apuntar la variable de puntero; "Lista de parámetros de función" indica a qué parámetros puede apuntar la variable de puntero. Lista funciones. En esta lista de parámetros, solo necesita escribir el tipo de parámetro de la función.

Vemos que la definición de un puntero de función es cambiar el "nombre de la función" en la "declaración de función" a "(nombre de la variable del puntero)". Pero lo que hay que tener en cuenta aquí es que los corchetes en ambos extremos de "(nombre de la variable de puntero)" no se pueden omitir, los corchetes cambian la prioridad del operador. Si se omiten los paréntesis, no es una definición de un puntero de función sino una declaración de función, es decir, se declara una función cuyo tipo de valor de retorno es un puntero.

Entonces, ¿cómo determinar si una variable de puntero es una variable de puntero que apunta a una variable o una variable de puntero que apunta a una función? Primero, verifique si hay "" delante del nombre de la variable. Si hay "", significa que es una variable de puntero; en segundo lugar, verifique si hay paréntesis con tipos de parámetros formales después del nombre de la variable. Si los hay, es una variable puntero que apunta a la función, es decir, un puntero a función, si no lo hay Es una variable puntero que apunta a una variable.

Una última cosa a tener en cuenta es que las variables de puntero a funciones no tienen las operaciones ++ y –.

Generalmente para facilitar su uso, elegiremos

tipo de valor de retorno de función typedef (* nombre de variable de puntero) (lista de parámetros de función);
por ejemplo

typedef int (*Fun1)(int);//声明也可写成int (*Fun1)(int x),但习惯上一般不这样。
typedef int (*Fun2)(int, int);//参数为两个整型,返回值为整型
typedef void (*Fun3)(void);//无参数和返回值
typedef void* (*Fun4)(void*);//参数和返回值都为void*指针

1.2 Cómo llamar a una función usando un puntero de función

Dejame darte un ejemplo:

int Func(int x);   /*声明一个函数*/
int (*p) (int x);  /*定义一个函数指针*/
p = Func;          /*将Func函数的首地址赋给指针变量p*/
p = &Func;          /*将Func函数的首地址赋给指针变量p*/

La función Func no toma paréntesis ni parámetros al asignar valores. Dado que el nombre de la función Func representa la primera dirección de la función, después de la asignación, la variable de puntero p apunta a la primera dirección del código de la función Func().

Escribamos un programa. Después de leer este programa, comprenderá cómo usar los punteros de función:

#include <stdio.h>
int Max(int, int);  //函数声明
int main(void)
{
    
    
    int(*p)(int, int);  //定义一个函数指针
    int a, b, c;
    p = Max;  //把函数Max赋给指针变量p, 使p指向Max函数
    printf("please enter a and b:");
    scanf("%d%d", &a, &b);
    c = (*p)(a, b);  //通过函数指针调用Max函数
    printf("a = %d\nb = %d\nmax = %d\n", a, b, c);
    return 0;
}
int Max(int x, int y)  //定义Max函数
{
    
    
    int z;
    if (x > y)
    {
    
    
        z = x;
    }
    else
    {
    
    
        z = y;
    }
    return z;
}

Una nota especial es que debido a que el nombre de la función en sí puede representar la dirección de la función (puntero), al obtener el puntero de la función, puede usar directamente el nombre de la función o tomar la dirección de la función.

p = Max se puede cambiar a p = &Max
c = (*p)(a, b) se puede cambiar a c = p(a, b) 3. El
puntero de función se utiliza como parámetro de una determinada función.
La variable de puntero de función es una variable y, por supuesto, también se puede utilizar como parámetro de una función.

Ejemplo:

include <stdio.h>
#include <stdlib.h>
 
typedef void(*FunType)(int);
//前加一个typedef关键字,这样就定义一个名为FunType函数指针类型,而不是一个FunType变量。
//形式同 typedef int* PINT;
void myFun(int x);
void hisFun(int x);
void herFun(int x);
void callFun(FunType fp,int x);
int main()
{
    
    
    callFun(myFun,100);//传入函数指针常量,作为回调函数
    callFun(hisFun,200);
    callFun(herFun,300);
 
    return 0;
}
 
void callFun(FunType fp,int x)
{
    
    
    fp(x);//通过fp的指针执行传递进来的函数,注意fp所指的函数有一个参数
}
 
void myFun(int x)
{
    
    
    printf("myFun: %d\n",x);
}
void hisFun(int x)
{
    
    
    printf("hisFun: %d\n",x);
}
void herFun(int x)
{
    
    
    printf("herFun: %d\n",x);
}

Producción:

4. Puntero de función como tipo de retorno de función
Con la base anterior, no debería ser difícil escribir una función cuyo tipo de retorno sea un puntero de función. El siguiente ejemplo es una función cuyo tipo de retorno es un puntero de función:

void (* func5(int, int, float ))(int, int)
{
    
    
    ...
}

Aquí, func5 toma (int, int, float) como parámetros y su tipo de retorno es void (*)(int, int). En lenguaje C, la declaración de variables o funciones también es una materia universitaria. Si quieres saber más sobre el tema de la declaración, puedes consultar mi artículo anterior "Programación experta en C" Notas de lectura (Capítulo 1-3). El capítulo 3 de este libro dedica un capítulo completo a explicar cómo leer declaraciones en lenguaje C.

5. Matriz de punteros de función

Antes de comenzar a explicar la función de devolución de llamada, finalmente introduzcamos la matriz de punteros de función. Dado que los punteros de función también son punteros, podemos usar matrices para almacenar punteros de función. Veamos un ejemplo de una matriz de punteros de función:

/* 方法1 */
void (*func_array_1[5])(int, int, float);
 
/* 方法2 */
typedef void (*p_func_array)(int, int, float);
p_func_array func_array_2[5];

Los dos métodos anteriores se pueden utilizar para definir una matriz de punteros de función. Definen una matriz de punteros de función con 5 elementos y escriben *void (*)(int, int, float)*.

6. Resumen de punteros de función.

Constante del puntero de función: Max;Variable del puntero de función: p;

Si las llamadas numéricas son como (*myFun)(10), será inconveniente y no estará acostumbrado a escribir y leer. Es por eso que los diseñadores del lenguaje C lo diseñaron para permitir que myFun(10) sea llamado de esta forma (esto es mucho más conveniente y es lo mismo que la forma de función en matemáticas).

Las variables de puntero de función también se pueden almacenar en una matriz. Método de declaración de matriz: int (*fArray[10]) (int);

2. Función de devolución de llamada

2.1 ¿Qué es una función de devolución de llamada?

Primero echemos un vistazo a cómo la Enciclopedia Baidu define la función de devolución de llamada:

Una función de devolución de llamada es una función llamada a través de un puntero de función. Si pasa un puntero de función (dirección) como parámetro a otra función, y cuando este puntero se usa para llamar a la función a la que apunta, decimos que es una función de devolución de llamada. La función de devolución de llamada no es llamada directamente por el implementador de la función, sino que es llamada por otra parte cuando ocurre un evento o condición específica para responder al evento o condición.
Este pasaje es relativamente largo y complicado. A continuación utilizo una imagen para ilustrar qué es una devolución de llamada:

Supongamos que queremos usar una función de clasificación para ordenar la matriz, luego, en el programa principal (programa principal), primero seleccionamos una función de clasificación de biblioteca (función de biblioteca) a través de la biblioteca. Pero existen muchos algoritmos de clasificación, incluida la clasificación por burbujas, la clasificación por selección, la clasificación rápida y la clasificación por combinación. Al mismo tiempo, es posible que también necesitemos ordenar objetos especiales, como estructuras específicas, etc. La función de la biblioteca seleccionará un algoritmo de clasificación según nuestras necesidades y luego llamará a la función que implementa el algoritmo para completar el trabajo de clasificación. La función de clasificación llamada es la función de devolución de llamada.

Combinando esta imagen y la explicación anterior de la función de devolución de llamada, podemos encontrar que el punto más crítico para implementar la función de devolución de llamada es pasar el puntero de función a una función (la función de biblioteca en la imagen de arriba), y luego esta función puede La función de devolución de llamada se llama a través de este puntero. Tenga en cuenta que la función de devolución de llamada no es exclusiva del lenguaje C. Existen funciones de devolución de llamada en casi cualquier idioma. En lenguaje C, implementamos funciones de devolución de llamada mediante el uso de punteros de función.

Tengo entendido que: pasar un fragmento de código ejecutable a otros códigos, como pasar parámetros, y este código será llamado y ejecutado en un momento determinado, esto se llama devolución de llamada.

Si el código se ejecuta inmediatamente, se denomina devolución de llamada sincrónica y si se ejecuta más tarde, se denomina devolución de llamada asincrónica.

Una función de devolución de llamada es una función llamada a través de un puntero de función. Si pasa un puntero de función (dirección) como parámetro a otra función, y cuando este puntero se usa para llamar a la función a la que apunta, decimos que es una función de devolución de llamada.

La función de devolución de llamada no es llamada directamente por el implementador de la función, sino que es llamada por otra parte cuando ocurre un evento o condición específica para responder al evento o condición.

2.2 ¿Por qué utilizar la función de devolución de llamada?

Debido a que puede separar a la persona que llama del destinatario, a la persona que llama no le importa quién es el destinatario. Sólo necesita saber que hay una función llamada con un prototipo y restricciones específicos.

En resumen, la función de devolución de llamada permite al usuario pasar el puntero del método a llamar como parámetro a una función, de modo que la función pueda usar diferentes métodos de manera flexible cuando maneje eventos similares.

int Callback()    ///< 回调函数
{
    
    
    // TODO
    return 0;
}
int main()     ///<  主函数
{
    
    
    // TODO
    Library(Callback);  ///< 库函数通过函数指针进行回调
    // TODO
    return 0;
}

Las devoluciones de llamada parecen ser solo llamadas entre funciones, no diferentes de las llamadas a funciones ordinarias.

Pero si miras de cerca, puedes encontrar una diferencia clave entre los dos: en la devolución de llamada, el programa principal pasa la función de devolución de llamada a la función de la biblioteca como un parámetro.

De esta manera, siempre que cambiemos los parámetros pasados ​​a la función de la biblioteca, podemos lograr diferentes funciones ¿No te parece muy flexible? Y es muy bueno utilizar funciones de devolución de llamada cuando las funciones de la biblioteca son complejas o invisibles.

3 ¿Cómo utilizar la función de devolución de llamada?

int Callback_1(int a)   ///< 回调函数1
{
    
    
    printf("Hello, this is Callback_1: a = %d ", a);
    return 0;
}
 
int Callback_2(int b)  ///< 回调函数2
{
    
    
    printf("Hello, this is Callback_2: b = %d ", b);
    return 0;
}
 
int Callback_3(int c)   ///< 回调函数3
{
    
    
    printf("Hello, this is Callback_3: c = %d ", c);
    return 0;
}
 
int Handle(int x, int (*Callback)(int)) ///< 注意这里用到的函数指针定义
{
    
    
    Callback(x);
}
 
int main()
{
    
    
    Handle(4, Callback_1);
    Handle(5, Callback_2);
    Handle(6, Callback_3);
    return 0;
}

Como se muestra en el código anterior: puede ver que el parámetro en la función Handle() es un puntero. Al llamar a la función Handle() en la función main(), las funciones Callback_1()/Callback_2()/Callback_3() se le pasan. El nombre de la función en este momento es el puntero de la función correspondiente. En otras palabras, la función de devolución de llamada es en realidad un uso del puntero de función.

4. El siguiente es un ejemplo de una función de devolución de llamada simple que utiliza cuatro operaciones aritméticas:

#include <stdio.h>
#include <stdlib.h>
 
/****************************************
 * 函数指针结构体
 ***************************************/
typedef struct _OP {
    
    
    float (*p_add)(float, float); 
    float (*p_sub)(float, float); 
    float (*p_mul)(float, float); 
    float (*p_div)(float, float); 
} OP; 
 
/****************************************
 * 加减乘除函数
 ***************************************/
float ADD(float a, float b) 
{
    
    
    return a + b;
}
 
float SUB(float a, float b) 
{
    
    
    return a - b;
}
 
float MUL(float a, float b) 
{
    
    
    return a * b;
}
 
float DIV(float a, float b) 
{
    
    
    return a / b;
}
 
/****************************************
 * 初始化函数指针
 ***************************************/
void init_op(OP *op)
{
    
    
    op->p_add = ADD;
    op->p_sub = SUB;
    op->p_mul = &MUL;
    op->p_div = &DIV;
}
 
/****************************************
 * 库函数
 ***************************************/
float add_sub_mul_div(float a, float b, float (*op_func)(float, float))
{
    
    
    return (*op_func)(a, b);
}
 
int main(int argc, char *argv[]) 
{
    
    
    OP *op = (OP *)malloc(sizeof(OP)); 
    init_op(op);
    
    /* 直接使用函数指针调用函数 */ 
    printf("ADD = %f, SUB = %f, MUL = %f, DIV = %f\n", (op->p_add)(1.3, 2.2), (*op->p_sub)(1.3, 2.2), 
            (op->p_mul)(1.3, 2.2), (*op->p_div)(1.3, 2.2));
     
    /* 调用回调函数 */ 
    printf("ADD = %f, SUB = %f, MUL = %f, DIV = %f\n", 
            add_sub_mul_div(1.3, 2.2, ADD), 
            add_sub_mul_div(1.3, 2.2, SUB), 
            add_sub_mul_div(1.3, 2.2, MUL), 
            add_sub_mul_div(1.3, 2.2, DIV));
 
    return 0; 
}

  1. Instancia de función de devolución de llamada (muy útil)

Un pequeño proyecto para la red de módulos GPRS. Los estudiantes que lo han usado probablemente sepan que para implementar funciones de red inalámbrica para 2G, 4G, NB y otros módulos, deben seguir pasos como la inicialización del encendido del módulo, el registro de la red, consulta de calidad de la información de la red y conexión del servidor. Aquí se muestra un ejemplo: utilizar una función de máquina de estado (funciones que llaman a diferentes métodos de implementación en secuencia según los diferentes estados) y llamar a diferentes funciones en secuencia a través de funciones de devolución de llamada para implementar la función de red del módulo. , como sigue:

/*********  工作状态处理  *********/
typedef struct
{
    
    
 uint8_t mStatus;
 uint8_t (* Funtion)(void); //函数指针的形式
} M26_WorkStatus_TypeDef;  //M26的工作状态集合调用函数
 


 
/**********************************************
** >M26工作状态集合函数
***********************************************/
M26_WorkStatus_TypeDef M26_WorkStatus_Tab[] =
{
    
        
    {
    
    GPRS_NETWORK_CLOSE,  M26_PWRKEY_Off  }, //模块关机
    {
    
    GPRS_NETWORK_OPEN,  M26_PWRKEY_On  }, //模块开机
    {
    
    GPRS_NETWORK_Start,   M26_Work_Init  }, //管脚初始化
    {
    
    GPRS_NETWORK_CONF,  M26_NET_Config  }, /AT指令配置
    {
    
    GPRS_NETWORK_LINK_CTC,  M26_LINK_CTC  }, //连接调度中心  
    {
    
    GPRS_NETWORK_WAIT_CTC, M26_WAIT_CTC  },  //等待调度中心回复 
    {
    
    GPRS_NETWORK_LINK_FEM, M26_LINK_FEM  }, //连接前置机
    {
    
    GPRS_NETWORK_WAIT_FEM, M26_WAIT_FEM  }, //等待前置机回复
    {
    
    GPRS_NETWORK_COMM,  M26_COMM   }, //正常工作    
    {
    
    GPRS_NETWORK_WAIT_Sig,  M26_WAIT_Sig  },  //等待信号回复
    {
    
    GPRS_NETWORK_GetSignal,  M26_GetSignal  }, //获取信号值
    {
    
    GPRS_NETWORK_RESTART,  M26_RESET   }, //模块重启
}
/**********************************************
** >M26模块工作状态机,依次调用里面的12个函数   
***********************************************/
uint8_t M26_WorkStatus_Call(uint8_t Start)
{
    
    
    uint8_t i = 0;
    for(i = 0; i < 12; i++)
    {
    
    
        if(Start == M26_WorkStatus_Tab[i].mStatus)
        {
    
              
      return M26_WorkStatus_Tab[i].Funtion();
        }
    }
    return 0;
}

Por lo tanto, si alguien quiere crear un proyecto de red de módulos NB, puede copiar el marco anterior y solo necesita modificar la implementación específica dentro de la función de devolución de llamada, o agregar o reducir funciones de devolución de llamada, y luego la red de módulos se puede implementar de manera muy simple y rápida. .

Supongo que te gusta

Origin blog.csdn.net/weixin_40933653/article/details/133418822
Recomendado
Clasificación