跳至内容

Structures, Classes and Interfaces

结构、类和接口

结构

结构是一组任意类型的元素(void类型除外)。因此,结构结合了不同类型的逻辑相关数据。

结构声明

结构数据类型由以下描述确定:

struct structure_name
  {
   elements_description
  };

结构名称不能用作标识符(变量或函数的名称)。需要注意的是,在MQL4中,结构元素直接相邻排列,无需对齐。在C++中,使用以下指令向编译器指示这种顺序:

#pragma pack(1)

如果希望在结构中进行其他对齐,可以使用辅助成员、“填充器”来达到正确的大小。

示例:

struct trade_settings
  {
   uchar  slippage;     // value of the permissible slippage-size 1 byte
   char   reserved1;    // skip 1 byte
   short  reserved2;    // skip 2 bytes
   int    reserved4;    // another 4 bytes are skipped. ensure alignment of the boundary 8 bytes
   double take;         // values of the price of profit fixing
   double stop;         // price value of the protective stop
  };

这种对齐结构的描述仅适用于导入的dll函数传输。

注意:此示例说明了设计不当的数据。最好先声明double类型的较大数据,然后声明uchar类型的slippage成员。在这种情况下,无论#pragma pack()中指定的值如何,数据的内部表示始终相同。

如果结构包含string类型和/或[dynamic-array-object/)类型的对象,编译器会为这种结构分配一个隐式构造函数。该构造函数会重置所有string类型的结构成员并正确初始化动态数组的对象。

简单结构

不包含字符串或动态数组对象的结构称为简单结构;此类结构的变量可以自由复制到彼此,即使它们是不同的结构。简单结构的变量及其数组可以作为参数从DLL导入函数。

访问结构成员

结构名称成为新的数据类型,因此可以声明此类型的变量。一个项目内只能声明一次结构。使用点操作(.)访问结构成员。

示例:

struct trade_settings
  {
   double take;         // values of the profit fixing price
   double stop;         // value of the protective stop price
   uchar  slippage;     // value of the acceptable slippage
  };
//--- create up and initialize a variable of the trade_settings type
trade_settings my_set={0.0,0.0,5};
if (input_TP>0) my_set.take=input_TP;

类与结构的区别如下:

  • 在声明中使用class关键字;
  • 默认情况下,所有类成员都具有private访问说明符,除非另有说明。结构的data成员默认为public访问类型,除非另有说明;
  • 即使类中没有声明虚拟函数,类对象也始终具有虚拟函数表。结构不能拥有虚拟函数;
  • new运算符可以应用于类对象;该运算符不能应用于结构;
  • 类只能从类继承,而结构只能从结构继承。

类和结构可以拥有显式的构造函数和析构函数。如果构造函数被明确定义,则无法使用初始化序列来初始化结构或类变量。

示例:

struct trade_settings
  {
   double take;         // values of the profit fixing price
   double stop;         // value of the protective stop price
   uchar  slippage;     // value of the acceptable slippage
   //--- Constructor
          trade_settings() { take=0.0; stop=0.0; slippage=5; }
   //--- Destructor
         ~trade_settings() { Print("This is the end"); }
  };
//--- Compiler will generate an error message that initialization is impossible
trade_settings my_set={0.0,0.0,5};

构造函数和析构函数

构造函数是一种特殊函数,在创建结构或类的对象时自动调用,通常用于初始化类成员。接下来我们只讨论类,除非另有说明,结构也是如此。构造函数的名称必须与类名匹配。构造函数没有返回类型(可以指定void类型)。

定义的类成员——[字符串](../string-type/)、动态数组和需要初始化的对象——无论如何都会被初始化,无论是否有构造函数。

每个类可以有多个构造函数,它们根据参数数量和初始化列表的不同而不同。需要指定参数的构造函数称为参数化构造函数。

没有参数的构造函数称为默认构造函数。如果类中未声明构造函数,编译器在编译时会创建默认构造函数。

//+------------------------------------------------------------------+
//| Class for working with a date                                    |
//+------------------------------------------------------------------+
class MyDateClass
  {
private:
   int               m_year;          // Year
   int               m_month;         // Month
   int               m_day;           // Day of the month
   int               m_hour;          // Hour in a day
   int               m_minute;        // Minutes
   int               m_second;        // Seconds
public:
   //--- Default constructor
                     MyDateClass(void);
   //--- Parametric constructor
                     MyDateClass(int h,int m,int s);
  };

