English Русский Español Deutsch 日本語 Português
preview
从基础到中级:数组和字符串(三)

从基础到中级:数组和字符串(三)

MetaTrader 5示例 |
245 0
CODE X
CODE X

概述

此处提供的内容仅用于教育目的。在任何情况下,除了学习和掌握所提出的概念外,都不应将其视为任何目的。

在上一篇文章从基础到中级:数组和字符串(二 )中,我以一种非常直接易懂的方式演示了如何开始应用到目前为止所介绍的知识。目标是展示如何创建两种简单的解决方案,许多人认为这需要更高级的知识。然而,事实上,这篇文章中涵盖和实现的所有内容都可以由任何编程初学者实现。当然,前提是他们需要一点创造力,并应用到目前为止介绍的概念。

也就是说,尽管那里展示的内容确实有效,并且可以毫不费力地创建,但有一个小细节可能会阻碍许多初学者。这个细节与那篇文章中演示的应用程序直接相关。

你们中的许多人可能会发现自己在想:“怎么可能从两个看似简单的文本或短语中生成密码?我不明白为什么这行得通。尽管我遵循了这个想法并理解了代码,但这种事情对我来说没有任何意义。”亲爱的读者,事实是,当那些不是程序员的人观察到编程的某些方面时,它们并没有多大意义。一个这样的例子正是那篇文章中所做的,我们使用非常简单和基本的数学进行了文本操作。

由于这种概念在各种编程任务中被广泛使用,我认为值得更详细地解释它为什么有效。这无疑将帮助你开始像程序员一样思考,而不仅仅是用户。因此,本文的主要主题 —— 数组 —— 可能会稍微推迟。然而,我们仍将在这里讨论数组,尽管方式更简单。因此,让我们从第一个主题开始,了解为什么上一篇文章中展示的内容实际上有效。


转化数值

编程中最常见的任务之一是翻译和处理信息或数据库。编程从根本上讲就是关于这个的。如果你正在考虑学习如何编程,但不明白应用程序的目的是创建计算机可以解释的数据,然后将这些数据转换为人类可以理解的信息,那么你正朝着错误的方向前进。最好是停下来并从头开始。因为,事实上,编程完全基于这个简单的原则。我们有信息,必须使计算机能够理解。一旦计算机产生结果,我们需要将该结果转化为我们可以理解的东西。

计算机非常擅长处理 1 和 0。但当涉及到处理任何其他类型的信息时,它们是完全无用的。这同样适用于人类:我们很难解释 1 和 0 的字符串,但我们可以轻松理解单词或图形的含义。

现在,让我们更简单地谈谈一些概念。在计算机出现之初,第一批处理器有一个操作码集 —— 一组处理十进制值的指令。是的,亲爱的读者,最早的处理器可以理解 8 或 5 是什么。这些指令是 BCD 集的一部分,BCD 集允许处理器以人类有意义的方式处理数字,但使用二进制逻辑。

然而,随着时间的推移,BCD 指令不再使用。事实证明,设计一个能够执行十进制计算的电路比用二进制计算并将结果转换为十进制要复杂得多。因此,执行此翻译的责任转移到了程序员身上。

那时,处理浮点数实际上是一团糟,真正的“水果沙拉”。但这是另一个话题,就像到目前为止提出的工具和概念一样,还不可能解释浮点系统是如何工作的。在到达那里之前,我需要再介绍一些概念。

需要注意的是:BCD 系统至今仍在使用,尽管使用方式可能与您想象的不同。在以后的文章中,我们将研究与 BCD 系统相关的一些内容。

但让我们回到重点,这是第一个翻译库开始出现的时候,将十进制值转换为二进制值,反之亦然。其他在特定应用中更常见的数字基,如十六进制和八进制,也得到了支持。

好吧,这让我们回到之前文章中讨论的内容,我在其中展示了 MQL5 包含允许我们执行此类翻译的函数。在所有情况下,转换都是基于字符串的,字符串可以是输入或输出。此字符串表示人类可读的信息。然后,二进制数据被发送到计算机进行处理。然而,尽管这些库工作良好且非常方便,但它们隐藏了许多关于如何直接操纵数据的底层知识。正因为如此,一些编程课程实际上并没有培养程序员 —— 他们培养的是那些认为自己是程序员但不了解底层工作原理的人。

