Inheritance
继承
面向对象编程的特征是通过继承鼓励代码的重用。一个新的类是从现有的类创建的,这个现有的类被称为基类。派生类可以使用基类的成员,但也可以修改和补充它们。
许多类型都是现有类型的变体。通常为每个类型开发新的代码是很繁琐的。此外,新代码会引入新的错误。派生类继承了基类的描述,因此无需重新开发和测试代码。继承关系是层次化的。
层次化是一种方法,可以复制所有元素及其多样性和复杂性。它引入了对象的分类。例如,元素的周期表中有气体。它们具有所有周期元素固有的属性。
惰性气体是下一个重要的子类。层次化的意思是,惰性气体如氩气是一种气体,而气体又是系统的一部分。这样的层次化使得容易解释惰性气体的行为。我们知道它们的原子包含质子和电子,这一点对所有其他元素都是相同的。
我们知道它们在室温下处于气态,就像所有气体一样。我们知道惰性气体子类没有任何气体与其他元素发生常规化学反应,这是所有惰性气体的属性。
考虑一个几何形状的继承示例。为了描述各种简单的形状(圆形、三角形、矩形、正方形等),最好的方法是创建一个基类(ADT),它是所有派生类的祖先。
让我们创建一个基类CShape,它只包含描述形状的最常见成员。这些成员描述了任何形状的特征属性——形状的类型和主要锚点坐标。
示例:
//--- The base class Shape
class CShape
{
protected:
int m_type; // Shape type
int m_xpos; // X - coordinate of the base point
int m_ypos; // Y - coordinate of the base point
public:
CShape(){m_type=0; m_xpos=0; m_ypos=0;} // constructor
void SetXPos(int x){m_xpos=x;} // set X
void SetYPos(int y){m_ypos=y;} // set Y
};接下来,从基类创建新的派生类,在这些类中我们将添加必要的字段,每个字段指定一个特定的类。对于圆形形状,需要添加一个包含半径值的成员。正方形形状的特点是边长值。因此,从基类CShape继承的派生类将如下声明:
//--- The derived class circle
class CCircle : public CShape // After a colon we define the base class
{ // from which inheritance is made
private:
int m_radius; // circle radius
public:
CCircle(){m_type=1;}// constructor, type 1
};正方形形状类的声明类似:
//--- the derived class Square
class CSquare : public CShape // After a colon we define the base class
{ // from which inheritance is made
private:
int m_square_side; // square side
public:
CSquare(){m_type=2;} // constructor, type 2
};需要注意的是,在创建对象时,首先调用基类构造函数,然后才调用派生类的构造函数。当对象被销毁时,首先调用派生类的destructor,然后才调用基类destructor。
因此,通过在基类中声明最通用的成员,我们可以在派生类中添加指定特定类的额外成员。继承允许创建可以多次重用的强大代码库。
从已存在的类创建派生类的语法如下:
class class_name :
(public | protected | private) opt base_class_name
{
class members declaration
};派生类的一个方面是其成员的可视性(开放性)。public、protected和private关键字用于指示基类成员对派生类可用程度。在派生类头文件中的冒号后面的public关键字表示基类CShape的protected和public成员应作为派生类CCircle的protected和public成员继承。
基类的private类成员对派生类不可用。公共继承还意味着派生类(CCircle和CSquare)是CShapes。也就是说,正方形(CSquare)是一个形状(CShape),但形状不一定必须是正方形。
派生类是基类的修改,它继承了基类的protected和public成员。基类的构造函数和destructor无法被继承。除了基类的成员外,派生类中还添加了新的成员。
派生类可以包括与基类不同的成员函数的实现。它与重载没有任何共同之处,当相同函数名称的含义可能因不同的签名而不同。
在受保护继承中,基类的public和protected成员成为派生类的protected成员。在私有继承中,基类的public和protected成员成为派生类的private成员。
在受保护和私有继承中,“派生类的对象是基类的对象”这一关系不成立。受保护和私有继承类型很少使用,每种类型都需要谨慎使用。
应该理解,继承类型(公共、受保护或私有)不会影响从派生类到基类层次结构中访问基类成员的方式。无论哪种类型的继承,只有用public和protected访问说明符声明的基类成员才能在派生类中可用。让我们在以下示例中考虑这一点:
#property copyright "Copyright 2000-2024, MetaQuotes Ltd."
#property link "https://www.mql5.com"
#property version "1.00"
//+------------------------------------------------------------------+
//| Example class with a few access types |
//+------------------------------------------------------------------+
class CBaseClass
{
private: //--- The private member is not available from derived classes
int m_member;
protected: //--- The protected method is available from the base class and its derived classes
int Member(){return(m_member);}
public: //--- Class constructor is available to all members of classes
CBaseClass(){m_member=5;return;};
private: //--- A private method for assigning a value to m_member
void Member(int value) { m_member=value;};
};
//+------------------------------------------------------------------+
//| Derived class with errors |
//+------------------------------------------------------------------+
class CDerived: public CBaseClass // specification of public inheritance can be omitted, since it is default
{
public:
void Func() // In the derived class, define a function with calls to base class members
{
//--- An attempt to modify a private member of the base class
m_member=0; // Error, the private member of the base class is not available
Member(0); // Error, the private method of the base class is not available in derived classes
//--- Reading the member of the base class
Print(m_member); // Error, the private member of the base class is not available
Print(Member()); // No error, protected method is available from the base class and its derived classes
}
};在上面的示例中,CBaseClass只有一个公共方法——构造函数。创建类对象时,构造函数会自动调用。因此,私有成员m_member和受保护的方法Member()无法从外部调用。但在公共继承的情况下,基类的Member()方法可以从派生类中访问。
在受保护继承的情况下,基类的所有具有public和protected访问的成员都变为受保护的。这意味着如果基类的公共数据成员和方法可以从外部访问,那么在受保护继承中,它们只能从派生类及其进一步派生类访问。
//+------------------------------------------------------------------+
//| Example class with a few access types |
//+------------------------------------------------------------------+
class CBaseMathClass
{
private: //--- The private member is not available from derived classes
double m_Pi;
public: //--- Getting and setting a value for m_Pi
void SetPI(double v){m_Pi=v;return;};
double GetPI(){return m_Pi;};
public: // The class constructor is available to all members
CBaseMathClass() {SetPI(3.14); PrintFormat("%s",__FUNCTION__);};
};
//+------------------------------------------------------------------+
//| A derived class, in which m_Pi cannot be modified |
//+------------------------------------------------------------------+
class CProtectedChildClass: protected CBaseMathClass // Protected inheritance
{
private:
double m_radius;
public: //--- Public methods in the derived class
void SetRadius(double r){m_radius=r; return;};
double GetCircleLength(){return GetPI()*m_radius;};
};
//+------------------------------------------------------------------+
//| Script starting function |
//+------------------------------------------------------------------+
void OnStart()
{
//--- When creating a derived class, the constructor of the base class will be called automatically
CProtectedChildClass pt;
//--- Specify radius
pt.SetRadius(10);
PrintFormat("Length=%G",pt.GetCircleLength());
//--- If we uncomment the line below, we will get an error at the stage of compilation, since SetPI() is now protected
// pt.SetPI(3);
//--- Now declare a variable of the base class and try to set the Pi constant equal to 10
CBaseMathClass bc;
bc.SetPI(10);
//--- Here is the result
PrintFormat("bc.GetPI()=%G",bc.GetPI());
}示例表明,基类CBaseMathClass中的SetPI()和GetPi()方法对程序中的任何位置都是开放的且可调用。但同时,对于从它派生的CProtectedChildClass,这些方法只能从CProtectedChildClass类或其派生类中调用。
在私有继承的情况下,基类的所有具有public和protected访问的成员都变为私有的,在进一步的继承中无法调用它们。
MQL4没有多重继承。