可以在类描述中声明构造函数,然后定义其主体。例如,MyDateClass的两个构造函数可以如下定义:

//+------------------------------------------------------------------+
//| Default constructor                                              |
//+------------------------------------------------------------------+
MyDateClass::MyDateClass(void)
  {
//---
   MqlDateTime mdt;
   datetime t=TimeCurrent(mdt);
   m_year=mdt.year;
   m_month=mdt.mon;
   m_day=mdt.day;
   m_hour=mdt.hour;
   m_minute=mdt.min;
   m_second=mdt.sec;
   Print(__FUNCTION__);
  }
//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
MyDateClass::MyDateClass(int h,int m,int s)
  {
   MqlDateTime mdt;
   datetime t=TimeCurrent(mdt);
   m_year=mdt.year;
   m_month=mdt.mon;
   m_day=mdt.day;
   m_hour=h;
   m_minute=m;
   m_second=s;
   Print(__FUNCTION__);
  }

默认构造函数中,类的所有成员都使用TimeCurrent()函数进行初始化。在参数化构造函数中,只填充小时值。类的其他成员(m_year、m_month和m_day)将自动用当前日期初始化。

默认构造函数在初始化其类对象数组时具有特殊用途。所有参数都有默认值的构造函数不是默认构造函数。以下是一个示例:

//+------------------------------------------------------------------+
//| Class with a default constructor                                 |
//+------------------------------------------------------------------+
class CFoo
  {
   datetime          m_call_time;     // Time of the last object call
public:
   //--- Constructor with a parameter that has a default value is not a default constructor
                     CFoo(const datetime t=0){m_call_time=t;};
   //--- Copy constructor
                     CFoo(const CFoo &foo){m_call_time=foo.m_call_time;};

   string ToString(){return(TimeToString(m_call_time,TIME_DATE|TIME_SECONDS));};
  };
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
// CFoo foo; // This variant cannot be used - a default constructor is not set
//--- Possible options to create the CFoo object
   CFoo foo1(TimeCurrent());     // An explicit call of a parametric constructor
   CFoo foo2();                  // An explicit call of a parametric constructor with a default parameter
   CFoo foo3=D'2009.09.09';      // An implicit call of a parametric constructor
   CFoo foo40(foo1);             // An explicit call of a copy constructor
   CFoo foo41=foo1;              // An implicit call of a copy constructor
   CFoo foo5;                    // An explicit call of a default constructor (if there is no default constructor,
                                 // then a parametric constructor with a default value is called)
//--- Possible options to receive CFoo pointers
   CFoo *pfoo6=new CFoo();       // Dynamic creation of an object and receiving of a pointer to it
   CFoo *pfoo7=new CFoo(TimeCurrent());// Another option of dynamic object creation
   CFoo *pfoo8=GetPointer(foo1); // Now pfoo8 points to object foo1
   CFoo *pfoo9=pfoo7;            // pfoo9 and pfoo7 point to one and the same object
   // CFoo foo_array[3];         // This option cannot be used - a default constructor is not specified
//--- Show the value of m_call_time
   Print("foo1.m_call_time=",foo1.ToString());
   Print("foo2.m_call_time=",foo2.ToString());
   Print("foo3.m_call_time=",foo3.ToString());
   Print("foo4.m_call_time=",foo4.ToString());
   Print("foo5.m_call_time=",foo5.ToString());
   Print("pfoo6.m_call_time=",pfoo6.ToString());
   Print("pfoo7.m_call_time=",pfoo7.ToString());
   Print("pfoo8.m_call_time=",pfoo8.ToString());
   Print("pfoo9.m_call_time=",pfoo9.ToString());
//--- Delete dynamically created arrays
   delete pfoo6;
   delete pfoo7;
   //delete pfoo8;  // You do not need to delete pfoo8 explicitly, since it points to the automatically created object foo1
   //delete pfoo9;  // You do not need to delete pfoo9 explicitly. since it points to the same object as pfoo7
  }

如果你取消注释这些字符串

//CFoo foo_array[3];     // This variant cannot be used - a default constructor is not set

//CFoo foo_dyn_array[];  // This variant cannot be used - a default constructor is not set

那么编译器会为它们返回错误“未定义默认构造函数”。

如果类有用户定义的构造函数,编译器不会生成默认构造函数。这意味着如果在类中声明了参数化构造函数,但未声明默认构造函数,则无法声明该类的对象数组。编译器会为此脚本返回错误:

//+------------------------------------------------------------------+
//| Class without a default constructor                              |
//+------------------------------------------------------------------+
class CFoo
  {
   string            m_name;
public:
                     CFoo(string name) { m_name=name;}
  };
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Get the "default constructor is not defined" error during compilation
   CFoo badFoo[5];
  }

在这个例子中,CFoo类声明了参数化构造函数——在这种情况下,编译器在编译时不会自动创建默认构造函数。同时,当声明对象数组时,假设所有对象都应自动创建和初始化。在对象自动初始化期间,必须调用默认构造函数,但由于默认构造函数未明确声明且不是由编译器自动生成的,因此无法创建此类对象。因此,编译器在编译阶段会生成错误。

有一种特殊的语法可以使用构造函数来初始化对象。结构或类的成员构造函数初始化器可以在初始化列表中指定。

初始化列表是由逗号分隔的初始化器列表,位于构造函数的参数列表之后的冒号之后和主体之前(在开括号之前)。有一些要求:

  • 初始化列表只能用于构造函数
  • 父成员不能在初始化列表中初始化;
  • 初始化列表后面必须跟随函数的定义(实现)。

以下是几个用于初始化类成员的构造函数的示例。

//+------------------------------------------------------------------+
//| Class for storing the name of a character                        |
//+------------------------------------------------------------------+
class CPerson
  {
   string            m_first_name;     // First name
   string            m_second_name;    // Second name
public:
   //--- An empty default constructor
                     CPerson() {Print(__FUNCTION__);};
   //--- A parametric constructor
                     CPerson(string full_name);
   //--- A constructor with an initialization list
                     CPerson(string surname,string name): m_second_name(surname), m_first_name(name) {};
   void PrintName(){PrintFormat("Name=%s Surname=%s",m_first_name,m_second_name);};
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CPerson::CPerson(string full_name)
  {
   int pos=StringFind(full_name," ");
   if(pos>=0)
     {
      m_first_name=StringSubstr(full_name,0,pos);
      m_second_name=StringSubstr(full_name,pos+1);
     }
  }
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Get an error "default constructor is not defined"
   CPerson people[5];
   CPerson Tom="Tom Sawyer";                       // Tom Sawyer
   CPerson Huck("Huckleberry","Finn");             // Huckleberry Finn
   CPerson *Pooh = new CPerson("Winnie","Pooh");  // Winnie the Pooh
   //--- Output values
   Tom.PrintName();
   Huck.PrintName();
   Pooh.PrintName();

   //--- Delete a dynamically created object
   delete Pooh;
  }

在这种情况下,CPerson类有三个构造函数:

  1. 一个显式的默认构造函数,允许创建该类的对象数组;
  2. 一个带有一个参数的构造函数,它接收全名作为参数,并根据找到的空间将名称和第二名称分开;
  3. 一个带有两个参数的构造函数,包含初始化列表。初始化器为m_second_name(姓氏)和m_first_name(名字)。

请注意,使用列表进行初始化取代了赋值。单个成员必须按以下方式初始化:

class_member (a list of expressions)

在初始化列表中,成员可以以任何顺序出现,但类的所有成员将根据它们的声明顺序进行初始化。这意味着在第三个构造函数中,首先会初始化m_first_name成员,因为它首先被声明,然后才初始化m_second_name。当某些类成员的初始化依赖于其他类成员的值时,这一点需要考虑到。

如果基类未声明默认构造函数,同时声明了一个或多个带参数的构造函数,则应在初始化列表中调用基类的某个构造函数。它作为列表中的普通成员按逗号顺序出现,并在对象初始化时首先被调用,无论它在初始化列表中的位置如何。

//+------------------------------------------------------------------+
//| Base class                                                       |
//+------------------------------------------------------------------+
class CFoo
  {
   string            m_name;
public:
   //--- A constructor with an initialization list
                     CFoo(string name) : m_name(name) { Print(m_name);}
  };
//+------------------------------------------------------------------+
//| Class derived from CFoo                                          |
//+------------------------------------------------------------------+
class CBar : CFoo
  {
   CFoo              m_member;      // A class member is an object of the parent
public:
   //--- A default constructor in the initialization list calls the constructor of a parent
                     CBar(): m_member(_Symbol), CFoo("CBAR") {Print(__FUNCTION__);}
  };
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   CBar bar;
  }

在这个示例中,创建bar对象时会调用默认构造函数CBar(),其中首先调用了CFoo的构造函数,然后调用了m_member类成员的构造函数。

