
从基础到中级:联合(二)
概述
此处提供的内容仅用于教育目的。在任何情况下,除了学习和掌握所提出的概念外,都不应出于任何目的使用此应用程序。
在上一篇文章从基础到中级:联合(一)中,我们开始讨论什么是联合。事实上,从第一篇文章开始,这个话题就一直在构建,因为到目前为止讨论的所有内容以及未来文章将涵盖的所有内容都以某种方式相互关联。上一篇文章中提出的内容仅代表了联合这一更广泛主题的初始部分。这个主题非常广泛,就像数组的主题一样,两者密切相关。
考虑到这一点,在本文中,我们将进一步探讨联合。我们还将进一步扩展对数组的理解。所以,坐下来,放松一下,让我们深入探讨本文的第一个主题。
数组和联合
联合是编程中一个真正有趣的话题,它打开了更高级概念的大门。然而,重要的是要谨慎行事。一个常见的陷阱是假设理解基础知识就等于掌握概念。
许多人误解了联合,因为他们认为通过声明联合,他们就被限制在声明时定义的简单模型中。事实上,当我们正确地应用这些概念时,可能会有更多的可能性。
在上一篇文章中,我提到,当我们创建(或更准确地说,声明)一个联合时,我们正在定义一种新的、专门的数据类型。在某些方面,与字符串的处理方式类似。然而,当一些读者看到某些构造与这种数据类型一起使用时,他们可能会感到不安或怀疑。
为了展示这有多有用,以及为什么有人会选择这样做,我们需要了解一个小而关键的细节。由于联合确实是一种新的特殊数据类型,我们可以以各种方式使用它,无论是在它的声明中,还是在我们如何在给定的上下文中操作它。让我们看一下第一个例子。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. union un_01 05. { 06. uint u32_bits; 07. uchar u8_bits[4]; 08. }; 09. //+------------------------------------------------------------------+ 10. void OnStart(void) 11. { 12. un_01 info[2]; 13. 14. PrintFormat("The region is composed of %d bytes", sizeof(info)); 15. ZeroMemory(info); 16. View(info); 17. Set(info, 0xA1B2C3D4E5F6789A); 18. View(info); 19. for (uchar c = 0; c < info.Size(); c++) 20. info[c] = Swap(info[c]); 21. View(info); 22. } 23. //+------------------------------------------------------------------+ 24. void Set(un_01 &arg[], ulong value) 25. { 26. arg[0].u32_bits = (uint)(value >> 32); 27. arg[1].u32_bits = (uint)(value & 0xFFFFFFFF); 28. } 29. //+------------------------------------------------------------------+ 30. void View(const un_01 &arg[]) 31. { 32. Print("------------"); 33. for(uchar c = 0; c < arg.Size(); c++) 34. PrintFormat("Index: [ %d ] = 0x%I64X", c, arg[c].u32_bits); 35. } 36. //+------------------------------------------------------------------+ 37. un_01 Swap(const un_01 &arg) 38. { 39. un_01 info = arg; 40. 41. for (uchar i = 0, j = sizeof(info) - 1, tmp; i < j; i++, j--) 42. { 43. tmp = info.u8_bits[i]; 44. info.u8_bits[i] = info.u8_bits[j]; 45. info.u8_bits[j] = tmp; 46. } 47. 48. return info; 49. } 50. //+------------------------------------------------------------------+
代码 01
当我们运行代码 01 时,MetaTrader 5 终端将显示以下输出:
图 01
在这个代码 01 示例中,我们应用了迄今为止所涵盖的所有内容,包括如何处理数组以及如何处理联合。请注意第 12 行:我们正在声明一个静态数组。乍一看,这似乎有点奇怪,特别是如果你一直假设数组中只能使用原始类型。但在这里,我们使用了一种特殊类型,我们在第 4 行中定义了它。就这么简单。
现在,在第 14 行,第一条打印信息告诉我们变量 “info” 由 8 个字节组成。一开始,这可能感觉不太对。亲爱的读者,如果这是你的反应,我鼓励你重新阅读上一篇文章。因为 “info” 的大小确实是 8 个字节 - 这是由于联合中最大的成员 u32_bits 是 uint 类型。从本质上讲,我们声明的内容与下面显示的结构非常相似。
. . . 12. uint info[2]; . . .
我认为这不会让你感到困惑。然而,在同一行中有一个隐藏的语句,一个观察力更强的程序员也可能会注意到。这正是我们下面看到的:
. . . 12. uchar info[2][4]; . . .
哇!等一下 —— 你在这里说什么?查看同一代码有两种不同的方法吗?写同一件事的两种方法?现在我真的很困惑 —— 这对我来说没有任何意义。
亲爱的读者,这正是我一直强调需要学习和实践每篇文章所展示内容的原因。我看到的一个常见问题,尤其是在初学者中,是过度依赖语法和编码模式。许多人实际上并没有试图理解潜在的概念;他们只关注编写的代码。
正如前一篇文章中提到的,当我们创建一个联合时,我们本质上是在创建一种在更小的段中分解和共享数据的方法。您可以将一个长内存块划分为更小的部分,以操纵它的特定部分。这就是我们如何在不显式使用特定运算符的情况下旋转、移动或修改某些点。这并不是因为联合具有魔力,而是因为它允许我们同时以多种方式解读同一段内存。
虽然我之前没有提到这一点,但第 12 行的两种书写方式都代表了同一件事。区别仅在于数据的分割方式。在第一种情况下,我们有一个包含两个元素的数组,每个元素的大小为 4 个字节。在第二种情况下,我们还是有两个元素,每个元素仍然是 4 个字节,但结构不同。或者许多人认为多维数组很难理解。但实际上,多维数组(如第 12 行的第二个版本中的数组)只是以不同形式编写的同一个数组。
多维数组在各种情况下都非常有用,但当我们深入研究矩阵的主题时,它们会变得更容易理解,我们将在以后的文章中介绍。在那之前,还没有必要讨论这个问题。亲爱的读者,请耐心等待,专注于我们现在正在探索的内容。换句话说,活在当下,不要担心接下来会发生什么。
有了这个初步的解释,并澄清了关于代码 01 的一些潜在的混淆点,我们现在可以转向新的东西。
重要提示:我不会详细介绍代码 01 的工作原理,因为之前的文章已经介绍过了。之前已经展示并解释了那里所做的一切。如果你在理解上有困难,我建议你复习一下之前的材料。我希望您了解正在做的事情,以便您可以创建自己的解决方案 —— 可以产生与图像中显示的完全相同的结果的解决方案。不是相似的结果,而是相同的结果。
澄清这一点后,让我们继续下一个例子,如下所示。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. union un_01 05. { 06. uint u32_bits[2]; 07. uchar u8_bits[8]; 08. }; 09. //+------------------------------------------------------------------+ 10. void OnStart(void) 11. { 12. un_01 info; 13. 14. PrintFormat("The region is composed of %d bytes", sizeof(info)); 15. ZeroMemory(info); 16. View(info); 17. Set(info, 0xA1B2C3D4E5F6789A); 18. View(info); 19. info = Swap(info); 20. View(info); 21. } 22. //+------------------------------------------------------------------+ 23. void Set(un_01 &arg, ulong value) 24. { 25. arg.u32_bits[0] = (uint)(value >> 32); 26. arg.u32_bits[1] = (uint)(value & 0xFFFFFFFF); 27. } 28. //+------------------------------------------------------------------+ 29. void View(const un_01 &arg) 30. { 31. Print("------------"); 32. for(uchar c = 0; c < arg.u32_bits.Size(); c++) 33. PrintFormat("Index: [ %d ] = 0x%I64X", c, arg.u32_bits[c]); 34. } 35. //+------------------------------------------------------------------+ 36. un_01 Swap(const un_01 &arg) 37. { 38. un_01 info = arg; 39. 40. for (uchar i = 0, j = sizeof(info) - 1, tmp; i < j; i++, j--) 41. { 42. tmp = info.u8_bits[i]; 43. info.u8_bits[i] = info.u8_bits[j]; 44. info.u8_bits[j] = tmp; 45. } 46. 47. return info; 48. } 49. //+------------------------------------------------------------------+
代码 02
现在让我们来看一个代码示例,当执行时,它会产生不同的结果。这就是我们在代码 02 中观察到的。乍一看,它似乎与代码 01 几乎相同,不是吗?然而,一旦执行,它会生成不同的输出:
图 02
您可能会想:“但这看起来和图 01 中显示的一模一样!”那么,再看一眼,更仔细地观察图 02,并将其与图 01 进行比较。即使仔细检查了每个值,它们在你看来仍然相同吗?因为事实上,它们是不同的。原因就在于联合的声明方式。这次,联合包含两个数组,并且都是静态的。但这似乎有些不合逻辑。在代码 01 中,第 7 行声明的数组有 4 个元素。但现在,它有 8 个了。为什么呢?难道我们不能保留相同的 4 个元素吗?
为了正确理解这一点,让我们把它分解一下。首先,我们需要考虑为什么元素数量会发生改变,以及这对联合有什么影响。由于一个小的数学细节,为了避免不必要地使代码复杂化,我们将更改第 7 行声明的数组中的元素数量,使其始终为偶数。如果它是一个奇数,它会引入一个小问题,使结果更难解释。因此,那里总是使用偶数。假设我们将其设置为 6 个元素,而不是 8 个。代码中的其他所有内容均保持不变。其结果为以下结构:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. union un_01 05. { 06. uint u32_bits[2]; 07. uchar u8_bits[6]; 08. }; 09. //+------------------------------------------------------------------+ 10. void OnStart(void) 11. { . . .
片段 01
这里我们只改变了第 7 行的值。当我们使用此修改运行代码 02 时,我们得到的输出如下所示:
图 03
嗯,不太顺利。现在让我们再次更改第 7 行的值,这次更改为 10 个元素。更新后的版本如下所示:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. union un_01 05. { 06. uint u32_bits[2]; 07. uchar u8_bits[10]; 08. }; 09. //+------------------------------------------------------------------+ 10. void OnStart(void) 11. { . . .
片段 02
执行此版本时,输出如下:
图 04
这个结果看起来也不太乐观,一些值似乎已经消失了。此时,您可能会想:为什么会发生这种情况?好吧,这正是我们在这里的原因 —— 帮助您理解一些乍一看可能不太合理的东西。但随着你积累经验并不断练习,你会更容易看到事情是如何以及为什么会这样。关键始终是要专注于理解所应用的概念。
让我们回到第一个修改的情况,即导致图 03 的情况,并抛出一个错误,表明代码试图访问超出界限的内存。发生此错误是因为 “info” 变量由八个字节组成。现在请密切注意:在联合中,u8_bits 数组包含六个元素或六个字节,因为每个元素都是 uchar 类型,即一个字节宽。因此,当使用变量 j(此时引用 u8_bits 的第八个元素)时,程序会尝试访问不属于该数组的内存。这就是代码在第 43 行失败的原因。但这次失败并不是因为在 u8_bits 中使用了更多或更少的元素。在我们讨论如何正确调整代码以允许 u8_bits 中有 6 个元素之前,让我们首先了解第二种情况 - 与图 04 相对应的情况。
在第二种情况下,您可以看到信息包含 10 个字节或 10 个元素。这是因为 u8_bits 也声明有 10 个元素。因此,当执行 Swap 函数时(如第 36 行所示),部分值会被零覆盖,而这些零是由第 15 行的过程初始化的。换句话说,ZeroMemory 清除与 “info” 变量相关的整个内存块。
这就是为什么某些值似乎“消失”了。但实际上,它们只是被移动或替换到了内存中的其他地方。为了证明这一点,我们将对代码进行一个小的更改。为了避免遇到我们在图 03 和 04 中看到的相同问题,新版本的代码如下所示:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. union un_01 05. { 06. uint u32_bits[2]; 07. uchar u8_bits[8]; 08. }; 09. //+------------------------------------------------------------------+ 10. void OnStart(void) 11. { 12. un_01 info; 13. 14. PrintFormat("The region is composed of %d bytes", sizeof(info)); 15. ZeroMemory(info); 16. Set(info, 0xA1B2C3D4E5F6789A); 17. View(info); 18. Debug(info); 19. info = Swap(info); 20. View(info); 21. Debug(info); 22. } 23. //+------------------------------------------------------------------+ 24. void Set(un_01 &arg, ulong value) 25. { 26. arg.u32_bits[0] = (uint)(value >> 32); 27. arg.u32_bits[1] = (uint)(value & 0xFFFFFFFF); 28. } 29. //+------------------------------------------------------------------+ 30. void View(const un_01 &arg) 31. { 32. Print("------------"); 33. for(uchar c = 0; c < arg.u32_bits.Size(); c++) 34. PrintFormat("Index: [ %d ] = 0x%I64X", c, arg.u32_bits[c]); 35. } 36. //+------------------------------------------------------------------+ 37. void Debug(const un_01 &arg) 38. { 39. string sz = ""; 40. 41. Print("*************"); 42. for(uchar c = 0; c < (uchar)arg.u8_bits.Size(); c++) 43. sz = StringFormat("%s0x%X ", sz, arg.u8_bits[c]); 44. PrintFormat("Number of elements in %cinfo.u8_bits.Size()%c is %d\nInternal content is [ %s ]", 34, 34, arg.u8_bits.Size(), sz); 45. Print("*************"); 46. } 47. //+------------------------------------------------------------------+ 48. un_01 Swap(const un_01 &arg) 49. { 50. un_01 info = arg; 51. 52. for (uchar i = 0, j = (uchar)(info.u8_bits.Size() - 1), tmp; i < j; i++, j--) 53. { 54. tmp = info.u8_bits[i]; 55. info.u8_bits[i] = info.u8_bits[j]; 56. info.u8_bits[j] = tmp; 57. } 58. 59. return info; 60. } 61. //+------------------------------------------------------------------+
代码 03
当我们运行此代码时,我们将看到与下图类似的内容。
图 05
这就是事情开始变得更容易理解的地方。图 05 中突出显示了两个特定区域。请记住,此代码 03 是代码 02 的修改版本,并且我们再次关注的是第 7 行。为了标准化并正确理解发生了什么,我们为代码 03 中第 7 行的数组重用了之前在代码 02 中使用的相同值。这给了我们图 05 所示的输出。
现在,亲爱的读者,请密切注意。如果你想在自己的机器上重现这些结果,这一点至关重要。在代码 03 的第 52 行,我做了修改,以防止出现之前在图 03 中看到的相同错误。现在,这种修改使我们能够做我们以前尝试过的事情。
首先,让我们将片段 01 应用于代码 03。当我们这样做时,我们得到的结果如下:
图 06
我再次标记了重要的兴趣点,因为了解幕后发生的事情至关重要。尽管我们在 u8_bits 中使用的元素数量较少,但操作仍然会发生。但是,请看一下索引 1 处的内容。您会注意到部分数据保持不变。为什么呢?在回答这个问题之前,让我们将片段 02 应用到代码 03。
这样做之后,我们得到下图所示的结果:
图 07
是的,现在很明显,事情并不像最初看起来那么简单。但别担心,亲爱的读者。这里的一切实际上都很简单实用。你只需要花时间集中注意力和练习。你不能只是通读这篇文章就指望掌握它,你需要亲身实践。在图 06 和图 07 中,您都可以观察到内存正在轮转或镜像,正如预期的那样。但是,直接访问 u32_bits 元素时,数组索引中显示的值可能不是您最初预期的。这是因为我们处理的是不同的内存组成。
当你声明的数组完全覆盖了内存区域时,你会得到一个完美的表示。这就是为什么图 05 完全符合预期。但是,如果你对内存块填充不足或过满,导致部分未被覆盖或添加了超过所需的内容,结果就会变得扭曲和分散。这就是您在图 06 和 07 中看到的。其中一个区域填充不足,另一个区域填充过度。
现在你明白为什么修改第 7 行的元素数量如此重要了吗?如果你没有正确处理,在某些情况下,你最终会得到不正确的内存覆盖。这就是为什么实验和尝试不同的做事方式至关重要。附件不仅仅是文章的装饰元素,它们供您使用和学习。测试它们,调整它们,观察有什么变化。只有这样,您才能开始对这些文章中涵盖的每个编程元素有一个扎实的理解。这就是你如何从“复制粘贴”程序员转变为真正优秀的程序员。
我们已经为如何研究和解释这里提供的材料奠定了坚实的基础。我相信你已经准备好解决文章前面提出的在文章从基础到中级:数组(四)中的问题。事实上,我们将采用该代码并使其变得更好。为了解释如何做到这一点,我们将进入一个新主题。
把好事做得更好
我们在这里要做的既有趣又实用。但是,要完全理解以下内容,您必须掌握上一节的内容。否则,你可能会感到困惑和迷失。所以不要着急,花点时间仔细研究到目前为止显示的所有内容,只有这样,你才能使用 MQL5 继续进行下一个挑战。
既然提醒已经过去,让我们进入这个话题的核心。首先,我们将重新审视我刚才提到的前一篇文章中共享的一段代码。该示例如下所示。
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. //+------------------------------------------------------------------+
代码 04
执行时,此代码 04 产生如下所示的结果:
图 08
这是一个挑战:你的任务是将代码 04 转化为更简单、更优雅的东西,使用到目前为止你所学到的知识来实现。您可能会想:“我,一个刚刚开始学习编程的人,应该如何利用我所学到的知识来改进这段代码?这太疯狂了!”但那些不挑战自己或走出舒适区的人很快就会落后。
新技术和更好的概念不断出现,使我们的工作更容易,代码更高效,并将真正的程序员与只复制和粘贴的程序员分开。因为在每一步中,我们都会看到以更简单的方式实现相同结果的新方法。因此,让我们深入探讨如何应用前面的概念来改进代码 04。为此,我们首先需要分析当前代码实际在做什么。
从本质上讲,您将看到一些变量在特定序列中被声明和使用。具有最大内存宽度的变量为 uint 类型,在第 8 行声明并在第 28 行到第 35 行之间使用。使用的其他数据类型较小。知道这一点后,我们可以开始考虑一种解决方案,其中第 12 行声明的数组 “Info” 以产生相同内部内存结构的方式接收信息。但是,这个内存结构到底是什么呢?我们如何知道我们的新实现是否正确?
看图 08 - 在某个点上显示了“Info”数组的确切内容。因此,我们需要做的就是复制相同的结果,但要使代码更易于使用。
好的,这就是目标。正如本文和上一篇文章所展示的,我们可以使用联合来更好地组织数据。那么我们到底该怎么做呢?首先,亲爱的读者,您需要创建一个正确声明的联合来保存值。由于我们将使用 uint、ushort 和 uchar 值(基于第 28 行和第 48 行之间的操作),您可能认为需要三个不同的联合。但事实上,你只需要一个。其代码片段如下:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "Tutorial\File 01.mqh" 05. //+------------------------------------------------------------------+ 06. union un_Model 07. { 08. uint u32; 09. ushort u16; 10. uchar u8, 11. array[sizeof(uint)]; 12. }; 13. //+------------------------------------------------------------------+ 14. void OnStart(void) 15. { . . .
片段 03
非常好,看一下片段 03。在第 6 行,我们创建了一个联合,在接下来的几行中,我们声明了稍后要使用的离散类型。乍一看,这可能看起来有点令人困惑。然而,我们真正关心的部分是第 11 行。这就是我们能够访问联合数据类型所包含的整个内存区域的地方。请注意我如何声明一个静态数组,其元素数量等于联合中最大离散类型的大小。当您不确定联合中需要多少字节时,这是一种很好的方法。
现在你可能会想:“等一下。如果我理解正确的话,当我们修改一个值(或者更确切地说,修改联合中的变量0)时,我们不仅仅是在更改一个特定的变量。我们还会影响所有其他变量,因为它们共享相同的内存空间。是吗?”是的,亲爱的读者,你完全正确。因此,你可以得出结论:“我们不能使用这个片段中声明的联合。因为一旦我们给其中一个变量赋值,其他变量也会受到影响。”再说一次,你这样想是正确的。然而,我还没有解释我们将在这里利用什么。
我们对分配给特定变量的具体值并不感兴趣。我们关心的内容包含在数组中。这才是构建系统的关键。如果你已经完全理解了前几篇文章中提供的材料(更重要的是,如果你已经练习过),你应该意识到我们可以将一个数组复制到另一个数组中。知道我们要复制多少元素以及从哪个数组复制,我们可以显著改进从第 28 行到第 48 行运行的代码块。这很简单:我们复制数组。为此,我们将使用下面显示的代码。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "Tutorial\File 01.mqh" 005. //+------------------------------------------------------------------+ 006. union un_Model 007. { 008. uint u32; 009. ushort u16; 010. uchar u8, 011. array[sizeof(uint)]; 012. }; 013. //+------------------------------------------------------------------+ 014. void OnStart(void) 015. { 016. const uint ui = 0xCADA5169; 017. ushort us = 0x43BC; 018. uchar uc = B'01011101'; 019. 020. enum eValues { 021. U32_Bits, 022. U16_Bits, 023. U8_Bits 024. }; 025. 026. uchar Infos[], 027. counter = 0; 028. 029. PrintFormat("Translation personal.\n" + 030. "FUNCTION: [%s]\n" + 031. "ui => 0x%s\n" + 032. "us => 0x%s\n" + 033. "uc => B'%s'\n", 034. __FUNCTION__, 035. ValueToString(ui, FORMAT_HEX), 036. ValueToString(us, FORMAT_HEX), 037. ValueToString(uc, FORMAT_BINARY) 038. ); 039. 040. for (eValues c = U32_Bits; c <= U8_Bits; c++) 041. { 042. un_Model data; 043. 044. ArrayResize(Infos, Infos.Size() + 1); 045. switch (c) 046. { 047. case U32_Bits: 048. Infos[counter++] = sizeof(ui); 049. data.u32 = ui; 050. break; 051. case U16_Bits: 052. Infos[counter++] = sizeof(us); 053. data.u16 = us; 054. break; 055. case U8_Bits: 056. Infos[counter++] = sizeof(uc); 057. data.u8 = uc; 058. break; 059. } 060. 061. counter += (uchar)ArrayCopy(Infos, Swap(data).array, counter, sizeof(data) - Infos[counter - 1]); 062. } 063. 064. Print("******************"); 065. PrintFormat("The Infos block contains %d bytes.", Infos.Size()); 066. ArrayPrint(Infos); 067. Print("******************"); 068. 069. Procedure(Infos); 070. 071. ArrayFree(Infos); 072. } 073. //+------------------------------------------------------------------+ 074. un_Model Swap(const un_Model &arg) 075. { 076. un_Model info = arg; 077. 078. for (uchar i = 0, j = (uchar)(info.array.Size() - 1), tmp; i < j; i++, j--) 079. { 080. tmp = info.array[i]; 081. info.array[i] = info.array[j]; 082. info.array[j] = tmp; 083. } 084. 085. return info; 086. } 087. //+------------------------------------------------------------------+ 088. void Procedure(const uchar &arg[]) 089. { 090. Print("Translation personal.\n" + 091. "FUNCTION: ", __FUNCTION__); 092. 093. ulong value; 094. 095. for (uchar c = 0; c < arg.Size(); ) 096. { 097. value = 0; 098. for (uchar j = arg[c++], i = 0; (c < arg.Size()) && (i < j); i++, c++) 099. value = (value << 8) | arg[c]; 100. Print("0x", ValueToString(value, FORMAT_HEX), " B'", ValueToString(value, FORMAT_BINARY), "'"); 101. } 102. } 103. //+------------------------------------------------------------------+
代码 05
当我们运行代码 05 时,我们将看到以下结果。
图片 09
看来它已经起作用了,但事实并非如此。如果仔细检查图 09 中突出显示的区域并将其与图 08 进行比较,您会注意到值看起来是轮转的或镜像的。然而,这对我们来说不是问题,因为我已经展示了如何反转或镜像值。但在我们走这条路之前,我想向你们展示另一种可能的解决方案,根据具体情况,这可能就足够了。
请注意,图 09 中显示的值不仅在内存中镜像,而且在 OnStart 中显示的输出和在 Procedure 中看到的输出之间也镜像。然而,根据具体的上下文(这一点需要强调),我们可能可以忽略数组被镜像的事实,而是应用下面显示的方法。
. . . 62. //+------------------------------------------------------------------+ 63. void Procedure(const uchar &arg[]) 64. { 65. un_Model data; 66. 67. Print("Translation personal.\n" + 68. "FUNCTION: ", __FUNCTION__); 69. 70. for (uchar c = 0; c < arg.Size(); ) 71. { 72. ZeroMemory(data.array); 73. c += (uchar)(ArrayCopy(data.array, arg, 0, c + 1, arg[c]) + 1); 74. Print("0x", ValueToString(data.u32, FORMAT_HEX), " B'", ValueToString(data.u32, FORMAT_BINARY), "'"); 75. } 76. } 77. //+------------------------------------------------------------------+
片段 04
如果(让我明确强调这一点)在代码 05 中使用片段 04 是可以接受的,那么结果输出将显示如下:
图 10
亲爱的读者,请密切注意。在图 10 的红色突出显示区域中,我们有原始值。在蓝色区域中,我们看到输出值。请注意,与我们在图 09 中看到的不同,这些值没有镜像,而是与原始值匹配。然而,代表内存中确切内容的粉红色突出显示区域仍然是镜像或无序的,就像图 09 中显示的那样。也就是说,图 10 中的输入和输出值与图 08 中所示的值相同。
因此,如果可以忽略实际的内存内容,并且目标只是获得正确的输入和输出值(如图 10 所示),那么片段 04 中提出的解决方案可能确实有效。但是,根据具体情况,这种解决方案可能不适合我们真正想要或被允许使用的东西。在这种情况下,有必要显式地镜像数据,以便最终结果与图 08 所示完全匹配。
为了实现这一点,我们需要对代码进行一点调整,特别是在将值放入要传输的内存之前对其进行镜像。这很简单,只需将前面的代码替换为下面的代码即可。
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "Tutorial\File 01.mqh" 05. //+------------------------------------------------------------------+ 06. union un_Model 07. { 08. uint u32; 09. ushort u16; 10. uchar u8, 11. array[sizeof(uint)]; 12. }; 13. //+------------------------------------------------------------------+ 14. void OnStart(void) 15. { 16. const uint ui = 0xCADA5169; 17. ushort us = 0x43BC; 18. uchar uc = B'01011101'; 19. 20. uchar Infos[], 21. counter = 0; 22. 23. PrintFormat("Translation personal.\n" + 24. "FUNCTION: [%s]\n" + 25. "ui => 0x%s\n" + 26. "us => 0x%s\n" + 27. "uc => B'%s'\n", 28. __FUNCTION__, 29. ValueToString(ui, FORMAT_HEX), 30. ValueToString(us, FORMAT_HEX), 31. ValueToString(uc, FORMAT_BINARY) 32. ); 33. 34. { 35. un_Model data; 36. 37. ArrayResize(Infos, Infos.Size() + 1); 38. Infos[counter++] = sizeof(ui); 39. data.u32 = ui; 40. counter += (uchar)ArrayCopy(Infos, Swap(data).array, counter, 0, sizeof(ui)); 41. 42. ArrayResize(Infos, Infos.Size() + 1); 43. Infos[counter++] = sizeof(us); 44. data.u16 = us; 45. counter += (uchar)ArrayCopy(Infos, Swap(data).array, counter, 0, sizeof(us)); 46. 47. ArrayResize(Infos, Infos.Size() + 1); 48. Infos[counter++] = sizeof(uc); 49. data.u8 = uc; 50. counter += (uchar)ArrayCopy(Infos, Swap(data).array, counter, 0, sizeof(uc)); 51. } 52. 53. Print("******************"); 54. PrintFormat("The Infos block contains %d bytes.", Infos.Size()); 55. ArrayPrint(Infos); 56. Print("******************"); 57. 58. Procedure(Infos); 59. 60. ArrayFree(Infos); 61. } 62. //+------------------------------------------------------------------+ 63. un_Model Swap(const un_Model &arg) 64. { 65. un_Model info = arg; 66. 67. for (uchar i = 0, j = (uchar)(info.array.Size() - 1), tmp; i < j; i++, j--) 68. { 69. tmp = info.array[i]; 70. info.array[i] = info.array[j]; 71. info.array[j] = tmp; 72. } 73. 74. return info; 75. } 76. //+------------------------------------------------------------------+ 77. void Procedure(const uchar &arg[]) 78. { 79. Print("Translation personal.\n" + 80. "FUNCTION: ", __FUNCTION__); 81. 82. ulong value; 83. 84. for (uchar c = 0; c < arg.Size(); ) 85. { 86. value = 0; 87. for (uchar j = arg[c++], i = 0; (c < arg.Size()) && (i < j); i++, c++) 88. value = (value << 8) | arg[c]; 89. Print("0x", ValueToString(value, FORMAT_HEX), " B'", ValueToString(value, FORMAT_BINARY), "'"); 90. } 91. } 92. //+------------------------------------------------------------------+
代码 06
好的,我们已经做出修改。但即使实施了调整,我们也要看看结果。
图 11
这是一种经常让初学者望而却步的问题,这是因为只有第一个值是正确的,与我们在图 08 中看到的相符。其余值都是错误的。为什么呢?问题在于如何处理起始值 —— 它需要正确设置。这发生在第 45 行和第 50 行。由于 “start” 值当前设置为零,因此我们最终得到了错误的起始位置。复制操作仅影响联合体内数组的第 1 个元素。正如您在上一主题中看到的那样,该数组已被反转。所以现在的第 1 个元素原本是最后一个。
为了解决这个问题,我们需要修改第 45 行和第 50 行,如下所示:
. . . 34. { 35. un_Model data; 36. 37. ArrayResize(Infos, Infos.Size() + 1); 38. Infos[counter++] = sizeof(ui); 39. data.u32 = ui; 40. counter += (uchar)ArrayCopy(Infos, Swap(data).array, counter, 0, sizeof(ui)); 41. 42. ArrayResize(Infos, Infos.Size() + 1); 43. Infos[counter++] = sizeof(us); 44. data.u16 = us; 45. counter += (uchar)ArrayCopy(Infos, Swap(data).array, counter, sizeof(data) - sizeof(us), sizeof(us)); 46. 47. ArrayResize(Infos, Infos.Size() + 1); 48. Infos[counter++] = sizeof(uc); 49. data.u8 = uc; 50. counter += (uchar)ArrayCopy(Infos, Swap(data).array, counter, sizeof(data) - sizeof(uc), sizeof(uc)); 51. } . . .
片段 05
当您运行代码 06,合并片段05中显示的更改时,输出将如图 12 所示 ——这正是我们根据图 08 所期望的。换句话说,它工作得很好。然而,在查看片段 05 后,您很快就会意识到我们可以很容易地将其打包成一个循环,因为有相当多的重复部分。所以,这是最终的代码,可以在附录中找到。
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "Tutorial\File 01.mqh" 005. //+------------------------------------------------------------------+ 006. union un_Model 007. { 008. uint u32; 009. ushort u16; 010. uchar u8, 011. array[sizeof(uint)]; 012. }; 013. //+------------------------------------------------------------------+ 014. void OnStart(void) 015. { 016. const uint ui = 0xCADA5169; 017. ushort us = 0x43BC; 018. uchar uc = B'01011101'; 019. 020. enum eValues { 021. U32_Bits, 022. U16_Bits, 023. U8_Bits 024. }; 025. 026. uchar Infos[], 027. counter = 0; 028. 029. PrintFormat("Translation personal.\n" + 030. "FUNCTION: [%s]\n" + 031. "ui => 0x%s\n" + 032. "us => 0x%s\n" + 033. "uc => B'%s'\n", 034. __FUNCTION__, 035. ValueToString(ui, FORMAT_HEX), 036. ValueToString(us, FORMAT_HEX), 037. ValueToString(uc, FORMAT_BINARY) 038. ); 039. 040. for (eValues c = U32_Bits; c <= U8_Bits; c++) 041. { 042. un_Model data; 043. 044. ArrayResize(Infos, Infos.Size() + 1); 045. switch (c) 046. { 047. case U32_Bits: 048. Infos[counter++] = sizeof(ui); 049. data.u32 = ui; 050. break; 051. case U16_Bits: 052. Infos[counter++] = sizeof(us); 053. data.u16 = us; 054. break; 055. case U8_Bits: 056. Infos[counter++] = sizeof(uc); 057. data.u8 = uc; 058. break; 059. } 060. 061. counter += (uchar)ArrayCopy(Infos, Swap(data).array, counter, sizeof(data) - Infos[counter - 1]); 062. } 063. 064. Print("******************"); 065. PrintFormat("The Infos block contains %d bytes.", Infos.Size()); 066. ArrayPrint(Infos); 067. Print("******************"); 068. 069. Procedure(Infos); 070. 071. ArrayFree(Infos); 072. } 073. //+------------------------------------------------------------------+ 074. un_Model Swap(const un_Model &arg) 075. { 076. un_Model info = arg; 077. 078. for (uchar i = 0, j = (uchar)(info.array.Size() - 1), tmp; i < j; i++, j--) 079. { 080. tmp = info.array[i]; 081. info.array[i] = info.array[j]; 082. info.array[j] = tmp; 083. } 084. 085. return info; 086. } 087. //+------------------------------------------------------------------+ 088. void Procedure(const uchar &arg[]) 089. { 090. Print("Translation personal.\n" + 091. "FUNCTION: ", __FUNCTION__); 092. 093. ulong value; 094. 095. for (uchar c = 0; c < arg.Size(); ) 096. { 097. value = 0; 098. for (uchar j = arg[c++], i = 0; (c < arg.Size()) && (i < j); i++, c++) 099. value = (value << 8) | arg[c]; 100. Print("0x", ValueToString(value, FORMAT_HEX), " B'", ValueToString(value, FORMAT_BINARY), "'"); 101. } 102. } 103. //+------------------------------------------------------------------+
代码 07
最后的探讨
在这篇文章中,我们探讨了几个有趣的主题,明确的目标是理解数据是如何放置在内存中的以及它是如何工作的。虽然这里所涵盖的内容可能需要你比许多人愿意走的更远一点,但在理解这里所展示的内容方面取得的任何进展都将在未来对你大有裨益。
在这里,你可以看到,并不是所有事情都像乍一看那么复杂,也不是所有的事情都那么简单,以至于你可以通过快速阅读来理解所有事情。不断练习和学习是至关重要的。但首先,您需要努力理解开发任何应用程序所涉及的概念。
简单地接受另一个程序员编写的一些代码似乎更适合我们的事实,并不能帮助我们创建合适的东西。当时它可能看起来很合适,但当投入使用时,它可能会因意外问题而让你失望,如果没有正确的知识,你将无法解决这些问题。因此,请探索附件中的代码,并尝试练习和学习您在这里看到的内容。我们下篇文章再见。
本文由MetaQuotes Ltd译自葡萄牙语
原文地址: https://www.mql5.com/pt/articles/15503
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。