由于 MQL5 为我们提供了一定程度的创作自由,我们可以创建类似于这些库之一的东西(严格用于教育目的)。但值得注意的是:任何没有低级语言创建的库都不会比标准库更快或更高效。当我说低级语言时,我指的是用 C 或 C++ 编程。因为这两种语言可以生成与纯汇编语言非常相似的代码,所以没有什么比它们更快的了。所以,我们在这里探讨的内容严格来说是出于教育目的。

好的,现在我们已经介绍了这个初步的解释,让我们构建一个小型翻译器。根据我们目前所掌握的知识,这将是一项相当简单的任务。在这个翻译器中,我们将首先将二进制值转换为十六进制或八进制,因为这些更容易翻译,需要更少的操作。因此,我们将使用以下代码作为起点。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     long value = 0xFEDAF3F4F5F6F7F8;
07. 
08.     PrintFormat("Translation via standard library.\n" +
09.                 "Decimal: %I64i\n" +
10.                 "Octal  : %I64o\n" +
11.                 "Hex    : %I64X",
12.                 value, value, value
13.                );
14. }
15. //+------------------------------------------------------------------+

代码 01

当我们运行代码片段 01 时,终端将显示如图 01 所示的输出。请注意此处使用的格式说明符。如果使用不同的说明符,输出将不同。稍后尝试进行实验,以更好地理解这种格式的工作原理。

图 01

如您所见,这非常简单、实用和直接。但是标准库是如何做到这一点的呢?好吧,亲爱的读者,这就是我们即将探索的“魔法”。但在此之前,您需要了解另一个重要概念:所显示信息的格式。是的,这个输出背后有格式化。为了保持简单,我希望你在网上查找一个特定的表格。这个表很容易找到:它被称为 ASCII 表。为了使事情更容易,我将在下面提供此表的一个版本。还有其他版本包含更多细节,但您在图 02 中看到的内容对于我们的目的来说已经足够了。

图 02

我们感兴趣的表格部分是具有可打印字符的部分,位于图 02 的中间。左侧包含特殊字符,而右侧可能因表格版本而异;它甚至可以由我们的应用程序自定义。然而,在 MQL5 的情况下这是不可能的,因为创建该部分需要访问硬件的特定部分,而 MQL5 不允许这样做,并且在大多数高级语言中也受到限制。这种类型的定制通常只有在使用 C 或 C++ 等低级语言时才有可能。我提到这一点只是为了让您知道,表右侧的符号可能因实现而异。

好吧,但是为什么 ASCII 表在 MQL5 中对我们如此重要?事实上,ASCII 表不仅在这里很重要,对于任何想要操作数据的人来说都是必不可少的。还有其他编码表,例如 UTF-8,UTF-16 和 ISO 8859 等。它们中的每一个都有不同的值和符号,用于不同的目的。但在 MQL5 中,我们通常依赖 ASCII 表,除非在特殊情况下需要其他表之一。我们将在以后的讨论中讨论这些例外情况。

亲爱的读者,现在,为了翻译信息,你需要理解一个关键点。我们再来看图 02。要将二进制值转换为十六进制值,我们需要数字 0 到 9 和字母 A 到 F。

参照 ASCII 表,您会看到数字 0 对应于十进制值 48。从此处开始,每个后续数字增加 1。因此,例如,如果我们向前移动 6 位,对应于数字 6,我们将使用值 54。知道了吧?这同样适用于字母。大写字母 A 对应于值 65。这为我们提供了一个起点。只要记住,在十六进制中,字母 A 代表值 10,B 代表 11,依此类推,直到 F 代表 15。

一切都相当简单。唯一需要注意的是,在数字 9 和字母 A 之间,ASCII 表中有几个符号需要跳过。否则,您的十六进制字符串可能包含奇怪或不需要的字符。如果您直接访问 ASCII 表而不过滤掉中间的值,则会发生这种情况。然而,尽管这是可能的,但为了我们的目的,我们将采取略有不同的方法。这是因为我们的目标是解释为什么上一篇文章中显示的代码有效。

