函数指针(typedef)

MQL5 具有 typedef 关键字,可用于描述一种特殊类型的函数指针。

typedef 在 C++ 中有更广泛的应用,而在 MQL5 中,typedef 只用于函数指针。

新类型声明的语法如下:

typedef function_result_type ( *function_type )( [list_of_input_parameters] ) ;

function_type 标识符定义了一个类型名称,该名称成为指向任意函数的指针的同义词(别名),这类函数返回给定类型 function_result_type 的值,并接受输入参数列表 (list_of_input_parameters)。

例如,我们可以有两个具有相同原型的函数(两个 double 类型的输入参数,结果类型也是 double),它们执行不同的算术运算:加法和减法 (FuncTypedef.mq5)。

double plus(double v1double v2)
{
   return v1 + v2;
}
 
double minus(double v1double v2)
{
   return v1 - v2;
}

它们的通用原型很容易描述为指针用途:

typedef double (*Calc)(doubledouble);

该条目将 Calc 类型引入到程序中,使用该条目,你可以定义一个变量/参数,用于存储/传递对具有这种原型的任何函数的引用,包括 plusminus 函数。此为指针类型,因为说明中使用了字符 * (*Calc)。在学习 OOP 的时候,我们会深入学习指针星号的特性。

使用这样的指针类来创建自定义算法是很方便的,这些算法可以根据输入数据动态调用与别名相对应的不同函数。

特别是,我们可以引入一个通用的计算器函数:

double calculator(Calc ptrdouble v1double v2)
{
   if(ptr == NULLreturn 0;
   return ptr(v1v2);
}

它的第一个参数是用 Calc 类型声明的。由于这一点,我们可以传递一个带有合适原型的任意函数给它,并因此执行一些操作,而 calculator 函数并不知道这些操作的本质。计算器函数通过将调用委托给指针来实现这一点:ptr(v1, v2)。因为 ptr 是一个函数指针,所以这种语法不仅类似于函数调用,而且实际上调用指针所保存的函数。

注意,我们根据特殊值 NULL(NULL 相当于指针的零)预先检查 ptr 参数。事实是指针可能不指向任何位置,也就是说可能没有被初始化。因此,在脚本中,我们描述了一个全局变量:

Calc calc;

它没有指针。如果不是针对 NULL 的“保护”,用“空”指针 calc 调用 calculator 会导致运行时错误“函数指针调用无效”。

在第一个参数中使用不同的指针调用 calculator 函数会产生以下结果(显示在注释中):

void OnStart()
{
   Print(calculator(plus12));   //  3
   Print(calculator(minus12));  // -1
   Print(calculator(calc12));   //  0
}

注意,如果没有显式初始化,所有函数指针都用零值填充。这适用于给定类型的全局变量和局部变量。

typedef 定义的指针类型可以从函数中返回,例如:

Calc generator(ushort type)
{
   switch(type)
   {
      case '+': return plus;
      case '-': return minus;
   }
   return NULL;
}

此外,函数指针的类型经常用于回调函数(callback,参见 FuncCallback.mq5)。假设我们有一个执行冗长计算的 DoMath 函数(很可能是在一个单独的 中实现的)。就用户界面的便利性和友好性而言,向用户显示一个进度指示是很棒的。为此,您可以定义一个特殊类型的函数指针,用于通知已完成工作的百分比 (ProgressCallback),并将此类型的参数添加到 DoMath 函数中。在 DoMath 代码中,应该定期调用传递的函数:

typedef void (*ProgressCallback)(const float percent);
 
void DoMath(double &bigdata[], ProgressCallback callback)
{
   const int N = 1000000;
   for(int i = 0i < N; ++i)
   {
      if(i % 10000 == 0 && callback != NULL)
      {
         callback(i * 100.0f / N);
      }
      
      // long calculations
   }
}

然后,发起调用的代码可以定义所需的回调函数,将指向它的指针传递给 DoMath,并随着计算的进行接收更新。

void MyCallback(const float percent)
{
   Print(percent);
}
  
void OnStart()
{
   double data[] = {0};
   DoMath(dataMyCallback);
}

函数指针仅适用于 MQL5 中定义的自定义函数。它们不能指向 MQL5 API 的 内置函数