English Русский Español Deutsch 日本語 Português
preview
从基础到中级:联合(一)

从基础到中级:联合(一)

MetaTrader 5示例 |
40 3
CODE X
CODE X

概述

此处提供的材料仅用于教育目的。在任何情况下,除了学习和掌握所提出的概念外,都不应出于任何目的使用此应用程序。

在上一篇文章从基础到中级:数组(四)中,我们探索了一个非常酷且极其有趣的概念。尽管许多人认为这是一个高级话题,但在我看来,这是每个编程初学者都应该知道的。这是因为,如果使用得当,上一篇文章中介绍的概念可以真正打开整个可能性的世界。有了它,我们可以做一些原本很难甚至不可能实现的事情。

此外,同样的概念也用于另一种类型的背景,我们将在更合适的时候介绍。为了避免给任何人带来过度的焦虑,这里有一个小建议:广泛学习和实践前一篇文章中所涵盖的内容。深入了解这些知识很重要。没有它,从现在开始,任何事情都毫无意义。接下来的一切看起来都像魔法。

好吧,也许说除非你理解了上一篇文章,否则什么都说不通,对我来说有点夸张。然而,这并没有改变这样一个事实,即上一篇文章是迄今为止发表的最重要的一篇文章。至少对于那些真正想成为优秀程序员的人来说是这样。对于那些渴望用编程语言做任何事情的人来说,上一篇文章中介绍的概念不仅限于 MQL5,它适用于任何编程语言,特别是在正确使用计算资源方面。

好的,在开始之前,我们需要讨论一下本文的先决条件。尽管一些讲师可能会认为我夸大其词,但在我看来,除非你至少对上一篇文章中的内容有了肤浅的理解,否则你不太可能理解我们在这里要做的事情。我不是说你不明白,但要跟上这些解释肯定会困难得多。

因此,前一篇文章是一种分水岭时刻。一方面,我们有所有基本的编程材料;现在,我们正转向更高级的材料。正如文章标题所示,我们将讨论 UNION,但不是一般意义上的“联盟”,我们指的是某些编程语言中出现的术语 UNION(联合) 。和往常一样,我们将开始一个新的主题,使实现和编码过程更加愉快和有趣。


UNION 的诞生

在上一篇文章中,我们实现了以下代码:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "Tutorial\File 01.mqh"
05. //+------------------------------------------------------------------+
06. void OnStart(void)
07. {
08.     const uint  ui = 0xCADA5169;
09.     ushort      us = 0x43BC;
10.     uchar       uc = B'01011101';
11. 
12.     uchar       Infos[],
13.                 counter = 0;
14.     uint        start,
15.                 number;
16. 
17.     PrintFormat("Translation personal.\n" +
18.                 "FUNCTION: [%s]\n" +
19.                 "ui => 0x%s\n" + 
20.                 "us => 0x%s\n" +
21.                 "uc => B'%s'\n",
22.                 __FUNCTION__,
23.                 ValueToString(ui, FORMAT_HEX),
24.                 ValueToString(us, FORMAT_HEX),
25.                 ValueToString(uc, FORMAT_BINARY)
26.                );
27.     
28.     number = sizeof(ui) + 1;
29.     start = Infos.Size();
30.     ArrayResize(Infos, start + number);
31.     Infos[counter++] = sizeof(ui);
32.     Infos[counter++] = (uchar)(ui >> 24);
33.     Infos[counter++] = (uchar)(ui >> 16);
34.     Infos[counter++] = (uchar)(ui >> 8);
35.     Infos[counter++] = (uchar)(ui & 0xFF);
36. 
37.     number = sizeof(us) + 1;
38.     start = Infos.Size();
39.     ArrayResize(Infos, start + number);
40.     Infos[counter++] = sizeof(us);
41.     Infos[counter++] = (uchar)(us >> 8);
42.     Infos[counter++] = (uchar)(us & 0xFF);
43. 
44.     number = sizeof(uc) + 1;
45.     start = Infos.Size();
46.     ArrayResize(Infos, start + number);
47.     Infos[counter++] = sizeof(uc);
48.     Infos[counter++] = (uc);
49. 
50.     Print("******************");
51.     PrintFormat("The Infos block contains %d bytes.", Infos.Size());
52.     ArrayPrint(Infos);
53.     Print("******************");
54. 
55.     Procedure(Infos);
56. 
57.     ArrayFree(Infos);
58. }
59. //+------------------------------------------------------------------+
60. void Procedure(const uchar &arg[])
61. {
62.     Print("Translation personal.\n" +
63.           "FUNCTION: ", __FUNCTION__);
64. 
65.     ulong value;
66. 
67.     for (uchar c = 0; c < arg.Size(); )
68.     {
69.         value = 0;
70.         for (uchar j = arg[c++], i = 0; (c < arg.Size()) && (i < j); i++, c++)
71.             value = (value << 8) | arg[c];
72.         Print("0x", ValueToString(value, FORMAT_HEX), " B'", ValueToString(value, FORMAT_BINARY), "'");
73.     }
74. }
75. //+------------------------------------------------------------------+

