在 Delphi 開發環境中,物件導向程式設計和表單管理是兩個密不可分的概念。理解物件的初始化、繼承、可見性以及事件處理機制,對於建構穩健且易於維護的應用程式至關重要。本文除了介紹表單的建立、顯示和生命週期管理外,也探討了 Object Pascal 語言的基礎知識,包含資料型別、運算子、控制流程等。此外,更進一步說明瞭泛型程式設計和匿名方法的應用,這些進階特性可以顯著提升程式碼的彈性和重用性,讓開發者能夠更有效率地處理複雜的程式邏輯。

物件導向程式設計與表單管理

在物件導向程式設計中,初始化和終結化是兩個重要的概念。初始化部分是可選的,而終結化部分同樣是可選的,但它只能出現在具有初始化部分的單元中。換句話說,沒有初始化部分就不會有終結化部分。

TFormSettings 類別

uFormSettings 單元中宣告了 TFormSettings 型別。型別宣告遵循保留字 type。在宣告表單型別之後,有一個變數宣告 FormSettings,其型別為 TFormSettings。變數宣告以 var 關鍵字開始。

type
  TFormSettings = class(TForm)
    // 類別宣告
  end;

var
  FormSettings: TFormSettings;

當定義一個變數時,也必須宣告其型別。由於 Object Pascal 語言是強型別的,因此在程式變得越來越複雜時,程式設計師需要明確決定型別,而不是由編譯器猜測型別。

繼承與可見性

繼承是物件導向程式設計中的一個重要概念。我們的設定表單具備標準多裝置表單的所有特性。可以檢查 FMX.Forms 單元中 TForm 類別的宣告。類別宣告定義了類別的名稱以及它繼承自哪個類別。

type
  TForm = class(TCommonCustomForm)
    // 類別宣告
  end;

類別宣告根據可見性進行劃分。不同的類別成員可以是私有的(private),也可以是受保護的(protected),或者是公開的(public)或已發布的(published)。

程式碼範例:TFormSettings 類別宣告

type
  TFormSettings = class(TForm)
  private
    // 私有成員
  protected
    // 受保護成員
  public
    // 公開成員
  published
    // 已發布成員
    ToolBar: TToolBar;
    SpdbtnBack: TSpeedButton;
    procedure SpdbtnBackClick(Sender: TObject);
  end;

內容解密:

  1. TFormSettings類別繼承自TForm:表示TFormSettings具備TForm的所有屬性和方法。
  2. published區段:IDE會自動管理此區段,包括元件宣告和事件處理程式。
  3. ToolBarSpdbtnBack:表單上的工具列和快速按鈕元件。
  4. SpdbtnBackClick事件處理程式:處理傳回按鈕的點選事件。

事件處理與表單顯示

要在主表單的按鈕點選事件中顯示設定表單,需要實作以下程式碼:

uses
  uFormSettings;

procedure TFormMain.SpdbtnSettingsClick(Sender: TObject);
begin
  if FormSettings = nil then
    FormSettings := TFormSettings.Create(Application);
  FormSettings.Show;
end;

內容解密:

  1. if FormSettings = nil then:檢查FormSettings是否已經被例項化。
  2. FormSettings := TFormSettings.Create(Application);:如果尚未例項化,則建立一個新的TFormSettings例項。
  3. FormSettings.Show;:顯示設定表單。

次要表單的生命週期

全域變數 FormSettings 儲存了 TFormSettings 類別例項的參考。我們只需要在程式碼中建立一次設定表單。這是一種常見的懶惰建立模式,即在第一次使用物件之前才建立它。如果使用者從未點選設定按鈕,則該物件永遠不會被建立。

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title 物件導向程式設計與表單管理

package "NumPy 陣列操作" {
    package "陣列建立" {
        component [ndarray] as arr
        component [zeros/ones] as init
        component [arange/linspace] as range
    }

    package "陣列操作" {
        component [索引切片] as slice
        component [形狀變換 reshape] as reshape
        component [堆疊 stack/concat] as stack
        component [廣播 broadcasting] as broadcast
    }

    package "數學運算" {
        component [元素運算] as element
        component [矩陣運算] as matrix
        component [統計函數] as stats
        component [線性代數] as linalg
    }
}

