
从基础到中级:数组和字符串(一)
概述
此处提供的内容仅用于教育目的。在任何情况下,除了学习和掌握所提出的概念外,都不应出于任何目的使用此应用程序。
在上一篇文章“从基础到中级:运算符优先级”中,我们稍微讨论了在代码中使用分解时应采取的预防措施。遇到乍一看正确的代码并不罕见,但在某些情况下最终会产生完全出乎意料的结果。这类问题通常与如何实现分解直接相关。让一段代码提供一致的结果似乎是一项微不足道的任务。然而,如果没有适当的知识(如那篇文章所示),随着越来越多的实现不佳的分解被引入代码中,灾难性故障的可能性就会增加。
由于正确创建分解的理论部分可能非常无聊和乏味,我们不会详细讨论它。相反,让我们专注于事情的实际方面。也就是说,亲爱的读者,我鼓励你尽可能地尝试提供的示例代码。尝试更改分解,甚至修改控制流,使其与附件中显示的略有不同。
这种做法非常重要,因为它可以帮助你学习如何通过走一条你自己创造的不同道路来达到同样的结果。尽管许多人可能认为这是浪费时间,但事实上,这是最有效的学习方法。这是因为附件包含的代码是功能性的,并能产生有效的结果。你的任务是开发一个仍然产生相同结果的代码的不同版本。通过实践,你将能够创建自己的解决方案,即使起初它们看起来不专业。
在简短的介绍之后,让我们继续讨论本文的主题。理解前面的内容有一个先决条件。您应该已经对声明和使用变量和常量有了扎实的理解。尽管这里的重点更多地放在运算符上,而不是其他任何东西上,但声明、初始化和使用变量和常量的能力对于遵循将要解释的内容至关重要。在这里,我们将开始讨论一些特殊的数据类型。我会尽一切努力避免只停留在理论上。这个主题,以及我们在上一篇文章中介绍的运算符优先级,可能是更复杂的领域之一。然而,就目前而言,我们将从最简单的角度来处理这个问题,以便清楚地介绍这个主题。尽管如此,在我们深入探讨一个更具挑战性的话题之前,这些基础知识是必要的。那么,让我们开始吧。
数组和字符串
毫无疑问,对于使用静态类型语言的新程序员来说,这是最令人困惑和沮丧的话题之一。这是因为 Python 和 JavaScript 等动态类型语言几乎透明地处理这些主题,允许程序员使用它们而不必太担心定义或结构。
相比之下,由于这两个实体是如何在内部处理的,像 C 和 C++ 这样的语言使这个主题变得更加复杂。在这些语言中,数组和字符串不被视为不同的实体。事实上,根据你如何使用它们,你可能会在同一个上下文中遇到第三种甚至第四种类型的实体。简而言之,对于初学者来说,这是一个复杂的主题。
然而,在 MQL5 中,我们发现自己处于 C/C++ 等静态类型语言和 Python 或 JavaScript 等动态类型语言之间的中间地带。我为什么这么说呢?与 C 和 C++ 不同,在 C 和 C++ 中通常可以忽略数组的数据类型,在某些情况下,也可以忽略字符串的数据类型,但 MQL5 不允许这种灵活性。至少在没有特定操纵的情况下不会如此。这实际上在一定程度上简化了学习过程。但是,如果没有坚实的基础,在 MQL5 中执行某些类型的数据操作几乎是不可能的,尤其是在 C 或 C++ 中可以轻松完成的操作。也就是说,作为初学者,你不太可能在 MQL5 中编写出超出你控制范围的东西。与 C 或 C++ 不同,在 C 或 C++ 中,即使是一个小错误也会变成一颗定时炸弹,随时准备在最轻微的失误中引爆。
但对我们来说,学习 MQL5 比学习如何用 C 或 C++ 完成相同的任务要容易得多。这就是为什么我很高兴分享和翻译我对 C 和 C++ 的知识,以帮助将其应用于 MQL5。也许,我可以帮助你们中的一些人在此过程中达到强大的编程专业水平。
现在让我们进入主题。首先,让我们明确一点。从某种意义上说,数组和字符串是一回事。至少从数据如何存储在内存中的角度来看。更确切地说,字符串只不过是一种特殊的数组。我之所以称之为“特殊”,是因为一个特定的细节:字符串包含一个标记,指示它的结束位置。常规数组中不存在此标记。
让我与其他编程语言进行快速比较以澄清这一点。一些语言,例如基于 BASIC 的语言(是的,MS-DOS 时代的老派 BASIC),将字符串的长度存储在其第一个字符中。这允许在字符串中使用任何字符,因为长度是预先明确提供的。第一个字符可以占用多个字节,永远不会显示。它对用户来说仍然是不可见的,只能通过代码访问。此方法使您可以自由使用字符串中的任何字符或数值。但它将字符串的最大长度限制为初始字符的位宽。
基于 C 和 C++ 的语言使用不同的方法。在这些语言中,使用特殊字符或值来指示字符串的结束位置。字符串作为本机数据类型的概念在 C 或 C++ 中并不存在。通常,使用空值或零值作为终止符。当此值出现在序列中时,它标志着字符串的结束。一方面,这种方法允许您创建一个几乎可以占用所有可用内存的字符串。另一方面,这意味着您不能在字符串内容中使用空字符。
由于 MQL5 基于 C 和 C++,因此它遵循相同的逻辑。然而,与 C 和 C++ 不同,MQL5 确实包含字符串类型。因此,我们不能总是只操纵字符串。在这些情况下,使用数组会更合适。当然,这样做会带来其他问题。幸运的是,MQL5 库足够强大,可以为大多数这些问题提供解决方法。有些很简单,有些则比较复杂。
我为什么要解释这一切?原因很简单:如果不正确理解字符串和数组是如何建模的,你将被限制在一组狭窄的选项中,无法执行某些任务。尤其是更高级的编程操作。经常听到人们抱怨“你不能在 MQL5 中做这个或那个”。但是当你深入挖掘时,你经常会发现这些程序员被困在一个有限的概念框架内,这阻止了他们超越标准库的基本产品。
这并不是说使用标准库反映了缺乏知识。事实上,恰恰相反。但如果你不了解事物是如何真正相互联系的,你会经常发现自己在抱怨某些事情做不到。
所以,亲爱的读者,请理解:数组可以被认为是一种通用字符串,几乎可以容纳任何东西。另一方面,字符串是一种更受约束的构造,它有限制,有时甚至是彻底的限制。如果你真的想构建一些东西,但不知道数组或字符串所施加的约束,你根本无法做到。数组可能有点难以掌握的原因之一是,字符串本质上是一个 uchar 或 ushort 类型的数组,具体取决于所使用的字符编码。然而,纯数组可以是任何类型:从类到布尔值。这无疑使初学者的事情变得复杂。但既然我的目的是向你们展示一条切实可行的前进道路,我们将从最基本的类型开始。那么,让我们从字符串开始。
字符串数据类型
当我们创建或声明一个变量为字符串时,我们实际上是在告诉编译器创建一个数组。这是一种特殊的数组,它包含一个字符或值来指示字符串的结束位置。在更深层次上,当使用字符串变量时,我们不需要手动管理内存分配。这是由系统自动处理的,因为需要或多或少的内存。
这使我们能够做各种各样的事情,而不用太担心。另一方面,也有一些局限性,但我们现在不会担心这些。让我们专注于能做什么。记住,真正的精通只有通过持续的练习才能实现。在这里,我们只是触及表面。那么,让我们从第一段代码开始,如下所示:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. string sz0 = "Hello world !!!", 07. sz1 = "My first operation with strings...", 08. sz2; 09. 10. sz2 = sz0 + sz1; 11. 12. Print(sz2); 13. } 14. //+------------------------------------------------------------------+
代码 01
亲爱的读者,请密切注意。这里我们创建了三个字符串变量。其中两个在创建时被声明和初始化,第三个接收组合其他两个的结果,从而创建一个新字符串。第 12 行只是将结果打印到终端,如下所示:
图 01
这里的关键细节如下:请注意,在第 10 行,我们使用了加法 (+) 运算符。这是默认情况下对字符串执行定义操作的少数(如果不是唯一)运算符之一。在这种情况下,它将一个字符串与另一个字符串连接起来。但是,要非常小心这种概念,因为运算符并不总是按照我们期望的方式运行,尤其是在与不同数据类型一起使用时。但我们稍后将更详细地介绍这个话题。目前,在处理你不熟悉的代码时要小心。
所以,我们在终端中显示了一个句子。但是,如果我们想把它分成多行呢?我们该怎么做呢?一种方法是使用两个单独的 Print 语句。但是,如果你想直接在字符串中插入换行符,你可以通过使用某些特殊字符来实现。
字符串中可以包含各种特殊字符。所有这些 — 至少是MQL5支持的那些 — 都源自 C/C++。因此,如果你想在特定点添加换行符,你只需要包含这样的内容:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. string sz0 = "Hello world !!!", 07. sz1 = "My first operation with strings...", 08. sz2; 09. 10. sz2 = sz0 + "\n" + sz1; 11. 12. Print(sz2); 13. } 14. //+------------------------------------------------------------------+
代码 02
这里我们有一个使用这种标记的例子。这些标记,例如“\n”,通常被称为转义序列。如果你做一些研究,你会发现许多这样的小代码。当您在字符串中包含转义序列时,输出会发生变化,使我们能够实现如下所示的结果:
图 02
然而,转义序列并不止于此。例如,我们可以使用 NULL 标记截断字符串,如下例所示:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. string sz0 = "Hello world !!!", 07. sz1 = "My first operation \0with strings...", 08. sz2; 09. 10. sz2 = sz0 + "\n" + sz1; 11. 12. Print(sz2); 13. } 14. //+------------------------------------------------------------------+
代码 03
当执行此代码 03 时,MetaTrader 5 终端将显示以下输出:
图 03
请注意这是多么容易和简单。但除此之外,我们还可以创建包含数值的字符串。在这种情况下,我们需要依赖 MQL5 标准库中的函数,使转换更容易、更高效。也许在未来的某个时候,我会向您展示如何为类似的目的构建自己的自定义版本。但那是另一个主题了。目前,通过使用 MQL5 中提供的内置转换函数,我们可以将数字转换为字符串,并将字符串转换回数字。
此功能非常有用,而且通常是必不可少的,特别是在构建数据分析系统时。下面是一个简单的例子。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. int i_value = 65538; 07. double d_value = 0.25; 08. color cor = clrRed; 09. string sz0; 10. 11. sz0 = "Color: " + ColorToString(cor) + "\nInteger: " + IntegerToString(i_value) + "\nDouble: " + DoubleToString(d_value); 12. 13. Print(sz0); 14. } 15. //+------------------------------------------------------------------+
代码 04
当你运行代码 04 时,你会看到一个类似于下图所示的输出:
图 04
请注意,我们能够创建一个包含各种信息的字符串。然而,在某些情况下,我们需要构造的字符串遵循非常特定的格式。这可能是由于我们正在处理的信息的性质或数据本身的格式要求。在这种情况下,我们需要采取与迄今为止不同的方法。考虑到接下来的事情的性质,我认为最好把它分成一个新的话题。这样,您将更容易学习和应用这里提供的知识。
格式化字符串
当我们谈论文本格式时,许多人会立即想到文字处理器。但在编程中,格式化是指在使用或显示某些信息之前,通常需要满足特定的标准。
这些标准构成了字符串格式。乍一看,这似乎相对简单易行。它允许我们以相当简单的方式构建具有高度特定内容的字符串。然而,作为一名程序员,您需要注意一些重要的考虑因素。其中一个细节涉及输出参数的构造和使用。如果配置得当,这些参数即使对初学者程序员来说也会变得更容易,因为它们在很大程度上取代了我们在上一个主题中使用的手动字符串构造方法。同时,它们在我们如何格式化输出方面提供了极大的灵活性,使我们能够构建具有精确结构的字符串。
现在,您可能已经在某些代码中看到了 PrintFormat 过程的使用。此函数允许我们在 MetaTrader 5 终端中以字符串的形式显示格式化输出。然而,有时我们不希望或根本不适合将此输出直接发送到终端。在许多情况下,我们可能希望将格式化的数据存储在文件中,或者在图表上的图形对象中使用它。在这种情况下,PrintFormat 并不总是正确的工具。幸运的是,MQL5 提供了另一个更适合这些用例的函数:StringFormat。
StringFormat 函数使用与 PrintFormat 相同的参数结构。但与 PrintFormat 不同的是,它的输出以字符串的形式返回。这使得它在各种情况下都非常有用。
因此,我们可以采用代码 04 的输出逻辑并对其进行调整以生成格式化的字符串。我明白,在这个阶段,一些参数可能有点令人困惑。所以我建议你花时间仔细研究每个参数。阅读 PrintFormat 函数的官方文档。它提供了有关每个格式化选项以及如何构建所需字符串输出的详细信息。为了说明这一点,让我们修改代码 04 以包含字符串格式。这让我们得到下面显示的版本:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. int i_value = 65538; 07. double d_value = 0.25; 08. color cor = clrRed; 09. string sz0; 10. 11. sz0 = StringFormat("Color: %s\nInteger: %d\nDouble : %.2f", ColorToString(cor), i_value, d_value); 12. 13. Print(sz0); 14. } 15. //+------------------------------------------------------------------+
代码 05
通过运行代码 05,我们将在 MetaTrader 5 终端中看到类似下图的结果:
图 05
看看图 04 和图 05 之间的区别。尽管这两个示例使用了非常相似的代码,但图 05 中显示的信息以我们的代码中明确定义的格式出现,特别是在第 11 行的实现过程中。现在,你可能会说,在这种情况下,这样的格式化并不是真正必要的。但是,考虑到在某些时候,您将使用十六进制值的可能性。许多程序都使用这些值,特别是在处理位操作时。在这些场景中,如果你想可视化这些值以确保一切按预期工作,你该怎么做?
嗯,这得看情况。但一般来说,代码 05 中显示的格式化方法对于显示十六进制值非常有用。有些人甚至可能会说,为此目的编写一段自定义代码是值得的。不过,在我看来,这样做更多的是为了个人满足,而不是实际需要。无论如何,每个程序员都可以自由地做出自己的选择。但是,由于您仍在开始编程,我建议您继续使用标准库提供的函数和过程。在这种情况下,这意味着使用 StringFormat 函数创建稍后可能要显示的值的十六进制表示。为此,我们将继续使用代码 05,但我们将更改用于构造输出的字符串格式。幸运的是,这非常简单直接:您需要做的就是用下面显示的版本替换第 11 行。
sz0 = StringFormat("Color: 0x%X\nInteger: 0x%X\nDouble : %.2f", cor, i_value, d_value);
运行此修改后的代码时,您将看到以下输出:
图 06
很有趣,不是吗?但我们在这里遇到了一个小问题。这正是我之前提到你需要注意字符串格式化所涉及的细节的原因。在这种情况下,问题在于颜色值。请注意,它以十六进制格式显示。然而,图 06 中显示的值不一定代表红色。事实上,它可能代表完全不同的东西。记住,颜色值通常被格式化为RGB,有时也被格式化为ARGB。因此,仅仅通过查看十六进制值,就很难分辨出它的真正含义。但只需稍作调整,我们就可以将输出更改为更直观、更易读。为此,只需更改代码,如下所示:
sz0 = StringFormat("Color: 0x%06X\nInteger: 0x%X\nDouble : %.2f", cor, i_value, d_value);
一旦你执行了这个新修改的代码,结果将如下:
图 07
等一下!这绝对不是红色的。这里显示的是蓝色。那么这里发生了什么?问题可能是我们在值前添加零以匹配特定格式吗?这会把事情搞砸吗?好吧,是的,也不是。这里真正发生的事情有点微妙。为了正确解释它,我们需要展示另一个使用相同格式概念但应用于不同类型值的示例。
让我们修改变量 i_value 的值,并要求程序以与格式化颜色相同的方式对其进行格式化。也就是说,根据显示的值,可能会有前导零。下面的例子应该会让事情变得更清楚:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. int i_value = 0xF; 07. double d_value = 0.25; 08. color cor = clrRed; 09. string sz0; 10. 11. sz0 = StringFormat("Color :\t0x%06X\t=>\t%s\n" + 12. "Integer:\t0x%06X\t=>\t%d\n" + 13. "Double :\t%.2f", cor, ColorToString(cor), i_value, i_value, d_value); 14. 15. Print(sz0); 16. } 17. //+------------------------------------------------------------------+
代码 06
当我们运行代码 06 时,我们将看到如下内容:
图 08
在这里,我对输出进行了结构化,以便您更容易理解我想要演示的内容。请注意,在代码的第 11 行和第 12 行中,我们打印了两个值。这样做是为了直观地演示为什么十六进制颜色值以这种方式显示。如您所见,i_value 结果似乎非常好。现在,让我们将 i_value 更改为更大的数字。一旦你这样做(如下一个版本的代码所示),输出将保持一致,如下图所示:
int i_value = 0xDA09F;
图片 09
所以,是的,格式化效果很好。但是为什么它不能以十六进制正确显示颜色值呢?因为即使我们经常期望颜色值遵循 RGB 格式,它们实际上是以 BGR 格式存储在内部的。这意味着字节顺序颠倒了。因此,即使十六进制字段中的值看起来是错误的,它也是正确的。我们能否修复此问题,使输出与更直观的格式相匹配?是的,我们可以做到。根据从前面的例子中获得的知识,进行这种调整实际上非常简单。实现此目的的修改代码如下所示:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. void OnStart(void) 05. { 06. int i_value = 0xDA09F; 07. double d_value = 0.25; 08. color cor = clrRoyalBlue; 09. string sz0; 10. 11. sz0 = StringFormat("Color :\t0x%06X\t=>\t%s\n" + 12. "Integer:\t0x%06X\t=>\t%d\n" + 13. "Double :\t%.2f", cor, ColorToString(cor), i_value, i_value, d_value); 14. 15. Print(sz0); 16. PrintFormat("Color value in hexadecimal (RGB format): %s", ColorToStrHex(cor)); 17. } 18. //+------------------------------------------------------------------+ 19. string ColorToStrHex(const color arg) 20. { 21. return StringFormat("0x%02X%02X%02X", (arg & 0xFF), ((arg >> 8) & 0xFF), (arg >> 16) & 0xFF); 22. } 23. //+------------------------------------------------------------------+
代码 07
请注意,我们在第 19 行定义了一个小函数。根据我之前文章中提出的概念,这个函数很容易理解。当我们执行它时,函数返回一个十六进制字符串。但是,当我们分析颜色值时,此返回值现在将反映预期的 RGB 格式。当您运行代码 07 时,您将看到类似于图 10 所示的输出。
图 10
现在有件有趣的事。在图 10 的第一行中,您可以看到以十六进制格式显示的颜色值。但这个值与我们的预期不符。但是,如果你看一下同一张图像的最后一行,你会发现我们所期望的值,假设颜色被读取为 RGB。您在第一行看到的值实际上是按字节顺序颠倒的,但我们可以翻转它们,使结果以预期的格式显示。重要提示:图 10 最后一行显示的值并不代表代码 07 第 8 行中定义的实际颜色。这里的格式化字符串只是显示 ColorToString 函数如何重新排列字节以获得正确的表示。所以,不用担心。我更改了颜色值,以确保解释在视觉和逻辑上都有意义。
最后的探讨
在本文中,我们介绍了一个主题的第一部分,它比最初看起来要深入得多,也更先进。我理解一些读者可能对今天的解释感到有点困惑。但我向你保证,如果你花时间仔细复习和练习,你很快就能从用户沟通的角度开发出非常有用和强大的应用程序。
尽管我们今天所探索的只是可能的表面,但你已经有了足够的材料来研究和应用于你的项目。在下一篇文章中,我们将更深入地探讨更高级的格式和内存处理,这对于解释未来的主题至关重要。所以,在那之前,享受附件中的文件,练习你所学到的知识。下篇文章再见!
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/15441
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.