析构函数是特殊函数,当类对象被销毁时自动调用。析构函数的名称以类名加上波浪号(~)的形式书写。字符串、动态数组和需要解绑定的对象将无论如何都会被解绑,无论是否有析构函数。如果有析构函数,这些操作将在调用析构函数后执行。

析构函数总是虚拟的,无论是否用virtual关键字声明。

定义类方法

类函数方法可以在类内部或外部定义。如果方法在类内部定义,则其主体紧跟在方法声明之后。

示例:

class CTetrisShape
  {
protected:
   int               m_type;
   int               m_xpos;
   int               m_ypos;
   int               m_xsize;
   int               m_ysize;
   int               m_prev_turn;
   int               m_turn;
   int               m_right_border;
public:
   void              CTetrisShape();
   void              SetRightBorder(int border) { m_right_border=border; }
   void              SetYPos(int ypos)          { m_ypos=ypos;           }
   void              SetXPos(int xpos)          { m_xpos=xpos;           }
   int               GetYPos()                  { return(m_ypos);        }
   int               GetXPos()                  { return(m_xpos);        }
   int               GetYSize()                 { return(m_ysize);       }
   int               GetXSize()                 { return(m_xsize);       }
   int               GetType()                  { return(m_type);        }
   void              Left()                     { m_xpos-=SHAPE_SIZE;    }
   void              Right()                    { m_xpos+=SHAPE_SIZE;    }
   void              Rotate()                   { m_prev_turn=m_turn; if(++m_turn>3) m_turn=0; }
   virtual void      Draw()                     { return;                }
   virtual bool      CheckDown(int& pad_array[]);
   virtual bool      CheckLeft(int& side_row[]);
   virtual bool      CheckRight(int& side_row[]);
  };

SetRightBorder(int border)到Draw()的函数直接在CTetrisShape类中声明和定义。

CTetrisShape()构造函数和方法CheckDown(int& pad_array[])、CheckLeft(int& side_row[])以及CheckRight(int& side_row[])仅在类内部声明,但尚未定义。这些函数的定义将在代码后面部分介绍。为了在类外部定义方法,使用作用域解析运算符,类名作为作用域。

示例:

//+------------------------------------------------------------------+
//| Constructor of the basic class                                   |
//+------------------------------------------------------------------+
void CTetrisShape::CTetrisShape()
  {
   m_type=0;
   m_ypos=0;
   m_xpos=0;
   m_xsize=SHAPE_SIZE;
   m_ysize=SHAPE_SIZE;
   m_prev_turn=0;
   m_turn=0;
   m_right_border=0;
  }
//+------------------------------------------------------------------+
//| Checking ability to move down (for the stick and cube)           |
//+------------------------------------------------------------------+
bool CTetrisShape::CheckDown(int& pad_array[])
  {
   int i,xsize=m_xsize/SHAPE_SIZE;
//---
   for(i=0; i<xsize; i++)
     {
      if(m_ypos+m_ysize>=pad_array[i]) return(false);
     }
//---
   return(true);
  }

公共、受保护和私有访问修饰符

开发新类时,建议限制外部对成员的直接访问。为此,使用private或protected关键字。在这种情况下,隐藏的数据只能从同一类的函数方法中访问。如果使用protected关键字,则可以从该类的子类的方法中访问隐藏的数据。同样的方法可以用来限制对类函数的访问。

如果需要完全开放对类成员和/或方法的访问,使用public关键字。

示例:

class CTetrisField
  {
private:
   int               m_score;                            // Score
   int               m_ypos;                             // Current position of the figures
   int               m_field[FIELD_HEIGHT][FIELD_WIDTH]; // Matrix of the well
   int               m_rows[FIELD_HEIGHT];               // Numbering of the well rows
   int               m_last_row;                         // Last free row
   CTetrisShape     *m_shape;                            // Tetris figure
   bool              m_bover;                            // Game over
public:
   void              CTetrisField() { m_shape=NULL; m_bover=false; }
   void              Init();
   void              Deinit();
   void              Down();
   void              Left();
   void              Right();
   void              Rotate();
   void              Drop();
private:
   void              NewShape();
   void              CheckAndDeleteRows();
   void              LabelOver();
  };

任何在public:修饰符之后声明(并且在下一个访问修饰符之前)的类成员和方法,都可以被程序引用到类对象中。在这个示例中,这些成员包括CTetrisField()、Init()、Deinit()、Down()、Left()、Right()、Rotate()和Drop()函数。

