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类有三个构造函数:
- 一个显式的默认构造函数,允许创建该类的对象数组;
- 一个带有一个参数的构造函数,它接收全名作为参数,并根据找到的空间将名称和第二名称分开;
- 一个带有两个参数的构造函数,包含初始化列表。初始化器为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
*/由于联合允许程序以不同方式解释相同的内存数据,因此当需要不寻常的类型转换时经常使用它们。
联合不能参与继承,并且由于其本质,也不能拥有静态成员。在其他方面,联合的行为类似于结构,其所有成员都具有零偏移量。以下类型不能成为联合成员:
与类类似,联合也可以拥有构造函数和析构函数以及方法。默认情况下,联合成员具有公共访问类型。要创建私有元素,使用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。