Pascal - Klassen

Sie haben gesehen, dass Pascal-Objekte einige Merkmale eines objektorientierten Paradigmas aufweisen. Sie implementieren Kapselung, Verstecken von Daten und Vererbung, haben aber auch Einschränkungen. Zum Beispiel nehmen Pascal-Objekte nicht am Polymorphismus teil. Daher werden Klassen häufig verwendet, um ein korrektes objektorientiertes Verhalten in einem Programm zu implementieren, insbesondere in der GUI-basierten Software.

Eine Klasse wird fast genauso definiert wie ein Objekt, ist jedoch eher ein Zeiger auf ein Objekt als auf das Objekt selbst. Technisch bedeutet dies, dass die Klasse auf dem Heap eines Programms zugewiesen wird, während das Objekt auf dem Stapel zugewiesen wird. Mit anderen Worten, wenn Sie eine Variable als Objekttyp deklarieren, nimmt sie auf dem Stapel so viel Platz ein wie die Größe des Objekts. Wenn Sie jedoch eine Variable vom Klassentyp deklarieren, nimmt sie immer die Größe eines Zeigers an auf dem Stapel. Die tatsächlichen Klassendaten befinden sich auf dem Heap.

Pascal-Klassen definieren

Eine Klasse wird wie ein Objekt mithilfe der Typdeklaration deklariert. Die allgemeine Form einer Klassendeklaration lautet wie folgt:

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;

Es lohnt sich, folgende wichtige Punkte zu beachten:

  • Klassendefinitionen sollten nur unter den Typdeklarationsteil des Programms fallen.

  • Eine Klasse wird mit dem definiert class Stichwort.

  • Felder sind Datenelemente, die in jeder Instanz der Klasse vorhanden sind.

  • Methoden werden innerhalb der Definition einer Klasse deklariert.

  • Es gibt einen vordefinierten Konstruktor namens Createin der Root-Klasse. Jede abstrakte Klasse und jede konkrete Klasse ist ein Nachkomme von Root, daher haben alle Klassen mindestens einen Konstruktor.

  • Es gibt einen vordefinierten Destruktor namens Destroyin der Root-Klasse. Jede abstrakte Klasse und jede konkrete Klasse ist ein Nachkomme von Root, daher haben alle Klassen mindestens einen Destruktor.

Definieren wir eine Rechteckklasse mit zwei Datenelementen vom Typ Integer - Länge und Breite sowie einige Elementfunktionen zum Bearbeiten dieser Datenelemente und eine Prozedur zum Zeichnen des Rechtecks.

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;

Schreiben wir ein vollständiges Programm, das eine Instanz einer Rechteckklasse erstellt und das Rechteck zeichnet. Dies ist das gleiche Beispiel, das wir bei der Erörterung von Pascal-Objekten verwendet haben. Sie werden feststellen, dass beide Programme mit den folgenden Ausnahmen fast gleich sind:

  • Sie müssen die Anweisung {$ mode objfpc} einschließen, um die Klassen verwenden zu können.

  • Sie müssen die Anweisung {$ m +} für die Verwendung von Konstruktoren einfügen.

  • Die Klasseninstanziierung unterscheidet sich von der Objektinstanziierung. Nur das Deklarieren der Variablen schafft keinen Platz für die Instanz. Sie verwenden den Konstruktor create, um Speicher zuzuweisen.

Hier ist das vollständige Beispiel -