任何在private:修饰符之后声明(并且在下一个访问修饰符之前)的成员,只能被该类的成员函数访问。对元素的访问修饰符总是以冒号(:)结尾,并且可以在类定义中多次出现。

继承过程中可以重新定义对基类成员的访问。

‘final’修饰符

在类声明中使用’final’修饰符会禁止从此类进行进一步继承。如果类接口不需要进一步修改,或者出于安全原因不允许修改,则用’final’修饰符声明该类。此外,类的所有成员也将被隐式视为final。

class CFoo final
  {
  //--- Class body
  };

class CBar : public CFoo
  {
  //--- Class body
  };

如果尝试从具有’final’修饰符的类继承,如上面的示例所示,编译器将返回错误:

cannot inherit from 'CFoo' as it has been declared as 'final'
see declaration of 'CFoo'

联合(union)

联合是一种特殊的数据类型,由共享同一内存区域的多个变量组成。因此,联合提供了以两种或更多不同方式解释相同位序列的能力。联合声明类似于结构声明,并以union关键字开始。

union LongDouble
{
  long   long_value;
  double double_value;
};

与结构不同,联合的各个成员属于同一内存区域。在这个示例中,LongDouble联合声明了共享同一内存区域的long和double类型值。请注意,无法同时存储long整数值和double实数值(与结构不同),因为long_value和double_value变量在内存中重叠。另一方面,MQL5程序可以随时处理包含整数(long)或实数(double)值的联合数据。因此,联合允许接收两种或更多种表示相同数据序列的方式。

在联合声明期间,编译器会自动分配足够的内存区域来存储最大类型(按体积计算)。访问联合元素的语法与结构相同——点运算符

union LongDouble
{
  long   long_value;
  double double_value;
};
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   LongDouble lb;
//--- get and display the invalid -nan(ind) number
   lb.double_value=MathArcsin(2.0);
   printf("1.  double=%f                integer=%I64X",lb.double_value,lb.long_value);
//--- largest normalized value (DBL_MAX)
   lb.long_value=0x7FEFFFFFFFFFFFFF;
   printf("2.  double=%.16e  integer=%I64X",lb.double_value,lb.long_value);
//--- smallest positive normalized (DBL_MIN)
   lb.long_value=0x0010000000000000;
   printf("3.  double=%.16e  integer=%.16I64X",lb.double_value,lb.long_value);
  }
/*  Execution result
    1.  double=-nan(ind)                integer=FFF8000000000000
    2.  double=1.7976931348623157e+308  integer=7FEFFFFFFFFFFFFF
    3.  double=2.2250738585072014e-308  integer=0010000000000000
*/

由于联合允许程序以不同方式解释相同的内存数据,因此当需要不寻常的类型转换时经常使用它们。

联合不能参与继承,并且由于其本质,也不能拥有静态成员。在其他方面,联合的行为类似于结构,其所有成员都具有零偏移量。以下类型不能成为联合成员:

  • 动态数组
  • 字符串
  • 指向对象和函数的指针
  • 类对象
  • 具有构造函数或析构函数的结构对象
  • 具有1-5点成员的结构对象

与类类似,联合也可以拥有构造函数和析构函数以及方法。默认情况下,联合成员具有公共访问类型。要创建私有元素,使用private关键字。所有这些可能性都在示例中展示,展示了如何将颜色类型转换为ARGB,如ColorToARGB()函数所示。

//+------------------------------------------------------------------+
//| Union for color(BGR) conversion to ARGB                          |
//+------------------------------------------------------------------+
union ARGB
  {
   uchar             argb[4];
   color             clr;
   //--- constructors
                     ARGB(color col,uchar a=0){Color(col,a);};
                    ~ARGB(){};
   //--- public methods
public:
   uchar   Alpha(){return(argb[3]);};
   void    Alpha(const uchar alpha){argb[3]=alpha;};
   color   Color(){ return(color(clr));};
   //--- private methods
private:
   //+------------------------------------------------------------------+
   //| set the alpha channel value and color                            |
   //+------------------------------------------------------------------+
   void    Color(color col,uchar alpha)
     {
      //--- set color to clr member
      clr=col;
      //--- set the Alpha component value - opacity level
      argb[3]=alpha;
      //--- interchange the bytes of R and B components (Red and Blue)
      uchar t=argb[0];argb[0]=argb[2];argb[2]=t;
     };
  };
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- 0x55 means 55/255=21.6 % (0% - fully transparent)
   uchar alpha=0x55;
