Home » Tutorials » Object Pascal/RTL » Das Überladen von Operatoren

Das Überladen von Operatoren

Was alles möglich ist und wie es geht

In diesem Abschnitt wollen wir uns ansehen, welche Operatoren überhaupt überladen werden können. In der Delphi-Hilfe ist (wenn auch nicht ganz ersichtlich) eine Tabelle zu finden, welche alle Operatoren mit Namen und Symbol auflistet.

Operator Beschreibung Deklarationssignatur Symbol
Implicit Implizite Typumwandlung Implicit(a: Typ): Ergebnistyp;
Explicit Explizite Typumwandlung Explicit(a: Typ): Ergebnistyp;
Negative Negatives Vorzeichen Negative(a: Typ): Ergebnistyp;
Positive Positives Vorzeichen Positive(a: Typ): Ergebnistyp; +
Inc Inkrementieren (Erhöhen) Inc(a: Typ): Ergebnistyp; inc
Dec Dekrementieren (Erniedrigen) Dec(a: Typ): Ergebnistyp; dec
LogicalNot Logisches Nicht LogicalNot(a: Typ): Ergebnistyp; not
BitwiseNot Bitweises Nicht BitwiseNot(a: Typ): Ergebnistyp; not
Trunc Abschneiden Trunc(a: Typ): Ergebnistyp; trunc
Round Runden Round(a: Typ): Ergebnistyp; round
Equal Gleichheits-Operator Equal(a: Typ; b: Typ): Ergebnistyp; =
NotEqual Ungleichheits-Operator NotEqual(a: Typ; b: Typ): Ergebnistyp; <>
GreaterThan Größer-Als-Operator GreaterThan(a: Typ; b: Typ): Ergebnistyp; >
GreaterThanOrEqual Größer-Als-Oder-Gleich-Operator GreaterThanOrEqual(a: Typ; b: Typ): Ergebnistyp; >=
LessThan Kleiner-Als-Operator LessThan(a: Typ; b: Typ): Ergebnistyp; <
LessThanOrEqual Kleiner-Als-Oder-Gleich-Operator LessThanOrEqual(a: Typ; b: Typ): Ergebnistyp; <=
Add Addition Add(a: Typ; b: Typ): Ergebnistyp; +
Subtract Subtraktion Subtract(a: Typ; b: Typ): Ergebnistyp;
Multiply Multiplikation Multyply(a: Typ; b: Typ): Ergebnistyp; *
Divide Division Divide(a: Typ; b: Typ): Ergebnistyp; /
IntDivide (Integer-)Division IntDivide(a: Typ; b:Typ): Ergebnistyp; div
Modulus Modulo-Operation (Rest) Modulus(a: Typ; b: Typ): Ergebnistyp; mod
LeftShift Linksshift (Verschiebung nach links) LeftShift(a: Typ; b: Typ): Ergebnistyp; shl
RightShift Rechtsshift (Verschiebung nach rechts) RightShift(a: Typ; b: Typ): Ergebnistyp; shr
LogicalAnd Logisches Und LogicalAnd(a: Typ; b: Typ): Ergebnistyp; and
LogicalOr Logisches Oder LogicalOr(a: Typ; b: Typ): Ergebnistyp; or
LogicalXor Logisches Exklusiv-Oder LogicalXor(a: Typ; b: Typ): Ergebnistyp; xor
BitwiseAnd Bitweises Und BitwiseAnd(a: Typ; b: Typ): Ergebnistyp; and
BitwiseOr Bitweises Oder BitwiseOr(a: Typ; b: Typ): Ergebnistyp; or
BitwiseXor Bitweises Exklusiv-Oder BitwiseXor(a: Typ; b: Typ): Ergebnistyp; xor

Die generelle Funktionsweise ist recht simpel. Nehmen wir an, wir möchten den Operator für die Addition überladen. Dann ist dies auf unser Beispiel mit TMegaInt übertragen folgendermaßen möglich:

unit test;

interface
  type
    TMegaInt = record
     
      class operator add(a, b: TMegaInt): TMegaInt;
      ...
    end;

    ...

implementation
  class operator TMegaInt.add(a, b: TMegaInt): TMegaInt;
  begin
    ...
  end;