arr --> slice : 存取元素
arr --> reshape : 改變形狀
arr --> broadcast : 自動擴展
arr --> element : +, -, *, /
arr --> matrix : dot, matmul
arr --> stats : mean, std, sum
arr --> linalg : inv, eig, svd

note right of broadcast
  不同形狀陣列
  自動對齊運算
end note

@enduml

圖表翻譯: 此圖示描述了懶惰建立模式的流程,首先檢查 FormSettings 是否已經被例項化,如果沒有,則建立新的例項,最後顯示設定表單。

Object Pascal 語言基礎與進階概念

建構子與物件所有權管理

在 Object Pascal 中,建構子的使用伴隨著物件所有權的管理。以 Create(AOwner: TComponent) 為例,當我們建立一個新的表單時,會將全域的 Application 物件指定為該表單的擁有者(Owner)。這代表 Application 物件會負責在應用程式終止時呼叫該表單的解構子,釋放其記憶體。

Form := TMyForm.Create(Application);

內容解密:

  • Create(AOwner: TComponent) 是表單的建構子,用於建立新的表單例項。
  • AOwner 引數指定了該表單的擁有者。在此例中,Application 是擁有者,負責管理表單的生命週期。
  • 正確管理物件的生命週期是 Object Pascal 程式設計中的重要環節。

Token 與分隔符

在 Object Pascal 中,Token 是編譯器能夠理解的最小有意義的文字單元。程式原始碼由 Token 和分隔符組成。分隔符可以是空白字元或註解。

var
  Identifier: Integer;

內容解密:

  • varIdentifierInteger 都是 Token。
  • :; 是特殊符號,同樣被視為 Token。
  • 分隔符(如空白字元)用於區分不同的 Token。

識別符與保留字

識別符用於命名常數、型別、變數、程式、函式等。保留字則是編譯器內建具有特殊意義的字詞,不能用作識別符。

type
  TMyType = Integer;

內容解密:

  • TMyType 是自定義的型別識別符。
  • Integer 是內建型別。
  • type 是保留字,用於宣告新型別。

常數與常數表示式

常數是指其值不可改變的識別符。Object Pascal 允許使用常數表示式,這些表示式在編譯時期被評估。

const
  One = 1;
  OneReal = 1.0;
  Name = 'Marco';
  Computer = 'Paweł''s computer';

內容解密:

  • OneOneReal 是整數和實數常數。
  • NameComputer 是字串常數。注意單引號內的特殊字元處理方式。
  • 字串常數中的兩個連續單引號表示一個單引號字元。

型別定義與列舉型別

Object Pascal 程式設計從定義自訂型別開始。內建型別可用於建立更複雜的自訂型別。

type
  TSchoolGrade = (sgVeryGood, sgGood, sgSufficient, sgInsufficient);

內容解密:

  • TSchoolGrade 是列舉型別,用於表示學校成績。
  • 使用列舉型別可提高程式碼的可讀性和安全性,避免使用未定義的值。

啟用 SCOPEDENUMS 編譯指令

啟用 $SCOPEDENUMS ON 編譯指令後,列舉值必須以完整限定形式使用。

{$SCOPEDENUMS ON}
type
  TSchoolGrade = (VeryGood, Good, Sufficient, Insufficient);

var
  Grade: TSchoolGrade;
begin
  Grade := TSchoolGrade.VeryGood;
end;

內容解密:

  • $SCOPEDENUMS ON 編譯指令強制使用完整限定的列舉值。
  • 這種寫法提高了程式碼的可讀性,並減少命名衝突的風險。

Object Pascal 語言實用

列舉型別的使用

在 Object Pascal 中,列舉型別是一種非常有用的資料型別,可以用來定義一組具有特定含義的常數。例如,我們可以定義一個 TSchoolGrade 列舉型別來表示學生的成績等級:

