
从基础到中级:操作符
概述
此处提供的材料仅用于教育目的。在任何情况下,除了学习和掌握所提出的概念外,都不应出于任何目的使用该应用程序。
在上一篇文章“从基础到中级:变量(三)“中,我们探索了预定义变量和一种有趣的解释函数的方式。然而,到目前为止讨论的一切都导致了一个共同的挑战,这是新程序员面临的最大困难之一,尤其是那些从事小型个人项目的程序员。这一挑战源于不同数据类型的存在。
正如在“从基础到中级:变量(二)"中,MQL5 将数据分为多种类型。然而,为了正确解释数据类型,我们需要建立正确的上下文。那个背景正是本文的主题:基本操作符。在我们有效地讨论数据类型之前,理解这些操作符是至关重要的。
有些人可能会觉得这个话题很简单,没有必要涵盖。然而,正是因为它看起来微不足道,它才变得至关重要。许多编码错误源于对这一基本概念的误解。
话虽如此,让我们深入探讨本文的第一个主题。
数据类型和操作符
在非类型化语言中,讨论操作符和数据类型通常是不必要的。例如,像 10 除以 3 这样的操作将毫无问题地产生预期的结果。然而,在像 MQL5 这样的强类型语言以及 C 和 C++ 中,这种简单的划分不会产生单个结果,而是产生两个不同的结果。在某些情况下,甚至更多,但我们将在稍后的讨论中探讨这些额外的案例。目前,我们将重点讨论两种不同结果的可能性。
等一下,两个结果?这说不通!你可能会想:你疯了吗?每次我们将 10 除以 3,我们都会得到 3.3333......没有其他可能的答案!好吧,如果这就是你的想法,那么这篇文章绝对适合你。这里的目标是澄清,在编程中,事情并不总是像乍一看那样工作。
首先,让我们研究一个比处理循环小数更简单但仍然演示单个操作如何产生两个完全不同的结果的案例。下面,我们来看一个简单的代码示例。就是这样:
1. //+------------------------------------------------------------------+ 2. #property copyright "Daniel Jose" 3. //+------------------------------------------------------------------+ 4. void OnStart(void) 5. { 6. Print(5 / 2); 7. } 8. //+------------------------------------------------------------------+
代码 01
这个简单的代码 01 将用于说明一个有趣的概念,这个概念往往会引起严重的混淆。您可能认为,即使不在 MetaTrader 5 终端中运行代码,您也已经知道第六行执行的操作的结果。然而,我向你发起挑战:你真的知道会显示什么值吗?答案可能会让你感到惊讶,因为它取决于用于计算的数据类型。当然,预期结果是2.5。但是,如果执行代码 01,您将看到终端打印出 2。但这是为什么呢?难道计算机不知道如何计算这么简单的表达式吗?事实上,计算机本身并不知道如何执行算术。计算机在加法方面表现出色,但在大多数其他数学运算方面却很糟糕。
“等等,你是想骗我们吗?”当然不是!尽管这看起来有些奇怪,但这是一个基本事实:计算机只能执行加法。即便如此,它们仍然会对分数感到困难。如果你让计算机将两个分数值相加,它可能并不总是产生正确的结果。这一切都归结为数字在计算机内存中的表示方式。
计算机只理解 0 和 1,即开启和关闭状态。它们天生无法识别 2 或 3 这样的数字。相反,它们依靠布尔逻辑来处理数据和执行计算。通过以某种方式进行操作,它们完成了我们要求的计算。但是如果我在计算器中输入 5 除以 2,我会得到 2.5。那么为什么 MQL5 返回 2 而不是 2.5?
这正是数据类型发挥作用的原因。在第六行,两个值都是整数类型。因此,编译器决定输出也必须是整数。相比之下,计算器允许结果是整数或浮点数(在编程中也称为小数)。这是许多程序员,特别是初学者感到困惑的地方。在动态类型语言中,结果总是相同的。然而,在 MQL5 等强类型语言中,根据您如何定义数据类型,存在多种可能的结果。
要修复代码 01 以显示 2.5 的正确结果,我们需要对其进行修改,如下例所示:
1. //+------------------------------------------------------------------+ 2. #property copyright "Daniel Jose" 3. //+------------------------------------------------------------------+ 4. void OnStart(void) 5. { 6. Print((double)(5 / 2)); 7. } 8. //+------------------------------------------------------------------+
代码 02
当我们进行代码 02 中的修改时,结果现在将是明确的。此过程称为 typecasting 或类型转换。MQL5文档详细解释了这一概念,许多其他编程语言参考文献也是如此。
对于 MQL5,您可以参考类型转换部分,其中的插图有助于阐明隐式转换的工作原理,始终倾向于更复杂的类型,通常是双精度型。下面展示的就是其中一个例子。
图 01
由于文档已经解释了数据类型如何相互缩放,因此我们在这里不再赘述这些细节。然而,好奇的读者可能会想:为什么会发生这种情况?
理解这一点将有助于你掌握许多其他概念。例如,为什么执行数学运算有时会产生不正确或无意义的结果?为什么一个值最初看起来是正确的,但后来使用时似乎是不正确的?
这些不一致源于数据在计算机内存中的表示方式。为了正确地解释这一点,让我们引入一个新主题。
位宽
在“从基础到中级:变量(二)“一文的最后,我们查看了一张表格,其中显示了存储在内存中的不同数据类型的限制。但是,有一个重要的细节:浮点类型(double 和 float)的表示方式与整数类型(int、ushort 等)不同。现在,我们将只关注整数值。浮点数需要更深入的解释,我们稍后会介绍。这一点至关重要,因为盲目信任浮点计算可能会导致严重的不准确。
首先,让我们将整数值分解为最基本的单位:位(比特)。一个类型可以表示的最高值对应于 2 的所用位数次幂。例如,使用 4 位,您可以表示 16 个不同的值。使用 10 位,您可以表示 1024 个值,依此类推。
然而,这仅适用于正值。负数需要稍作调整。对于有符号整数,范围由 2 的 (位数 - 1) 次幂决定,即该值的负数减一。这乍一听可能有点令人困惑,但实际上却相当简单。例如,使用相同的四位,但同时表示正值和负值,我们可以从 -8 到 7。使用 10 位,我们可以从 -512 到 511。但请等一等,它不应该是 -8 到 8 或者 -512 到 512 吗?毕竟,如果我们将 8 和 7 相加,我们会得到 15,而不是 16;如果我们将 512 和 511 相加,我们会得到 1023,而不是 1024。为什么会这样?缺失的值为零。零在范围中占据一个位置,这就是为什么计数会略微偏离。
为了进一步澄清,我们需要了解负数是如何存储在内存中的。让我们看看下图:
图 02
这里,我们看到一个 8 位的值,它可以表示无符号字符 (uchar) 或有符号字符 (char)。区别在于最高有效位 (MSB,Most Significant Bit)。对于 uchar (无符号),所有值都是正数(由 u 前缀表示),MSB 不是问题,允许值从 0 到 255(总共 256 个值)。但是,对于 char (有符号),MSB 决定值是正数还是负数。如果 MSB 为一,则该值为负数,如果 MSB 为零,则该值为正数。因此,我们可以计算从 0 到 127 的正值。但是,由于零是无符号的,并且这是唯一一个 MSB 为开而其他位不为开的情况,因此它被解释为 -128,不存在“负零”这样的情况。这也解释了为什么对于负值,计数总是比可能值的一半多一个值。
很有趣,不是吗?但现在一切只会越来越好。如果你已经理解了上述解释,那么你就已经理解了 ABS 或 MathAbs 函数在许多编程语言中是如何将负值转换为正值的,反之亦然。要执行此转换,我们只需要更改 MSB 的状态。
然而,如果我们不注意我们所做的事情,意想不到的事情可能会发生在我们身上。例如,如果将两个正值相加,比如 100 和 40,就会得到一个负值。“等一下,什么?如果两个数字都是正数,那么总和也必须是正数!"在日常数学中,这是正确的。但在计算领域,事情并不总是如预期的那样运作。让我们通过以下代码示例来探索这一现象。看看下面的代码:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. char value1 = 100, 07. value2 = 40, 08. Result; 09. 10. Result = value1 + value2; 11. Print(value1, " + ", value2, " = ", Result); 12. } 13. //+------------------------------------------------------------------+
代码 03
执行此代码后,屏幕上将显示以下结果:
图 03
天啊!它是什么?这是不可能的。不,亲爱的读者,这绝对不是不可能的。事实上,这正是为什么需要写这篇文章。您必须明白,在强类型语言中,选择正确的数据类型会直接影响计算结果。许多程序员因为没有掌握这个基本概念而感到非常困难。更糟糕的是,许多人仅仅因为对这些看似简单的问题缺乏认识而被误导。然而,如果没有正确的理解,你很容易受到对准确性和安全性的错误保证。
此时,你可能会想:“好的,我们正在使用一种可以表示正数最大 127 的值和负数最小 -128 的值的数据类型。使用更大的类型(例如 int,使用 32 位而不是 8 位)不是更好吗?”是的,亲爱的读者,这似乎是一个合理的解决方案。然而,这并不是真正的问题所在。问题是,在某个时候,将达到最大可表示值,当这种情况发生时,计算将不可避免地以某种方式失败。请记住,我们仍然只处理整数类型。浮点数带来了更大的复杂性。
因此,在深入研究浮点运算之前,首先了解整数运算至关重要。然而,这里有一点需要强调:并非总是能够始终使用更大的数据类型。在处理字符串时,这种限制尤其明显。这就是事情变得更加复杂的地方。字符串或字符序列可以使用 8 位或 16 位编码。大多数情况下,计算机依赖于 ASCII 表。它是在计算早期创建的 8 位编码标准。然而,由于 ASCII 不足以表示某些字符,因此必须开发其他编码方案。这导致在一些程序中引入了 16 位字符编码。然而,使用 1 6位编码并不能创造无限的可能性。它只是通过将最高有效位 (MSB) 移至位 15,将范围从 256 个值扩展到 65536 个值。
即便如此,字符串最终只不过是一组更简单的值,但它允许更大的表示范围。例如,在 MQL5 中,您可以创建一个 128 位的值,即使最大的预定义类型 (ulong) 被限制为 64 位。但这怎么可能呢?事实上它非常简单。如果您了解序列中的每个位在打开或关闭时对值的贡献,则可以将活动位的相应值相加。通过这样做,几乎神奇的事情发生了:你获得了表示任何可以想象的价值的能力。
这就是为什么在代码 03 中将 100 和 40 相加时结果为 -116 的原因。这是因为在这种情况下, -116 就代表 +140。起初,这可能看起来很荒谬,但在检查二进制值时,您会看到以下内容:
图 04
换言之,相同的二进制值在一种情况下可以被解释为正,在另一种情况中可以解释为负,这完全是取决于 MSB 是如何处理的。因此,您必须非常谨慎,特别是在创建循环时。如果管理不当,这些与类型相关的问题可能会导致计算和循环的行为不可预测,即使一切似乎都正常运行。类型选择错误或超过给定类型的最大限制可能会导致整个程序失控。
有一次,我们提到使用更大的数据类型可以解决这个问题。但为什么这种方法在某些情况下有效,而在其他情况下无效?答案在于 MSB 是如何移位的。您可以在下图中看到这种效果:
图 05
这是一个简单的概念,不是吗?现在,这涵盖了算术运算的基础知识。然而,到目前为止,我们讨论的所有内容都与算术操作符有关。接下来我们需要探索另一类关键的操作符:逻辑操作符。为了解释和正确区分这些概念,我们将转向一个新的主题。
逻辑操作符
逻辑操作符在位级别进行操作。虽然它们有时可以应用于字节或整个位集,但它们主要是为在位级别上运行而设计的。起初,这可能看起来有点令人困惑,但随着时间的推移,你会发现这是有道理的。与用于执行计算的算术操作符不同,逻辑操作符用于评估条件。通常,逻辑运算测试值是否满足特定条件。由于这些操作符在与控制结构一起使用时最有效,因此我们在这里只提供一个简要的概述,以便您为即将到来的主题做好准备。
尽管逻辑操作符在与条件语句一起使用时更有意义,但它们也可以用于执行小规模操作。事实上,每个 CPU 操作实际上都比算术更合乎逻辑。尽管算术逻辑单元(ALU)以算术和逻辑命名,但逻辑运算是 CPU 功能的基础。
就逻辑运算符而言,我们主要有 AND(与)、OR(或)、NOT(非),在某些(但不是全部)编程语言中还有 XOR(异或)。除此之外,我们还有右移和左移操作。这些简单的操作是所有计算逻辑的基础。事实上,任何事物都可以从这些基本操作中构建出来。这正是 ALU(CPU 的“大脑”)运行的原理。
此时,您可能正在查看 MQL5 文档(或其他编程语言的参考资料)并想知道:“但还有更多的逻辑运算符,例如大于(>)、小于(<)等。为什么它们没有包括在这里?”好问题,亲爱的读者。为了方便起见,一些语言提供了额外的逻辑运算符或比较函数。然而,在硬件层面上,这些操作比编程语言中呈现的方式简单得多。需要明确的是,这并不是对这些实现的批评。事实上,它们使编程变得更加容易。例如,比较两个值的最简单方法是减法。然而,另一种方法涉及应用逐位 XOR 运算。如果所有位都匹配,则结果为零。如果任何位不同,则值不相等。执行减法时,我们可以分析 MSB 以确定结果是零(等于)、负(小于)还是正(大于)。这使我们能够在没有显式比较运算符的情况下建立数值关系。
为了明确这个概念,让我们来看一个简单的例子,它确定一个值是大于、小于还是等于另一个值。为此,我们将使用以下代码片段:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. short value1 = 230, 07. value2 = 250; 08. 09. Print("Result #1: ", value1 - value2); 10. Print("Result #2: ", value2 - value1); 11. Print("Result #3: ", value2 ^ value1); 12. } 13. //+------------------------------------------------------------------+
代码 04
这里我们有一个简单而典型的数值分析系统。执行此代码时,您将在终端中看到以下结果:
图 06
现在,请注意前面提到的关于确定一个值是否大于、小于或等于另一个值的内容。为了让事情变得更有趣,我们使用一个 16 位有符号值,它允许我们表示 65536 个不同的值。但是,由于它是一个有符号值,意味着它既可以保存负数也可以保存正数,所以范围将从 -32768 到 32767 变化。如果我们坚持分析 8 位范围内的值,我们就不会遇到确定一个值是否大于、小于或等于另一个值的问题。但是使用 16 位值,我们可以表示更广的范围,从 -255 到 255,这非常好。话虽如此,还是有方法可以使其变得更好。但我们暂时不会深入探讨这个问题,因为有一些概念需要首先解释。不过,代码 04 还是相当迷人和有趣的。
那么,让我们来分解一下这里发生的事情。由于 value1 明显小于 value2,因此用一个值减去另一个值将得到一个负值,从而证实第一个值确实较小。这发生在代码 04 的第 9 行。另一方面,当我们从 value2 中减去 value1 时,如第 10 行所示,结果为正,表示 value2 大于 value1。最后,在第 11 行,我们进行比较以检查值是否相等。由于结果为非零,我们可以明确地得出结论,这些值并不相同。
这个概念非常有趣,当你意识到 ALU 中没有减法运算时,它变得更加有趣。事实上,ALU 中执行的任何操作本质上都只是一个和,结合了一些逻辑运算。即使是加法操作,其核心也可以简化为逻辑操作。
然而,为了正确地演示这一点,我们需要使用一些控制函数。由于本文中尚未解释控制函数,因此我们暂时不会深入探讨如何在 ALU 级别实现它。但别担心,我们会在以后的文章中介绍这一点。一旦你了解了如何实现这些技术,你就可以使用 MQL5 完成更高级、更令人兴奋的任务,而不仅仅是创建指标、脚本和 EA 交易系统。
我是否会演示这个实现仍然不确定,因为这里的主要目标纯粹是教育。但我会考虑一下。
我同意,移位运算符仍然缺乏适当的表示,因为它们在 MQL5 文档中几乎没有被提及。然而,这些操作符具有非常具体的用途并且旨在执行特定的任务。这就是为什么它们在最常见的 MQL5 代码中没有被广泛使用的原因,尤其是在使用指标和 EA 交易的时候。但是在用 MQL5 创建的应用程序中处理图像时,这些运算符的使用频率要高得多。
然而,如果它们用于非常具体的活动,它们也可以用于其他目的。一旦我们深入研究控制函数,就会探索这样的一项任务。
最后的探讨
在本文中,我们介绍了编程的一些重要细节,这些细节在使用强类型语言时会产生重大影响。虽然讨论的一些概念对许多人来说可能是新的,但我们只是触及了这个话题的表面。因此,亲爱的读者,我建议您花时间学习基本的 MQL5 文档,以加深您对这里介绍的概念的理解。此外,我强烈建议探索布尔逻辑,尽管它很简单,但它将有助于简化您在编码任务中遇到的许多计算。了解如何处理正值和负值以及这些细微差别如何发挥作用,从长远来看会产生巨大的影响。
在附件中,我将包括这里讨论的四个代码中的三个,以便您可以按照自己的节奏学习它们。在下一篇文章中,我们将开始探索控制函数或运算符。那时,事情会变得非常激动人心,真正的乐趣也会开始。期待很快与您见面!
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/15305