Wir haben also einen Datentyp (einen Record oder eine Klasse), für welchen wir die Operatoren überladen möchten. Unterhalb der eigentlichen Record-Felder werden die zu überladenden Operatoren aufgelistet (den notwendigen Prototypen kann man der obigen Tabelle entnehmen) und dann im implementation-Teil entsprechend implementiert. Sie funktionieren dabei recht ähnlich wie Funktionen, denn der Rückgabewert wird ebenfalls in Result übergeben. Die Typen müssen nicht gezwungenermaßen übereinstimmen, so ist es durchaus möglich, dass aus einer speziellen Multiplikation zweier TMegaInts plötzlich ein Extended herauskommt (Nur als mögliches Beispiel).
Übrigens ist es durchaus möglich, dass auch die überladenen Operatoren innerhalb des Recors noch einmal überladen werden. Das ist häufig dann der Fall, wenn die implizite oder explizite Typumwandlung genutzt wird. Dabei ist das Schlüsselwort overload jedoch nicht notwendig:

unit test;

interface
  type
    TMegaInt = record
      ...
      class operator implicit(a: TMegaInt): String;
      class operator implicit(a: TMegaInt): Extended;
      ...
    end;

Implizite und explizite Typumwandlung

Der eine oder andere mag jetzt mit der impliziten und expliziten Typumwandlung schon etwas anfangen können. Für diejenigen, denen diese Begriffe nichts sagen, betrachten wir die Typumwandlung kurz.
Als grobe Faustregel gilt, dass bei einer impliziten Typumwandlung keine Daten verloren gehen, bei der expliziten Typumwandlung aber schon. So ist die Umwandlung von Byte zu Integer ohne Verluste von Informationen durchzuführen, da ein Integer definitiv ein Byte aufnehmen kann. Umgekehrt ist dies jedoch nicht ohne weiteres möglich, da ein Byte nicht genügend Platz besitzt, um einen Integer aufzunehmen. Die Folge wäre, dass man Ziffern von dem Integer abschneiden müsste, damit er in den Byte-Datentyp passen würde. Genauso würde es sich mit Fließkommazahlen verhalten. Einen Integer in eine Fließkommazahl umzuwandeln ist möglich, ohne dass sich der Wert des ursprünglichen Integers ändert. Wandelt man aber eine Fließkommazahl in einen Integer um, so gehen eventuelle Nachkommastellen verloren.
Betrachten wir dazu kurz ein Beispiel einer impliziten Typumwandlung.

var
  i: Integer;
  b: Byte;

begin
  b := 42;
  i := b;
end.

Nun auch ein Beispiel für eine explizite Typumwandlung:

var
  c: char;
  b: byte;

begin
  b := 42;
  c := char(b);
end.

Jetzt mag vielleicht auch auffallen, dass die implizite Typumwandlung ohne zusätzlichen Quellcode geschieht. Zumeist handelt es sich bei diesem Typumwandlungen um diejenigen Konvertierungen, die man beim Programmieren gar nicht erst bemerkt, da sich Delphi von selbst darum kümmert.
Die expliziten Typumwandlungen hingegen benötigen immer zusätzlichen Quellcode und müssen vom Programmierer in der Regel deutlich angegeben werden. Immerhin besteht die Möglichkeit, dass dann Daten verloren gehen. Man sollte sich den Schritt zur expliziten Typumwandlung also genau überlegen.

Operatorprioritäten

Bevor es zur eigentlichen Anwendung geht, schauen wir uns zunächst noch kurz die sogenannten Operatorprioritäten an. Es handelt sich im Prinzip dabei um die Vorschriften, welche Operatoren zuerst ausgeführt werden und welche erst später. Das Prinzip kennen wir auch aus der Schule: Punktrechnungen werden vor Strichrechnungen ausgeführt. Dies kann wiederum durch Klammern verändert werden usw. Die folgende Tabelle zeigt, in welcher Reihenfolge Delphi die Operatoren auswertet. Wenn sich zwei Operatoren auf der gleichen Höhe befinden, dann wird ein Ausdruck von links nach rechts ausgewertet.

@, not
*, /, div, mod, and, shl, shr, as
+, -, or, xor
=, <>, <, >, <=, >=, in, is

Die Tabelle ist übrigens absteigend sortiert, so dass die Operatoren mit der höchsten Priorität ganz oben stehen. In der Liste befinden sich zudem die Operatoren @, as, in und is, die jedoch nur der Vollständigkeit halber aufgeführt sind. Wir verfügen leider nicht über die Möglichkeit, auch diese Operatoren zu überladen.
Diese Operatorprioritäten sind übrigens fester Bestandteil von Delphi und können von uns nicht abgeändert werden. Möchten wir also die Auswertungsreihenfolge ändern, bleibt uns nur die Möglichkeit des Klammernsetztens.