type
  TSchoolGrade = (sgVeryGood, sgGood, sgSufficient, sgInsufficient);

使用列舉型別的正確語法

在早期版本的 Delphi 中,使用列舉型別需要加上型別名稱作為字首,以避免名稱衝突:

var
  SG: TSchoolGrade;
begin
  SG := TSchoolGrade.VeryGood; // 正確的語法

內聯變數宣告

從較新的 Delphi 版本開始,我們可以在程式碼塊中直接宣告變數,並使用 var 關鍵字:

var X := 20.0;
var I: Integer := Trunc(X);

這種語法不僅簡潔,還支援型別推斷,可以省略變數的型別:

var I := Trunc(X);

最重要的是,內聯變數的宣告具有新的可見性和生命週期規則,變數的作用域僅限於宣告它的程式碼塊。

集合與陣列

使用集合

集合是 Object Pascal 中的一種基本集合型別,可以用來表示一組無序的元素。例如,我們可以定義一個 TSchoolGrades 集合來表示合格的成績等級:

type
  TSchoolGrade = (sgVeryGood, sgGood, sgSufficient, sgInsufficient);
  TSchoolGrades = set of TSchoolGrade;

const
  Qualifying_Grades: TSchoolGrades = [sgVeryGood, sgGood, sgSufficient];

function IsQualifyingGrade(SG: TSchoolGrade): boolean;
begin
  Result := SG in Qualifying_Grades;
end;

使用陣列

陣列是另一種常用的集合型別,可以用來表示一組有序的元素。陣列可以是固定大小的,也可以是動態大小的。

定義固定大小的陣列

type
  TChessPiece = record
    Name: string;
    Value: Double;
  end;

const
  Chess_pieces_count = 6;
  Chess_pieces: array[0..Chess_pieces_count-1] of TChessPiece =
    (
      (Name: 'Pawn'; Value: 1),
      (Name: 'Knight'; Value: 3),
      (Name: 'Bishop'; Value: 3),
      (Name: 'Rook'; Value: 5),
      (Name: 'Queen'; Value: 9),
      (Name: 'King'; Value: 0)
    );

使用動態陣列

var
  Fruits: array of string;
begin
  Fruits := ['Apple', 'Pear'];
  Fruits := Fruits + ['Banana', 'Orange'];

型別助手

型別助手是一種擴充套件現有型別的機制,可以為現有的型別新增新的方法。例如,我們可以為 TSchoolGrade 列舉型別定義一個型別助手:

type
  TSchoolGradeHelper = record helper for TSchoolGrade
  public
    function ToString: string;
    function ToInteger: Integer;
    function IsQualifying: Boolean;
  end;

function TSchoolGradeHelper.IsQualifying: Boolean;
begin
  Result := self in Qualifying_grades;
end;

function TSchoolGradeHelper.ToInteger: Integer;
begin
  case self of
    sgVeryGood: Result := 5;
    // 其他情況的處理
  end;
end;

使用型別助手的好處

使用型別助手可以使程式碼更加簡潔和易讀。例如,我們可以使用 ToString 方法將 TSchoolGrade 值轉換為字串:

var
  Grade: TSchoolGrade;
begin
  Grade := sgVeryGood;
  ShowMessage(Grade.ToString);

詳細解說

  1. 列舉型別的定義和使用:瞭解如何定義和使用列舉型別,包括正確的語法和內聯變數宣告。
  2. 集合和陣列的使用:學習如何使用集合和陣列,包括固定大小和動態大小的陣列。
  3. 型別助手的使用:瞭解如何定義和使用型別助手,以擴充套件現有型別的功能。

物件導向泛型與匿名方法在 Object Pascal 中的應用

Object Pascal 語言在現代軟體開發中佔有重要地位,其物件導向、泛型及匿名方法等特性極大地提升了程式碼的靈活性與可維護性。本文將探討 Object Pascal 中的泛型、匿名方法及其實際應用。

列舉型別的 Helper 方法