//--- color type is represented as 0x00BBGGRR
   color test_color=clrDarkOrange;
//--- values of bytes from the ARGB union are accepted here
   uchar argb[];
   PrintFormat("0x%.8X - here is how the 'color' type look like for %s, BGR=(%s)",
               test_color,ColorToString(test_color,true),ColorToString(test_color));
//--- ARGB type is represented as 0x00RRGGBB, RR and BB components are swapped
   ARGB argb_color(test_color);
//--- copy the bytes array
   ArrayCopy(argb,argb_color.argb);
//--- here is how it looks in ARGB representation
   PrintFormat("0x%.8X - ARGB representation with the alpha channel=0x%.2x, ARGB=(%d,%d,%d,%d)",
               argb_color.clr,argb_color.Alpha(),argb[3],argb[2],argb[1],argb[0]);
//--- add opacity level
   argb_color.Alpha(alpha);
//--- try defining ARGB as 'color' type
   Print("ARGB as color=(",argb_color.clr,")  alpha channel=",argb_color.Alpha());
//--- copy the bytes array
   ArrayCopy(argb,argb_color.argb);
//--- here is how it looks in ARGB representation
   PrintFormat("0x%.8X - ARGB representation with the alpha channel=0x%.2x, ARGB=(%d,%d,%d,%d)",
               argb_color.clr,argb_color.Alpha(),argb[3],argb[2],argb[1],argb[0]);
//--- check with the ColorToARGB() function results
   PrintFormat("0x%.8X - result of ColorToARGB(%s,0x%.2x)",ColorToARGB(test_color,alpha),
               ColorToString(test_color,true),alpha);
  }
/* Execution result
   0x00008CFF - here is how the color type looks for clrDarkOrange, BGR=(255,140,0)
   0x00FF8C00 - ARGB representation with the alpha channel=0x00, ARGB=(0,255,140,0)
   ARGB as color=(0,140,255)  alpha channel=85
   0x55FF8C00 - ARGB representation with the alpha channel=0x55, ARGB=(85,255,140,0)
   0x55FF8C00 - result of ColorToARGB(clrDarkOrange,0x55)
*/

接口

接口允许确定类可以实现的特定功能。实际上,接口是一种不能包含任何成员的类,并且可能没有构造函数和/或析构函数。在接口中声明的所有方法都是纯虚拟的,即使没有显式定义。

使用"interface"关键字定义接口。示例:

//--- Basic interface for describing animals
interface IAnimal
  {
//--- The methods of the interface have public access by default
   void Sound();  // The sound produced by the animal
  };
//+------------------------------------------------------------------+
//|  The CCat class is inherited from the IAnimal interface          |
//+------------------------------------------------------------------+
class CCat : public IAnimal
  {
public:
                     CCat() { Print("Cat was born"); }
                    ~CCat() { Print("Cat is dead");  }
   //--- Implementing the Sound method of the IAnimal interface
   void Sound(){ Print("meou"); }
  };
//+------------------------------------------------------------------+
//|  The CDog class is inherited from the IAnimal interface          |
//+------------------------------------------------------------------+
class CDog : public IAnimal
  {
public:
                     CDog() { Print("Dog was born"); }
                    ~CDog() { Print("Dog is dead");  }
   //--- Implementing the Sound method of the IAnimal interface
   void Sound(){ Print("guaf"); }
  };
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- An array of pointers to objects of the IAnimal type
   IAnimal *animals[2];
//--- Creating child classes of IAnimal and saving pointers to them into an array
   animals[0]=new CCat;
   animals[1]=new CDog;
//--- Calling the Sound() method of the basic IAnimal interface for each child
   for(int i=0;i<ArraySize(animals);++i)
      animals[i].Sound();
//--- Deleting objects
   for(int i=0;i<ArraySize(animals);++i)
      delete animals[i];
//--- Execution result
/*
   Cat was born
   Dog was born
   meou
   guaf
   Cat is dead
   Dog is dead
*/
  }

抽象类类似,接口对象不能在没有继承的情况下创建。接口只能从其他接口继承,并且可以是类的父级。接口始终公开可见

接口不能在类或结构声明内声明,但可以将接口指针保存在void *类型的变量中。一般来说,任何类的对象指针都可以保存在void *类型的变量中。要将void *指针转换为特定类的对象指针,使用dynamic_cast运算符。如果转换不可行,dynamic_cast操作的结果将为NULL

另请参阅

面向对象编程

最后更新于