从基础到中级:数组和字符串(二)·进阶篇
「左移右移其实是乘除以 2 的幂」
很多做 EA 的人初看 << 和 >> 会本能当成大于小于,其实双箭头是位平移,单箭头才是关系比较。移位运算符本质只是“乘或除以 2 的 n 次方”的简写,编译器处理位运算比先算指数再乘除更省。
下面这段 MT5 脚本把同一件事用两种方式做出来:i1 用左移,i2 用 MathPow 乘;i3 用右移,i4 用 MathPow 除。原始值 i0 = 153,shift = 1。
跑出来你会看到:左移 1 位得 306,右移 1 位得 76(整数截断)。也就是说 153 << 1 等价于 153 * 2^1,153 >> 1 等价于 153 / 2^1,结果一致但移位少了一次浮点指数运算。
把 shift 改成 3 再看:153 << 3 得到 168 而非 1224,因为 uchar 上限 255,超出位直接被丢掉。这不是 bug,是 MQL5 强类型截断。做仓位系数或周期换算时若用 uchar 存移位结果,越界会静默丢数据,建议改用 ushort/int 或先做边界判断。
别把正态当圣经
位运算省的那点开销在外汇短线 scalping 里基本无感;它真正的价值是代码可读和防手误写错指数。开 MT5 把下面代码贴进脚本,改 shift 从 1 到 3,看二进制输出就能验证截断点。
class="num">01. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">02. class="macro">#class="kw">property copyright "Daniel Jose" class="num">03. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">04. class="type">void OnStart(class="type">void) class="num">05. { class="num">06. const class="type">uchar shift = class="num">1; class="num">07. class="type">uchar i0 = class="num">153, class="num">08. i1, class="num">09. i2, class="num">10. i3, class="num">11. i4; class="num">12. class="num">13. i1 = i0 << shift; class="num">14. i2 = i0 * (class="type">uchar)MathPow(class="num">2, shift); class="num">15. i3 = i0 >> shift; class="num">16. i4 = i0 / (class="type">uchar)MathPow(class="num">2, shift); class="num">17. class="num">18. PrintFormat("Shift value : %d\n" + class="num">19. "Original : %s\t%d\n" + class="num">20. "Shifted to the left : %s\t%d\n" + class="num">21. "Multiplication result: %s\t%d\n" + class="num">22. "Shifted to the right : %s\t%d\n" + class="num">23. "Division result : %s\t%d" class="num">24. , shift, BinaryToString(i0), i0, class="num">25. BinaryToString(i1), i1, class="num">26. BinaryToString(i2), i2, class="num">27. BinaryToString(i3), i3, class="num">28. BinaryToString(i4), i4 class="num">29. ); class="num">30. }
把单字节翻成二进制串的笨办法
在 MT5 里做位级调试时,经常需要肉眼看某个 uchar 变量的二进制分布。上面这段函数接收一個 const uchar 参数,返回长度固定为 8 的字符串,每一位对应原字节从高到低的二进制值。 核心技巧是用 0x80 当掩码,从最高位开始逐位右移。while(mask) 在 mask 右移 8 次后变成 0 自动退出,不需要另写计数循环。 调用 BinaryToString(0x0F) 会得到 "00001111",调用 BinaryToString(0xFF) 则是 "11111111"。外汇与贵金属行情数据做旗标解析时,这种小工具能直接贴在脚本里用,但注意 MT5 脚本跑实盘关联逻辑属高风险操作,先在策略测试器验证。
class="num">32. class="type">class="kw">string BinaryToString(const class="type">uchar arg) class="num">33. { class="num">34. class="type">class="kw">string sz0 = ""; class="num">35. class="type">uchar mask = 0x80; class="num">36. class="num">37. while (mask) class="num">38. { class="num">39. sz0 = sz0 + ((arg & mask) == mask ? "class="num">1" : "class="num">0"); class="num">40. mask = mask >> class="num">1; class="num">41. } class="num">42. class="num">43. class="kw">return sz0; class="num">44. } class="num">45. class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 用全字母句搅出可记可换的口令串
把一句你闭眼能背的全字母短句当密钥母本,就能在 MT5 里用极少的代码搓出一个长度随原文拉伸的口令。核心思路是:取你私人口令短语的每个字符,对全字母基准串长度取模,再从基准串里抽字符拼起来。
下面这段是可直接丢进 MT5 脚本跑的基准版。第 06 行是你自己要记的葡语短句(43 个字符),第 13 行是 43 字符的英文全字母句。循环里对每个字符做 % StringLen(szCodePhrase),所以输出长度等于你私语的长度,本例固定出 43 位混排串。
原版输出里带空格,直接当密码不合规。把第 13 行基准串去掉空格并首字母大写后重跑,同样输入得到的却是完全不同的 43 位串——仅改这一行,结果剧烈漂移,说明模映射对基准串极度敏感。
若觉得还不够硬,继续在第 13 行基准串里塞符号和数字,例如变成 T1h3Q_uIcK9...,输出会再变一版且字符集更杂。外汇或贵金属账户接这类自生成口令时记住:本地脚本不联网、不保证抗暴力破解,仅作理解字符串与数组关系的练手,真实账户仍倾向用正规密码管理器。
class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart(class="type">void) { const class="type">class="kw">string sz_Secret_Phrase = "Um pequeno jabuti xereta viu dez cegonhas felizes"; Print("Result:\n", PassWord(sz_Secret_Phrase)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string PassWord(const class="type">class="kw">string szArg) { const class="type">class="kw">string szCodePhrase = "The quick brown fox jumps over the lazy dog"; class="type">class="kw">string szPsw = ""; class="type">uchar pos; for (class="type">int c = class="num">0; c < StringLen(szArg); c++) { pos = (class="type">uchar)(StringGetCharacter(szArg, c) % StringLen(szCodePhrase)); szPsw = StringFormat("%s%c", szPsw, StringGetCharacter(szCodePhrase, pos)); } class="kw">return szPsw; } class=class="str">"cmt">//+------------------------------------------------------------------+
「用字符取模拼出动态口令」
这段 MQL5 函数把传入的暗语按字符映射成另一串口令,核心在取模偏移。szCodePhrase 长度 35,传入字符的 Unicode 码点对其取余得到 pos,再从码表里抽对应字符拼进 szPsw。 例如暗语首字符若为大写 T(码点 84),84 % 35 = 14,便取码表第 14 位的 'r'。循环跑完 StringLen(szArg) 次,输出长度与输入完全一致。 想验证就直接把 sz_Secret_Phrase 改成自己的词,在 MT5 策略测试器里跑 OnStart,看 Result 打印。外汇与贵金属脚本涉及账号参数,实盘前务必先在模拟环境确认逻辑,这类映射仅作本地混淆,不提供任何加密强度保证。
class="type">class="kw">string PassWord(const class="type">class="kw">string szArg) { const class="type">class="kw">string szCodePhrase = "TheQuickBrownFoxJumpsOverTheLazyDog"; class="type">class="kw">string szPsw = ""; class="type">uchar pos; for (class="type">int c = class="num">0; c < StringLen(szArg); c++) { pos = (class="type">uchar)(StringGetCharacter(szArg, c) % StringLen(szCodePhrase)); szPsw = StringFormat("%s%c", szPsw, StringGetCharacter(szCodePhrase, pos)); } class="kw">return szPsw; } const class="type">class="kw">string szCodePhrase = ")0The!1quick@2brownclass="macro">#3fox$4jumps%5over^6the&7lazy*8dog(class="num">9";
把字符串当数组来使
在 MQL5 里,字符串本质上就是 uchar 元素的序列,完全可以按数组方式逐字符寻址。上面这段代码把原先直接拼字符串的做法改成了数组建模:先按源串长度开缓冲区,再逐个字符取模映射到另一句模板串的对应位置,最后用 CharArrayToString 拼回。 跑这段代码,输出和前一篇图 05 一致;但如果你改第 13 行的 szCodePhrase,结果会随模板串长度变化——因为取模分母是 StringLen(szCodePhrase)。这就是数组化处理带来的第一个实惠:替换映射表不用动循环逻辑。 别把正态当圣经 第 21 行用 szArg[c] % StringLen(szCodePhrase) 做位置映射,字符 ASCII 值对模板长度取模。若模板串长度为 43(原句刚好 43 字符),映射结果落在 0–42;换短模板会让碰撞概率上升,密文随机性下降。 现在就把代码贴进 MT5 脚本跑一遍,改第 13 行看输出偏移,比干看文档来得直接。外汇与贵金属杠杆交易高风险,这类字符串练习仅用于编程建模,与任何交易信号无关。
class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart(class="type">void) { const class="type">class="kw">string sz_Secret_Phrase = "Um pequeno jabuti xereta viu dez cegonhas felizes"; Print("Result:\n", PassWord(sz_Secret_Phrase)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string PassWord(const class="type">class="kw">string szArg) { const class="type">class="kw">string szCodePhrase = "The quick brown fox jumps over the lazy dog"; class="type">uchar psw[], pos; ArrayResize(psw, StringLen(szArg)); for (class="type">int c = class="num">0; szArg[c]; c++) { pos = (class="type">uchar)(szArg[c] % StringLen(szCodePhrase)); psw[c] = (class="type">uchar)szCodePhrase[pos]; } class="kw">return CharArrayToString(psw); } class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 记住这一条就够了
前面几篇把指标、结构、继承这些底子铺开,这一节想说的其实很直白:用 MQL5 摆弄文本和数据,并不需要你先熬成老手。附件 Anexo.zip 只有 2.43 KB,里面就是前面讲过的那些基础拼起来的实用小东西,开 MT5 直接拖进去就能跑。 真正卡住人的从来不是语法,而是不敢动手改参数、不敢把字符串当信号源来解析。你只要把前几篇的数组和结构体用熟,就能复刻出不少人以为要写几年才能做出的工具。 外汇和贵金属波动剧烈、杠杆高风险大,这类自写脚本只该用在模拟盘先验证逻辑。剩下的,下篇聊数组时再接着拆。