从基础到中级:结构(二)·进阶篇
◍ 用结构体收口批量记录的写法
在 MT5 脚本里处理多组同类数据,最忌散着开六个平行数组。原文第一段用了 Name[10]、Surname[10]、Address[10]、Position[10]、Remuneration[10]、Birth[10]、Hiring[10] 七个独立数组,记录数由 #define def_Number_of_Records 10 统一约束,但字段之间没有任何语义绑定,后期写入和遍历都容易错位。 更稳的做法是把这些字段收进一个 struct,再用该结构体开数组。下面第二段代码用 st_Register 把姓名、住址、薪资、出生与入职时间打包,然后声明 Record[def_Number_of_Records],十条人员记录就成了一个连续对象数组,而不是七条互不相干的序列。 开 MT5 按 F4 新建脚本,把第二段代码整段贴入 OnStart 内,编译后跑一遍,能在调试窗口看到 Record[0] 到 Record[9] 各自带着完整字段。外汇与贵金属相关的数据管理同样高风险,参数写错可能让后续统计全部偏移,建议先用固定 10 条样本验证结构再扩量。
class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart(class="type">void) { class="macro">#define def_Number_of_Records class="num">10 class="kw">struct st_Register { class="type">class="kw">string Name, Surname, Address, Position; class="type">class="kw">double Remuneration; class="type">class="kw">datetime Birth, Hiring; }; st_Register Record[def_Number_of_Records];
用结构体把多条记录塞进数组
在 MT5 里处理批量数据,先定义一个 struct 把字段捆在一起比散着写变量省事得多。下面这段把姓名、地址、岗位、薪资和日期收进 st_Register,再声明一个长度为 10 的数组,相当于预开了 10 个工位。 录入靠一个 NewRecord 函数,成功返回 true 才让 Counter 加 1,因此 Counter 最终就是实际写入条数。示例里连写 4 条 Brasil 记录,Print 输出会是 Number of records: 4。 ViewRecord(Record[3]) 直接按索引取第 4 条(Yara Alves,岗位 Accounting)。这种写法在外汇/贵金属策略里做参数批次回测时很有用,但注意数组越界会直接报错,MT5 高风险环境里建议先判 Counter 再取索引。
class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">struct st_Register { class="type">class="kw">string Name, Surname, Address, Position; class="type">class="kw">double Remuneration; class="type">class="kw">datetime Birth, Hiring; }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart(class="type">void) { st_Register Record[class="num">10]; class="type">uchar Counter = class="num">0, Index; Index = Counter; Counter += (NewRecord(Record[Counter], "Daniel", "Jose", "Brasil", "class="num">1971/class="num">03/class="num">30") ? class="num">1 : class="num">0); Record[Index] = UpdatePosition(Record[Index], "Chief Programmer"); Index = Counter; Counter += (NewRecord(Record[Counter], "Edimarcos", "Alcantra", "Brasil", "class="num">1974/class="num">12/class="num">07") ? class="num">1 : class="num">0); Record[Index] = UpdatePosition(Record[Index], "Programmer"); Index = Counter; Counter += (NewRecord(Record[Counter], "Carlos", "Almeida", "Brasil", "class="num">1985/class="num">11/class="num">15") ? class="num">1 : class="num">0); Record[Index] = UpdatePosition(Record[Index], "Junior Programmer"); Index = Counter; Counter += (NewRecord(Record[Counter], "Yara", "Alves", "Brasil", "class="num">1978/class="num">07/class="num">25") ? class="num">1 : class="num">0); Record[Index] = UpdatePosition(Record[Index], "Accounting"); Print("Number of records: ", Counter); ViewRecord(Record[class="num">3]); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool NewRecord(st_Register &arg, class="kw">const class="type">class="kw">string Name, class="kw">const class="type">class="kw">string Surname, class="kw">const class="type">class="kw">string Address, class="kw">const class="type">class="kw">string Birth) { arg.Name = Name; arg.Surname = Surname; arg.Address = Address;
「用结构体数组管多条协作记录」
这段 MT5 代码示范了如何用 struct 把多个交易协作者的静态资料收进一个固定长度数组,方便后续在 EA 或脚本里统一读取与改写。gl_Record[10] 意味着最多缓存 10 条记录,超出会越界报错,实盘前应按需调大或改动态数组。 NewCollaborator 内部把字符串生日通过 StringToTime 转成 datetime 赋给 arg.Birth,Hiring 则取 TimeLocal() 的本地计算机时间,返回 true 表示写入成功。注意 TimeLocal 不走经纪商服务器时区,回测和实盘可能差几个小时,跨时区团队要统一用 TimeTradeServer 才稳。 UpdatePosition 接收一个 st_Register 副本,改完 Position 字段后返回新结构体,原记录不被破坏——这种值传递在 MT5 里能避免误改全局状态。ViewRecord 用 PrintFormat 把姓氏、名、岗位和生日日期打到日志,方便你按 F4 编译后直接在 Experts 标签里核对。 OnStart 里先 ZeroMemory 清掉 gl_Record 残留,再连续塞了 3 条 Brasil 程序员资料,GetNumberRecord 取当前条数赋给 Index。你可以直接把这段代码贴进 MT5 脚本跑一遍,看日志输出是否和下面一致:3 条记录、Chief/Programmer/Junior 三个岗位。外汇与贵金属脚本涉及本地时间处理,时区错配可能引发信号误触发,属高风险操作,验证时先用模拟账户。
class="num">43. arg.Birth = StringToTime(Birth); class="num">44. arg.Hiring = TimeLocal(); class="num">45. class="num">46. class="kw">return true; class="num">47. } class="num">48. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">49. st_Register UpdatePosition(class="kw">const st_Register &arg, class="kw">const class="type">class="kw">string NewPosition) class="num">50. { class="num">51. st_Register info = arg; class="num">52. class="num">53. info.Position = NewPosition; class="num">54. class="num">55. class="kw">return info; class="num">56. } class="num">57. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">58. class="type">void ViewRecord(class="kw">const st_Register &arg) class="num">59. { class="num">60. PrintFormat("Collaborator: %s, %s\nPosition: %s\nBirth in: %s", class="num">61. arg.Surname, class="num">62. arg.Name, class="num">63. arg.Position, class="num">64. TimeToString(arg.Birth, TIME_DATE)); class="num">65. } class="num">66. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">001. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">002. class="macro">#class="kw">property copyright "Daniel Jose" class="num">003. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">004. class="kw">struct st_Register class="num">005. { class="num">006. class="type">uchar ID; class="num">007. class="type">class="kw">string Name, class="num">008. Surname, class="num">009. Address, class="num">010. Position; class="num">011. class="type">class="kw">double Remuneration; class="num">012. class="type">class="kw">datetime Birth, class="num">013. Hiring; class="num">014. }gl_Record[class="num">10]; class="num">015. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">016. class="type">void OnStart(class="type">void) class="num">017. { class="num">018. class="type">uchar Index; class="num">019. class="num">020. ZeroMemory(gl_Record); class="num">021. class="num">022. NewCollaborator("Daniel", "Jose", "Brasil", "class="num">1971/class="num">03/class="num">30", "Chief Programmer"); class="num">023. NewCollaborator("Edimarcos", "Alcantra", "Brasil", "class="num">1974/class="num">12/class="num">07", "Programmer"); class="num">024. NewCollaborator("Carlos", "Almeida", "Brasil", "class="num">1985/class="num">11/class="num">15", "Junior Programmer"); class="num">025. class="num">026. Index = GetNumberRecord(); class="num">027.
◍ 用结构体和函数封装一条记录的写入
这段 MT5 脚本演示了如何把「新增人员」动作拆成可复用的底层函数,而不是在主线里堆重复代码。主线先调 NewRecord 填好姓名、国籍、生日,再把返回的结构体交给 UpdatePosition 补上岗位,最后写回全局数组 gl_Record 并顺手赋 ID。 NewCollaborator 是更上层的封装:它内部建一个临时 st_Register,调 NewRecord 初始化,再靠 while 循环从 Index=0 开始找第一个 ID 为 0 的空位(第 51–55 行),找到后把 info.ID 设成 Index+1 并存入。注意第 53 行有个边界保护——若 Index 超出 gl_Record.Size() 直接返回 false,避免越界崩脚本。 NewRecord 里第 68 行用 StringToTime 把 "1978/07/25" 这类字符串转成时间戳,第 69 行用 TimeLocal 记入职时间,这两个调用在回测和实盘里取值不同,实盘跑会拿到你电脑本地时钟。 UpdatePosition 非常轻:拷贝一份传入结构体,改 Position 字段,返回副本(第 76–80 行)。这种「传值返回」写法在 MQL5 里没有性能焦虑,结构体不大时比传引用改原值更不容易留副作用。 验证方式:把上面代码贴进 MT5 的 EA 或脚本,Print 出来的 Number of records 会随你调用 NewCollaborator 的次数递增;外汇与贵金属交易相关的自动化工具均属高风险,任何封装逻辑都先在策略测试器跑通再上真仓。
if (NewRecord(gl_Record[Index], "Yara", "Alves", "Brasil", "class="num">1978/class="num">07/class="num">25")) { gl_Record[Index].ID = Index + class="num">1; gl_Record[Index] = UpdatePosition(gl_Record[Index], "Accounting"); } Print("Number of records: ", GetNumberRecord()); Print("--------------------"); for(class="type">uchar c = class="num">1; ViewRecord(c); c++) Print("********************"); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool NewCollaborator(class="kw">const class="type">class="kw">string Name, class="kw">const class="type">class="kw">string Surname, class="kw">const class="type">class="kw">string Address, class="kw">const class="type">class="kw">string Birth, class="kw">const class="type">class="kw">string Position) { st_Register info; class="type">uchar Index = class="num">0; if (!NewRecord(info, Name, Surname, Address, Birth)) class="kw">return class="kw">false; info = UpdatePosition(info, Position); class="kw">while (gl_Record[Index].ID) { if (Index >= gl_Record.Size()) class="kw">return class="kw">false; Index++; } info.ID = Index + class="num">1; gl_Record[Index] = info; class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool NewRecord(st_Register &arg, class="kw">const class="type">class="kw">string Name, class="kw">const class="type">class="kw">string Surname, class="kw">const class="type">class="kw">string Address, class="kw">const class="type">class="kw">string Birth) { arg.Name = Name; arg.Surname = Surname; arg.Address = Address; arg.Birth = StringToTime(Birth); arg.Hiring = TimeLocal(); class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+ st_Register UpdatePosition(class="kw">const st_Register &arg, class="kw">const class="type">class="kw">string NewPosition) { st_Register info = arg; info.Position = NewPosition; class="kw">return info; } class=class="str">"cmt">//+------------------------------------------------------------------+
用计数器与查表函数榨干记录数组
这段逻辑站在全局容器 gl_Record 上做两件事:数有效条目、按 ID 抽单条。外汇与贵金属 EA 里这类簿记函数跑在 OnTick 之外才安全,实时行情推送中误用可能拖慢执行并放大滑点风险。 第一段是个极简计数器。声明 uchar counter 从 0 起,for 循环里 c 从 0 走到 gl_Record.Size(),每遇到 gl_Record[c].ID 非零就把 counter 加 1,最后返回 counter。用 uchar 意味着记录数超过 255 会溢出,实盘前先确认你的注册表规模。 ViewRecord 则负责按 ID 查人。ZeroMemory 先把 st_Register info 清空,循环里只要没匹配到 info.ID 就继续扫,命中即赋值。PrintFormat 把姓氏、名字、岗位和出生日期(TIME_DATE 格式)打到日志;查不到就报“Record ID [x] not found.”,返回值是 info.ID 的布尔形态。 别把正态当圣经:这段代码假设 ID 唯一且非零代表有效。若你的写入逻辑会出现脏 ID,计数器会虚高,查表也可能首命中即停。开 MT5 把这段贴进脚本,用 Print 验证 Size 与 counter 的差值,就能立刻看出容器里有多少空槽。
{
class="type">uchar counter = class="num">0;
for (class="type">uchar c = class="num">0; c < gl_Record.Size(); counter += (gl_Record[c].ID ? class="num">1 : class="num">0), c++);
class="kw">return counter;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool ViewRecord(class="kw">const class="type">uchar ID)
{
st_Register info;
ZeroMemory(info);
for (class="type">uchar c = class="num">0; (c < gl_Record.Size()) && (!info.ID); c++)
info = (gl_Record[c].ID == ID ? gl_Record[c] : info);
if (info.ID)
PrintFormat("Collaborator: %s, %s\nPosition: %s\nBirth in: %s",
info.Surname,
info.Name,
info.Position,
TimeToString(info.Birth, TIME_DATE));
else
Print("Record ID [", ID ,"] not found.");
class="kw">return (class="type">bool) info.ID;
}
class=class="str">"cmt">//+------------------------------------------------------------------+「记住这一条就够了」
结构在 MQL5 里本质上就是一种特殊类型的变量,本文用不到 3 KB 的附件代码搭出了一个类数据库的小系统,教学目的已经达到。真要上生产环境,得补大量防错测试,但概念跑通比堆功能更重要。 下一篇会把这个结构用法往实用方向推,编码量可能明显降下来。你现在最该做的,是把附件 Anexo.zip 里的代码在 MT5 里跑一遍,改两个字段看看输出怎么变。外汇和贵金属波动剧烈,任何自写结构逻辑都先上模拟盘验证再谈实盘。