BlitzMax/语言/用户定义类型
定义类型允许你将相关的数据和程序代码组合成一个名为对象的单一实体。
声明用户定义类型的通用语法是
- Type 类型名 Extends 类型名
类型名必须是有效的标识符。Extends 部分是可选的。如果省略,用户定义类型将扩展内置的 Object 类型。
一旦声明,你就可以使用 New 运算符创建此类类型的实例。
在用户定义类型中,你可以声明以下内容
- 字段
- 字段是与用户定义类型的每个实例相关的变量。字段的声明方式与局部变量或全局变量相同,只是使用 Field 关键字。要访问对象的字段,请使用 . 运算符。
- 方法
- 方法是与用户定义类型的每个实例相关的类似函数的操作。方法的声明方式与函数相同,只是使用 Method 关键字。要访问对象的 方法,请使用 . 运算符。方法中的程序代码可以通过名称引用它们来访问同一对象中的其他字段、方法、函数、常量和全局变量。
- 函数
- 这些的声明方式与“普通”函数相同,并且可以使用 . 运算符访问。与方法不同,类型中的函数不与类型的实例相关联,而是与类型本身相关联。这意味着无论类型是否创建了任何实例,都可以使用此类函数。类型中的函数可以通过名称引用它们来访问同一类型中的其他函数、常量或全局变量。
- 常量和全局变量
- 这些的声明方式与“普通”常量和全局变量相同,并且可以使用 . 运算符访问。与类型函数一样,这些不与类型的实例相关联,而是与类型本身相关联。
这是一个用户定义类型的示例
Type MyType Const INC=1 Global Counter Field x,y,z Method Sum() Counter=Counter+INC Return x+y+z End Method Function Create:MyType() Return New MyType End Function End Type Local MyObject:MyType=MyType.Create() MyObject.x=10 MyObject.y=20 MyObject.z=30 Print MyObject.Sum() Print MyType.Counter
请注意,对象是通过调用 MyType 的 Create 函数而不是 New 来间接创建的。这是一种常用的技术,允许你在将对象返回给用户之前执行(可能是复杂的)初始化。
用户定义类型可以使用 Extends 关键字扩展其他用户定义类型。扩展类型意味着向现有类型添加更多功能。被扩展的类型通常被称为基类型,而生成的扩展类型通常被称为派生类型
Type BaseType Field x,y,z Method Sum() Return x+y+z End Method End Type Type DerivedType Extends BaseType Field p,q,r Method Sum() Return x+y+z+p+q+r End Method End Type
这种技术也被称为继承,因为派生类型从基类型继承了功能(尽管在此过程中没有人需要死亡!)。请注意,DerivedType 实际上有 6 个字段 - x、y、z、p、q 和 r。它从 BaseType 继承 x、y、z 并添加自己的字段 p、q、r。
BlitzMax 允许你将派生类型对象用于任何需要基类型对象的地方。这是因为派生类型对象是基类型对象——只是有一些“额外的东西”。例如,你可以将派生类型对象分配给基类型变量,或者将派生类型对象传递给期望基类型参数的函数。这确实是继承的全部意义所在——它不仅仅是一种节省打字的技术。
这种行为允许一种非常有用的技术,称为多态。这意味着对象能够根据其类型以不同的方式表现。这在 Blitzmax 中通过覆盖方法来实现。
请注意,在上面的示例中,方法 'Sum' 在基类型和派生类型中都具有相同的签名(参数和返回类型)。这不仅仅是巧合——它是语言要求的。每当你向派生类型中添加一个与基类型中现有方法同名的 方法时,它必须与基类型中的方法具有相同的签名。
但现在我们有了 2 个版本的 'Sum' - 哪个会被调用?这取决于对象的运行时类型。例如
Type BaseType Method Test:String() Return "BaseType.Test" End Method End Type Type DerivedType Extends BaseType Method Test:String() Return "DerivedType.Test" End Method End Type Local x:BaseType=New BaseType Local y:BaseType=New DerivedType Print x.Test() 'prints "BaseType.Test" - x's runtime type is BaseType Print y.Test() 'prints "DerivedType.Test" - as y's runtime type is DerivedType
请注意,当变量 y 初始化时,它被分配了一个 DerivedType 对象,即使 y 是一个 BaseType 变量。这是合法的,因为派生类型可以代替基类型使用。但是,这意味着 y 的运行时类型实际上是 DerivedType。因此,当调用 y.Test() 时,将调用 DerivedType 方法 Test()。
方法内部的程序代码可以访问两个名为 Self 和 Super 的特殊变量。
Self 指向与方法关联的对象,其类型是声明方法的用户定义类型的类型。
Super 也指向与方法关联的对象,但其类型是正在扩展的用户定义类型的类型。如果你需要调用当前正在执行方法的基类型版本,这将非常有用
Type BaseType Method Test() Print "BaseType.Test" End Method End Type Type DerivedType Extends BaseType Method Test() Super.Test 'calls BaseType's Test() method first! Print "DerivedType.Test" End Method End Type Local x:BaseType=New DerivedType x.Test
用户定义类型可以选择声明两个名为 New 和 Delete 的特殊方法。这两个方法都必须不带参数,并且任何返回值都会被忽略。
当使用 New 运算符首次创建对象时,会调用 New 方法。这允许你执行额外的初始化代码。
当内存管理器丢弃对象时,会调用 Delete 方法。请注意,关闭文件等关键关闭操作不应放在 Delete 中,因为你无法始终确定何时会调用 Delete。
用户定义类型和方法也可以通过在相应的声明中添加 Abstract 或 Final 来声明为抽象或最终
Type AbstractType Abstract Method AbstractMethod() Abstract End Type Type FinalType Final Method FinalMethod() Final Print "FinalType.FinalMethod" End Method End Type
声明用户定义类型为抽象意味着你无法使用 New 创建它的实例。但是,仍然可以扩展此类类型并创建这些派生类型的实例。声明方法为抽象意味着该方法没有实现,必须由派生类型实现。任何至少有一个抽象方法的用户定义类型本身都是抽象的。
声明用户定义类型为最终意味着它不能被扩展。声明方法为最终意味着派生类型不能覆盖该方法。最终用户定义类型的所有方法本身都是最终的。
抽象类型和方法主要用于创建“模板”类型和方法,这些类型和方法将实现细节留给派生类型。
最终类型和方法主要用于防止对类型的行为进行修改。