{$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.

Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:

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

Sichtbarkeit der Klassenmitglieder

Sichtbarkeit gibt die Zugänglichkeit der Klassenmitglieder an. Pascal-Klassenmitglieder haben fünf Arten der Sichtbarkeit -

Sr.Nr. Sichtbarkeit und Zugänglichkeit
1

Public

Diese Mitglieder sind immer erreichbar.

2

Private

Auf diese Mitglieder kann nur in dem Modul oder der Einheit zugegriffen werden, die die Klassendefinition enthält. Sie können von innerhalb oder außerhalb der Klassenmethoden aufgerufen werden.

3

Strict Private

Auf diese Mitglieder kann nur über Methoden der Klasse selbst zugegriffen werden. Andere Klassen oder untergeordnete Klassen in derselben Einheit können nicht auf sie zugreifen.

4

Protected

Dies ist dasselbe wie privat, außer dass diese Mitglieder für absteigende Typen zugänglich sind, selbst wenn sie in anderen Modulen implementiert sind.

5

Published

Dies ist dasselbe wie ein Public, aber der Compiler generiert Typinformationen, die für das automatische Streaming dieser Klassen erforderlich sind, wenn sich der Compiler im Status {$ M +} befindet. In einem veröffentlichten Abschnitt definierte Felder müssen vom Klassentyp sein.

Konstruktoren und Destruktoren für Pascal-Klassen

Konstruktoren sind spezielle Methoden, die automatisch aufgerufen werden, wenn ein Objekt erstellt wird. Wir nutzen dieses Verhalten also voll aus, indem wir viele Dinge durch Konstruktorfunktionen initialisieren.

Pascal bietet eine spezielle Funktion namens create () zum Definieren eines Konstruktors. Sie können beliebig viele Argumente an die Konstruktorfunktion übergeben.

Im folgenden Beispiel wird ein Konstruktor für eine Klasse mit dem Namen "Bücher" erstellt und der Preis und der Titel für das Buch zum Zeitpunkt der Objekterstellung initialisiert.

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.

Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:

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

Wie der implizite Konstruktor namens create gibt es auch eine implizite Destruktormethode destroy, mit der Sie alle in der Klasse verwendeten Ressourcen freigeben können.

Erbe

Pascal-Klassendefinitionen können optional von einer übergeordneten Klassendefinition erben. Die Syntax lautet wie folgt:

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

Das folgende Beispiel enthält eine Romanklasse, die die Books-Klasse erbt und je nach Anforderung weitere Funktionen hinzufügt.

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.

Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:

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

Es lohnt sich, folgende wichtige Punkte zu beachten:

  • Die Mitglieder der Bücherklasse haben protected Sichtweite.

  • Die Novels-Klasse hat zwei Konstruktoren, also die overload Der Operator wird zum Überladen von Funktionen verwendet.

  • Das Books.Display-Verfahren wurde deklariert virtual, so dass die gleiche Methode aus der Novels-Klasse kann override es.

  • Der Novels.Create-Konstruktor ruft den Basisklassenkonstruktor mit dem auf inherited Stichwort.

Schnittstellen

Schnittstellen werden definiert, um den Implementierern einen gemeinsamen Funktionsnamen bereitzustellen. Verschiedene Implementierer können diese Schnittstellen entsprechend ihren Anforderungen implementieren. Man kann sagen, Schnittstellen sind Skelette, die von Entwicklern implementiert werden. Das Folgende ist ein Beispiel für eine Schnittstelle -

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

Beachten Sie, dass eine Klasse, wenn sie eine Schnittstelle implementiert, alle Methoden der Schnittstelle implementieren sollte. Wenn eine Methode einer Schnittstelle nicht implementiert ist, gibt der Compiler einen Fehler aus.

Abstrakte Klassen

Eine abstrakte Klasse kann nicht instanziiert, sondern nur vererbt werden. Eine abstrakte Klasse wird angegeben, indem das Wortsymbol abstrakt wie folgt in die Klassendefinition aufgenommen wird:

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

Beim Erben von einer abstrakten Klasse müssen alle in der Klassendeklaration des Elternteils als abstrakt gekennzeichneten Methoden vom Kind definiert werden. Darüber hinaus müssen diese Methoden mit derselben Sichtbarkeit definiert werden.

Statisches Schlüsselwort

Wenn Sie Klassenmitglieder oder Methoden als statisch deklarieren, können Sie auf sie zugreifen, ohne dass eine Instanziierung der Klasse erforderlich ist. Auf ein als statisch deklariertes Mitglied kann nicht mit einem instanziierten Klassenobjekt zugegriffen werden (obwohl dies mit einer statischen Methode möglich ist). Das folgende Beispiel veranschaulicht das Konzept -

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.

Wenn der obige Code kompiliert und ausgeführt wird, ergibt sich das folgende Ergebnis:

12
31
31
51
51

Sie müssen die Direktive {$ static on} verwenden, um die statischen Elemente zu verwenden.