因此,为了实现这一目标,让我们来看看新代码。您可以在下面看到它。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     long value = 0xFEDAF3F4F5F6F7F8;
07. 
08.     PrintFormat("Translation via standard library.\n" +
09.                 "Decimal: %I64i\n" +
10.                 "Octal  : %I64o\n" +
11.                 "Hex    : %I64X",
12.                 value, value, value
13.                );
14.     PrintFormat("Translation personal.\n" +
15.                 "Decimal: %s\n" + 
16.                 "Octal  : %s\n" +
17.                 "Hex    : %s",
18.                 ValueToString(value, 0),
19.                 ValueToString(value, 1),
20.                 ValueToString(value, 2)
21.                );
22. }
23. //+------------------------------------------------------------------+
24. string ValueToString(ulong arg, char format)
25. {
26.     const string szChars = "0123456789ABCDEF";
27.     string sz0 = "";
28. 
29.     while (arg)
30.     {
31.         switch (format)
32.         {
33.             case 0:
34.                 arg = 0;
35.                 break;
36.             case 1:
37.                 sz0 = StringFormat("%c%s", szChars[(uchar)(arg & 0x7)], sz0);
38.                 arg >>= 3;
39.                 break;
40.             case 2:
41.                 sz0 = StringFormat("%c%s", szChars[(uchar)(arg & 0xF)], sz0);
42.                 arg >>= 4;
43.                 break;
44.             default:
45.                 return "Format not implemented.";
46.         }
47.     }
48. 
49.     return sz0;
50. }
51. //+------------------------------------------------------------------+

代码 02

当我们运行代码片段02时,终端输出如下。

图 03

由于对于那些一直在关注和研究前几篇文章内容的人来说,这段代码的大部分内容都很容易理解,我将只强调第 37 行和第 41 行中发生的事情。这是因为使用 AND 运算符来隔离某些位。它看上去可能很复杂,但实际上比看上去简单得多。

现在我们只关注第 37 行。我们知道八进制值只包含从 0 到 7 的数字。在二进制中,数字 7 表示为 111。因此,如果我们对任何值执行按位与运算,结果将是 0 到 7 之间的数字。同样的逻辑适用于第 41 行,但在这种情况下,结果值的范围是 0 到 15。

现在,看一下第 26 行。该行定义了创建输出时将使用的字符。在 MQL5 中,字符串是一种特殊的数组。因此,当我们以第 37 行和第 41 行访问的方式访问第26行定义的字符串时,我们基本上是在检索该字符串中的一个字符。因此事实上,此时,字符串不是作为传统字符串被访问,而是作为数组被访问。

由于索引的工作方式,元素计数总是从 0 开始。但是,这个 0 并不代表字符串的大小 —— 它代表其中的第一个字符。如果字符串为空,则其长度为 0。如果有内容,长度将大于 0。尽管如此,该索引仍将从 0 开始。

我明白,乍一看,这可能令人困惑,而且完全不合逻辑。但是,亲爱的读者,随着您对数组的使用越来越熟悉,这种逻辑将开始变得越来越自然。

也就是说,此代码片段 02 中仍然缺少一个元素:处理十进制值。为了正确处理十进制值,我们需要一个我尚未解释的功能 —— 具体来说,如何使用它。但为了避免让你没有一个适当的解释,让我们现在做一个简单的假设。仅仅为了确保数据的完美表示而不必要地使代码复杂化是没有意义的。此外,它目前的实现方式 —— 尽管缺少一个稍后将介绍的功能 —— 我们已经能够几乎完全准确地转换宽度高达 64 位的任何整数值。一旦我们探索了缺失的功能(涉及模板的创建和 typename 的使用),我们将能够毫不费力地转换任何值。在那之前,让我们只关注更简单的方面。

