最后由 麻爪 于 2026-9-15 14:00 编辑
第十八章 typedef
一、typedef关键字typedef 不是创造新的数据类型,它只是给已经存在的类型,起一个别名(外号)。编译器底层看待它,和原来的类型完全一模一样。 语法:typedef 原有类型 新别名; 比如:
- // 给 int 起别名,叫 ZB_Integer
- typedef int ZB_Integer;
- void OnTick()
- {
- ZB_Integer a = 100; // ZB_Integer 等价于 int
- Print(a);
- }
复制代码
如果单纯只是为了给数据类型改个名字,typedef 确实性价比很低,还容易增加阅读负担,如果没有特殊用途,新手不推荐滥用 typedef。实际上在比较简单的EA策略中,这个关键词几乎用不到。 但如果你编了一个很长长的结构体、类的名字,或者你要引用的结构、类的名字又臭又长,用 typedef来更换一下它们的名字,看起来更简洁。但如果 typedef只拿来改名字,也不建议使用,尤其对于新手,会产生混肴,改着改着就不知道谁是谁了。 typedef` 定义的类型别名,只服务于读代码的人和后期维护,说白了就是人看着方便,改着方便;但编译完成之后,对程序运行逻辑、执行效率、内存布局完全没有任何影响,编译器看待别名和原来的本名是一模一样的东西。 对于新手来说,可以全程忽略typedef 这个关键词,即使完全不用它,你写的EA也没啥影响。 但在处理函数指针方面,typedef确实也会带来一定的便利,想了解的可以钻研一下,不学这个也没啥影响。 二、typedef 和函数指针(一)函数指针我们先看一下没有所谓“函数指针”的情况下,系统是如何调用函数的: 示例中的三个策略函数,是作者自己写的,用来演示三个指标,大括号里代表该指标函数的处理过程,为了简化演示,只是让输入价格各自乘以一个系数,最终返还三个挂单价。 - // 自编的三个示例策略函数
- double ZB_SignalMA(double price) { return price * 1.01; }
- double ZB_SignalRSI(double price) { return price * 0.99; }
- double ZB_SignalMACD(double price) { return price * 1.005; }
复制代码 再设计一个EA输入参数(input),相当于人工选择策略1,也可以人工改成2或者3。这也是简化写法实际上选哪个策略函数,也是可以写成策略,让系统自己分析出来。
- input int StrategyType = 1; // 1代表MA策略,2代表RSI策略,3代表MACD策略
复制代码
下面是没有函数指针,硬写的策略选择代码:
- daibvoid OnTick()
- {
- double ask = Ask;
- double signal;
-
- if(StrategyType == 1) //如果选择策略1信号
- {
- signal = ZB_SignalMA(ask);//拿到策略1信号的价格
- }
- else if(StrategyType == 2) //如果选择策略2信号
- {
- signal = ZB_SignalRSI(ask);//拿到策略2信号的价格
- }
- else if(StrategyType == 3)//如果选择策略3信号
- {
- signal = ZB_SignalMACD(ask);//拿到策略3信号的价格
- }
-
- ······
- }
复制代码
如果你的EA策略只有一个,可以忽略函数指针的写法,如果你的EA策略函数有多个,那在这里需要你把所有可选择的函数都写出来,要写一大堆if/else,如果你需要在多个函数之中选一个来运行,可以用函数指针来处理,代码看上去会比较清爽,下面是常规函数指针的写法:
- // 三个策略函数(不变)
- double ZB_SignalMA(double price) { return price * 1.01; }
- double ZB_SignalRSI(double price) { return price * 0.99; }
- double ZB_SignalMACD(double price) { return price * 1.005; }
-
- //策略选择也不变
- input int StrategyType = 1;
-
- // 自定义一个pSignalFunc 函数指针:接收1个double参数,返回double,
- double (*pSignalFunc)(double);//函数指针的语法:数据类型 (*自定义函数指针名)(数据类型),指得是函数返回值的类型,而不是函数指针的类型
-
- // 在初始化的时候,一次性绑定好函数(只执行一次),相当于让函数指针记住一个指向
- int OnInit()
- {
- if(StrategyType == 1)
- pSignalFunc = ZB_SignalMA;
- else if(StrategyType == 2)
- pSignalFunc = ZB_SignalRSI;
- else if(StrategyType == 3)
- pSignalFunc = ZB_SignalMACD;
-
- return(INIT_SUCCEEDED);
- }
-
- void OnTick()
- {
- double signal = pSignalFunc(Ask); //调用方式完全没变!
- Print("signal = ",signal);
- // 后续下单逻辑
- }
复制代码
需要说明的是,这段代码只是演示函数指针如何使用,在实际EA中,完全像举例中这么写纯属多此一举。你会发现上面的代码中,一大堆的if/else还是存在,只是换了个地方罢了。也就是说如果你只需要在某处调用一次某个函数,哪怕这些函数是多选一的,还是老老实实写if/else比较靠谱。但如果你的函数不只是一个位置调用,而是多处调用,用函数指针来写,才能实现所谓的清爽和容易维护的效果。因为一处调用就代表着要写一处if/else,有多少处调用就要写多少个if/else,而改用函数指针来写时,就只需要写一处if/else了。当然也可以封装一个if/else函数,在需要调用的地方调用就好了,也不是非得写函数指针。 (二)typedef 和函数指针typedef可以给【同一类的函数原型】起别名。要理解这个用法,我们需要先介绍一些函数有关的概念。 程序中的函数,至少包含三部分: 一部分是函数原型,包括函数的参数和返回值,可以近似理解为输入和输出数据;其中函数的参数部分又叫做函数的签名,所谓函数签名,就是用来唯一描述一个函数 “长什么样、怎么调用” 的一套信息:包括函数的参数数量、每个参数的类型,以及参数的排列顺序。一些复杂函数,不止一个参数,各参数的类型也不尽相同。 第二部分是函数定义部分,也就是大括号{ }中包含的那些函数代码,是实现函数逻辑的主体。 第三部分是函数名,就是给函数起的名字,可以自定义,也有系统自带的。 在 MQL5/C 语言体系里,一个函数的类型,就是由它完整的函数原型决定的。函数原型 = 返回值类型 + 函数签名(参数数量、类型、顺序)。只要原型确定,这个函数的类型就确定了;函数名字不属于函数类型。比如前面我们举的例子:double ZB_SignalMA(double price),中间的“ZB_SignalMA”是自己取的函数名字,可以换成任意函数名字,括号里的“price”也是自己取的变量名字,指代一种数据,该举例函数完全可以写成:double XXX(double XXX),如果该函数有多个输入参数,就可以写成:double XXX(double XXX, double XXX, int xxx)。 通过这个简化写法,我们会发现,一个函数最重要的两部分在简化中保留了下来,一个是返回值的类型,也就是括号外面那个“double”,一个是输入参数的类型、数量、顺序,也就是括号里面那些内容,如果多个函数的这些内容相同,我们就可以判断这些函数是同类函数。 很明显,我们的三个策略函数,就属于同类函数:double XXX(double XXX),即输入一个浮点数参数,返还一个浮点数结果的函数。我们用typedef给这个函数原型double XXX(double XXX)起个别名: typedef double ZB_SignalFunc(double price); “ZB_SignalFunc”就是我们起的名字,相当于我们给这一类函数起了一个类别名字,看到这个名字,就知道是这一类函数。 这时候,之前常规星号函数指针的写法:double (*pSignalFunc)(double);就可以替换成ZB_SignalFunc *pSignalFunc; ,相当不不用再写那个带括号的函数原型的格式了。 细心的朋友应该可以发现,如果我们只有一次调用,且函数只有一个参数,用typedef来写函数指针纯粹自寻烦恼,没什么实际意义,还增加了代码复杂成都。 本质上,在这种情况下写函数指针都是自寻烦恼。 只有需要多处调用,且函数参数数量、类型较多、较复杂时,用typedef来写函数指针,才能发挥它的简化作用。因为一个函数只能有一个返回值类型,但可以拥有多个输入参数,且输入参数的类型还可以各不相同,对一些复杂的函数,小括号里面写五六个参数都属正常,常规函数指针写法,在这种情况下会很长,且容易写错。 |