函数重载

MQL5 允许在同一源代码中定义同名但参数数量或类型不同的函数。这种方法被称为函数重载。如果不同输入可以触发相同操作,则通常应用这种方法。签名的差异允许编译器根据传递的自变量自动确定调用哪个函数。但有一些具体要注意的细节。

函数不能仅仅是返回类型不同。如果只是返回类型不同,并不会触发重载机制,并返回错误“函数已经定义并且具有不同的类型”。

如果同名函数有不同数量的参数,并且“额外的”参数被声明为可选,那么编译器无法确定调用哪个参数。这将产生错误“对具有相同参数的重载函数的调用不明确”。

当调用重载函数时,编译器匹配可用重载中的自变量和参数。如果没有找到完全匹配,编译器会尝试添加/移除 const 修饰符,并执行数字类型扩展和 算术转换。在 对象指针的情况下,使用类继承规则。

使用相同位置处不同数量的参数或不相关的参数类型(例如一个数字和一个字符串),通常很容易选择。但是,如果要将参数类型隐式地从一种类型转换到另一种类型,可能会出现歧义。

例如,这里有两个求和函数:

double sum(double v1double v2)
{
   return v1 + v2;
}
 
int sum(int v1int v2)
{
   return v1 + v2;
}

那么下面的调用将导致一个错误:

sum(13.14); // overloaded function call is ambiguous

对于每次重载,编译器都会认为有错误:对于 double sum(double v1, double v2) 函数,需要隐式地将第一个自变量转换为 double,而对于 int sum(int v1, int v2),需要转换 int 中的第二个自变量。

可以这样理解术语“重载 (overload)”:一个重复使用的名称过度 (over)“承担”(load) 了比常规名称(常规名称只用于一个函数)重数倍的“职责”。

我们来试试重载矩阵转置的函数。我们已经有一个 2x2 数组的例子(参见 值参数和引用参数)。让我们对一个 3x3 数组实施同样的操作。多维数组的高维度(非零)参数的大小会改变类型,如 double [][2] 不同于 double [][3]。因此,我们将重载旧版本的函数:

void Transpose(double &m[][2]);

添加一个新函数 (FuncOverload.mq5):

void Transpose(double &m[][3]);

在新版本的实现中,使用 Swap 辅助函数来互换给定索引处的两个矩阵元素很方便。

void Transpose(double &m[][3])
{
   Swap(m01);
   Swap(m02);
   Swap(m12);
}
 
void Swap(double &m[][3], const int iconst int j)
{
   static double temp;
   
   temp = m[i][j];
   m[i][j] = m[j][i];
   m[j][i] = temp;
}

对不同大小的数组使用相同表示法,现在我们可以从 OnStart 中调用这两个函数。编译器自己会生成对正确版本的调用。

double a[2][2] = {{12}, {34}};
Transpose(a);
...
double b[3][3] = {{123}, {456}, {789}};
Transpose(b);

须注意,尽管参数上的 const 修饰符改变了函数的原型,但这个差异不足以构成重载。如果两个同名函数的区别仅在于某个参数有无 const,它们可以被认为是相同的。这将导致错误“函数已经定义并且有函数体”。发生这种错误是因为,对于值参数,在赋值自变量时 const 修饰符被丢弃(因为根据定义,值参数不能改变发起调用的代码中的自变量),这将不允许基于这种情况选择一个重合函数。

为了证明这一点,在脚本中添加一个函数就足够了:

void Swap(double &m[][3], int iint j);

对于现有函数,这是一个不成功的重载:

void Swap(double &m[][3], const int iconst int j);

这两个函数之间的唯一区别是 ij 参数的const 修饰符。因此,它们都适合用 int 类型的自变量调用和按值传递。

按引用传递参数时,仅通过 const/非 const 属性差异实现的重载会成功,因为对于引用而言,const 修饰符至关重要(它会改变类型并消除隐式转换的可能性)。这一点在脚本中通过几个函数得到了证明:

void SwapByReference(double &m[][3], int &iint &j)
{
   Print(__FUNCSIG__);
}
 
void SwapByReference(double &m[][3], const int &iconst int &j)
{
   Print(__FUNCSIG__);
}
 
void OnStart()
{
   // ...
   {
      int i = 0j = 1;
      SwapByReference(bij);
   }
   {
      const int i = 0j = 1;
      SwapByReference(bij);
   }
}

它们生成几乎为空的存根,其中每个函数的签名都是使用 Print(__FUNCSI__) 调用打印的。这样便可以确保根据自变量的 const 属性调用相应版本的函数。