掌握 MQL5:从入门到精通(第三部分)复杂数据类型和包含文件·进阶篇
「把行情对象拆成类才不会乱」
做 MT5 脚本时,最忌把价格线、窗口、订单全堆在主函数里。面向对象写法把每个实体(一条线、一个挂单)封成一个块:块内收数据也收操作,外部只管调用。线对象天然有起点、终点、粗细、颜色这些属性,也能自己「绘制」「旋转」「偏移复制」,属性和方法合起来就是类的成员。
类与结构表面像,内核不同:类成员默认私藏,结构全员可见。下面这段演示了 private/public 分区,以及用句点符访问公有成员。
[CODE]
class TestClass {
private:
double m_privateField;
bool Private_Method(){return false;}
public:
int m_publicField;
void Public_Method(void)
{
Print("Value of testElement is ", testElement );
}
};
TestClass myTestClassVariable;
myTestClassVariable.testElement = 5;
myTestClassVariable.PrintTestElement();
class PrivateAndPublic
{
private:
int a;
public:
int b;
};
PrivateAndPublic myVariable;
// myVariable.a = 5; // Compiler error!
myVariable.b = 10; // Success
[/CODE]
把上面例 11 的 myVariable.a = 5; 取消注释再编译,MT5 会直接报错「访问私有成员」——编译器这层拦截能少埋很多暗坑。
MQL5\Include 里早有一堆标准类,代码库也能扒到现成库。多数时候 #include 一下就能用别人写好的对象,不用自己造轮子。外汇和贵金属波动大、杠杆高,复用经过验证的类比手搓全局变量更安全。
除了类,MQL5 还能用 typedef 把函数本身变成变量类型,方便把 Buy/Sell 差异收敛进一个 Trade 函数。注意类型名前的星号不能省,且 typedef 只认自定义函数,标准函数如 MathMin 得包一层wrapper。
[CODE]
typedef function_result_type (*Function_type_name)(input_parameter1_type,input_parameter1_type ...);
int Add (int a,int b)
{
return (a+b);
}
int Diff (int a,int b)
{
return (a-b);
}
typedef int (*TFunc) (int, int);
void OnStart()
{
TFunc operate;
[/CODE]
初学者第一次读可跳过函数类型这节,不影响后面跟单逻辑;但看懂后,写「机器人你决定买还是卖」这类委托调用会顺手很多。
class TestClass { class="kw">private: class="type">class="kw">double m_privateField; class="type">bool Private_Method(){class="kw">return class="kw">false;} class="kw">public: class="type">int m_publicField; class="type">void Public_Method(class="type">void) { Print("Value of `testElement` is ", testElement ); } }; TestClass myTestClassVariable; myTestClassVariable.testElement = class="num">5; myTestClassVariable.PrintTestElement(); class PrivateAndPublic { class="kw">private: class="type">int a; class="kw">public: class="type">int b; }; PrivateAndPublic myVariable; class=class="str">"cmt">// myVariable.a = class="num">5; // Compiler error! myVariable.b = class="num">10; class=class="str">"cmt">// Success typedef function_result_type(*Function_type_name)(input_parameter1_type,input_parameter1_type ...); class="type">int Add(class="type">int a,class="type">int b) { class="kw">return (a+b); } class="type">int Diff(class="type">int a,class="type">int b) { class="kw">return (a-b); } typedef class="type">int (*TFunc) (class="type">int, class="type">int); class="type">void OnStart() { TFunc operate;
把函数塞进变量里动态切换
MQL5 里函数指针不是摆设,能把现成函数赋给一个变量,运行时再决定走哪条计算逻辑。下面这段演示了同一变量先后绑定加法与减法,输出从 8 变成 -2,说明调用点代码完全不用改,只换绑定目标。 实际写法先用 typedef 定义签名:double (*TCompare)(double, double),之后声明 TCompare extremumOfTwo,把它指向 MyMin 时 Print(extremumOfTwo(5,7)) 出 5,指向 MyMax 时同样调用出 7。外汇和贵金属波动快,这种写法适合做多策略开关,但杠杆品种高风险,参数切错可能瞬间扩大回撤。 在 MT5 里新建脚本粘入即可验证:改绑不同极值函数,不需要重写比较分支,维护一套行情扫描逻辑会轻很多。
operate = Add; class=class="str">"cmt">// Write a value to a variable(in this case, assign a function) Print(operate(class="num">3, class="num">5)); class=class="str">"cmt">// Use the variable as a normal function class=class="str">"cmt">// Function output: class="num">8 operate=Diff; Print(operate(class="num">3, class="num">5)); class=class="str">"cmt">// Function output: -class="num">2 } class=class="str">"cmt">//--- class="type">class="kw">double MyMin(class="type">class="kw">double a, class="type">class="kw">double b){ class="kw">return (MathMin(a,b)); } class=class="str">"cmt">//--- class="type">class="kw">double MyMax(class="type">class="kw">double a, class="type">class="kw">double b){ class="kw">return (MathMax(a,b)); } class=class="str">"cmt">//--- typedef class="type">class="kw">double (*TCompare) (class="type">class="kw">double, class="type">class="kw">double); class=class="str">"cmt">//--- class="type">void OnStart() { TCompare extremumOfTwo; compare= MyMin; Print(extremumOfTwo(class="num">5, class="num">7));class=class="str">"cmt">// class="num">5 compare= MyMax; Print(extremumOfTwo(class="num">5, class="num">7));class=class="str">"cmt">// class="num">7 }
◍ 用 #include 拆模块时编译器只认一次
做大型 EA 或指标时,把常量、数据类型、工具函数拆到独立文件再用 #include 拉进来,是 MT5 里最基本的模块化手段。好处很直接:代码好导航、团队能并行写、写好的 .mqh 可以反复复用。
#include 有两条路径写法:尖括号 <SomeFile.mqh> 是相对 MQL5\Include 目录搜,双引号 "AnyOtherPath.mqh" 是相对当前文件搜。一般放在文件开头,但不是强制,看具体逻辑需求。
关键坑在于:同一个文件在一个程序里只会被插入一次。下面这段测试脚本试图连续两次 #include "1.mqh",第一次 i=1 正常打印,第二次 i=2 时编译器直接忽略,终端日志只输出 This is include with number 1。
限制的原因很硬核——.mqh 里通常放变量和函数声明。全局层不允许同名变量重复声明,函数也必须唯一。如果两个模块都引了同一个标准类(比如 Arrays\List.mqh 里的 CList),没有“只引一次”的规则,编译器会报重定义错误。正因为有这条,循环依赖(A 包含 B、B 又包含 A 的部分元素)在 MQL5 里也能编译过。
MT5 自带标准库在 MQL5\Include,终端里点“文件→打开数据目录”就能进。自己的 .mqh 放同目录或子目录都行,MetaEditor 导航面板的 Include 文件夹里能直接翻。
class="macro">#include <SomeFile.mqh> class=class="str">"cmt">// Angle brackets specify search relative to MQL5\Include directory class="macro">#include "AnyOtherPath.mqh" class=class="str">"cmt">// Quotes specify search relative to current file Print("This is include with number "+i); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class=class="str">"cmt">//--- class="type">int i=class="num">1; class="macro">#include "class="num">1.mqh" i=class="num">2; class="macro">#include "class="num">1.mqh" } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">// Script output: class=class="str">"cmt">// class=class="str">"cmt">// This is include with number class="num">1 class=class="str">"cmt">// class=class="str">"cmt">// The second attempt to use the same file will be ignored class=class="str">"cmt">//+------------------------------------------------------------------+
「把模块拆出去之后怎么落地」
回头看这一路:预处理器 #include 让库文件外挂进程序,结构、联合、类对象以及函数类型这些“复杂类型”其实只是要先声明再使用,操作和 int、double 没本质区别。结构变量用花括号初始化,函数类型变量直接当函数名调,这套机制把 EA 拆成多文件模块后,维护成本会明显下降。 下面这段是类封装与函数类型变量的实际写法,注意 private 成员在类外赋值会直接编译报错,所有调用必须塞进函数体内。 把 compare 指到 MyMin 时 extremumOfTwo(5,7) 打印 5,换到 MyMax 打印 7——函数类型变量改一下指向,同一行调用就翻转了极值逻辑,这种写法在动态切换策略分支时很实用。 外汇与贵金属杠杆高、滑点随机,这类封装只解决代码组织问题,不暗示任何胜率;真要上 MT5 验证,先把文末代码粘进脚本编译,确认 private 报错和函数类型切换输出符合预期再谈别的。
class PrivateAndPudlic { class="kw">private: class="type">int a; class="kw">public: class="type">int b; }; PrivateAndPudlic myVariable; class=class="str">"cmt">// 全局变量 class="type">void OnStart(){ class=class="str">"cmt">// 对操作的所有调用(此处为赋值)必须只在函数内部进行 class=class="str">"cmt">//myVariable.a = class="num">5; //编译器错误! myVariable.b = class="num">10; class=class="str">"cmt">//没关系,没关系 } class="type">void OnStart(){ class PrivateAndPudlic { class="kw">private: class="type">int a; class="kw">public: class="type">int b; }; PrivateAndPudlic myVariable; class=class="str">"cmt">//myVariable.a = class="num">5; //编译器错误! myVariable.b = class="num">10; class=class="str">"cmt">//没关系,没关系 } compare= MyMin; Print(extremumOfTwo(class="num">5, class="num">7));class=class="str">"cmt">// class="num">5 compare= MyMax; Print(extremumOfTwo(class="num">5, class="num">7));class=class="str">"cmt">// class="num">7