在我们的EA程序这栋“房子”里,每一份需要储存的数据,都需要一个“家”(变量)。现在,我们需要做出一个最基础也最重要的建筑决策:是给这份数据一间私密的、用完就走的“客房”(局部变量),还是把它放在一个谁都能看到的“公共大厅”(全局变量)?
这个决策,将直接影响你的EA代码的清晰度、稳定性和未来维护的难易度。
局部变量:EA的“私人客房”与“草稿纸”
局部变量,是定义在某一个函数或代码块{}
内部的变量。
它最核心的特点就是“阅后即焚”,生命周期极短:
- “出生”:当程序执行到它所在的函数时,它才被创建。
- “活动”:它的活动范围仅限于这个函数内部,别的函数根本不知道它的存在。
- “死亡”:函数一旦执行完毕,这个局部变量就会被立刻销毁,其占用的内存也会被完全释放。
示例1:函数内的局部变量
void OnTick()
{
int x = 5; // 局部变量x,只在OnTick函数这个“房间”里有效
Print("x=", x);
}
void SomeOtherFunction()
{
// Print(x); // ❌ 错误!这里是另一个房间,看不见x
}
示例2:代码块里的“临时”局部变量
它的作用范围甚至可以更小,只在一个if
或for
循环的{}
代码块里有效。
void OnStart()
{
for (int i = 0; i < 5; i++)
{
Print("i=", i); // 变量i是局部变量,只在这个for循环中有效
}
// Print(i); // ❌ 错误!离开了for循环的范围,i已经“死亡”了
}
我们为什么偏爱局部变量?
局部变量的“健忘”和“短命”不是缺点,反而是巨大的优点。它保证了每个函数都是一个“无菌实验室”。函数每次被调用,都像是在一张全新的草稿纸上进行计算,用完就扔。这可以确保上一次的计算结果,绝对不会意外地污染到下一次的计算,极大地提升了代码的安全性和稳定性。
核心原则:能用局部的,就绝不用全局的。这是专业程序员的铁律。
全局变量:EA的“中央公告板”
全局变量,是定义在所有函数之外的变量,我们通常把它放在代码文件的最顶部。
它的特点与局部变量正好相反——“坚韧不拔”:
- “出生”:EA程序一开始加载,它就被创建。
- “活动”:它的活动范围覆盖整个EA文件,任何函数都可以随时读取和修改它。
- “死亡”:直到EA程序从图表上被移除,它才会被销毁。
它就像是房子大厅里的一块公共白板,任何房间的人都能在上面写字,也都能看到别人写了什么。
// 全局变量,定义在所有函数之外,推荐以g_开头
int g_tradeCount = 0;
void OnTick()
{
// OnTick函数可以读取并修改它
if (some_trade_condition)
{
g_tradeCount++;
Print("这是本EA的第 ", g_tradeCount, " 笔交易。");
}
}
void OnDeinit(const int reason)
{
// EA退出时,OnDeinit函数也可以读取它,用于最终总结
Print("EA退出,总计交易次数: ", g_tradeCount);
}
什么时候“必须”使用全局变量?
当我们需要一个数据来记录整个EA程序的宏观状态,并且这个状态需要被多个、不同功能的函数共享时。最典型的场景就是:
- 统计EA的总交易次数、总盈亏。
- 记录EA的初始化是否成功。
- 保存需要在多个函数之间流转的、重要的程序状态信息。
从技术上说,我们上一节课讲的input
(或extern
)外部变量,也是全局变量的一种,因为它们也具有全局的活动范围。
学长避坑指南:全局变量是“双刃剑”,请谨慎使用
全局变量的便利性背后,隐藏着巨大的风险。新手最容易犯的错误就是为了图方便,大量使用全局变量,这会迅速让你的代码变成一团难以维护的“意大利面”。
- 逻辑混乱:当一个全局变量的值不对时,你很难追踪到底是哪个函数在哪个环节把它改错了。
- 命名冲突:不同功能的代码块可能会无意中用到同名的全局变量,互相覆盖,导致莫名其妙的BUG。
强制建议:为所有全局变量加上g_
前缀(g是global的缩写)。 例如:g_magicNumber
, g_totalProfit
。这不是语法要求,而是代码江湖里一条保命的“军规”。当你在函数的代码深处看到一个g_
开头的变量时,你的大脑会立刻响起警报:“注意!这是一个全局变量,修改它可能会影响到其他地方!”
实战总结:跟踪交易次数与总盈利
下面这个例子,完美地展示了全局变量的正确用法。
// === 全局状态追踪 ===
int g_tradeCount = 0; // 全局变量:用于累计交易次数
double g_totalProfit = 0.0; // 全局变量:用于累计总盈利
// OnTrade函数会在每次交易活动时被MT4自动调用
void OnTradeTransaction(const MqlTradeTransaction& trans,
const MqlTradeRequest& request,
const MqlTradeResult& result)
{
// 当一笔交易从持仓列表中被移除时 (平仓或止损止盈)
if(trans.type == TRADE_TRANSACTION_DEAL_REMOVE)
{
// 我们可以查询这笔历史订单的盈利
if(HistorySelect(trans.deal))
{
double profit = HistoryDealGetDouble(trans.deal, DEAL_PROFIT);
// 更新我们的全局变量
g_tradeCount++;
g_totalProfit += profit;
Print("一笔交易完成。累计交易次数: ", g_tradeCount, ", 累计盈利: ", g_totalProfit);
}
}
}
在这个场景里,交易次数和总盈利是整个EA的宏观状态,必须使用全局变量来贯穿始终地追踪,这就是全局变量最适合发挥作用的地方。