Pascal - 類



您已經看到 Pascal 物件展現了面向物件正規化的一些特性。它們實現了封裝、資料隱藏和繼承,但它們也有一些侷限性。例如,Pascal 物件不參與多型性。因此,類被廣泛用於在程式中實現正確的面向物件行為,尤其是在基於 GUI 的軟體中。

類的定義方式與物件幾乎相同,但它是指向物件的指標,而不是物件本身。從技術上講,這意味著類是在程式的堆上分配的,而物件是在堆疊上分配的。換句話說,當您宣告一個物件型別變數時,它會在堆疊上佔用與物件大小一樣大的空間,但是當您宣告一個類型別變數時,它始終會在堆疊上佔用一個指標的大小。實際的類資料將在堆上。

定義 Pascal 類

類的宣告方式與物件相同,使用型別宣告。類宣告的一般形式如下:

type class-identifier = class  
   private
      field1 : field-type;  
      field2 : field-type;  
        ...
   
   public
      constructor create();
      procedure proc1;  
      function f1(): function-type;
end;  
var classvar : class-identifier;

值得注意以下重要幾點:

  • 類定義應該只出現在程式的型別宣告部分。

  • 類是使用class關鍵字定義的。

  • 欄位是類每個例項中存在的資料項。

  • 方法在類的定義中宣告。

  • 根類中有一個預定義的建構函式,名為Create。每個抽象類和每個具體類都是根類的後代,因此所有類至少有一個建構函式。

  • 根類中有一個預定義的解構函式,名為Destroy。每個抽象類和每個具體類都是根類的後代,因此所有類至少有一個解構函式。

讓我們定義一個矩形類,它有兩個整數型別的資料成員 - 長度和寬度,以及一些用於操作這些資料成員的成員函式和一個用於繪製矩形的過程。

type
   Rectangle = class
   private
      length, width: integer;
   
   public
      constructor create(l, w: integer);
      procedure setlength(l: integer);
      function getlength(): integer;
      procedure setwidth(w: integer);
      function getwidth(): integer;
      procedure draw;
end;

讓我們編寫一個完整的程式,該程式將建立一個矩形類的例項並繪製矩形。這是我們在討論 Pascal 物件時使用的同一個示例。您會發現這兩個程式幾乎相同,但有以下例外:

  • 您需要包含{$mode objfpc}指令才能使用類。

  • 您需要包含{$m+}指令才能使用建構函式。

  • 類例項化與物件例項化不同。僅宣告變數不會為例項建立空間,您將使用建構函式 create 來分配記憶體。

這是一個完整的示例:

{$mode objfpc} // directive to be used for defining classes
{$m+}		   // directive to be used for using constructor

program exClass;
type
   Rectangle = class
   private
      length, width: integer;
   
   public
      constructor create(l, w: integer);
      procedure setlength(l: integer);
      
      function getlength(): integer;
      procedure setwidth(w: integer);
      
      function getwidth(): integer;
      procedure draw;
end;
var
   r1: Rectangle;

constructor Rectangle.create(l, w: integer);
begin
   length := l;
   width := w;
end;

procedure Rectangle.setlength(l: integer);
begin
   length := l;
end;

procedure Rectangle.setwidth(w: integer);
begin
   width :=w;
end;

function Rectangle.getlength(): integer;
begin
   getlength := length;
end;

function Rectangle.getwidth(): integer;
begin
   getwidth := width;
end;

procedure Rectangle.draw;
var
   i, j: integer;
begin
   for i:= 1 to length do
   begin
      for j:= 1 to width do
         write(' * ');
      writeln;
   end;
end;

begin
   r1:= Rectangle.create(3, 7);
   
   writeln(' Draw Rectangle: ', r1.getlength(), ' by ' , r1.getwidth());
   r1.draw;
   r1.setlength(4);
   r1.setwidth(6);
   
   writeln(' Draw Rectangle: ', r1.getlength(), ' by ' , r1.getwidth());
   r1.draw;
end.

編譯並執行上述程式碼後,將產生以下結果:

Draw Rectangle: 3 by 7
* * * * * * *
* * * * * * *
* * * * * * *
Draw Rectangle: 4 by 6
* * * * * * 
* * * * * * 
* * * * * * 
* * * * * * 

類成員的可見性

可見性指示類成員的可訪問性。Pascal 類成員有五種可見性型別:

序號 可見性和可訪問性
1

公共 (Public)

這些成員始終可訪問。

2

私有 (Private)

這些成員只能在包含類定義的模組或單元中訪問。它們可以從類方法內部或外部訪問。

3

嚴格私有 (Strict Private)

這些成員只能從類本身的方法訪問。同一單元中的其他類或子類無法訪問它們。

4

受保護 (Protected)

這與私有相同,只是這些成員可被子型別訪問,即使它們是在其他模組中實現的。

5

已釋出 (Published)

這與公共相同,但是如果編譯器處於{$M+}狀態,編譯器會生成自動流式傳輸這些類所需的資訊。在已釋出部分中定義的欄位必須是類型別。

Pascal 類的建構函式和解構函式

建構函式是特殊的方法,每當建立物件時都會自動呼叫它們。因此,我們透過建構函式充分利用這種行為來初始化許多事情。

Pascal 提供了一個名為 create() 的特殊函式來定義建構函式。您可以將任意數量的引數傳遞給建構函式。

下面的示例將為名為 Books 的類建立一個建構函式,它將在物件建立時初始化書籍的價格和標題。

program classExample;

{$MODE OBJFPC} //directive to be used for creating classes
{$M+} //directive that allows class constructors and destructors
type
   Books = Class 
   private 
      title : String; 
      price: real;
   
   public
      constructor Create(t : String; p: real); //default constructor
      
      procedure setTitle(t : String); //sets title for a book
      function getTitle() : String; //retrieves title
      
      procedure setPrice(p : real); //sets price for a book
      function getPrice() : real; //retrieves price
      
      procedure Display(); // display details of a book
end;
var
   physics, chemistry, maths: Books;

//default constructor 
constructor Books.Create(t : String; p: real);
begin
   title := t;
   price := p;
end;

procedure Books.setTitle(t : String); //sets title for a book
begin
   title := t;
end;

function Books.getTitle() : String; //retrieves title
begin
   getTitle := title;
end;

procedure Books.setPrice(p : real); //sets price for a book
begin
   price := p;
end;

function Books.getPrice() : real; //retrieves price
begin
   getPrice:= price;
end;

procedure Books.Display();
begin
   writeln('Title: ', title);
   writeln('Price: ', price:5:2);
end;

begin 
   physics := Books.Create('Physics for High School', 10);
   chemistry := Books.Create('Advanced Chemistry', 15);
   maths := Books.Create('Algebra', 7);
   
   physics.Display;
   chemistry.Display;
   maths.Display;
end.

編譯並執行上述程式碼後,將產生以下結果:

Title: Physics for High School
Price: 10
Title: Advanced Chemistry
Price: 15
Title: Algebra
Price: 7

與名為 create 的隱式建構函式一樣,還有一個隱式解構函式方法 destroy,您可以使用它來釋放類中使用的所有資源。

繼承

Pascal 類定義可以選擇從父類定義繼承。語法如下:

type
childClas-identifier = class(baseClass-identifier) 
< members >
end; 

以下示例提供了一個 novels 類,它繼承了 Books 類並根據需要添加了更多功能。

program inheritanceExample;

{$MODE OBJFPC} //directive to be used for creating classes
{$M+} //directive that allows class constructors and destructors

type
   Books = Class 
   protected 
      title : String; 
      price: real;
   
   public
      constructor Create(t : String; p: real); //default constructor
      
      procedure setTitle(t : String); //sets title for a book
      function getTitle() : String; //retrieves title
      
      procedure setPrice(p : real); //sets price for a book
      function getPrice() : real; //retrieves price
      
      procedure Display(); virtual; // display details of a book
end;
(* Creating a derived class *)