代码 01

当我们运行代码 01 时,我们得到如下所示的结果:

图 01

在图 01 中,我们可以看到第 8、9 和 10 行定义的值被传递到位于第 60 行的过程。然而,当我们查看该过程所需的数据类型时,我们注意到传递的不是单个值,而是一个数组。乍一看,这似乎没有任何意义。许多人在查看第 60 行的程序时,会希望看到涉及数组的操作。但这里发生的事情并不完全如此。我们实际看到的是数组中包含的值的转录或翻译,以重建传递的值。

请注意,我们无法知道调用过程时使用的变量的名称或类型。那是因为没有提到这类信息。我们只知道元素的数量和每个变量对应的每个元素的值。

许多程序员(甚至是一些经验丰富的程序员)认为这种方法完全不可接受,因为源代码中的值和命名变量之间没有直接映射。然而,他们经常忘记,对于 CPU 来说,变量的名称是无关紧要的。CPU 看到的只是一系列数字。就是这样,它不知道变量或常量的名称是什么。这种信息与它完全无关。

因此,我们在代码 01 中看到的创建的内存模型看起来像下面所示的表示:

图 02

我们正在讨论的概念可能会非常令人困惑,尤其是当我们稍后研究另一个看起来与这个概念非常相似的概念时。

在图 02 中,我们看到图 01 中突出显示的值以及一些标记。这些标记以绿色显示,用于以下目的:

一个新的值从这里开始,它由许多元素组成。

蓝色矩形代表每个元素块。也就是说,如果我们不使用数组,而是使用离散变量,我们将需要 6 个单独的变量,因为图 02 中有 6 个蓝色矩形。但是,我们还有红色矩形,它们代表数组中的每个元素。由于有 10 个元素,所以我们看到 10 个红色矩形。如果每个元素由两个值组成,我们也可以有 5 个红色矩形。

然而,由于碎片问题(在我看来,这个主题太高级了,无法在这里解释),我们为每个元素使用最小大小。这有助于避免碎片化,即使它稍后会稍微减慢解码过程。该过程由第 70 行的循环执行,尽管许多人可能认为它是由第 67 行的循环处理的。事实上,解码发生在第 70 行的循环中。

好了,我相信到目前为止,这部分并不太难。但现在,仔细看看图 02,停下来想一想:有没有一种方法可以读取蓝色矩形而不必经过红色矩形?或者,换言之:仅仅通过观察蓝色矩形,就有可能从红色矩形中获得值吗?这将使我们的生活变得更加轻松。它将加快第 28 行和第 48 行之间的编码过程,并简化解码,因为我们将直接来自蓝色矩形。这与代码 01 中当前发生的情况不同,我们正在分解每个蓝色矩形以获取将成为红色元素的内容。这些红色元素存储在数组中。

事实上,亲爱的读者,从这个概念中诞生的这个想法正是我们所知的联合的诞生。当我们使用联合时,我们创建一个共享内存结构,将蓝色矩形划分为不同大小的单元。每个单元或元素的大小取决于正在开发的代码或应用程序的目的和目标。但是,一旦定义了联合,它就允许程序员以完全个性化的方式控制更大块的每个部分。这个概念在 C 和 C++ 代码中得到了广泛的应用,其中联合可以帮助我们以一种非常简单、流畅和安全的方式操纵整个元素链。

为了理解联合是如何工作的,让我们从一些简单的东西开始,然后再回到修改代码 01。那么,让我们检查一下下面显示的代码:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     union un_01
07.     {
08.         ulong   u64_bits;
09.         uint    u32_bits[2];
10.     }info;
11. 
12.     uint tmp;
13. 
14.     info.u64_bits = 0xA1B2C3D4E5F6789A;
15. 
16.     PrintFormat("Before modification: 0x%I64X", info.u64_bits);
17. 
18.     tmp = info.u32_bits[0];
19.     info.u32_bits[0] = info.u32_bits[1];
20.     info.u32_bits[1] = tmp;
21. 
22.     PrintFormat("After modification : 0x%I64X", info.u64_bits);
23. }
24. //+------------------------------------------------------------------+

