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 建立它的例項。但是,仍然可以擴充套件此類型別並建立這些派生型別的例項。宣告方法為抽象意味著該方法沒有實現,必須由派生型別實現。任何至少有一個抽象方法的使用者定義型別本身都是抽象的。
宣告使用者定義型別為最終意味著它不能被擴充套件。宣告方法為最終意味著派生型別不能覆蓋該方法。終端使用者定義型別的所有方法本身都是最終的。
抽象型別和方法主要用於建立“模板”型別和方法,這些型別和方法將實現細節留給派生型別。
最終型別和方法主要用於防止對型別的行為進行修改。