拆分类声明和定义
在大型软件项目中,为了方便起见,可以将类分成简短说明(声明)和定义(包含主要实现细节)。在某些情况下,如果类之间以某种方式相互引用,即没有事先声明就无法完整定义任何一个类,则这种隔离很有必要。
我们在 指标 一节中看到了一个前向声明示例(参见文件 ThisCallback.mq5),其中 Manager 和 Element 类包含相互指向的指针。其中的类以一种简短的形式预先声明:格式为包含关键字 class 和名称的头文件:
class Manager; |
然而,这是尽可能简短的声明。它只注册名称,并且可以将编程接口的说明推迟到某个时间,但这种说明必须在代码的后面部分遇到。
更常见的是,声明包含接口的完整说明:它指定类的所有变量和方法头,但不包含它们的主体(代码块)。
方法定义是单独编写的:其方法头使用完全限定名称,名称中包含类名(如果方法上下文高度嵌套,则使用多个类和命名空间)。所有类名和方法名使用上下文选择运算符 '::' 连接。
type class_name [:: nested_class_name...] :: method_name([parameters...])
|
理论上,您可以直接在类说明块中定义方法的某一部分(通常小型函数会这样做),而某些部分则可以单独提取出来(通常是大型函数)。但是,每个方法只能有一个定义(也就是说,不允许先在类块中定义方法,然后再单独定义)和一个声明(类块中的定义也是声明)。
方法声明和定义中的参数列表、返回类型和 const 修饰符(如果有)必须完全匹配。
我们来看看如何将类的描述和定义与脚本 ThisCallback.mq5( 指针一节中的示例)分开:我们创建一个名为 ThisCallback2.mq5 的类似脚本。
前置声明 Manager 仍然位于开头。此外,Element 和 Manager 这两个类仅仅声明,但没有实现:仅以分号结束,没有方法体代码块。
class Manager; // preliminary announcement
|
源代码的第二部分包含所有方法的实现(实现本身保持不变)。
Element::Element(Manager &t) : owner(&t)
|
结构体也支持单独的方法声明和定义。
请注意,构造函数初始化列表(位于名称和 ':' 之后)是定义的一部分,因此必须位于函数体之前(换句话说,在仅有头文件的构造函数声明中不允许使用初始化列表)。
通过单独编写声明和定义,可以开发源代码必须封闭的 库。在本例中,声明放在一个扩展名为 mqh 的单独头文件中,而定义放在一个扩展名为 mq5 的同名文件中。该程序被编译并以 ex5 文件(附带描述外部接口的头文件)的形式分发。
在这种情况下,可能会出现一个问题:为什么部分内部实现(尤其是数据(变量)的组织方式)在外部接口中可见?严格来说,这表明类层次结构的抽象程度不足。所有提供外部接口的类都不应公开任何实现细节。
换句话说,如果我们设定的目标是从某个库中导出上述类,那么我们需要将它们的方法拆分为若干基类(提供 API 说明,但不含数据字段),而 Manager 和 Element 则继承自这些基类。同时,在基类的方法中,我们不能使用任何来自派生类的数据,而且,总的来说,它们根本不包含任何实现。这怎么可能呢?
为此,需要使用抽象方法、抽象类和接口的技术。