继承算法
OOP属性特点是通过继承算法鼓励代码重复使用。新类从现存的,基本类中生成。衍生类使用基本类成员,但是也做以更改和补充。
许多类型是现存类型的变异。为每个类开发新代码非常乏味。此外,新代码意味着新的错误。衍生类继承了基本类的描述,因此不必重复开发和重复测试代码。继承关系是层级体系。
层级是一个允许复制所有多样性和复杂性元素的类函数。它介绍了对象等级。例如,元素周期表有气体。它们对所有的周期元素控制固有的属性。
惰性气体是下一重要子集的 构成。层级就是惰性气体,例如氩是气体,是系统的一部分。这样的层级可以很轻松的解释惰性气体。我们知道其原子包括质子和电子,对于所有其他元素也是如此。
我们知道如其他气体一样它们在常温也是气体状态。我们知道惰性气体子集中,无气体能与其他化学元素发生化学反应,它是所有惰性气体的特性。
考虑到几何图形的继承例子。描述各种简单图形(圆形,三角形,矩形,正方形等等),最好的方法就是创建基本类(ADT),它是所有衍生类的先祖。
让我们创建一个基本类 CShape,它包括描述图形最常用的构件。这些构件描述任何图形所特有的属性-图像类型和定位点主坐标。
示例:
//--- 基本类的形状 class CShape{} { protected: int m_type; // 形状类型 int m_xpos; // 基本点的X - 坐标 int m_ypos; // 基本点的Y - 坐标 public: CShape(){m_type=0; m_xpos=0; m_ypos=0;} // constructor void SetXPos(int x){m_xpos=x;} // 设置 X void SetYPos(int y){m_ypos=y;} // 设置 Y }; |
下一步,创建基本类衍生出的新类,这里我们可以添加说明类的必要的字段。对于圆形添加包括半径值构件是必须的。正方形以边值为特点。因此,由继承基本类CShape而衍生的类如下声明:
//--- 派生类 圆形 class CCircle : public CShape // 冒号后定义基本类 { // 从继承算法开始 private: int m_radius; // 圆弧半径 public: CCircle(){m_type=1;}// 构造函数, 类型 1 }; |
正方形类声明类似:
//--- 派生类 方形 class CSquare : public CShape // 冒号后定义基本类 { // 从继承算法开始 private: int m_square_side; // 方形的边 public: CSquare(){m_type=2;} // 构造函数,类型2 }; |
注意对象创建时首先调用基本类构造函数,然后调用衍生类的构造函数。当对象毁坏时首先调用衍生类的析构函数,然后调用基本类析构函数。
因此,通过声明基本类中常用构件,我们可以在衍生类中添加额外构件,指定特殊类。继承算法允许创建多次重复使用的强大的代码函数库。
从现存类创建衍生类的句法如下:
class class_name : (public | protected | private) opt base_class_name { class members declaration }; |
衍生类一方面就是其构件的可见性(公开),继承人(继承)。关键字public, protected 和private用于指定范围,该范围中基本类构件也对衍生类有效。衍生类表头中冒号后的Public关键字表明基本类CShape的protected和public构件应该继承为衍生类CCircle的protected和public构件。
基本类的private类构件对衍生类无效。public继承也意味着衍生类 (CCircle and CSquare) 就是 CShapes。也就是,正方形(CSquare)是一个图形(CShape),但是图形却不一定就是正方形。
衍生类是基本类的变体,它继承了基本类的protected 和 public构件。基本类的构造函数和析构函数不能继承。除了基本类的构件,新构件也会添加进衍生类中。
不同于基本类,衍生类包括执行构件函数。与重载无共同点,同名函数的意思会因签名不同而不同。
在protected继承中,基本类的public和protected构件成为衍生类的protected构件。在private 继承中,基本类的public 和protected构件成为衍生类的private构件。
在protected 和 private 继承中, “衍生类对象就是基本类对象”的关系不是真的。 protected 和 private 继承类型也很少见,每一个都需要小心使用。
应该了解继承类型(public,protected或private)不会影响从衍生类访问继承层次结构的基类成员的方法。任何继承类型中,只有public 和 protected访问说明符声明的基类成员可用衍生类。让我们考虑一下下面的示例:
#property copyright "Copyright 2000-2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| 一些访问类型的示例类 | //+------------------------------------------------------------------+ class CBaseClass { private: //--- 从衍生类,private成员不可用 int m_member; protected: //--- 从基类及其衍生类,protected方法不可用 int Member(){return(m_member);} public: //--- 类构造函数可用于类的所有成员 CBaseClass(){m_member=5;return;}; private: //--- 将值分配给m_member的private方法 void Member(int value) { m_member=value;}; }; //+------------------------------------------------------------------+ //| 有错误的衍生类 | //+------------------------------------------------------------------+ class CDerived: public CBaseClass // 由于默认的原因,public继承规范可以忽略。 { public: void Func() // 在衍生类中,定义调用到基类成员的函数。 { //--- 试图修改基类的private成员 m_member=0; // 错误,基类private成员不可用 Member(0); // 错误,基类的private方法不可用在衍生类 //--- 阅读基类的成员 Print(m_member); // 错误,基类的private成员不可用 Print(Member()); // 没有错误,protected方法可从基类及其衍生类使用 } }; |
在上面的示例中,CBaseClass只有一个public方法――构造函数。当创建类对象时构造函数会自动调用。因此,private成员m_member和protected方法Member()不能从外部调用。但是如果是public继承类型,基类的Member()方法将可以从衍生类使用。
如果是protected继承类型的情况下,所有public和protected访问权限的基类成员都会成为protected。这就意味着如果public基类的数据成员和方法从外部访问,那么protected继承类型的情况下,他们只能从衍生类及其衍生品的类中使用。
//+------------------------------------------------------------------+ //| 一些访问类型的示例类 | //+------------------------------------------------------------------+ class CBaseMathClass { private: //--- private成员不可从衍生类使用 double m_Pi; public: //--- 获取和设置m_Pi值 void SetPI(double v){m_Pi=v;return;}; double GetPI(){return m_Pi;}; public: // 类构造函数可用于所有成员 CBaseMathClass() {SetPI(3.14); PrintFormat("%s",__FUNCTION__);}; }; //+------------------------------------------------------------------+ //| 一个衍生类,在此m_Pi不能修改 | //+------------------------------------------------------------------+ class CProtectedChildClass: protected CBaseMathClass // Protected继承类型 { private: double m_radius; public: //--- 衍生类中的Public方法 void SetRadius(double r){m_radius=r; return;}; double GetCircleLength(){return GetPI()*m_radius;}; }; //+------------------------------------------------------------------+ //| 脚本启动函数 | //+------------------------------------------------------------------+ void OnStart() { //--- 创建衍生类时,基类的构造函数将自动调用 CProtectedChildClass pt; //--- 指定半径 pt.SetRadius(10); PrintFormat("Length=%G",pt.GetCircleLength()); //--- 如果评论下面的字符串,我们在编译阶段将得到一个错误,因为SetPi()现在是protected的类型 // pt.SetPI(3); //--- 现在声明基类变量,尝试将Pi常量设置等于10 CBaseMathClass bc; bc.SetPI(10); //--- 下面是结果 PrintFormat("bc.GetPI()=%G",bc.GetPI()); } |
实例表明,基类CBaseMathClass的方法SetPI()和GetPi()是开放状态并可以从程序的任何地方进行调用。但与此同时,对于从其衍生的CProtectedChildClass来说,这些方法只能从CProtectedChildClass类或其衍生类的方法进行调用。
如果是private继承类型,所有public和protected访问权限的基类成员都会成为private,并且在进一步继承中无法进行调用。
MQL5没有多继承算法。
方法隐藏
如果派生类定义了一个与基类中同名的方法,则父方法将被隐藏(在 MQL 编译器的早期版本中,子方法被基类中同名方法重载,所有这些方法在父方法中都保持可用)。
要调用隐藏基类方法,必须在调用时明确指定其作用域:
class Base { public: void Print(int x) { ::Print("Base int: ", x); } void Print(double y) { ::Print("Base double: ", y); } }; class Derived : public Base { public: void Print(string s) { ::Print("Derived string: ", s); } }; void OnStart() { Derived d; d.Print("text"); // 调用 Derived::Print(string) d.Print(10); // Base::Print 隐藏, 无法被调用 d.Base::Print(10); // 显式调用父类的隐藏方法 } |
使用 “using” 恢复重载
using 运算符将父级的隐藏重载返回到子级作用域:
class Base { protected: void Print(int x) { ::Print("Base int: ", x); } void Print(double y){ ::Print("Base double: ", y); } }; class Derived : public Base { public: void Print(string s){ ::Print("Derived string: ", s); } using Base::Print; // 返回基类中的所有 Print 重载 }; void OnStart() { Derived d; d.Print("text"); // Derived::Print(string) d.Print(42); // Base::Print(int) d.Print(3.14); // Base::Print(double) } |
如果删除了 “using Base::Print;”,d.Print(42) 和 d.Print(3.14) 调用将不可用;只会保留 Derived::Print(string)
此外,在这个例子中,你可以看到基类中的受保护方法在派生类中变得可访问(protected 被替换为 public)。
可访问性和作用域
using 运算符不仅可以将基类方法返回到派生类的作用域,还可以更改它们的访问级别。带有 protected 修饰符的基类方法只能从派生类方法中访问。然而,using 运算符也允许我们让外部代码访问它们 ―― 如果 using 声明位于派生类的 public 部分之内的话。
class Base { protected: void Hidden() { ::Print("Base::Hidden"); } }; class Derived : public Base { public: using Base::Hidden; // 方法变成了 'public' }; void OnStart() { Derived d; d.Hidden(); // 现在可用了 } |
另见
架构和类