代码 02

注意一个概念是如何自然地引出另一个概念的。如前所述,如果不理解数组,您将无法掌握代码中此时正在执行的操作。如果不理解上一篇文章,你在这里看到的将毫无意义。但现在,我认为现在是解释什么是联合及其实际目的的正确时机。

当您运行代码 02 时,您将在 MetaTrader 5 终端中看到如下所示的图像:

图 03

这是联合的重点,请注意它看起来是多么神奇。这只是我们所能做的第一步,但让我们确切地了解一下代码 02 中发生了什么。首先,必须声明一个联合,如第 6 行所示。换句话说,我们使用保留关键字 union,后跟作为联合名称的标识符。MQL5 中声明联合的方式与 C 或 C++ 中声明联合的方式有所不同。在 MQL5 中,无法创建匿名联合。这意味着 union 关键字后面的标识符必须存在。

在 C 和 C++ 等语言中,当省略标识符时,我们会得到所谓的匿名联合。匿名联合的缺点是,你不能在它的声明块之外引用它。我们稍后将在另一个代码片段中看到这样的示例。但现在,让我们继续关注代码 02。如您所见,一旦声明了联合并赋予其名称,它就以左括号 { 开头,以右括号 } 结尾。此块内的所有内容都是联合的一部分,并共享相同的内存区域。

现在,我们一直在等待的关键部分来了。内存中的所有内容都必须以字节为单位来考虑,绝对的所有内容。因此,为了正确定义联合,您需要考虑如何按字节划分内存。再看一下图 02,那里,我们有 10 个字节。如何对这些进行分组(尽管“分组”可能不是最准确的术语)将指导您创建联合。第 6 行定义的联合将占用 8 个字节的内存。其最大的成员是变量 u64_bits。

那么 u32_bits 变量呢?它是一个数组。由于它的每个元素占用四个字节,那么它是否还会占用另外八个字节?那么联合不应该总共占用16个字节吗?不,亲爱的读者。该联合实际上只占用 8 个字节。这是因为 u64_bits 和 u32_bits 共享相同的内存空间。

我知道一开始这可能看起来非常令人困惑。所以,让我们慢慢来,因为如果我们跳过解释中的任何步骤,事情只会变得更加复杂。

代码 02的目标正是在内存的一部分和另一部分之间执行交换。最后,我们要轮转存储在内存中的信息。为此,我们需要一个临时变量,它是在第 12 行声明的。重要的一点是:这个临时变量的大小(以字节为单位)必须等于或大于联合中的最小变量。通常,我们使用相同的类型,以确保保持正确的大小。这对于实现预期结果非常重要。

然后,在第 14 行,我们初始化联合。请密切关注此处。第 10 行声明了使用其内存区域的实际变量。但是因为我们不能直接引用该内存区域(因为它可能包含多个变量),所以我们必须告诉编译器我们想要访问联合的哪个成员变量。您可以使用属于该联合的任何成员。如果您正确执行此操作,编译器将理解并分配正确的值,并相应地更新内存区域。

为了更好地理解这一点,让我们再次回到图 02。想象一下整个图像实际上是一个联合 —— 这与事实相差不远。(我们稍后会更详细地重新讨论这个想法)。现在想象每个红色矩形都有一个名称。如果您想访问其中一个,您只需告诉编译器该矩形的名称,它就会从内存的特定部分读取或写入该部分。

很酷吧?但让我们一步一个脚印吧。首先,您需要处理代码 02。一旦我们为 u64_bits 成员分配一个值,名为 info 的整个内存区域现在就会保存第 14 行指定的值。

为了确认这一点,我们使用第 16 行显示内存内容。这将在终端中打印我们的第一行输出。现在是最有趣的部分:我们希望交换该内存中的值,本质上是在同一内存空间中创建一个新值。利用联合提供的内存共享,我们可以快速轻松地完成此操作。

第一步是使用我们的临时变量来存储其中一个值。它在第 18 行完成。在第 19 行,我们将数组索引 1 处的值赋给索引 0。此时,我们的内存有点混乱,因为它只包含原始值的一部分。为了完成这个过程,我们使用第 20 行将索引 0 中的原始值移动到索引 1 中。交换现在完成,第 22 行打印结果 —— 这是图 03 中显示的第二行。

