最后由 麻爪 于 2026-9-23 17:16 编辑
第二十三章 语句和表达式(上)
我用了一个多月的时间,终于把MQL5数据类型囫囵吞枣得学完了。细心的朋友会发现这里面提及了许多奇奇怪怪的概念。这章要讲的运行式已经脱离了数据类型的范畴,开始讲“语句”了,所以我们得先缕缕这些旧概念,防止学新东西时昏了头。
(1)我们之前看的数据类型、变量、常量、指针、引用等,属于“词法 + 数据层面”,讲的是数据是什么、长什么样、存在哪。 (2)表达式(expression),仍然绑定数据类型,是拿数据做计算,算出一个结果值。它非常像数学上的代数计算式,但范围比代数算式更广,是广义的 “能算出一个结果的式子”。编程语言表达式不只做加减乘除,还可以比较大小、进行逻辑计算,以及取地址、new、函数调用等这些也属于表达式。 (3)语句(statement)属于逻辑执行层面,手册上叫”运行式“,不是常规叫法,语句指挥程序 “做什么动作”,比如执行、分支、循环、赋值、函数调用等,这些都是程序的“动作”。
在这些所谓的“层面”概念下,会有一堆更细碎的概念,我们也必须捋一下: (1)标识符:你给变量、结构体、类、数组、函数之类的东西起的名字; (2)关键字:MQL5语法给自己预留的专用符号; (3)字面常量:代码中直接写死的数字、文字标语,说白了就是运行期间不变的数据; (4)操作符/运算符(同一个东西):加减乘除、等于这类操作符号; (5)分隔符:逗号、分号、括号等标点符号,用来划分段落边界; (6)空白符:主要有三种,空格、换行、Tab(制表符),用于隔开标识符、关键字、运算符,除此之外本身没有运算或其他语法方面的含义,编译的时候会被编译器丢弃;多个空白符,比如按了三次空格、五次回车,系统都当它是一个,但是不能在标识符或关键字内部打空格或者回车,前面还说过不能在单行注释的内部回车,另起一行的单行注释,系统当它的代码正文的,同时也不能省略其他符号之间必须有的空格; 这些细碎的概念代表着代码中每一个不能继续分割的最小单元,这些单元就是Token(词法单元)。简单来说,学了一个月数据类型一直在“词法层面”打转,只管 “这个东西是什么”,不知道词和词如何搭在一起。而“语法层面”要做的是拿切好的一堆 Token,把这些 “单词”按照这门语言的规则拼起来。 说白了,现在我们终于开始要学一点MQL5的语法了。
一、表达式 表达式一般由标识符、运算符、字面常量、分隔符四种符号组成。简单来说: (1)标识符(含变量和函数)和字面常量就是需要计算的元素,就像代数式里的“元”; (2)运算符表示计算逻辑,加减乘除大于号等于号之类的; (3)表达式中的分隔符,主要用来表示运算顺序,也就是所谓的优先级,所以常用的就这么几种: - `( )` 圆括号:两种作用 —— 改变运算优先级,或者函数调用时包裹参数 - `,` 逗号:逗号运算符,依次计算多个表达式,最终返回最后一个的值 - `.` 点号:访问结构体、类的成员变量或函数 - `[ ]` 方括号:访问数组元素 表达式中的各项组成部分可以有多个,一个写完的表达式以分号分隔符;结尾(形成一个语句),多个表达式可以写成一行,用逗号隔开,一个比较长的表达式也可以写成多行。如果表达式另起一行却没写分号,系统会当该表达式还没写完,自动把第二行的内容接到第一行上。 表达式(Expression)的核心功能:求值。任何能算出一个确定值的代码片段,都是表达式,所以一定有返回值。算术运算返回数值、关系运算返回 0/1、赋值返回左侧值、函数调用返回结果 —— 只要是表达式,就一定能产出一个值。在语法上没有结束标记,本身就是一个计算单元,不是完整指令。 二、语句 语句是一条完整的可执行指令,不要求必须有返回值(可以有),有明确的结束边界。最常见的边界就是分号 `;`,还有大括号 `{}`。它可以嵌入任意表达式,也可以有自己专属的结构符号。语句可以包含表达式所含有的所有内容,同时也具备表达式没有的东西,比如: - 分号 ;(语句的专属结束标记) - 大括号 { }(语句块的专属边界标记) - if else while for switch 等控制关键字(这些是语句结构,不是运算符号) 通常,一个表达式加上一个分号;,就可以组成一个语句,而且是语句里最基础、最常见的一种形式,叫做表达式语句。但这只是语句的一种,”非表达式语句“,即由关键字、结构标记等组成,核心功能不是计算值,就是构成程序骨架、控制执行流程的关键部分,本身不产生返回值,其内部也可以完全不包含任何表达式。但一段能用的代码肯定是二者混着用的,我们只是在格式上做一下区分,避免出现格式错误就好了。
三、算术运算 MQL5中的算数运算,就指的数学上的加减乘除。比如: 加法运算 i = j + 2; 表示两个数值相加,没有顺序区分,谁加谁都一样 减法运算 i = j - 3; 表示两个数值相减 一元取负运算 x = - x; 表示只对单个数字取反,不是两个数相减 乘法运算 z = 3 * x; 表示两个数值相乘,没有顺序区分,谁乘谁都一样 除法运算 i = j / 5; 表示两个数相除 取模(求余) 运算 minutes = time % 60; 表示做完除法之后的余数 后置自增运算 i++; 表示先用变量的值,之后再加 1 前置自增运算 ++i; 表示先把变量 + 1,再使用新的值 后置自减运算 k--; 表示先用变量的值,之后再减 1 前置自减运算 --k; 表示先把变量 - 1,再使用新的值 这里稍微需要提醒一下的是,普通的加法、减法运算符(`+`、`-`),变量和常量都可以参与运算。但自增`++`、自减`--`运算符,只能作用于变量,不能用于常量,因为常量的值不允许被修改。 另外,手册强烈建议,写 EA 交易逻辑时,永远把自增`++`、自减`--`单独放一行,不要嵌套在复杂计算表达式里。涉及仓位、手数、价格的计算,稳定、可读永远优先。 所谓”嵌套在复杂计算表达式里“,就是指类似手册上举的例子”int k = i++ + ++i;“这样,两个自增变量进行计算,这里面会有歧义,第一种就是各自基于初始值,自增最后统一生效,也就是各算各的,第二种第一个自增改完 i,第二个直接用改后的 i。当然MQL5语言有自己的规则,你这么写每次编译都按同样的规则去执行,但架不住我们的复杂EA总难免调用其他语言编写一些辅助软件,编译规则很不相同,就会出现不同的结果。你不必去深究原手册上的例子是怎么个过程,到底歧义是怎么发生的,只记住自增自减这类的表达式,单独写就好了。
四、赋值运算 在数据类型中,我们学了一大堆类似int a;这样的写法,它的含义是声明一个变量,按我们盖房子的比如,相当于是在仓库的场地上放了一个空盒子占位子,里面将来可以放材料。但此时这个盒子里面是空的,没有数据,我们写int a;a = 10;相当于做了两个动作,先声明一个变量,然后在这个空盒子里放了一个整数10数据,这就叫给变量赋值。如果直接写成int a = 10;,相当于声明变量同时给盒子里放一个整数10的数据,同样也是给变量赋值,不过这种赋值方式我们一般当它是个初始值,后面会变的,又叫给变量“初始化”。 手册上管赋值叫做”运算“,意思是类似a = 10;这样的写法,也是一个表达式——赋值运算表达式,表达式本身有返回值,也就是总会有个结果,表达式a = 10;的返回值就是”10“,所以我们这样写int b = (a = 10); ,是典型的”赋值表达式嵌套“,把赋值表达式的返回值赋值给b。搜易赋值不只是把一个具体的数字赋值给变量,同时也可以把任意能产出一个确定返回值的表达式的结果,不管是简单的算术运算,还是复杂的函数调用,还是多层嵌套的组合表达式,只要最终能算出结果、且类型和左边变量匹配,就能赋值给变量。 还有所谓的连续赋值,比如a = b = c = 5;,意思是从右往左依次执行,把 5 赋给 c → 把 c 的结果值赋给 b → 把 b 的结果值赋给 a。每一段赋值表达式的值都是 5,一路向左传递。并不是把5一次赋值给a、b、c。在这种简单的常量同类型连续赋值中,看不出什么区别,在其他情况下,会有本质不同,所以不要理解错了。
五、复合赋值运算符 通用公式:y 运算符= x 等价于 y = y 运算符 x。是编程里非常通用的简写语法,核心规则完全一致:先拿左边的变量和右边的值做指定运算,再把运算结果重新赋值给左边的变量。 (一) 算术类复合赋值 就是在普通加减乘除、取模的基础上结合赋值,是日常写代码最常用的一类。 在这里需要特别聊一下赋值运算中的等于号”=“,与数学上的等于号含义完全不同,严格来说,这个”=“都不能叫等于号,而是赋值符号。它并不代表左右两侧的数值相等,而是把右侧的表达式的返回值赋值给左侧的变量。所以在编程中,出现y=y+1这种在数学上明显不等的式子,不要有这方面的疑惑。在数学代数式中,字母y代表一个未知数,虽然未知,但确是一个固定不会变的数字。而编程中代表变量名字的字母,可以是一个字母也可以是一个单词,它代表是不是具体某个数,而是一个装数字的”空间“也就是我们说的”盒子“,所谓赋值就是把数字放进盒子里,一个整型变量y,里面可以是7,可以是8,可以是任意符合int规则的整数,但y本身不是数,所以y=y+1这种写法,千万不要用代数式的思想去理解,会把脑子搞乱的。 (二)位运算类复合赋值 是位运算和赋值的结合。位运算不是对数值大小做计算,而是直接操作整数二进制的每一位。所有整数在内存里都是二进制存储的,比如数字 6 就是 `110`,数字 5 就是 `101`。位运算就是对每一位单独做逻辑运算。算术复合赋值日常高频使用,比如累加盈利、统计手数,都可以用 `sum += value` 简写。但位运算复合赋值在普通 EA 逻辑里用得不多,但在处理系统标志位、订单状态掩码、权限参数的时候会经常碰到。我们在这里只做了解。 补充三个位逻辑运算的基本规则: - 按位 与(&):两位都是 1,结果才是 1,否则为 0(常用于提取、清除某一位)
- 按位 或(|):只要有一位是 1,结果就是 1(常用于设置某一位为 1)
- 按位 异或(^):两位不一样结果为 1,一样为 0(常用于翻转某一位)
六、关系运算 所谓关系运算,就是比较两个数的大小 / 相等关系,总共有六种关系运算符。 (一) == 等于运算符 用来判断左右两个值是否完全相等,相等则返回 TRUE(1),不相等返回 FALSE(0)。写法必须是两个等号,和单个等号 `=`(赋值)完全不同。例如:`price == stopLoss` 判断当前价格是否等于止损价。 (二)!= 不等于运算符 用来判断左右两边的值是不是不一样,和 `==` 正好相反,不相等时返回 TRUE,相等时返回 FALSE。例如`orderType != OP_BUY` 判断订单类型是否”不是买单“。 (三)< 小于运算符 用来判断左边的值是不是比右边小,只有左边”严格“小于右边时才成立,返回 TRUE;等于或大于都返回 FALSE。例如`lots < 0.01` 判断手数是否小于 0.01。 (四)> 大于运算符 用来判断左边的值是不是比右边大,只有左边”严格“大于*右边时才成立,返回 TRUE;等于或小于都返回 FALSE。例如`profit > 0` 判断账户盈利是否大于 0。 (五)<= 小于等于运算符,又叫:不大于 用来判断左边的值小于”或者“等于右边,满足其中一个就算成立,返回 TRUE;两种情况都不满足返回FALSE。相当于在 `<` 的成立的范围内增加了 “相等” 的情况,且要求满足其一即为真。比如`drawdown <= 0.1` 判断最大回撤不大于 10%,我们有时候也说小于10%(含),含就是包含等于的情况。 (六)>= 大于等于运算符,又叫:不小于 用来判断左边的值大于”或者“等于右边,满足其一就算成立,返回 TRUE;两种情况都不满足返回FALSE。相当于在 `>` 的成立的范围内增加了 “相等” 的情况,且要求满足其一即为真。比如`barsTotal >= 100` 判断 K 线总数不少于 100 根,我们有时候也说大于100 根(含),含就是包含等于的情况。 给新手的忠告。我第一次看到这里,有点想当然,这不就是三年级数学的内容吗?这就像前面的等于号,如果你按数学内容来理解,那就叉屁了。数学上的表达式,真就是顾名思义,表达一种具体的意思,例如8>5,这种写法就是表达一个8大于5的真命题,没有结果,没有返回值,也不存在所谓的”运算“关系。但编程表达式,一定代表着某种”运算“关系,代码中的8>5要表达的就是,8是否大于5,是就返回一个结果TRUE,不是就返回一个结果FALSE,而且这个所谓的结果/返回值,还可以拿去继续运算或者给其他函数调用。一定要区分这里的认知区别。 |