A structure is a set of elements of any type (except for the void type). Thus, the structure combines logically related data of different types.
The structure data type is determined by the following description:
The structure name can't be used as an identifier (name of a variable or function). It should be noted that in MQL5 structure elements follow one another directly, without alignment. In C++ such an order is made to the compiler using the following instruction:
If you want to do another alignment in the structure, use auxiliary members, "fillers" to the right size.
Such a description of aligned structures is necessary only for transferring to imported dll-functions.
Attention: This example illustrates incorrectly designed data. It would be better first to declare the take and stop large data of the double type, and then declare the slippage member of the uchar type. In this case, the internal representation of data will always be the same regardless of the value specified in #pragma pack().
If a structure contains variables of the string type and/or object of a dynamic array, the compiler assigns an implicit constructor to such a structure. This constructor resets all the structure members of string type and correctly initializes objects of the dynamic array.
Structures that do not contain strings, class objects, pointers and objects of dynamic arrays are called simple structures. Variables of simple structures, as well as their arrays can be passed as parameters to functions imported from DLL.
Copying of simple structures is allowed only in two cases:
To provide an example, let's develop the CustomMqlTick custom structure with its contents identical to the built-in MqlTick one. The compiler does not allow copying the MqlTick object value to the CustomMqlTick type object. Direct typecasting to the necessary type also causes the compilation error:
Therefore, only one option is left – copying the values of the structure elements one by one. It is still allowed to copy the values of the same type of CustomMqlTick.
The ArrayPrint() function is called for a check to display the arr array value in the journal.
The second example shows the features of copying simple structures by the lineage. Suppose that we have the Animal basic structure, from which the Cat and Dog structures are derived. We can copy the Animal and Cat objects, as well as the Animal and Dog objects to each other but we cannot copy Cat and Dog to each other, although both are descendants of the Animal structure.
Complete example code:
Another way to copy simple types is using a union. The objects of the structures should be members of the same union – see the example in union.
The name of a structure becomes a new data type, so you can declare variables of this type. The structure can be declared only once within a project. The structure members are accessed using thepoint operation (.).
The special pack attribute allows setting the alignment of structure or class fields.
where n is one of the following values: 1, 2, 4, 8 or 16. It may be absent.
'pack(1)' is applied by default for structures. This means that the structure members are located one after another in memory, and the structure size is equal to the sum of its members' size.
Alignment of the structure fields may be needed when exchanging data with third-party libraries (*.DLL) where such alignment is applied.
Let's use some examples to show how alignment works. We will apply a structure consisting of four members with no alignment.
Structure fields are to be located in memory one after another according to the declaration order and type size. The structure size is 15, while an offset to the structure fields in the arrays is undefined.
Now declare the same structure with the alignment of 4 bytes and run the code.
The structure size has changed so that all members of 4 bytes and more has an offset from the beginning of the structure multiple of 4 bytes. Smaller members are to be aligned to their own size boundary (for example, 2 for 'short'). This is how it looks (the added byte is shown in gray).
In this case, 1 byte is added after the s.c member, so that the s.s (sizeof(short)==2) field has the boundary of 2 bytes (alignment for 'short' type).
The offset to the beginning of the structure in the array is also aligned to the 4-byte boundary, i.e. the addresses of the a, a and a[n] elements are to be multiple of 4 bytes for Simple_Structure arr.
Let's consider two more structures consisting of similar types with 4-bytes alignment but different member order. In the first structure, the members are located in type size ascending order.
As we can see, the structure size is 8 and consists of the two 4-byte blocks. The first block contains the fields with 'char' and 'short' types, while the second one contains the field with 'int' type.
Now let's turn the first structure into the second one, which differs only in the field order, by moving the 'short' type member to the end.
Although the structure content has not changed, altering the member sequence has increased its size.
Alignment should also be considered when inheriting. Let's demonstrate this using the simple Parent structure having a single 'char' type member. The structure size without alignment is 1.
Let's create the Children child class featuring the 'short' (sizeof(short)=2) type member.
As a result, when setting alignment to 2 bytes, the structure size is equal to 4, although the size of its members is 3. In this example, 2 bytes are to be allocated to the Parent class, so that the access to the 'short' field of the child class is aligned to 2 bytes.
The knowledge of how memory is allocated for the structure members is necessary if an MQL5 application interacts with third-party data by writing/reading on the files or streams level.
The MQL5\Include\WinAPI directory of the Standard Library contains the functions for working with the WinAPI functions. These functions apply the structures with a specified alignment for the cases when it is required for working with WinAPI.
offsetof is a special command directly related to the pack attribute. It allows us to obtain a member offset from the beginning of the structure.
The use of the 'final' specifier during structure declaration prohibits further inheritance from this structure. If a structure requires no further modifications, or modifications are not allowed for security reasons, declare this structure with the 'final' modifier. In addition, all the members of the structure will also be implicitly considered final.
If you try to inherit from a structure with the 'final' modifier as shown in the above example, the compiler will return an error:
Classes differ from structures in the following:
Classes and structures can have an explicit constructor and destructor. If your constructor is explicitly defined, the initialization of a structure or class variable using the initializing sequence is impossible.
A constructor is a special function, which is called automatically when creating an object of a structure or class and is usually used to initialize class members. Further we will talk only about classes, while the same applies to structures, unless otherwise indicated. The name of a constructor must match the class name. The constructor has no return type (you can specify the void type).
Each class can have multiple constructors, differing by the number of parameters and the initialization list. A constructor that requires specifying parameters is called a parametric constructor.
A constructor can be declared in the class description and then its body can be defined. For example, two constructors of MyDateClass can be defined the following way:
In the default constructor, all members of the class are filled using the TimeCurrent() function. In the parametric constructor only hour values are filled in. Other members of the class (m_year, m_month and m_day) will be automatically initialized with the current date.
The default constructor has a special purpose when initializing an array of objects of its class. The constructor, all parameters of which have default values, is not a default constructor. Here is an example:
If you uncomment these strings
then the compiler will return an error for them "default constructor is not defined".
If a class has a user-defined constructor, the default constructor is not generated by the compiler. This means that if a parametric constructor is declared in a class, but a default constructor is not declared, you can not declare the arrays of objects of this class. The compiler will return an error for this script:
In this example, the CFoo class has a declared parametric constructor - in such cases, the compiler does not create a default constructor automatically during compilation. At the same time when you declare an array of objects, it is assumed that all objects should be created and initialized automatically. During auto-initialization of an object, it is necessary to call a default constructor, but since the default constructor is not explicitly declared and not automatically generated by the compiler, it is impossible to create such an object. For this reason, the compiler generates an error at the compilation stage.
There is a special syntax to initialize an object using a constructor. Constructor initializers (special constructions for initialization) for the members of a struct or class can be specified in the initialization list.
An initialization list is a list of initializers separated by commas, which comes after the colon after the list of parameters of a constructor and precedes the body (goes before an opening brace). There are several requirements:
Here is an example of several constructors for initializing class members.
In this case, the CPerson class has three constructors:
Note that the initialization using a list has replaced an assignment. Individual members must be initialized as:
In the initialization list, members can go in any order, but all members of the class will be initialized according to the order of their announcement. This means that in the third constructor, first the m_first_name member will be initialized, as it is announced first, and only after it m_second_name is initialized. This should be taken into account in cases where the initialization of some members of the class depends on the values in other class members.
If a default constructor is not declared in the base class, and at the same time one or more constructors with parameters are declared, you should always call one of the base class constructors in the initialization list. It goes through the comma as ordinary members of the list and will be called first during object initialization, no matter where in the initialization list it is located.
In this example, when creating the bar object, a default constructor CBar() will be called, in which first a constructor for the parent CFoo is called, and then comes a constructor for the m_member class member.
A destructor is a special function that is called automatically when a class object is destroyed. The name of the destructor is written as a class name with a tilde (~). Strings, dynamic arrays and objects, requiring deinitialization, will be de-initialized anyway, regardless of the destructor presence or absence. If there is a destructor, these actions will be performed after calling the destructor.
Destructors are always virtual, regardless of whether they are declared with the virtual keyword or not.
Class function-methods can be defined both inside the class and outside the class declaration. If the method is defined within a class, then its body comes right after the method declaration.
Functions from SetRightBorder(int border) to Draw() are declared and defined directly inside the CTetrisShape class.
The CTetrisShape() constructor and methods CheckDown(int& pad_array), CheckLeft(int& side_row) and CheckRight(int& side_row) are only declared inside the class, but not defined yet. Definitions of these functions will be further in the code. In order to define the method outside the class, the scope resolution operator is used, the class name is used as the scope.
When developing a new class, it is recommended to restrict access to the members from the outside. For this purpose keywords private or protected are used. In this case, hidden data can be accessed only from function-methods of the same class. If the protected keyword is used, hidden data can be accessed also from methods of classes - inheritors of this class. The same method can be used to restrict the access to functions-methods of a class.
Any class members and methods declared after the specifier public: (and before the next access specifier) are available in any reference to the class object by the program. In this example these are the following members: functions CTetrisField(), Init(), Deinit(), Down(), Left(), Right(), Rotate() and Drop().
Any members that are declared after the access specifier to the elements private: (and before the next access specifier) are available only to members-functions of this class. Specifiers of access to elements always end with a colon (:) and can appear in the class definition many times.
Any class members declared after the protected: access specifier (and up to the next access specifier) are available only to members-functions of this class and members-functions of the class descendants. When attempting to refer to the members featuring the private and protected specifiers from the outside, we get the compilation stage error. Example:
When compiling the code, the error message is received — an attempt to call the remote copy operator:
The second string below provides a more detailed description — the copy operator in class B was explicitly deleted, since the unavailable copy operator of class A is called:
Access to the members of the basis class can be redefined during inheritance in derived classes.
The delete specifier marks the class members-functions that cannot be used. This means if the program refers to such a function explicitly or implicitly, the error is received at the compilation stage already. For example, this specifier allows you to make parent methods unavailable in a child class. The same result can be achieved if we declare the function in the private area of the parent class (declarations in the private section). Here, using delete makes the code more readable and manageable at the level of descendants.
The compiler message:
The 'delete' specifier allows disabling auto casting or the copy constructor, which otherwise would have to be hidden in the private section as well. Example:
During the compilation, we get the error messages:
The use of the 'final' specifier during class declaration prohibits further inheritance from this class. If the class interface requires no further modifications, or modifications are not allowed for security reasons, declare this class with the 'final' modifier. In addition, all the members of the class will also be implicitly considered final.
If you try to inherit form a class with the 'final' specifier as shown in the above example, the compiler will return an error:
Union is a special data type consisting of several variables sharing the same memory area. Therefore, the union provides the ability to interpret the same bit sequence in two (or more) different ways. Union declaration is similar to structure declaration and starts with the union keyword.
Unlike the structure, various union members belong to the same memory area. In this example, the union of LongDouble is declared with long and double type values sharing the same memory area. Please note that it is impossible to make the union store a long integer value and a double real value simultaneously (unlike a structure), since long_value and double_value variables overlap (in memory). On the other hand, an MQL5 program is able to process data containing in the union as an integer (long) or real (double) value at any time. Therefore, the union allows receiving two (or more) options for representing the same data sequence.
During the union declaration, the compiler automatically allocates the memory area sufficient to store the largest type (by volume) in the variable union. The same syntax is used for accessing the union element as for the structures – point operator.
Since the unions allow the program to interpret the same memory data in different ways, they are often used when an unusual type conversion is required.
The unions cannot be involved in the inheritance, and they also cannot have static members due to their very nature. In all other aspects, the union behaves like a structure with all its members having a zero offset. The following types cannot be the union members:
Similar to classes, the union is capable of having constructors and destructors, as well as methods. By default, the union members are of public access type. In order to create private elements, use the private keyword. All these possibilities are displayed in the example illustrating how to convert a color of the color type to ARGB as does the ColorToARGB() function.
An interface allows determining specific functionality, which a class can then implement. In fact, an interface is a class that cannot contain any members, and may not have a constructor and/or a destructor. All methods declared in an interface are purely virtual, even without an explicit definition.
An interface is defined using the "interface" keyword. Example:
Like with abstract classes, an interface object cannot be created without inheritance. An interface can only be inherited from other interfaces and can be a parent for a class. An interface is always publicly visible.
An interface cannot be declared within a class or structure declaration, but a pointer to the interface can be saved in a variable of type void *. Generally speaking, a pointer to an object of any class can be saved into a variable of type void *. In order to convert a void * pointer to a pointer to an object of a particular class, use the dynamic_cast operator. If conversion is not possible, the result of the dynamic_cast operation will be NULL.