如果你认为这很疯狂,那就准备好一个更有趣的例子,我们以简单有效的方式镜像整个内容。看一下下面显示的代码 03:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. void OnStart(void)
05. {
06.     union un_01
07.     {
08.         ulong   u64_bits;
09.         uchar   u8_bits[sizeof(ulong)];
10.     };
11. 
12.     un_01 info;
13. 
14.     info.u64_bits = 0xA1B2C3D4E5F6789A;
15. 
16.     PrintFormat("The region is composed of %d bytes", sizeof(info));
17. 
18.     PrintFormat("Before modification: 0x%I64X", info.u64_bits);
19. 
20.     for (uchar i = 0, j = sizeof(info) - 1, tmp; i < j; i++, j--)
21.     {
22.         tmp = info.u8_bits[i];
23.         info.u8_bits[i] = info.u8_bits[j];
24.         info.u8_bits[j] = tmp;
25.     }
26. 
27.     PrintFormat("After modification : 0x%I64X", info.u64_bits);
28. }
29. //+------------------------------------------------------------------+

代码 03

这个代码 03 比上一个更有趣。当您运行它时,您将在 MetaTrader 5 终端中看到下图 04 所示。

图 04

伙计,真是件疯狂的事。但真的很酷,很有趣。我一直在尝试自己做这类事情:挣扎、流汗、奋力拼搏,只是为了让它发挥作用。然后出现了这种方法,展示了我们如何以如此简单、易行和实用的方式做到这一点。确实令人印象深刻。我很喜欢它!但现在我有几个问题。

让我们一步一步地走,这样我就能更好地理解了。第一个问题:第 9 行到底发生了什么?使用联合时,通常会包含数组,以便更容易访问共享内存的特定部分。还有其他方法可以在没有数组的情况下做到这一点,但我们稍后会对此进行研究。大多数时候,您需要访问整个内存区域,以便正确操作它。因此看到第 9 行出现的内容并不罕见。尽管在真实世界的代码中,声明可能看起来有点不同。原则和目的保持不变:创建一种访问联合内部内存块每个元素的方法。

下一个问题:第 12 行声明的那个奇怪的东西是什么?这对我来说不合理啊。第 12 行完美地演示了我们在代码 02 中讨论的内容。还记得我们提到过 MQL5 中不允许匿名联合吗?好吧,因为我们在第 6 行使用标识符 un_01 定义了联合类型,所以我们现在能够声明该类型的变量,就像第 12 行那样。您可以有多个变量,每个变量都包含不同的联合值,所有这些变量都基于相同的标识符。这是因为当你定义一个特殊类型(联合是一种特殊类型)时,你可以在整个代码中重用它的标识符,就像任何其他类型一样。

请记住,此标识符遵循与任何其他变量或常量相同的范围和可见性规则。之前的文章中已经讨论过这一点。但这里有一件重要的事情要记住:联合永远是一个变量,它不能是一个常数。即使你将其指向一个常数值,联合本身也将始终被视为一个变量。一种特殊类型的变量,很像字符串,但仍然是一个变量。

这就是为什么这些文章的结构是这样的,这样你就可以在看到它们被应用之前理解某些基本概念。如果不了解变量和常数之间的区别,就很难解释我们在这里讨论的内容。另一个非常重要的点是:在任何情况下,联合内的数组都不会是动态类型,它将永远是静态的。

因此,试图使用动态数组创建大规模联合是没有意义的。如果你尝试这样做,编译器将无法理解你想做什么。

最后一个细节你可能还没有注意到:第 20 行上的循环如何能够镜像内存区域的内容?为了使镜像工作,您必须使用一个只迭代内存区域一半的计数器。由于我们总是使用偶数个元素,所以这很容易做到。因此,第 20 行上的循环确实可以反映离散类型的任何值 —— 只要你做出必要的调整(不是对循环本身,而是对第六行联合中块的声明)。当然,您还需要调整第 14 行声明的值。但除此之外,不需要对代码进行进一步的更改。

示例:如果要镜像 int 类型变量或任何 32 位值,则只需将第 8 行和第 9 行的类型声明从 ulong 更改为 int。完成上述操作后,只需将第 14 行声明的值更新为所需的值即可。代码现在能够镜像 int 而不是 ulong。就这么简单。

有一种更简单的方法可以实现这一点。但是,我还没有介绍我们可以在 MQL5 中使用的另一个概念。因此,就目前而言,上述方法是最简单的方法。