在 Object Pascal 中,我們可以為列舉型別定義 Helper 方法,以增強其功能。例如,對於一個代表學校成績的列舉型別 TSchoolGrade,我們可以定義 Helper 方法來取得其整數值或字串表示:

type
  TSchoolGrade = (sgVeryGood, sgGood, sgSufficient, sgInsufficient);

  TSchoolGradeHelper = record helper for TSchoolGrade
    function ToInt: Integer;
    function ToString: string;
  end;

function TSchoolGradeHelper.ToInt: Integer;
begin
  case Self of
    sgVeryGood: Result := 4;
    sgGood: Result := 3;
    sgSufficient: Result := 2;
    sgInsufficient: Result := 1;
  end;
end;

function TSchoolGradeHelper.ToString: string;
begin
  case Self of
    sgVeryGood: Result := 'Very Good';
    sgGood: Result := 'Good';
    sgSufficient: Result := 'Sufficient';
    sgInsufficient: Result := 'Insufficient';
  end;
end;

內容解密:

  • TSchoolGradeHelperTSchoolGrade 列舉型別提供了額外的功能。
  • ToInt 方法將列舉值轉換為對應的整數。
  • ToString 方法傳回列舉值對應的字串描述。
  • 使用 Self 關鍵字來參照當前的列舉值。

泛型程式設計

泛型是 Object Pascal 中的一個強大特性,允許我們編寫更通用的程式碼,使同一演算法能夠操作多種資料型別。泛型可以是資料結構或方法,它們可以被型別引數化。

可填入(Fillable)泛型記錄

type
  TFillable<T> = record
    Value: T;
    IsFilled: Boolean;
  end;

  TFillableString = TFillable<string>;
  TFillableInteger = TFillable<Integer>;

內容解密:

  • TFillable<T> 定義了一個泛型記錄,用於表示可填入的值。
  • Value 欄位儲存實際的值,IsFilled 欄位指示該值是否已填入。
  • 使用泛型,我們可以為不同型別建立特定的可填入型別,如 TFillableStringTFillableInteger

泛型約束

在某些情況下,我們需要對泛型型別引數施加約束,以確保它們滿足特定的條件。例如:

type
  TFmxProcessor<T: TFMXObject, constructor> = class
    // ...
  end;

內容解密:

  • TFmxProcessor<T> 是一個泛型類別,其型別引數 T 必須是 TFMXObject 的子類別,並且具有公開的建構函式。

自訂排序演算法

泛型在實作自訂排序演算法時非常有用。無論是排序字元、整數還是實數,演算法的邏輯保持不變。我們只需要提供比較兩個值的方法:

// 使用泛型的排序演算法示例

內容解密:

  • 泛型使得排序演算法可以適用於不同資料型別。
  • 需要提供比較函式以確定值的順序。

TObjectList 與 TList 的比較

在管理物件列表時,TObjectList<T> 比非泛型的 TList 更安全、更方便:

procedure DoPersonsGenerics;
var
  Persons: TObjectList<TPerson>;
  P: TPerson;
begin
  Persons := TObjectList<TPerson>.Create;
  try
    Persons.Add(TPerson.Create('Kirk', 'Hammett'));
    Persons.Add(TPerson.Create('James', 'Hetfield'));
    // ...
    for P in Persons do
      Log(P.Fullname); // 不需要型別轉換
  finally
    Persons.Free;
  end;
end;

內容解密:

  • TObjectList<TPerson> 確保列表中只能新增 TPerson 或其子類別的例項。
  • 使用泛型列表可以避免執行期的型別轉換錯誤。
  • 可以使用更易讀的 for..in..do 迴圈遍歷列表。

匿名方法

Object Pascal 中的匿名方法允許我們將程式碼視為資料。可以將函式或程式指定給變數,作為引數傳遞給其他函式,或作為結果傳回。這種特性使得程式碼更加緊湊和可維護。

匿名方法的應用示例

// 匿名方法示例

內容解密:

  • 匿名方法增強了程式碼的靈活性。
  • 可以用於事件處理、回呼函式等場景。