type
   Novels = Class(Books)
   private
      author: String;
   
   public
      constructor Create(t: String); overload;
      constructor Create(a: String; t: String; p: real); overload;
      
      procedure setAuthor(a: String); // sets author for a book
      function getAuthor(): String; // retrieves author name
      
      procedure Display(); override;
end;
var
   n1, n2: Novels;

//default constructor 
constructor Books.Create(t : String; p: real);
begin
   title := t;
   price := p;
end;

procedure Books.setTitle(t : String); //sets title for a book
begin
   title := t;
end;

function Books.getTitle() : String; //retrieves title
begin
   getTitle := title;
end;

procedure Books.setPrice(p : real); //sets price for a book
begin
   price := p;
end;

function Books.getPrice() : real; //retrieves price
begin
   getPrice:= price;
end;

procedure Books.Display();
begin
   writeln('Title: ', title);
   writeln('Price: ', price);
end;

(* Now the derived class methods  *)
constructor Novels.Create(t: String);
begin
   inherited Create(t, 0.0);
   author:= ' ';
end;

constructor Novels.Create(a: String; t: String; p: real);
begin
   inherited Create(t, p);
   author:= a;
end;

procedure Novels.setAuthor(a : String); //sets author for a book
begin
   author := a;
end;

function Novels.getAuthor() : String; //retrieves author
begin
   getAuthor := author;
end;

procedure Novels.Display();
begin
   writeln('Title: ', title);
   writeln('Price: ', price:5:2);
   writeln('Author: ', author);
end;

begin 
   n1 := Novels.Create('Gone with the Wind');
   n2 := Novels.Create('Ayn Rand','Atlas Shrugged', 467.75);
   n1.setAuthor('Margaret Mitchell');
   n1.setPrice(375.99);
   n1.Display;
   n2.Display;
end.

編譯並執行上述程式碼後,將產生以下結果:

Title: Gone with the Wind
Price: 375.99
Author: Margaret Mitchell
Title: Atlas Shrugged
Price: 467.75
Author: Ayn Rand

值得注意以下重要幾點:

  • Books 類的成員具有protected可見性。

  • Novels 類有兩個建構函式,因此overload運算子用於函式過載。

  • Books.Display 過程已宣告為virtual,以便來自 Novels 類的相同方法可以override它。

  • Novels.Create 建構函式使用inherited關鍵字呼叫基類建構函式。

介面

定義介面是為了為實現者提供一個通用的函式名稱。不同的實現者可以根據自己的需求實現這些介面。您可以說,介面是框架,由開發人員實現。以下是一個介面示例:

type  
   Mail = Interface  
      Procedure SendMail;  
      Procedure GetMail;  
   end;  
   
   Report = Class(TInterfacedObject,  Mail)  
      Procedure SendMail;  
      Procedure GetMail;  
   end;  

請注意,當一個類實現一個介面時,它應該實現介面的所有方法。如果未實現介面的方法,則編譯器將給出錯誤。

抽象類

抽象類是不能例項化,只能繼承的類。抽象類是透過在類定義中包含單詞符號 abstract 來指定的,如下所示:

type
   Shape = ABSTRACT CLASS (Root)
      Procedure draw; ABSTRACT;
      ...
   end;

從抽象類繼承時,必須由子類定義父類宣告中標記為抽象的所有方法;此外,這些方法必須使用相同的可見性定義。

靜態關鍵字

將類成員或方法宣告為靜態,使它們無需例項化類即可訪問。宣告為靜態的成員不能使用例項化的類物件訪問(儘管可以訪問靜態方法)。以下示例說明了這個概念:

program StaticExample;
{$mode objfpc}
{$static on}
type
   myclass=class
      num : integer;static;
   end;
var
   n1, n2 : myclass;
begin
   n1:= myclass.create;
   n2:= myclass.create;
   n1.num := 12;
   writeln(n2.num);
   n2.num := 31;
   writeln(n1.num);
   writeln(myclass.num);
   myclass.num := myclass.num + 20;
   writeln(n1.num);
   writeln(n2.num);
end.

編譯並執行上述程式碼後,將產生以下結果:

12
31
31
51
51

您必須使用指令{$static on}才能使用靜態成員。

廣告