现在请注意,第 6 行声明的类型是有符号整数。因此,我们需要检查该值是否为负数。然而,为了避免事情变得不必要的复杂,现在让我们假设所有值都是无符号整数。换句话说,负值将不会被表示。有了这个假设,我们可以修改代码并检查值是否正确显示。这是通过使用下面显示的实现方法来实现的。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     long value = 0xFEDAF3F4F5F6F7F8;
07. 
08.     PrintFormat("Translation via standard library.\n" +
09.                 "Decimal: %I64u\n" +
10.                 "Octal  : %I64o\n" +
11.                 "Hex    : %I64X",
12.                 value, value, value
13.                );
14.     PrintFormat("Translation personal.\n" +
15.                 "Decimal: %s\n" + 
16.                 "Octal  : %s\n" +
17.                 "Hex    : %s\n" +
18.                 "Binary : %s",
19.                 ValueToString(value, 0),
20.                 ValueToString(value, 1),
21.                 ValueToString(value, 2),
22.                 ValueToString(value, 3)
23.                );
24. }
25. //+------------------------------------------------------------------+
26. string ValueToString(ulong arg, char format)
27. {
28.     const string szChars = "0123456789ABCDEF";
29.     string  sz0 = "";
30. 
31.     while (arg)
32.         switch (format)
33.         {
34.             case 0:
35.                 sz0 = StringFormat("%c%s", szChars[(uchar)(arg % 10)], sz0);
36.                 arg /= 10;
37.                 break;
38.             case 1:
39.                 sz0 = StringFormat("%c%s", szChars[(uchar)(arg & 0x7)], sz0);
40.                 arg >>= 3;
41.                 break;
42.             case 2:
43.                 sz0 = StringFormat("%c%s", szChars[(uchar)(arg & 0xF)], sz0);
44.                 arg >>= 4;
45.                 break;
46.             case 3:
47.                 sz0 = StringFormat("%c%s", szChars[(uchar)(arg & 0x1)], sz0);
48.                 arg >>= 1;
49.                 break;
50.             default:
51.                 return "Format not implemented.";
52.         }
53. 
54.     return sz0;
55. }
56. //+------------------------------------------------------------------+

代码 03

作为额外的奖励,在代码片段 03 中,我还添加了将值转换为二进制表示的功能。就像前面的情况一样简单。无论如何,一旦执行,代码 03 的输出如下所示。

图 04

亲爱的读者,请密切关注这里发生的事情。这可能是整篇文章中讨论和演示的最重要的一点。代码 03 和代码 02 之间几乎没有区别。但是,如果比较图 03 和图 04,您会注意到一些不同之处。

区别在于使用标准库进行翻译时显示的值。仔细查看十进制的值,你会发现它是不同的。然而,这不太合理,因为第六行在代码 03 和代码 04 中完全相同。那么,导致值以如此意外的方式打印的差异在哪里呢?

亲爱的读者,区别就在第九行。仔细检查代码 03 的第九行,然后仔细检查代码 04。仅凭这个微小的变化就足以将值(根据第六行声明的类型,应该是负值)转换为正值。有一种方法可以解决这种问题。但如前所述,它需要一个尚未解释的概念。

在此之前,在将值打印到终端时要小心。一个小小的疏忽可能会导致你误解计算值。这就是为什么研究和实践所展示的内容至关重要。仅仅阅读这些文档或文章不足以使你成为一名熟练的程序员。你必须积极练习并消化所呈现的一切。

也就是说,尽管存在这种微小的差异,但你可以看到,将值转换为十进制表示是绝对正确的。请注意,这种转换所需的数学非常简单。在第 35 行中,我们使用模运算符 % 来获得除以 10 的余数,这表示应该使用 szChars 中的哪个字符。然后,在第 36 行,我们更新 arg 的值。为此,我们只需将 arg 除以 10。结果值将在下一步中使用。这就是我们如何将二进制值转换为人类可读的值。

基于这种解释,我相信现在可以更清楚地理解上一篇文章中显示的代码示例。然而,我们仍然可以改进之前展示的内容。为此,我们将使用上一篇文章中提供的最后一段代码,即更直接地使用数组的代码。但为了顺利进行,让我们开始一个新主题。


设置密码长度