在结束本文之前,让我们探讨一下可以用联合做的最后一件事。这与它们在函数和过程中的使用有关。为了证明这一点,我们将采用代码 03 并对其进行修改,以便首先将循环移入函数中。然后,我们将以过程的形式应用相同的逻辑。无论哪种方式,目标都是让函数或过程为我们执行镜像,然后我们将在主例程中显示结果。

是的,有很多方法可以做到这一点。但在这里,我们将使用教学的方法,因为我们的目标是展示我们如何在更广泛的背景下使用联合。让我们从我认为最容易理解的代码开始,这是因为我们将使用与代码 03 中看到的非常相似的实现。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. union un_01
05. {
06.     ulong u64_bits;
07.     uchar u8_bits[sizeof(ulong)];
08. };
09. //+------------------------------------------------------------------+
10. void OnStart(void)
11. {
12.     un_01 info;
13. 
14.     info.u64_bits = 0xA1B2C3D4E5F6789A;
15. 
16.     PrintFormat("The region is composed of %d bytes", sizeof(info));
17. 
18.     PrintFormat("Before modification: 0x%I64X", info.u64_bits);
19. 
20.     Swap(info);
21. 
22.     PrintFormat("After modification : 0x%I64X", info.u64_bits);
23. }
24. //+------------------------------------------------------------------+
25. void Swap(un_01 &info)
26. {
27.     for (uchar i = 0, j = sizeof(info) - 1, tmp; i < j; i++, j--)
28.     {
29.         tmp = info.u8_bits[i];
30.         info.u8_bits[i] = info.u8_bits[j];
31.         info.u8_bits[j] = tmp;
32.     }
33. }
34. //+------------------------------------------------------------------+

代码 04

代码 04 相当容易理解。但是,您需要注意一些重要的细节。首先,联合不再是局部的,而是全局的。这样做是为了授予第 25 行声明的程序对我们在第 4 行定义的特殊类型的访问权限。如果不将联合设为全局的,就不可能使用我们在第 25 行的参数定义中声明为 un_01 的特殊类型。请注意,我们在这里所做的只是将之前在主例程中的代码移动到一个单独的过程中。我们不再使用代码 03 第 20 行的循环,而是在同一行调用我们的新过程。本质上,我们刚刚公开了一段以前是私有的代码。我确信您可以毫无困难地理解代码 04。也就是说,现在让我们来看一个不同的场景,在这个场景中,我们使用函数而不是过程。该示例如下所示。

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. union un_01
05. {
06.     ulong u64_bits;
07.     uchar u8_bits[sizeof(ulong)];
08. };
09. //+------------------------------------------------------------------+
10. void OnStart(void)
11. {
12.     un_01 info;
13. 
14.     info.u64_bits = 0xA1B2C3D4E5F6789A;
15. 
16.     PrintFormat("The region is composed of %d bytes", sizeof(info));
17. 
18.     PrintFormat("Before modification: 0x%I64X", info.u64_bits);
19. 
20.     PrintFormat("After modification : 0x%I64X", Swap(info).u64_bits);
21. }
22. //+------------------------------------------------------------------+
23. un_01 Swap(const un_01 &arg)
24. {
25.     un_01 info = arg;
26. 
27.     for (uchar i = 0, j = sizeof(info) - 1, tmp; i < j; i++, j--)
28.     {
29.         tmp = info.u8_bits[i];
30.         info.u8_bits[i] = info.u8_bits[j];
31.         info.u8_bits[j] = tmp;
32.     }
33. 
34.     return info;
35. }
36. //+------------------------------------------------------------------+

代码 05

好吧,这里的事情有点复杂,但这只是因为在这个阶段,这对你来说可能是一个新概念。请注意,该代码与我们之前使用过程时看到的代码非常相似。然而,如果你仔细看看最后三个例子中的第 20 行,代码05中显示的版本可能对经验有限的程序员来说是最困难的。但没有理由恐慌。毕竟,我们只是使用第 23 行声明的函数。首先,让我们强调一些有趣的事情。在代码 03 和代码 04 中,第 12 行声明的变量的内容不可避免地被修改,这是事实。然而,在代码 05 中,第 12 行声明的相同变量的内容未被修改

怎么会这样?我们不是通过镜像操作来产生图 04 所示的结果吗?镜像确实正在发生。在最后这三个例子中,结果将与您在图 04 中看到的一致。但当我说第12行的变量没有被修改时,我的意思就是 —— 它保持不变。您可以通过注意到在第 23 行,我们将值作为常量引用传递来验证这一点。

