从基础到中级:数组和字符串(二)·进阶篇
📘

从基础到中级:数组和字符串(二)·进阶篇

第 2/2 篇

「左移右移其实是乘除以 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,看二进制输出就能验证截断点。

MQL5 / C++
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 脚本跑实盘关联逻辑属高风险操作,先在策略测试器验证。

MQL5 / C++
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...,输出会再变一版且字符集更杂。外汇或贵金属账户接这类自生成口令时记住:本地脚本不联网、不保证抗暴力破解,仅作理解字符串与数组关系的练手,真实账户仍倾向用正规密码管理器。

MQL5 / C++
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 打印。外汇与贵金属脚本涉及账号参数,实盘前务必先在模拟环境确认逻辑,这类映射仅作本地混淆,不提供任何加密强度保证。

MQL5 / C++
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 行看输出偏移,比干看文档来得直接。外汇与贵金属杠杆交易高风险,这类字符串练习仅用于编程建模,与任何交易信号无关。

MQL5 / C++
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 直接拖进去就能跑。 真正卡住人的从来不是语法,而是不敢动手改参数、不敢把字符串当信号源来解析。你只要把前几篇的数组和结构体用熟,就能复刻出不少人以为要写几年才能做出的工具。 外汇和贵金属波动剧烈、杠杆高风险大,这类自写脚本只该用在模拟盘先验证逻辑。剩下的,下篇聊数组时再接着拆。

常见问题

左移n位等价于乘2的n次方,右移n位等价于除以2的n次方向下取整;调试时打印移位前后数值对照即可验证。
从最高位到最低位逐位与1做与运算,把结果拼成字符数组再转字符串;代码虽长但逻辑直白不易错。
可以。小布能按全字母句规则和时间种子自动搅出可读口令,并提醒你每周换一次,省去手算取模。
模数取26或36较稳,对应纯字母或字母加数字;配合时间戳低位取模能降低相邻时刻重复概率。
只做按位读取、截取、单字符替换时直接下标访问最省;涉及大量插入删除再考虑转真实数组结构。