要开始讨论我们可以在代码 06 中修改的内容(如上一篇文章所示),我们首先需要看一下这里的那段代码。如下所示。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     const string sz_Secret_Phrase = "Um pequeno jabuti xereta viu dez cegonhas felizes";
07. 
08.     Print("Result:\n", PassWord(sz_Secret_Phrase));
09. }
10. //+------------------------------------------------------------------+
11. string PassWord(const string szArg)
12. {
13.     const string szCodePhrase = "The quick brown fox jumps over the lazy dog";
14. 
15.     uchar   psw[],
16.             pos;
17. 
18.     ArrayResize(psw, StringLen(szArg));
19.     for (int c = 0; szArg[c]; c++)
20.     {
21.         pos = (uchar)(szArg[c] % StringLen(szCodePhrase));
22.         psw[c] = (uchar)szCodePhrase[pos];
23.     }
24.     return CharArrayToString(psw);
25. }
26. //+------------------------------------------------------------------+

代码 04

现在让我们从以下事实开始。我们可以使用两种类型的数组:静态数组和动态数组。决定数组是静态还是动态的是它的声明方式。这与声明中使用 static 关键字无关。由于这个主题一次解释有点复杂,我将给出一个简短的总结,以便您理解代码 04。

动态数组的声明如第 15 行所示。请注意,我们有变量名,在本例中是“psw”,后跟一个没有任何内容的左括号和右括号 []。这是一个动态数组。动态意味着我们可以在运行时定义它的大小。事实上,我们必须这样做,因为尝试访问未分配的动态数组的任何元素都会被视为错误,并会导致代码立即终止。因此,我们在第 18 行分配了足够的内存来存储数组中的数据。

请记住:字符串是一种特殊的数组。除非声明为常量,否则字符串是一个动态数组,不需要手动分配内存。编译器本身添加了必要的例程来自动处理此问题,而无需我们的直接参与。但是,由于这里我们使用的不是字符串,而是常规数组,因此我们需要明确地告知我们想要或需要多少内存。这就是一个小而重要的细节所在。

在代码 04 的第 18 行执行过程中,如果我们指定要生成的密码的长度,我们就可以以非常有效的方式控制事情。同时,我们可以更好地管理将要创建的密码。为此,我们需要对代码 04 做一些小的修改。这些修改如下所示。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     const string sz_Secret_Phrase = "Um pequeno jabuti xereta viu dez cegonhas felizes";
07. 
08.     Print("Result:\n", PassWord(sz_Secret_Phrase, 8));
09. }
10. //+------------------------------------------------------------------+
11. string PassWord(const string szArg, const uchar SizePsw)
12. {
13.     const string szCodePhrase = "The quick brown fox jumps over the lazy dog";
14. 
15.     uchar   psw[],
16.             pos;
17. 
18.     ArrayResize(psw, SizePsw);
19.     for (int c = 0; szArg[c]; c++)
20.     {
21.         pos = (uchar)(szArg[c] % StringLen(szCodePhrase));
22.         psw[c] = (uchar)szCodePhrase[pos];
23.     }
24.     return CharArrayToString(psw);
25. }
26. //+------------------------------------------------------------------+

代码 05

现在请密切关注以下事实。在代码 05 的第 8 行中,我们向第 11 行的函数传递了一个新参数。第 18 行使用相同的参数来确定密码将包含多少个字符:在本例中为 8 个字符。但是,如果您尝试运行代码 05,它将会失败。原因就在第 22 行。这是因为,在第 6 行,我们定义了一个包含 8 八个以上字符的秘密短语。由于第 19 行的循环只有当它在字符串 szArg(即第 6 行声明的字符串)中找到 NULL 符号时才会停止,因此我们最终会到达第 22 行,在此我们尝试访问无效的内存位置。这是一个错误,程序将会崩溃。

但这实际上并不是一个真正的问题。我们需要做的就是决定是使用第 6 行中定义的整个短语,还是只使用其中的一部分来完成我们想要的 8 个字符的密码。根据我们的决定,最终代码的行为方向略有不同。这就是为什么学习如何编程很重要。一个程序员可能会提出某种解决方案,但这可能不是最适合你的。然而,通过沟通,可以实现相互理解。能够编码可以让你决定什么在你的情况下最有效,而不是完全依赖别人的决定。

好吧,让我们做出以下决定:我们将使用整个秘密短语。但与此同时,我们将把第13行的字符串修改为另一个字符串,即前一篇文章中显示的字符串。这样我们就能得到更有趣的东西。