现在情况似乎更加令人困惑。早些时候,我提到联合不能与常量一起使用,现在我说我们可以。很令人困惑,不是吗?好吧,也许我没有解释清楚。或者赋值和声明之间可能存在一些混淆。第 23 行的声明将参数定义为常量。这意味着传递给它的变量不能在函数内修改。然而,为了使函数运行,我们仍然需要一个可修改的变量。这正是我们在第 25 行创建的。该变量被修改,其结果在第 34 行返回。

这就是很多人可能会感到困惑的地方。如果 Swap 是代码 05 中的一个函数,并且它返回一个变量,那么我们是否应该在使用它之前将该返回值分配给另一个变量?这取决于不同情况,亲爱的读者。然而,正如前一篇文章所讨论的,由于函数是一种变量,我们可以使用第四行声明的特殊类型直接访问返回的数据。

这仅仅是因为函数明确返回了该特定类型才有可能。如果返回值是离散类型,则第 20 行所示的实现将不可行。在这种情况下,我们需要另一种机制来实现相同的结果。但由于这个话题可能会引起不必要的混乱,所以我将留到下次再讨论。有很多方法可以实现它。


最后的探讨

在本文中,我们开始探讨什么是联合。我们对可能应用联合的最初几个实际场景进行了实验。然而,我们在这里看到的只是基础知识,这是我们将在未来的文章中继续开发的一组更大的概念和技术的一部分。亲爱的读者,这里有一条黄金秘诀给你。仔细练习和学习你在这里看到的一切。

在附件中,您将找到本文中讨论的主要代码示例。在下一篇文章中,我们将更深入地探讨MQL5编程的基础。期待很快与您见面!

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

附加的文件 |
Anexo.zip (1.98 KB)
最近评论 | 前往讨论 (3)
Константин Сандалиди
Константин Сандалиди | 3 6月 2025 在 02:20
什么都不清楚!我希望有人能开始写 "从零基础到初学者 "系列文章!您有一个初学者,在编程方面受过两次高等教育....
CODE X
CODE X | 3 6月 2025 在 10:28
Константин Сандалиди # :
什么都不知道!非常希望有人能发起一个 "从零基础到初学者 "的系列文章!您的这位入门者拥有两个高级编程课程 ....

但我写这篇文章的目的正是如此。让一个人从零开始。不过,你所进入的这篇文章的材料已经比较先进了。我建议您从这篇文章开始:

从基础到中级:变量 (I)

详情:所有包含更高级内容的文章开头都有链接,您可以查看前一篇文章。但我建议从这一篇开始。事实上,这是第一篇。我从头开始讲解 🙂👍。

antar
antar | 3 6月 2025 在 10:56
Константин Сандалиди #:
什么都不清楚!我希望有人能开始写 "从零基础到初学者 "系列文章!你有一个拥有两个编程学位的初学者....

这是从葡萄牙语翻译过来的,不是最好的。😑

俄语原著会更清晰。例如这本书

《精通日志记录(第二部分):格式化日志》 《精通日志记录(第二部分):格式化日志》
在本文中,我们将探讨如何在类库中创建和应用日志格式化工具。我们将从格式化工具的基本结构讲起,一直到样例的实现。到本文结束时,您将掌握在该库中格式化日志的必要知识,并理解其背后的工作原理。
交易中的神经网络:搭配区段注意力的参数效率变换器(PSformer) 交易中的神经网络:搭配区段注意力的参数效率变换器(PSformer)
本文讲述新的 PSformer 框架,其适配雏形变换器架构,解决与多元时间序列预测相关的问题。该框架基于两项关键创新:参数共享(PS)机制,和区段注意力(SegAtt)。
分析交易所价格的二进制代码(第一部分):技术分析的新视角 分析交易所价格的二进制代码(第一部分):技术分析的新视角
本文提出了一种基于将价格波动转换为二进制代码的技术分析创新方法。作者展示了市场行为的各个方面——从简单的价格波动到复杂形态——如何被编码为一系列的0和1。
构建自优化型MQL5智能交易系统(EA)(第3部分):动态趋势跟踪与均值回归策略 构建自优化型MQL5智能交易系统(EA)(第3部分):动态趋势跟踪与均值回归策略
金融市场通常被静态划分为震荡市或趋势市两种模式。这种简化分类虽便于短期交易决策。然而,却与真实市场行为脱节。在本文中,我们将深入探讨市场如何精准地在这两种模式间切换,并利用这方面的认知提升算法交易策略的可靠性。