值参数和引用参数
自变量可以通过两种方式传递给函数:通过值传递和通过引用传递。
到目前为止,我们看到的所有情况都是通过值传递的。这种方法意味着,通过调用代码片段准备的自变量值被复制到一个新变量中,也就是与函数对应的输入变量。否则,自变量和输入变量无法关联起来。函数内部变量的所有后续操作都不会以任何方式影响自变量。
为说明引用参数,请在类型的右侧添加一个 & 符号。许多程序员喜欢在参数名后面附加一个 & 符号,从而强调参数是对给定类型的引用。例如,以下各项是等效的:
void func(int ¶meter);
|
函数被调用时,不会为引用参数创建对应的局部变量。相反,为该参数指定的自变量,在函数内部以输入参数的名称(别名)出现。因此,该值不会被拷贝,而是在内存中的相同地址处使用。因此,对函数中参数的修改体现在其关联自变量的状态。由此出现一个重要的特征。
只能指定一个变量(左值,参见 赋值运算符)作为引用参数的自变量。否则,我们将遇到错误“参数作为引用传递,应为变量”。
按引用传递用于以下几种情况:
- 通过消除值拷贝来提高程序的效率;
- 在使用 return 返回单个值还不够的情况下,将修改后的数据从函数传递给发起调用的代码;
第一点对于潜在的大型变量(如字符串或数组)尤其重要。
为了区分引用参数的第一个和第二个用途,如果作者不希望改变函数内部的参数时,则建议添加 const 修饰符。这将提醒您并向其他开发人员表明,在函数内部传递变量不会带来副作用。
如果可以对引用参数应用 const 修饰符而未应用,则会导致整个函数调用层次结构出现问题。事实上,调用这样的函数需要非常量自变量。否则,将出现错误“常量变量不能作为引用传递”。结果可能会逐渐导致为了代码的可编译性,而不得不从所有函数中的所有参数中去掉 const 修饰符。这实际上扩大了潜在 bug 的范围,无意中破坏了变量。应该以相反的方式纠正这种情况:在不需要返回和修改值的地方使用 const。
为了比较在 FuncDeclaration.mq5 脚本中传递参数的几种方式,实现了几个函数:FuncByValue ― 按值传递,FuncByReference― 按引用传递, FuncByConstReference ― 按常量引用传递。
void FuncByValue(int v)
|
在 OnStart 函数中,我们调用所有这些函数,并观察它们对用作自变量的 i 变量的影响。注意,通过引用传递参数不会改变函数调用语法。
void OnStart()
|
字面量只能传递给 FuncByValue 函数,因为其他函数需要引用(即变量)作为自变量。
FuncByReference 函数不能用变量 j 调用,因为后者被声明为常量,并且该函数声明了改变其参数的能力(或意图),因为它没有 const 修饰符。这将产生错误“常量变量不能作为引用传递”。
脚本还介绍了 Transpose 函数:该函数用于转置一个 2x2 矩阵,该矩阵通过引用作为二维数组传递。
void Transpose(double &m[][2])
|
从 OnStart 函数调用演示了本地数组 a内容的预期变化。
double a[2][2] = {{-1, 2}, {3, 0}};
|
在 MQL5 中,数组参数总是作为动态数组的内部结构传递(参见 数组特性 一节)。因此,这种参数的说明必须在第一维中具有开放大小,即第一对方括号内为空。
这并不妨碍在必要时向函数传递实际自变量,实际自变量是一个定长数组(如我们的例子所示)。然而,ArrayResize 之类的函数无法调整大小或以其他方式重新组织这种掩码定长数组。
除了第一维之外,数组在所有维度上的大小都必须与参数和自变量相匹配。否则,我们将遇到错误“不允许参数转换”。具体来说,TransposeVector 函数在例子中定义如下:
void TransposeVector(double &v[])
|
尝试对二维数组 a 调用该函数在 OnStart 中被注释掉了,因为这生成了上面的错误:数组维数不匹配。
除了通过值或引用来传递参数,还有另一种方法:传递指针。与 C++ 不同,MQL5 只支持用于对象类型的 指针用于 (类)。我们将在第三章研究这个特性。