然而,在我们实际编写代码之前,我希望您回到上一节所示的图 02,仔细查看第 6 行声明的字符串中出现的表中的每个值。你注意到这些数据中有什么有趣的地方吗?关键是,如果我们决定使用第 6 行的整个短语,我们就必须在这里使用其他技巧。因为如果我们按照下面的代码所示进行更改,我们将会遇到一些问题......

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     const string sz_Secret_Phrase = "Um pequeno jabuti xereta viu dez cegonhas felizes";
07. 
08.     Print("Result:\n", PassWord(sz_Secret_Phrase, 8));
09. }
10. //+------------------------------------------------------------------+
11. string PassWord(const string szArg, const uchar SizePsw)
12. {
13.     const string szCodePhrase = ")0The!1quick@2brown#3fox$4jumps%5over^6the&7lazy*8dog(9";
14. 
15.     uchar   psw[],
16.             pos,
17.             i = 0;
18. 
19.     ArrayResize(psw, SizePsw);
20.     for (int c = 0; szArg[c]; c++)
21.     {
22.         pos = (uchar)(szArg[c] % StringLen(szCodePhrase));
23.         psw[i++] = (uchar)szCodePhrase[pos];
24.         i = (i == SizePsw ? 0 : i);
25.     }
26.     return CharArrayToString(psw);
27. }
28. //+------------------------------------------------------------------+

代码 06

我们实际上并没有使用整个短语,而只是其中的一部分。事实上,我们只使用第 6 行定义的秘密短语的最后 8 个字符。可以通过运行代码 06 并将结果与上一篇文章中显示的结果进行比较来确认这一点,其中使用了与代码 06 相同的短语。

然而,即使不执行代码 06,简单地分析它也会发现,尽管第 23 行不再导致代码失败,但效果很小。这是因为第 24 行迫使我们在定义为密码限制的确切位置不断覆盖密码值。因此,尽管第 6 行的短语包含所有这些字符,但最终看起来好像只有其中 8 个字符真正存在。这让我们在创建秘密短语时产生了一种虚假的安全感。

现在,如图 02 所示,每个符号都由一个值定义,因此我们可以使用这个数组来计算这些值的总和。这样,所有符号都得到了有效利用。但亲爱的读者,请密切关注这里的一个重要细节。我们可以在数组中分配的最大值由数组本身使用的类型决定。我将在下一篇文章中对此进行更详细的介绍。现在,我们可以对代码进行更多的调整,如下所示。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     const string sz_Secret_Phrase = "Um pequeno jabuti xereta viu dez cegonhas felizes";
07. 
08.     Print("Result:\n", PassWord(sz_Secret_Phrase, 8));
09. }
10. //+------------------------------------------------------------------+
11. string PassWord(const string szArg, const uchar SizePsw)
12. {
13.     const string szCodePhrase = ")0The!1quick@2brown#3fox$4jumps%5over^6the&7lazy*8dog(9";
14. 
15.     uchar   psw[],
16.             pos,
17.             i = 0;
18. 
19.     ArrayResize(psw, SizePsw);
20.     ArrayInitialize(psw, 0);
21.     for (int c = 0; szArg[c]; c++)
22.     {
23.         pos = (uchar)(szArg[c] % StringLen(szCodePhrase));
24.         psw[i++] += (uchar)szCodePhrase[pos];
25.         i = (i == SizePsw ? 0 : i);
26.     }
27.     return CharArrayToString(psw);
28. }
29. //+------------------------------------------------------------------+

代码 07

现在我们终于有了充分利用这个秘密短语的东西。请注意,代码中只需要进行最小的更改。在这些变化中,在第 20 行,我们指出数组应该用一个特定的值初始化,在本例中为 0。如果您愿意,您可以选择不同的初始值。这样做会在以后有所不同,特别是如果你的秘密短语包含重复的字符。

有几种不同的方法可以避免在不同位置为同一符号分配相同的值。下一篇文章将会对此进行更详细的介绍。但这里的关键细节在于第 24 行。现在我们正在求和这些值,这意味着我们充分利用了这两个短语。然而,有一个问题。当我们运行代码时,这一点变得清晰起来。

图 05

