From Basic to Intermediate: Struct (IV)
Introduction
In the previous article, "From Basic to Intermediate: Struct (III)", we began exploring a topic that causes significant confusion among beginners: the difference between structured and organized code. Many confuse the fact that code is well-organized with it being structured. While the concepts might seem similar, they are not exactly the same. However, this article serves merely as a starting point for something more complex, elegant, and fascinating within the realm of structured programming.
Since there are several concepts that may be more or less challenging to grasp depending on prior practice, we will strive to present each concept in a clear and objective manner. The goal is for you to gain a proper understanding of what structured code is and how it can be used to build almost anything. I say "almost" because there is a limitation to what structured code can govern. When we approach this limitation, it will be necessary to introduce another concept: the class. At that point, we will leave structured programming behind and move into object-oriented programming (OOP). For now, however, we can explore many things and have a lot of fun creating several examples of structured code to truly grasp the concepts and limitations of structured programming.
Alright, let's continue from where we left off in the previous article. That’s where we mentioned and demonstrated how the public and private sections of code are used. While we didn’t explain why this is done, we will begin from that point now.
The private section of a code in a structure
Since every element defined in a structure is public by default, I see no need to explain the public section, as it does not need to be declared in the code—it is implicit, being declared within the structure. However, the private section of the code is different. In this case, the section must be explicitly stated. But this has certain implications for the code and how we work with it. Let's start with something simple. And since the goal is educational, don’t try to find the logic behind why the code should be implemented in a particular way. Try to grasp the concept, as that is what truly matters for us.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. struct st_Data 05. { 06. //+----------------+ 07. double Values[]; 08. //+----------------+ 09. void Set(const double &arg[]) 10. { 11. ArrayCopy(Values, arg); 12. } 13. //+----------------+ 14. double Average(void) 15. { 16. double sum = 0; 17. 18. for (uint c = 0; c < Values.Size(); c++) 19. sum += Values[c]; 20. 21. return sum / Values.Size(); 22. } 23. //+----------------+ 24. double Median(void) 25. { 26. double Tmp[]; 27. 28. ArrayCopy(Tmp, Values); 29. ArraySort(Tmp); 30. if (!(Tmp.Size() & 1)) 31. { 32. int i = (int)MathFloor(Tmp.Size() / 2); 33. 34. return (Tmp[i] + Tmp[i - 1]) / 2.0; 35. } 36. return Tmp[Tmp.Size() / 2]; 37. } 38. //+----------------+ 39. }; 40. //+------------------------------------------------------------------+ 41. #define PrintX(X) Print(#X, " => ", X) 42. //+------------------------------------------------------------------+ 43. void OnStart(void) 44. { 45. const double H[] = {2.05, 1.97, 1.87, 1.75, 1.99, 2.01, 1.83}; 46. st_Data Info; 47. 48. Info.Set(H); 49. PrintX(Info.Average()); 50. PrintX(Info.Median()); 51. } 52. //+------------------------------------------------------------------+
Code 01
Code 01 does something very simple, practical, and understandable. The idea is to create a fully structured code. To do this, we must first define our own structure. This is done on line 04. Now, pay attention: the only thing typically done in this structure is the declaration on line 07. Because that's exactly what was explained and studied in the articles about structures. However, in the previous article, we began discussing structured code, and to achieve it, we added something more to the structure. In this case, we will move on to adding functions and/or procedures. So, internal subroutines will appear within the structure, which will define the structured code.
Nevertheless, these subroutines, whether functions or procedures, must be part of a context, and this context is related to the variables present in the structure or the purpose for which the structure is designed and implemented. For now, I think you have understood everything. Once the structure is defined, we can proceed to use it. To illustrate the use of this specific structure, we will utilize what is inside the OnStart procedure present on line 43.
First, on line 45, we define an array of numeric constants. It doesn't matter what these values represent; we simply need them to exist. Now, on line 46, we declare a variable to access the structure defined on line 04. After this, we have two paths we can follow. The first is to use what we see on line 48; the second we will consider later. After executing line 48, the array declared on line 07 within the structure will be populated with the values that interest us at the moment. This is where things start to get interesting.
Follow the reasoning to understand how structured code makes it easier to comprehend the purpose of a variable. When we declare Info on line 46, we don't know what this declaration is for; we simply need a variable of this specific type. But since the structure contains internal functions and procedures that give context to the values it holds, we know what kind of activities we can undertake when using it. If these functions and procedures were not declared, our structure could serve any purpose (and still can). This relates to a question that remains open. However, by looking at lines 49 and 50, we see that our structure is aimed at calculating the average and median of the very data we input into it.
Things like this create what we know as context. In other words, something only makes sense when we understand why it exists. Without it, any variable, function, or procedure could mean anything and serve any purpose. Therefore, when executing code 01, we will see the following result:

Figure 01
In other words, we are not interested in what the values specified on line 45 mean or how they relate to the real world. But we can assert that, within the context provided by the value structure, the result to be displayed should be as follows: Such things can be extended in numerous ways because whenever we need something related to the data in the structure, we can use the context to emphasize and simplify the understanding of the result itself, as the structure itself provides the context for that kind of information.
Please note that we could have created similar code for the same purpose, but we would lack a real context linking the data in the structure to the generated answer. And now we come to the part that many beginners often find difficult to grasp. It points out that since no access section is specified in the structure, everything within it is considered public. In other words, we can manipulate the information in a completely arbitrary manner. To demonstrate this and understand the complexity of the process, let's modify the code as follows:
. . . 40. //+------------------------------------------------------------------+ 41. #define PrintX(X) Print(#X, " => ", X) 42. //+------------------------------------------------------------------+ 43. void OnStart(void) 44. { 45. const double H[] = {2.05, 1.97, 1.87, 1.75, 1.99, 2.01, 1.83}; 46. const double K[] = {12, 4, 7, 23, 38}; 47. 48. st_Data Info; 49. 50. Info.Set(H); 51. PrintX(Info.Average()); 52. 53. ArrayPrint(Info.Values, 2); 54. ZeroMemory(Info); 55. ArrayCopy(Info.Values, K); 56. 57. PrintX(Info.Median()); 58. 59. ArrayPrint(Info.Values, 2); 60. } 61. //+------------------------------------------------------------------+
Code 02
In code 02, which is just a fragment of the full code (which will be in the appendix), we can see that only the part related to the OnStart procedure has been modified. However, this modification, while not changing the context of the structure, ultimately shattered all hopes of obtaining appropriate values. This happens because the internal variable within the structure can be altered—or more precisely, it can be accessed. And this is indeed very dangerous. One might think: «Of course, the values will be different when we run this code». This is obvious, because line 55 assigns new values to the variable defined on line 04, which, in turn, is inside the structure.
I don’t understand what the problem is here, since we can clearly see what’s happening. That’s correct, dear reader, but I must remind you that these are educational codes, and for this reason, errors are easy to spot in them. In real code, you would struggle to achieve this, as the context being used is preserved and present within the structure. However, the fact that on line 55 we force the variable present in the structure to be changed while the structure is unaware of it complicates the situation significantly and makes it difficult to understand why the results are incorrect and do not match expectations.
This type of error is known as an «encapsulation error», because code that should not see something does see it, or, even worse, manages to change a variable that should not be altered. However, the problem is even more serious. To understand this, we need to know the result of executing code 02. This part can be seen below:

Figure 02
Be careful, as this is a reason for failure in a programmer's test, because if an employer wants to assess your level of knowledge about possible errors in code, you will find yourself in an uncomfortable position. When line 50 is executed, enough memory is allocated to store the values from line 45. So far, so good. Therefore, when line 53 is executed, we see the values declared on line 45. That is, the code works as expected, and the structure is defined correctly. However, when line 54 is executed, all variables present in the structure will be reset. This is not an error. In fact, in many cases, it is even permissible and desirable, as a structure may contain several predefined elements, and we want to remove them all.
But when line 55 is executed, an error occurs in the code. This happens because the allocated memory WAS NOT RELEASED; it was merely reset. Therefore, the actual content of the memory is displayed on line 59. Thus, the median K highlighted in image 02 is incorrect. «But why is it wrong? I don't understand». To understand this, you need to know what the median K should be. If you look at code 02, assuming you know how to find the median, you will see that the correct value is 12, not 7. The reason for the incorrect value is that in the structure, which is visible due to line 59, there are zeros or elements that SHOULD NOT BE PRESENT.
This is precisely why we need to use the private section of the code. This brings us to the question that formed the basis for this topic: understanding why and when to use the private section. But one might think: «Bro, what if I, instead of using everything as shown in code 02, accidentally repeated line 50 on line 55, only replacing H with K? Wouldn't that solve the problem?». In this case- no. The structure code contains a small flaw, but we will return to that later. The idea is to understand that we can make certain mistakes without realizing it. However, by applying the right concepts, such errors can be avoided, and the code will be much easier to fix.
To solve the first problem, which is that we can directly access the variable declared in the structure, we need to modify the code as shown below:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. struct st_Data 05. { 06. //+----------------+ 07. private: 08. //+----------------+ 09. double Values[]; 10. //+----------------+ 11. public: 12. //+----------------+ 13. void Set(const double &arg[]) 14. { 15. ArrayCopy(Values, arg); 16. } 17. //+----------------+ 18. double Average(void) 19. { 20. double sum = 0; 21. 22. for (uint c = 0; c < Values.Size(); c++) 23. sum += Values[c]; 24. 25. return sum / Values.Size(); 26. } 27. //+----------------+ 28. double Median(void) 29. { 30. double Tmp[]; 31. 32. ArrayCopy(Tmp, Values); 33. ArraySort(Tmp); 34. if (!(Tmp.Size() & 1)) 35. { 36. int i = (int)MathFloor(Tmp.Size() / 2); 37. 38. return (Tmp[i] + Tmp[i - 1]) / 2.0; 39. } 40. return Tmp[Tmp.Size() / 2]; 41. } 42. //+----------------+ 43. }; 44. //+------------------------------------------------------------------+ 45. #define PrintX(X) Print(#X, " => ", X) 46. //+------------------------------------------------------------------+ 47. void OnStart(void) 48. { 49. const double H[] = {2.05, 1.97, 1.87, 1.75, 1.99, 2.01, 1.83}; 50. const double K[] = {12, 4, 7, 23, 38}; 51. 52. st_Data Info; 53. 54. Info.Set(H); 55. PrintX(Info.Average()); 56. 57. ArrayPrint(Info.Values, 2); 58. ZeroMemory(Info); 59. ArrayCopy(Info.Values, K); 60. 61. PrintX(Info.Median()); 62. 63. ArrayPrint(Info.Values, 2); 64. } 65. //+------------------------------------------------------------------+
Code 03
Now pay attention, because many programmers do not use the private section in structures precisely because of what is about to happen.
Remember, we said the whole problem was caused by line 59? In code 03, we tell the compiler that the variable declared inside the structure is of type private, meaning it will NO LONGER BE VISIBLE OUTSIDE THE STRUCTURE'S BODY. This happens precisely because of line 07. But we need line 11 to allow other elements to be accessible outside the structure's body; in this case, the functions and procedures. Otherwise, the construct would become completely useless.
When we try to compile code 03, the compiler will issue warning messages indicating errors in the code. This is part of the process, as we have changed the way the structure is used. These errors can be seen just below:

Figure 03
One might expect the error to occur only on line 59 of code 03, since that is where we assign something to the variable. However, four errors occured. Why? The reason is simple. The errors on lines 57, 59, and 63, which can be seen in image 03, are precisely related to the fact that we are trying to access something we can no longer access because the variable is private within the structure. For this reason, it can only be accessed within the structure where it is declared, which creates and maintains the context of the structure's existence and the variable itself.
But what about the error on line 58, where we use the library function ZeroMemory to completely clear the data in the structure? Why did this error appear now? The reason for this is that we are creating a context for the structure and the data it contains.
Consequently, we can no longer directly access or modify the data structure, as this would break encapsulation and could affect the internal data context. It is precisely these concepts—encapsulation and context—that ensure the data in the structure always remains intact and secure. This forces us to implement a number of new solutions to preserve these concepts. This is how we begin to build fully structured programming.
«But wait a second. If we can't do everything the way we used to, how are we going to keep this code operational? Encapsulation and context seem to have been created just to make our lives harder. I prefer to program the way it was done before—it's much simpler». Well, I partly agree with you and with many programmers who, initially encountering something new, think exactly the same way. I thought so myself when I started programming. And I hated the need to create structured code because I didn't see much point in it, as it often forces us to think through and detail how everything should actually work. But over time, I got used to it, especially as your codes become more and more complex. That's when you realize that structured programming makes a huge difference.
Let's now return to the code itself. Since code 03 cannot generate an executable file, we must fix it to make it work. To do this, we will modify it again as shown below:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. struct st_Data 05. { 06. //+----------------+ 07. private: 08. //+----------------+ 09. double Values[]; 10. //+----------------+ 11. public: 12. //+----------------+ 13. void Set(const double &arg[]) 14. { 15. ArrayFree(Values); 16. ArrayCopy(Values, arg); 17. } 18. //+----------------+ . . . 43. //+----------------+ 44. }; 45. //+------------------------------------------------------------------+ 46. #define PrintX(X) Print(#X, " => ", X) 47. //+------------------------------------------------------------------+ 48. void OnStart(void) 49. { 50. const double H[] = {2.05, 1.97, 1.87, 1.75, 1.99, 2.01, 1.83}; 51. const double K[] = {12, 4, 7, 23, 38}; 52. 53. st_Data Info; 54. 55. Info.Set(H); 56. PrintX(Info.Average()); 57. 58. Info.Set(K); 59. PrintX(Info.Median()); 60. } 61. //+------------------------------------------------------------------+
Code 04
In code 04, we have one possible implementation of what was previously done in code 02, where we wanted to generate the average value H and then the median of the K values. But note, we are not interested in examining the internal content of the structure, as we know which values will be used for the analysis. However, look at line 15 in code 04. Here, we are fixing the error that occurred when trying to assign new internal values to the structure. That’s why I say this is just one of the possible solutions—depending on each specific case, this cleanup could be handled differently.
For example, suppose we need the code to perform exactly what was done in code 02. In other words, we want to clear the structure’s values as was done in line 54 of code 02, and also be able to output the contents of the variables. How can we solve this problem while preserving the concepts of encapsulation and context? For this purpose, one possible proposal is given below. This is the most interesting part of the game, because every programmer can think up and develop different ways to solve the same task.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. struct st_Data 05. { 06. //+----------------+ 07. private: 08. //+----------------+ 09. double Values[]; 10. //+----------------+ 11. public: 12. //+----------------+ 13. void Set(const double &arg[]) 14. { 15. ArrayFree(Values); 16. ArrayCopy(Values, arg); 17. } 18. //+----------------+ 19. void ZeroMemory(void) 20. { 21. Print(__FUNCTION__); 22. ArrayFree(Values); 23. } 24. //+----------------+ 25. void ArrayPrint(void) 26. { 27. Print(__FUNCTION__); 28. ArrayPrint(Values, 2); 29. } 30. //+----------------+ 31. double Average(void) 32. { 33. double sum = 0; 34. 35. for (uint c = 0; c < Values.Size(); c++) 36. sum += Values[c]; 37. 38. return sum / Values.Size(); 39. } 40. //+----------------+ 41. double Median(void) 42. { 43. double Tmp[]; 44. 45. ArrayCopy(Tmp, Values); 46. ArraySort(Tmp); 47. if (!(Tmp.Size() & 1)) 48. { 49. int i = (int)MathFloor(Tmp.Size() / 2); 50. 51. return (Tmp[i] + Tmp[i - 1]) / 2.0; 52. } 53. return Tmp[Tmp.Size() / 2]; 54. } 55. //+----------------+ 56. }; 57. //+------------------------------------------------------------------+ 58. #define PrintX(X) Print(#X, " => ", X) 59. //+------------------------------------------------------------------+ 60. void OnStart(void) 61. { 62. const double H[] = {2.05, 1.97, 1.87, 1.75, 1.99, 2.01, 1.83}; 63. const double K[] = {12, 4, 7, 23, 38}; 64. 65. st_Data Info; 66. 67. Info.Set(H); 68. Info.ArrayPrint(); 69. PrintX(Info.Average()); 70. Info.ZeroMemory(); 71. 72. Info.Set(K); 73. PrintX(Info.Median()); 74. Info.ArrayPrint(); 75. } 76. //+------------------------------------------------------------------+
Code 05
If you run code 05 in MetaTrader 5, the result will be as follows:

Figure 04
Please note that in image 04 we highlight certain aspects. But of course, you might ask: why are you highlighting these things? The reason can be seen in code 05. «Look at lines 19 and 25. What madness are you committing here? Is this even allowed?»
As I recently mentioned, the most interesting part is precisely that every programmer can develop different ways to solve the same task. In structured programming, you can freely use the names of functions or library procedures, as I am doing here in code 05. However, you must be careful not to generate a recursive call unintentionally, as happens here where we are overloading the standard functions of the MQL5 library.
And we are doing this within the context present in the structure. This construct will not create any problems, especially since the code in lines 68, 70, and 74 is much simpler to understand than if a special tag were created, as we can clearly see which procedure or function should be executed. But there is one small detail: as already mentioned, caution is required when implementing it. To demonstrate how this works in practice, I added lines 21 and 27 to the code so you can follow the execution flow and understand that we are not using standard library calls from the very beginning. First, the compiler uses the procedures declared within the structure, and only then proceeds with further actions.
In the case of the ArrayPrint call, the compiler first uses what is declared within the structure. This happens because in lines 68 and 74, we inform it that the code being executed is inside the structure. Only after resolving this issue will the compiler address the second problem—the call in line 28. This call will direct the execution flow to the code present in the standard MQL5 library.
Please note that even without declaring a private section, which would make the variable declared in line 9 exclusive to the structure and hidden from the rest of the code, we could easily convert code 05 into code 02, as we would only need to make a few changes. The same can be done in reverse: convert code 02 into code 05. However, in this case, the conversion would not be very smooth, precisely because of line 55 in code 02. Yet, if the execution issue in code 02 is not a problem for you, a solution can be implemented that is 100% aligned with what code 02 does, within the framework of 100% structured programming. To achieve this, we need to do the following:
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. struct st_Data 05. { 06. //+----------------+ 07. private: 08. //+----------------+ 09. double Values[]; 10. //+----------------+ 11. public: 12. //+----------------+ 13. void Set(const double &arg[]) 14. { 15. ArrayFree(Values); 16. ArrayCopy(Values, arg); 17. } 18. //+----------------+ 19. void ArrayCopy(const double &arg[]) 20. { 21. ArrayCopy(Values, arg); 22. } 23. //+----------------+ 24. void ZeroMemory(void) 25. { 26. ZeroMemory(Values); 27. } 28. //+----------------+ 29. void ArrayPrint(void) 30. { 31. ArrayPrint(Values, 2); 32. } 33. //+----------------+ 34. double Average(void) 35. { 36. double sum = 0; 37. 38. for (uint c = 0; c < Values.Size(); c++) 39. sum += Values[c]; 40. 41. return sum / Values.Size(); 42. } 43. //+----------------+ 44. double Median(void) 45. { 46. double Tmp[]; 47. 48. ArrayCopy(Tmp, Values); 49. ArraySort(Tmp); 50. if (!(Tmp.Size() & 1)) 51. { 52. int i = (int)MathFloor(Tmp.Size() / 2); 53. 54. return (Tmp[i] + Tmp[i - 1]) / 2.0; 55. } 56. return Tmp[Tmp.Size() / 2]; 57. } 58. //+----------------+ 59. }; 60. //+------------------------------------------------------------------+ 61. #define PrintX(X) Print(#X, " => ", X) 62. //+------------------------------------------------------------------+ 63. void OnStart(void) 64. { 65. const double H[] = {2.05, 1.97, 1.87, 1.75, 1.99, 2.01, 1.83}; 66. const double K[] = {12, 4, 7, 23, 38}; 67. 68. st_Data Info; 69. 70. Info.Set(H); 71. PrintX(Info.Average()); 72. Info.ArrayPrint(); 73. 74. Info.ZeroMemory(); 75. Info.ArrayCopy(K); 76. 77. PrintX(Info.Median()); 78. Info.ArrayPrint(); 79. } 80. //+------------------------------------------------------------------+
Code 06
When executing code 06, we will obtain exactly the same result as in image 02. However, note how code 06 differs from code 02.
First of all, code 02 does not adhere to encapsulation, and for this reason, the structure’s context becomes compromised, as at some point we may face the risk of altering the internal contents of the structure without the structure being aware of it.
Secondly, since code 06 fully utilizes structured code, any issues or failures cannot be attributed to simple oversight, because during the creation of structural procedures, we are aware of what we are creating. Therefore, if a failure occurs, it is precisely due to incorrect usage or an attempt to modify the structure's context, even without understanding its original purpose.
Such things create a lot of confusion when we transition from structured programming to OOP, because there we can completely change the meaning of an object—which in theory should be a structure—into something that has nothing to do with the original object. In the future, when we discuss OOP, we will return to this question of context.
Therefore, it is very important not to skip steps and not to move forward too quickly. Trying to understand something without first grasping the simpler concepts that complicated its implementation will not help you as a programmer. The better a concept is understood, the easier it is to apply it in a wide variety of situations.
«I think I'm starting to understand the purpose of structured programming. Essentially, it helps us create safer and more efficient code. But I have a question: everything we've done so far had to be done manually. That is, we didn't get much help from the compiler on issues like those discussed when explaining overloading and the use of function and procedure templates. Can we avoid generating overload and use function and procedure templates with the compiler's help in this type of programming to create structured code? Would that still be structured programming?»
That is certainly an excellent question. However, I will leave you with the desire to learn how to do it, at least until the release of the next article.
Concluding thoughts
In this article, we explored how to create so-called structured code, where the entire context and methods for manipulating variables and information are placed within a structure to establish a suitable context for implementing any code we need. We have seen the necessity of using a private section to separate what is public from what is not, thereby adhering to the rule of encapsulation and preserving the integrity, security, and reliability of the context for which the data structure was created. We also observed that even in structured code, mistakes can be made—or rather, a cascade of bugs can be triggered—precisely by introducing functions or procedures that violate the originally intended context, thus turning simple code into something complex and difficult to maintain and use.
The question of using templates in this type of implementation was also raised. Since this is a much more complex topic, and this article already provides ample material for understanding and practice, we will begin discussing the topic of templates in structured code in the next article. But you can start paying attention to it now by using the codes provided here. This will help you put into practice the hypothesis of using concepts for things you haven't yet considered or explored.
Translated from Portuguese by MetaQuotes Ltd.
Original article: https://www.mql5.com/pt/articles/15860
Warning: All rights to these materials are reserved by MetaQuotes Ltd. Copying or reprinting of these materials in whole or in part is prohibited.
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.
ARIMA Forecasting Indicator in MQL5
Price Action Analysis Toolkit Development (Part 61): Structural Slanted Trendline Breakouts with 3-Swing Validation
Quantitative Analysis of Trends: Collecting Statistics in Python
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use