掌握 MQL5:从入门到精通(第三部分)复杂数据类型和包含文件·进阶篇
📘

掌握 MQL5:从入门到精通(第三部分)复杂数据类型和包含文件·进阶篇

第 2/2 篇

「把行情对象拆成类才不会乱」

做 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] 初学者第一次读可跳过函数类型这节,不影响后面跟单逻辑;但看懂后,写「机器人你决定买还是卖」这类委托调用会顺手很多。

MQL5 / C++
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 里新建脚本粘入即可验证:改绑不同极值函数,不需要重写比较分支,维护一套行情扫描逻辑会轻很多。

MQL5 / C++
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 文件夹里能直接翻。

MQL5 / C++
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 报错和函数类型切换输出符合预期再谈别的。

MQL5 / C++
  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

常见问题

把每类行情对象(如K线、指标、持仓)各自封装成一个class,内部只管自己的数据和方法,主程序调用接口即可,逻辑会清晰很多。
可以,用函数指针或委托把不同算法存进变量,运行时按条件赋值切换,不用写一堆if判断分支。
小布可以扫描你的策略文件结构,提示哪些包含模块被重复引用、哪些全局名可能撞车,省去手动翻代码的麻烦。
包含文件靠预处理贴入,重复include同一份全局定义会报重定义,通常用#ifndef守卫或只在本模块编译一次来规避。
在模块入口打轻量日志或状态输出,加载时打印模块版本号,实盘面板一眼能看出当前跑的是哪份落地代码。