那么,你会如何使用这个密码呢?这并不容易,对吧?这里发生的事情是,数组包含由总和计算的值,但它们受到限制。我们需要确保这些计算值实际上映射到第 13 行声明的字符串中的一个字符。为了实现这一点,我们需要添加另一个循环。代码的最终版本如下所示。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     const string sz_Secret_Phrase = "Um pequeno jabuti xereta viu dez cegonhas felizes";
07. 
08.     Print("Result:\n", PassWord(sz_Secret_Phrase, 8));
09. }
10. //+------------------------------------------------------------------+
11. string PassWord(const string szArg, const uchar SizePsw)
12. {
13.     const string szCodePhrase = ")0The!1quick@2brown#3fox$4jumps%5over^6the&7lazy*8dog(9";
14. 
15.     uchar   psw[],
16.             pos,
17.             i = 0;
18. 
19.     ArrayResize(psw, SizePsw);
20.     ArrayInitialize(psw, 0);
21.     for (int c = 0; szArg[c]; c++)
22.     {
23.         pos = (uchar)(szArg[c] % StringLen(szCodePhrase));
24.         psw[i++] += (uchar)szCodePhrase[pos];
25.         i = (i == SizePsw ? 0 : i);
26.     }
27.     
28.     for (uchar c = 0; c < SizePsw; c++)
29.         psw[c] = (uchar)(szCodePhrase[psw[c] % StringLen(szCodePhrase)]);
30. 
31.     return CharArrayToString(psw);
32. }
33. //+------------------------------------------------------------------+

代码 08

现在,经过代码 08 第 28 行的循环之后,我们得到了下图中可以看到的结果。

图 06

这就是我所说的幸运巧合。我刚才在谈论密码如何包含重复字符,以及我们如何解决这个问题。令我们高兴的是,结果是重复的字符。真是运气太好了!(笑)


最终结论

在本文中,我们部分探讨了二进制值如何转换为其他格式。我们还迈出了理解如何将字符串视为数组的第一步。此外,我们还学习了如何在代码中使用数组时避免一个非常常见的错误。然而,我们在这里看到的结局是一场愉快的事故,我们将在下一篇文章中对此进行更详细的探讨。在那里,我们还将看到如何防止这种情况在未来发生。我们还将继续扩展对数组中数据类型的理解。期待很快与您见面!

本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/15461

附加的文件 |
Anexo.zip (2.81 KB)
在MQL5中创建交易管理员面板(第七部分):可信任用户、密码恢复与加密技术 在MQL5中创建交易管理员面板(第七部分):可信任用户、密码恢复与加密技术
每次刷新图表、通过管理面板EA添加新交易品种或重启终端时触发的安全提示,可能会让人感觉繁琐。在本次讨论中,我们将探索并实现一项功能,该功能通过跟踪登录尝试次数来识别可信用户。在达到一定次数的失败尝试后,应用程序将切换至高级登录流程,该流程还为可能忘记密码的用户提供密码恢复功能。此外,我们还将介绍如何将加密技术有效集成到管理面板中,以增强安全性。
借助成交量精准洞悉交易动态:超越传统OHLC图表 借助成交量精准洞悉交易动态:超越传统OHLC图表
一种将成交量分析与机器学习技术(特别是LSTM神经网络)相结合的算法交易系统。与主要关注价格波动的传统交易方法不同,该系统强调成交量模式及其衍生指标,以预测市场走势。该方法包含三个主要组成部分:成交量衍生指标分析(一阶和二阶导数)、基于LSTM的成交量模式预测,以及传统技术指标。
精通日志记录(第一部分):MQL5中的基础概念与入门步骤 精通日志记录(第一部分):MQL5中的基础概念与入门步骤
欢迎开启另一段探索之旅!本文是一个特别系列的开篇之作,我们将逐步创建一个专为MQL5语言开发者量身定制的日志操作库。
MQL5 交易管理面板开发指南(第六部分):交易管理面板(续篇) MQL5 交易管理面板开发指南(第六部分):交易管理面板(续篇)
在本文中,我们对多功能管理面板的“交易面板”进行升级。我们引入一个强大的辅助函数,大幅简化代码,提高可读性、可维护性与运行效率。同时演示如何无缝集成更多按钮,并优化界面,以支持更广泛的交易任务。无论是持仓管理、订单调整,还是简化交互,本文将助您打造稳健且易用的交易管理面板。