• No results found

Sometimes, you will need to change the way a function operates in a derived class. Or you may want to define a function in a parent class, but take care of the implementation details in the derived class. You can

accomplish this by using virtual functions.

Let's use the example of a car again: A car class would have a function to change gears. However, the process of changing gears in a car with a manual transmission is different than changing gears with an automatic transmission. Therefore, we would declare a virtual gear changing function in our parent class, and then write the actual function in our derived classes.

Here's what a car class would look like in code: class Car

{

public:

virtual int ShiftGears(int gear) { return(gear); } };

The name of our class is Car. We've declared a single function – a virtual function named ShiftGears(). We've added an empty function body to the ShiftGears() declaration, containing only a single return operator. The ShiftGears() function is declared with the virtual keyword. This means that the function will be defined in the derived classes.

class ManualCar : public Car {

public:

int ShiftGears(int gear); };

This class is named ManualCar, and is for a manual transmission vehicle. Here is where we define the ShiftGears() function. The function is declared with the same type and parameters as the function in the Car class. Notice that we do not use the virtual keyword here. The body of the function is defined

elsewhere, and contains the logic for shifting gears using a manual transmission. Other classes derived from the Car class would define ShiftGears() in a similar manner.

If a derived class has a function with the same name as a function in the parent class, the function in the derived class will override the function in the parent class. This process of redefining functions in derived classes is an OOP concept known as polymorphism.

The Init() function in our CiMA class can be declared as a virtual function in our CIndicator class. This ensures that any class derived from CIndicator must implement the Init() class:

class CIndicator {

protected: int handle; public:

virtual int Init() { return(handle); } };

The Init() function is declared as a public member of the class. The virtual keyword specifies that the implementation of the function will be carried out in any derived classes. The body of the function is declared on the same line. In this example, the function simply returns the value of the handle variable. When we create a derived class based on CIndicator, the Init() function must be implemented in the new class.

Objects

Now that we've created a class for a moving average indicator, let's create an object. You create an object the same way you create a variable, enumeration or structure: The class name is used as the type, and the object is given a unique identifier:

This creates an object named objMa, based on the class CiMA. When an object is created, the constructor for that object is executed automatically. Since the CiMA class is derived from the CIndicator class, the

constructor from the CIndicator class is made available to the CiMA class, and will execute automatically upon creation of an object based on the CiMA class. Thus, the CIndicator() constructor is called, and the main[] array is set as a series array upon creation of the object:

CIndicator::CIndicator(void) {

ArraySetAsSeries(main,true); }

Once the object is declared, we can access any public members using the dot (.) operator. The first thing we'll need to do is initialize our indicator with the Init() function:

objMa.Init(_Symbol,0,MAPeriod,0,MAMethod,MAPrice);

Remember that the Init() function was declared as a virtual function in the CIndicator class, and defined in the CiMA class. The Init() function creates an indicator handle for the moving average indicator using the specified settings. The indicator handle is stored in the protected variable handle, which is defined in the CIndicator class.

Next, we'll use the Main() function to fill the main[] array with indicator values, and retrieve the indicator value for a specific bar. Since the main[] array is a protected class member, we can only access it through a public function such as the Main() function. This line of code prints the moving average value for the current bar to the log:

Print(objMa.Main());

You can create as many objects as necessary for your program. If you need a second moving average indicator, then declare it using a different unique identifier, and access the public members of the object as shown above. By creating classes to perform common tasks, you save time and reduce errors, as well as reducing the amount of code in your program. The remainder of the book on expert advisors will focus on the creation of classes to perform common trading tasks.

Chapter 7 - The Structure of an MQL5 Program

Before we start developing MQL5 programs, let's take a minute to address the structure of an MQL5 program. All MQL5 programs share the same basic structure. At the top of the file will be the preprocessor directives. Next are the input and global variables. Finally, the functions, classes and event handlers of the program are defined.