ytwinkyWiki : Erste Hilfe zur Benutzung von Type's als Objekt

ytwinkyWiki :: Categories :: PageIndex :: RecentChanges :: RecentlyCommented :: Login/Register
Erste Hilfe zur Benutzung von Type's als Objekt
(Übersetzung 2006 von ytwinky, MD, aber ohne Gewähr..)
Einleitung

Dies Tutorial richtet sich an Benutzer, die mehr über die neuen Möglichkeiten wissen wollen, die zu Type hinzugefügt wurden, allgemein bezogen als 'Types As Objects' und 'der ganze OOP-Kram'. Es soll durch diese neuen Möglichkeiten führen und richtet sich somit an Benutzer, die das noch nicht ganz verstehen, aber es lernen möchten. Ein Type in FreeBASIC ist an 'Sammel'-Datentyp, wie eine struct in C oder ein Record in Pascal.
Hier ist mal ein ganz kurzes Beispiel einer typischen Type-Anwendung:

Type Person_Info
Vorname As String
Nachname As String
Hausnummer As Integer
Strasse As String
Ort As String
End Type

In diesem Beispiel wird ein Type wie eine Art Behälter der betreffenden Daten verwendet; es könnte hier ein Eintrag in einem Adreßbuch sein. Mit den neuen Möglichkeiten, kann er eher wie eine Klasse in C++ benutzt werden, da er mehr 'kann', als nur einfache Datenfelder zu enthalten. It becomes a way to express an idea of an object, und macht so objekt-orientiertes Programmieren viel einfacher. Wir werden uns diese neuen Möglichkeiten jetzt ansehen.

Property

Wir beginnen mit einem Blick auf Property. Wenn eine Property zu einem Type hinzugefügt wird, kann auf sie wie ein gewöhnliches Element zugegriffen
werden, doch was passiert ist, daß anstatt nur zu setzen oder holen, eine Funktion aufgerufen wird. Sehen wir uns ein Beispiel an:

Type bar
Declare Property x() As Integer
Declare Property x(Byval n As Integer)
p_x As Integer
End Type

Property bar.x() As Integer
Print "bar.x()"
Property = p_x
End Property

Property bar.x(Byval n As Integer)
Print "bar.x(ByVal n As Integer)"
p_x = n
End Property

'---

Dim foo As bar

foo.x = 5
Print foo.x

Wir schließen in unsere TYPEs Deklarationen für Property ein, sie sind vergleichbar mit normalen Funktions-Deklarationen. Die Erste dekariert einen Holer, die Zweite einen Setzer. Das p_x Element ist nur ein normales Element.

Als nächstes schreiben wir den Code für die Properties, wieder ist die Syntax sehr ähnlich wie die bei normalen Funktionen. Beachte die Art, wie der Wert zurückgegeben wird: Anstatt von 'Function = Wert', schreiben wir 'Property = Wert'. Natürlich geht 'Return Wert' genauso. Beachte auch, daß der Zugriff auf das Element auch direkt mit p_x funktioniert, das Schlüsselwort this läßt sich ebenfalls verwenden, z.B.: 'this.p_x = n', die Benutzung von this ist normalerweise nicht nötig, kann aber bei mehrdeutigen Umständen hilfreich sein.

Dann folgt etwas Test-Code, der zeigt, wie wir die Property wie ein ganz normales Element benutzen können. Wird das Programm ausgeführt, zeigt es auf dem Schirm auch an, daß der Property Holen/Setzen-Code aufgerufen wurde.

Nun ist dieser Code reichlich trivial, aber wer die Idee erstmal verstanden hat, wird sehen, daß es gute Zwecke erfüllen kann. Vorstellbar wäre z.B. das Schreiben eines GUI und der Type entspricht einem Button auf dem Schirm. Möglich wäre dann 'button.text = "Hallo Welt!"' und mit dem Property-Code wird dann der Bildschirm aktualisiert. Oder vielleicht wird der Type benutzt, um irgendeine List zu verwalten, da wäre dann möglich 'list.size += 10' mit dem Code in der Property, der die Liste vergrößert.

Constructor/Destructor

Constructoren sind Funktionen, die aufgerufen werden, wenn der Type erstellt wird, also z. B. bei Dim. Ein Destructor ist eine Funktion, die aufgerufen wird, wenn der Type den Scope verläßt, das könnte sein, wenn das Programm endet(für einen Type im Hauptprogramm), oder wenn eine Funktion beendet wird(bei einem lokalen Type). Siehe folgendes Beispiel, erweitert aus dem letzten.

Type bar
Declare Constructor()
Declare Destructor()
Declare Property x() As Integer
Declare Property x(Byval n As Integer)
p_x As Integer Ptr
End Type

Constructor bar()
Print "Constructor bar()"
p_x = Allocate(Sizeof(Integer))
*p_x = 10
End Constructor

Destructor bar()
Print "Destructor bar()"
DeAllocate(p_x)
End Destructor

Property bar.x() As Integer
Print "bar.x()"
Property = *p_x
End Property

Property bar.x(Byval n As Integer)
Print "bar.x(ByVal n As Integer)"
*p_x = n
End Property

'---

Dim foo As bar

Print foo.x
foo.x = 5
Print foo.x

Wieder ist die Syntax irgendwie ähnlich der bei normalen Funktionen. Beachte, daß hier p_x zu einem Integer Ptr geändert wurde. Der Constructor reserviert den Speicher für this, wenn foo erzeugt wird und vergibt einen Default-Wert dafür, dann wird die Speicherreservierung zurückgenommen, wenn es zerstört wird. So können Constructoren und Destructoren zum Erstellen von Sachen benutzt werden, was sie dann wieder beseitigen, wenn es erledigt ist.
Es ist wieder ein einfaches Beispiel, aber z.B. bei irgendeiner Liste, kann die Liste erstellt werden und auch wieder gelöscht werden bei Ende.
Da kann ganz schön praktisch sein.

Methods

In einem Type können auch reguläre Subs und Functions vorkommen, in mancher
Ausdrucksweise werden sie dann Methoden genannt. Wir führen unser Beispiel fort:

Type bar
Declare Constructor()
Declare Destructor()
Declare Property x() As Integer
Declare Property x(Byval n As Integer)
Declare Sub Mul5()
Declare Function Addr() As Integer Ptr
p_x As Integer Ptr
End Type

Constructor bar()
Print "Constructor bar()"
p_x = Allocate(Sizeof(Integer))
*p_x = 10
End Constructor

Destructor bar()
Print "Destructor bar()"
DeAllocate(p_x)
End Destructor

Property bar.x() As Integer
Print "bar.x()"
Property = *p_x
End Property

Property bar.x(Byval n As Integer)
Print "bar.x(ByVal n As Integer)"
*p_x = n
End Property

Sub bar.mul5()
*p_x *= 5
End Sub

Function bar.Addr() As Integer Ptr
Function = p_x
End Function

'---

Dim foo As bar

Print foo.x
foo.x = 5
Print foo.x
foo.mul5()
Print foo.x
Print "Adresse p_x zeigt auf", foo.Addr()

Diesmal haben wir eine Sub hinzugefügt, die den Integer auf den p_x zeigt mit fünf multipliziert und eine Funktion, die die Speicher-Adresse holt, die der Pointer enthält.

Private/Public

Standardmäßig sind alle Elemente des bar-types public, d.h. wir können sie lesen/schreiben oder aufrufen. Manchmal jedoch ist es besser, sie private zu machen. Nehmen wir z.B. unser Element p_x. Momentan können wir schreiben Print *foo.p_x und damit den Wert ausgeben, auf den es zeigt. Wir könnten es private machen, damit nur die Elemente vom bar-type (Constructor, Destructor, Property, und die Methoden) darauf zugreifen können. So stellen wir sicher, daß wir nur auf p_x zugreifen können, durch die Art, wie wir es benutzen. Würden wir z.B. 'DeAllocate(foo.p_x)' iim Hauptprogramm schreiben, würde der Destructor, wenn er läuft, versuchen, ihn erneut freizugeben, bekannt als 'doppelte Freigabe'. Wir ändern die Type-Deklaration wie folgt:

Type bar
Declare Constructor()
Declare Destructor()
Declare Property x() As Integer
Declare Property x(Byval n As Integer)
Declare Sub Mul5()
Declare Function Addr() As Integer Ptr
Private:
p_x As Integer Ptr
End Type

Wir fügen 'Print *foo.p_x' im Hauptprogramm ein und versuchen, es zu kompilieren. Es wird eine Meldung vom fbc kommen 'error 173: Illegal member access, found 'p_x' in 'Print *foo.p_x'', dies zeigt, daß der Compiler wirklich auf die Tatsache stößt, daß wir p_x private gemacht haben. Wird 'private:' oder 'public:' benutzt, folgen alle Elemente nach dieser Anweisung auch dieser Regel.
Hier ist ein eher sinnfreies Beispiel, einfach, um die Syntax zu zeigen:

Type bar
Private:
a As Integer
b As Integer
Public:
c As Integer
d As Integer
Private:
e As Integer
End Type

Im obigen Type sind die Elemente 'a', 'b' und 'e' private, 'c' und 'd' sind public.

Operator overloading (Operatoren überladen)

'Operator overloading' ist ein Weg, dem Compiler mitzuteilen, was zu tun ist, falls wir irgendeine Operation ausführen wollen, die unseren Type betrifft. Z.B.:

Type bar
n As Integer
End Type

Dim As bar x, y, z

z = x + y

Nun würde der Compiler normalerweise einen Fehler melden, wenn er hierauf trifft, da er keine Ahnung hat, wie zwei Types addiert werden könnten, aber wir können festlegen, was wir da durchgeführt haben möchten. So geht's:

Type bar
n As Integer
End Type

Operator +(Byref lhs As bar, Byref rhs As bar) As bar
Operator = Type(lhs.n + rhs.n)
End Operator

Dim As bar x, y, z

x.n = 5
y.n = 10
z = x + y
Print z.n

In diesem Beispiel, dienen lhs und rhs dem Bezug auf die linke und rechte Seite des Operators. Beachte auch den Ausdruck Type(lhs.n + rhs.n), er erstellt den Type, der zurückgegeben wird. Wäre der Type von der Form:

Type bar
x As Integer
y As Integer
z As Integer
End Type

Dieser wäre dann zu erzeugen mit Type(xpart, ypart, zpart)

Die meisten oder alle Operatoren lassen sich überladen und die meisten von ihnen sind binär-Operatoren, d.h. sie haben zwei Operanden wie das + im obigen Beispiel. Einige sind unäre Operatoren, die nur die rechte Seite haben, wie Not oder das unäre Minus. Das würde wohl so gemacht werden: 'Operator NOT(ByRef rhs As bar) As bar'

Es gibt einige Spezialfälle, wo sie innerhalb des Types deklariert werden müssen, das sind die Zuweisungsoperatoren und Casts.

Zuweisungsoperatoren sind Sachen wir += -= mod= etc und auch Let.
Let wird benutzt in Anweisungen wie:

Dim As bar foo
Dim As Integer x
foo = x

Und Casts sind ungefähr das Umgekehrte, sie werden verwendet, beim Umwandeln in einen anderen Datentyp wie:

Dim As bar foo
Dim As Integer x
x = foo

Es folgt ein kurzs Beispiel für Let und Cast:

Type bar
n As Integer
Declare Operator Let(Byref rhs As Integer)
Declare Operator Let(Byref rhs As String)
Declare Operator Cast() As String
End Type

Operator bar.Let(Byref rhs As Integer)
n = rhs
End Operator

Operator bar.Let(Byref rhs As String)
n = Val(rhs)
End Operator

Operator bar.Cast() As String
Operator = Str(n)
End Operator

Operator +(Byref lhs As bar, Byref rhs As bar) As bar
Operator = Type(lhs.n + rhs.n)
End Operator

Dim As bar x, y, z

x = 5
y = "10"
z = x + y
Print z

Es müssen verschiedene Lets und Casts vorhanden sein für jeden Datentyp, der vom Programm unterstützt werden soll. Die Operatoren, die im Type deklariert werden müssen, werden 'nonstatic' genannt, diejenigen, wo das nicht nötig ist, nennt man 'global. Es gibt einen technischen Grunddafür: Die 'nonstatic'-Operatoren müssen wissen, auf welche Instanz (in technischem Jargon, in unserem Beispiel oben könnten wir sagen, daß x eine Instanz von bar ist) des Types sie sich beziehen und dies wird durch ein verstecktes 'this'-Element erreicht. Dieser versteckte 'this'-Bezug legt fest, wie die anderen Elemente, wie Operatoren und Methods erkennen, welcher Instanz des Types der Aufruf gilt. Die meisten Operatoren lassen sich überladen, hier ist eine Liste derjenigen, bei denen es momentan funktioniert:

Zuweisungsoperatoren:
let, +=, -= *=, /=, \=, mod=, shl=, shr=, and=, or=, xor=, imp=, eqv=, ^=
Unäre Operatoren:
-, not, @, *, ->
Binäre Operatoren:
+, -, *, /, \, mod, shl, shr, and, or, xor, imp, eqv, ^, =, <>, <, >, <=, >=

Overloaded Constructors/Methods

Wie bei normalen Funktionen können unser Type Constructor und die Methoden überladen werden. Für Constructoren ist dies ein Weg, Einzelheiten anzugeben, wie diese Instanz erzeugt werden sollte. Hier ist ein kurzes Beispiel:

Type bar
Declare Constructor()
Declare Constructor(Byval initial_val As Integer)
x As Integer
End Type

Constructor bar()
x = 10
End Constructor

Constructor bar(Byval initial_val As Integer)
x = initial_val
End Constructor

Dim foo As bar
Print foo.x

Dim baz As bar = bar(25)
Print baz.x

Der erste Constructor, der, der keine Argumente hat, ist der Standard-Constructor. So wird foo.x auf einen Anfangswert von 10 gesetzt. Aber wir haben ja noch einen anderen Constructor angegeben, der auch einen Anfangswert übernimmt. Beachte die Art, wie wir das bewerkstelligen 'Dim baz As bar = bar(25)'. Der Standard-Constructor kann auch weggelassen werden, aber dann muß jedesmal ein Anfangswert mit dem Constructor, der einen Wert übernimmt, angegeben werden. Es kann keinen überladenen Destructor geben, weil es keinen Weg gibt, manuell festzulegen, welcher aufgerufen würde.

Überladene Methoden sind sehr ähnlich:

Type bar
Declare Sub foo()
Declare Sub foo(Byval some_value As Integer)
Declare Sub foo(Byref some_value As String, Byval some_other As Integer)
x As Integer
End Type

Sie funktionieren genauso, wie normal überladene Funktionen.

Schlußbemerkung
------------------------

Ich hoffe, dies Tutorial war hilfreich, obwohl immernoch ein paar zu lernende Dinge übriggeblieben sind, aber wer dies hier erstmal verstanden hat, sollte nicht allzuviel Schwierigkeiten beim weiteren Vorgehen haben. Es gibt noch ein bißchen mehr Info im Wiki und in den Foren und natürlich auch in Teil 2 dieses Tutorials - Beginners Guide to Types as Objects (Part 2)

More reading(Das übersetz ich nicht, da die Links auf das englische Wiki verweisen :D)

  • Property
  • Constructor
  • Destructor
  • Operator
  • This
  • Anonymous Types
  • Types as Objects
  • Public:
  • Private:
  • Protected:

There are no comments on this page. [Add comment]

Page History :: 2006-12-30 21:05:41 XML :: Translator: ytwinky :: Search:
Valid XHTML 1.0 Transitional ∞ :: Valid CSS ∞ :: Powered by Wikka Wakka Wiki 1.1.6.0 ∞


ytwinkyWiki : Erste Hilfe zur Benutzung von Type's als Objekt2

ytwinkyWiki :: Categories :: PageIndex :: RecentChanges :: RecentlyCommented :: Login/Register

Erste Hilfe zur Benutzung von Type's als Objekt (Teil 2)

Einleitung

Willkommen beim 2. Teil des Tutorials. Für diesen Teil sollte Teil 1 auf alle Fälle gelesen sein, die Beispiele ausprobiert und möglichst durch eigene Tests erweitert worden sein. Es folgen nun ein paar Themen, die in Teil 1 nicht enthalten waren.

Indexed Property

Eine Indexed Property ist eine Property, die sich wie ein Array verhält, außer daß wie bei einer regulären Property, eine Funktion aufgerufen wird, wenn darauf zugegriffen wird. Zum Anfang ein sehr kurzes Beispiel, nur um die Syntax zu zeigen.

Type foo
Declare Property bar(Byval index As Integer, Byval value As Integer)
Declare Property bar(Byval index As Integer) As Integer
dummy As Integer
End Type

Property foo.bar(Byval index As Integer, Byval value As Integer)
Print "Property set, index=" & index & ", value=" & value
End Property

Property foo.bar(Byval index As Integer) As Integer
Print "Property get, index=" & index
Property = 0
End Property

Dim baz As foo

baz.bar(0) = 42
Print baz.bar(0)

Wie man sieht, ist die Deklaration unserer Indexed Property ziemlich ähnlich zu einer regulären, außer daß wir diesmal ein Argument für den Index angegeben haben. Der Type enthält ein Dummy-Integer Element, weil er wenigstens ein Datenelement haben muß. Wie man sieht, wird die Property dann benutzt mit (0) als Hinweis, daß wir das nullte Element holen/setzen wollen, so wie wir das auch bei einem normalen Array tun würden.
Nun folgt ein etwas sinnvolleres Beispiel mit Beschreibung:

Type foo
Declare Constructor(Byval num_elements As Integer)
Declare Destructor()
Declare Property bar(Byval index As Integer, Byval value As Integer)
Declare Property bar(Byval index As Integer) As Integer
private:
x As Integer Ptr
size As Integer
End Type

Constructor foo(Byval num_elements As Integer)
x = CAllocate(num_elements * sizeof(Integer))
size = num_elements
End Constructor

Destructor foo()
DeAllocate(x)
End Destructor

Property foo.bar(Byval index As Integer, Byval value As Integer)
If (index >= 0) And (index < size) Then
x[index] = value
Else
Error 6
End If
End Property

Property foo.bar(Byval index As Integer) As Integer
If (index >= 0) And (index < size) Then
Property = x[index]
Else
Error 6
End If
End Property

Dim baz As foo = foo(10)

baz.bar(1) = 42
Print baz.bar(1)

Diesmal wurden ein Constructor und Destructor hinzugefügt, die einen dynamischen Speicherbereich x reservieren und freigeben mit der Anzahl der Elemente, die im Constructor steht. Dann, wenn die Property-Funktionen ausgeführt werden, wird beim Index geprüft, ob er innerhalb der Array-Grenzen liegt und falls ja, wird das angeforderte Holen/Setzen ausgeführt. Ist der angegebene Index außerhalb der Grenzen, tritt 'Error 6' auf, damit das Programm mit FB's 'out of bounds error' abgebrochen werden kann, dies kann natürlich durch eigene Fehlerbehandlungsroutinen ersetzt werden. Ausprobieren läßt sich das, indem der Code 'baz.bar(1) = 42' durch 'baz.bar(10) = 42' ersetzt wird, da wir nur 10 Elemente(Index 0-9) angegeben haben, tritt dies dann in Aktion.

Copy Constructor.

Ein Copy Constructor ist ein spezieller Constructor-Typ, der zum Erstellen einer Kopie eines bestehenden Objektes dient. Mit einem Code wie diesem:

Type foo
...
End Type

Dim As foo a
Dim As foo b = a

Was passiert ist, daß FreeBASIC automatisch versteckten Code zum Erstellen von b(als Kopie von a) erzeugt. Dies ist der Standard-Copy Constructor, und kopiert einfach nur die Datenfelder(Elemente) 'rüber. Wir können auch unseren eigenen Copy Constructor definieren. Hier folgt ein kurzes Schnipsel, das zeigt, wie er deklariert wird.

Type foo
Declare Constructor(Byref obj As foo)
...
End Type

Zufällig ist das gerade jetzt hilfreich, aus einem Grund, der jetzt erklärt wird.[A.d.Ü:Das ist nicht holperig..]

Deep/Shallow Copy

Im vorigen Beispiel haben wir mit dem Code 'Dim As foo b = a', das benutzt, was man Shallow Copy nennt, die Datenfelder werden einfach nur kopiert, manchmal ist die jedoch nicht erwünscht. Was würde passieren, wenn eins der Elemente ein Pointer ist? Nun, die Adresse, auf die der Pointer zeigt, würde kopiert werden, mit dem Effekt, daß beide Objekte auf denselben Speicherbereich zeigen. Ein Beispiel dafür folgt:

Type foo
x As Integer Ptr
End Type

Dim As foo a

a.x = Allocate(sizeof(Integer))
*a.x = 42

Dim As foo b = a

Print *a.x, *b.x

*a.x = 420

Print *a.x, *b.x

DeAllocate(a.x)

Weil beide auf den selben Speicherbereich zeigen, betreffen Änderungen bei einem auch den anderen. Wie schon im vorigen Kapitel beim Copy Constructor erklärt, erzeugt FreeBASIC standardmäßig den Code für shallow copies. Das ist auch wahr bei einer Zuweisung wie:

Dim As foo a, b

b = a

Auch in diesem Fall erzeugt FreeBASIC einen Standard-Zuweisungsoperator (Let) zum Durchführen der Shallow Copy. Um 'deep copies' zu machen, müssen wir einen Copy Constructor definieren und einen Zuweisungsoperator, der überladen wird, um unseren Type zu akzeptieren.
Hier ist ein Benutzungsbeispiel:

Type foo
Declare Constructor()
Declare Constructor(Byref obj As foo)
Declare Destructor()
Declare Operator Let(Byref obj As foo)
x As Integer Ptr
End Type

Constructor foo()
Print "Default ctor"
x = CAllocate(sizeof(Integer))
End Constructor

Constructor foo(Byref obj As foo)
Print "Copy ctor"
x = CAllocate(sizeof(Integer))
*x = *obj.x
End Constructor

Destructor foo()
Print "dtor"
DeAllocate(x)
End Destructor

Operator foo.Let(Byref obj As foo)
Print "Let"
*x = *obj.x
End Operator

Dim As foo a

*a.x = 42

Dim As foo b = a 'Uses the copy constructor

Print *a.x, *b.x

*a.x = 420

Print *a.x, *b.x

Man erkennt, daß der Copy Constructor in der Zeile 'Dim As foo b = a' aufgerufen wird und diesmal reservieren wir etwas Speicher und kopieren die Daten in den neuen Copy Constructor, sodaß wir x in einem Objekt ändern können, ohne dies auch im anderen zu tun.
Wenn wir den Haupt-Code wie folgt ändern:

Dim As foo a, b

*a.x = 42
b = a 'Der Zuweisungsoperator (Let) wird diesmal hier benutzt

Print *a.x, *b.x

*a.x = 420

Print *a.x, *b.x

dann wird diesmal der Zuweisungsoperator benutzt. Beachte, daß wir im Zuweisungsoperator-Code keinen Speicher zu reservieren brauchen, da dies schon der Standard-Constructor gemacht hat, wir müssen also nur die Daten kopieren. Die Zeile '*x = *obj.x' führt diese Kopie durch. Wenn wir etwas Komplizierteres hätten, wie ein dynamisches Speicher-Array, dann müßten wir auch den Speicher neu reservieren, damit er die korrekte Größe für die zu kopierenden Daten hat. Hier ist ein fortgeschritteneres Beispiel, um das zu zeigen:

Type foo
Declare Constructor(Byval num_elements As Integer)
Declare Constructor(Byref obj As foo)
Declare Destructor()
Declare Operator Let(Byref obj As foo)
x As Integer Ptr
size As Integer
End Type

Constructor foo(Byval num_elements As Integer)
Print "Default ctor"
x = CAllocate(sizeof(Integer) * num_elements)
size = num_elements
End Constructor

Constructor foo(Byref obj As foo)
Print "Copy ctor"
x = CAllocate(sizeof(Integer) * obj.size)
size = obj.size
For i As Integer = 0 To size - 1
x[i] = obj.x[i]
Next i
End Constructor

Destructor foo()
Print "dtor"
DeAllocate(x)
End Destructor

Operator foo.Let(Byref obj As foo)
Print "Let"
x = ReAllocate(x, sizeof(Integer) * obj.size)
size = obj.size
For i As Integer = 0 To size - 1
x[i] = obj.x[i]
Next i
End Operator

Dim As foo a = foo(5)

a.x[0] = 42
a.x[1] = 420

Dim As foo b = a 'benutzt den copy constructor

Print a.x[0], a.x[1], b.x[0], b.x[1]

b.x[0] = 10
b.x[1] = 20

Print a.x[0], a.x[1], b.x[0], b.x[1]

b = a 'benutzt jetzt den Zuweisungsoperator

Print a.x[0], a.x[1], b.x[0], b.x[1]

Das mag auf den ersten Blick sehr komplex erscheinen, aber es ist es wert, nochmal gelesen zu werden und mit den Beispielen zu experimentieren. Es ist nicht mehr sehr geheimnisvoll, wenn man sich dran gewöhnt hat.

Übergabe von Objekten an Funktionen ByVal

Die Idee von 'deep' und 'shallow copies' trifft auch zu für die Übergabe eines Objekts an eine Funktion byVal. Wird eine Referenz zu einem Objekt (ByRef) übergeben, kann das Objekt geändert werden, und diese Änderungen bleiben erhalten, aber die Übergabe geht auch byVal, d.h. das Objekt kann geändert werden, ohne daß diese Änderungen außerhalb der Funktion erhalten bleiben. Wird ein Objekt einer Funktion byVal übergeben, wird eine neue Kopie erstellt und wenn das Object einen Copy Constructor hat, wird dieser ausgeführt, wenn nicht, wird die versteckte 'shallow copy' benutzt. Ist die Funktion dann beendet, wird der Destructor des Objekts ausgeführt.

New/Delete

New und Delete sind spezielle Operatoren zum dynamischen Zuweisen von Speicher und zur Freigabe. Da sie mit dynamischem Speicher arbeiten, werden sie mit Pointern benutzt. In allen Beispielen haben wir bis jetzt einfach Dim zum Erstellen unserer Objekte benutzt. Damit werden sie im Stack-Bereich erstellt, aber mit New können wir sie dynamisch erzeugen, das kann mehr Flexibilität geben, so wie das Benutzen von Allocate/ DeAllocate mit normalem Speicher. Weiter ist es wichtig, zu wissen, daß bei New nicht geprüft werden muß, ob der Pointer NULL ist nach einem New, wie man es vielleicht bei Allocate tun müßte. Geht New schief, ruft das eine Exception hervor, die dann das Programm beendet. In neueren Versionen von FreeBASIC, wird es wahrscheinlich eine Art 'try..catch'-Mechanismus geben, damit solche Exceptions besser abgefangen werden können, aber zum jetzigen Zeitpunkt ist das noch nicht eingebaut.

Es gibt zwei verschiedene Arten von New/Delete. Die erste Art erzeugt einfach ein einziges Element oder Objekt, z.B.:

Dim As Integer Ptr foo = new Integer

*foo = 1
Print *foo

Delete foo

Es wird ein neuer Integer erzeugt und dann mittels Delete wieder freigegeben. Nicht vergessen: Es werden Ptr benutzt, denn es handelt sich um dynamischen Speicher. Für einfache Datentypen kann auch ein Standardwert mot Klammern hinter dem Datentyp angegeben werden, z.B.:

Dim As Integer Ptr foo = new Integer(42)

Print *foo

Delete foo

Das geht auch beu UDT's mit nur einfachen Datafeldern:

Type foo
x As Integer
y As Integer
End Type

Dim As foo Ptr bar = new foo(1, 2)

Print bar->x, bar->y

Delete bar

Diese Initialisierung funktioniert nicht bei komplexeren Types (mit Constructoren/Destructoren etc.), ein sinvolles Feature ist jedoch, daß bei New/Delete mit Objekten auch der Constructor und Destructor aufgerufen wird, siehe folgendes Beispiel:

Type foo
Declare Constructor()
Declare Destructor()
x As Integer
y As Integer
End Type

Constructor foo()
Print "ctor"
End Constructor

Destructor foo()
Print "dtor"
End Destructor

Dim As foo Ptr bar = new foo

Delete bar

Man wird sehen, daß der Constructor und Destructor des Objekts augerufen werden.

Die zweite Art von New/Delete ist zum Erstellen von Arrays, hier wird jetzt die Anzahl der Elemente nach dem Datentyp in eckigen Klammern '[]' angegeben. Wird diese Array-Version verwendet, muß auch 'delete[]' anstatt von 'delete' genommen werden, so daß FreeBASIC weiß, daß ein Array gelöscht wird, hier ist ein einfaches Beispiel mit einem Integer-Typ:

Dim As Integer Ptr foo = new Integer[20]

foo[1] = 1
Print foo[1]

Delete[] foo

Es erzeugt ein dynamisches Array mit 20 Integer Elementen(d.h. 0..19). Es sollte beachtet werden, daß dies sich von Allocate unterscheidet, daß die Anzahl der Bytes als Argumentbenutzt; mit New, sollte die Anzahl der Elemente angegeben werden. Die Array-Methode geht genauso für Objekte:

Type foo
Declare Constructor()
Declare Destructor()
x As Integer
y As Integer
End Type

Constructor foo()
Print "ctor"
End Constructor

Destructor foo()
Print "dtor"
End Destructor

Dim As foo Ptr bar = new foo[3]

Delete[] bar

Wird dieser Code ausgeführt, werden drei Constructor/Destructor-Paare ausgeführt, da wir ein Array mit drei Instanzen von foo erstellt haben.

Es darf nie vergessen werden, Delete oder Delete[] für jede Speicherzuweisung mit New aufzurufen, sonst entsteht ein Speicherleck, so wie auch DeAllocate aufgerufen werden muß für jeden Speicher, der mit Allocate zugewiesen wird.

Name Mangling

Name Mangling, oder auch Name Decoration, ist etwas, was im Verborgenen geschieht, auf einer niederen Ebene und deshalb ist es auch nicht essentialbesonders wichtig, etwas darüber zu wissen. Der Grund für das Name Mangling ist, Probleme zu lösen, wenn mehrere Funktionen denselben Namen haben, was passiert, wenn Funktionen überladen werden oder wenn sie Teil eines Types sind. Nehmen wir z.B. die überladenen Subs, wie folgt:

Sub foo overload ()

End Sub

Sub foo(Byval i As Integer)

End Sub

Hätten wir kein Name Mangling, wären beide auf niederer Ebene bekannt als FOO, was zu einem Namensfehler führen würde, so müssen sie also gekennzeichnet werden, um herauszufinden, welche denn nun bei einem Aufruf benutzt werden soll. Beim ersten Sub erstellt der Compiler eine Sub namens _Z3FOOv und bei der zweiten eine Sub namens _Z3FOOi. Der Compiler 'erinnert' sich dann hieran und wählt die richtige Sub für den Aufruf, jenachdem, wie sie aufgewrufen wird, z.B. 'foo()' ruft also _Z3FOOv und 'foo(1)' ruft dann _Z3FOOi auf. Wir können hier sogar herausfinden, daß das 'v' für void (ohne Argument)steht und 'i' steht für Integer. Die ganzen Details vom Name Mangling sind ziemlich Komplex und nicht nei allen Compilern gleich, diee Microsoft Compiler benutzen [selbstverständlich :D A.d.Ü] ein anderes Name Mangling-Schema als GNU-Compiler, andere Compiler benutzen benutzen auch ganz andere Schemata. Die Hauptsache, die wir wissen müssen, ist, daß FreeBASIC der GCC 3.x ABI (Application binary interface) folgt, was bedeutet, daß jede überladene Funktion oder komplexe Type nur zu den Compilern kompatibel ist, die dasselbe Schma benutzen. Das ist eine unglückliche Einschränkung, aber es ist nicht unbedingt ein FreeBASIC Problem. Es ist bei allen Compilern, die weitergehende Möglichkeiten verwenden und sogar wenn alle Compiler-Authoren einem allgemeinen Name Mangling-Scheme zustimmten, gäbe es immer noch genug Punkte für Inkompatibilitäten.

Implicit this

Wiedermal ist es nicht unbedingt wichtig zu wissen und meistens passierts im Verborgenen auf einer niederen Ebene. Wenn eine Funktion eines Objekts aufgerufen wird, dann wird als Erstes ein versteckter Parameter übergeben, sodaß die Funktion weiß, welche Instanz des Objekts gemeint ist. Das stimmt auch bei den Property/Constructor/Destructor/Operator-Elementen. Wenn wir uns das folgende Beispiel ansehen:

Type foo
Declare Sub bar(Byval n As Integer)
x As Integer
End Type

Sub foo.bar(Byval n As Integer)
x = n
End Sub

Dim baz As foo
baz.bar(5)

Was wirklich im Verborgenen geschieht, ist etwas im Wesentlichen äquivalent hierzu:

Type foo
x As Integer
End Type

Sub foo_bar(Byref _this As foo, Byval n As Integer)
_this.x = n
End Sub

Dim baz As foo
foo_bar(baz, 5)

Diese Method benutzt ein explicites 'this', das oft in Sprachen benutz wird, die nicht die Fähigkeiten besitzen, es einfacher zu machen. OOP ist wirklich nur ein Konzept, das meistens in fast jeder Sprache umgesetzt werden kann. Einige Sachen sind schwieriger einzubauen, wie Constructoren, man müßte extra eune 'create' oder 'init' Funktion aufrufen. Für ein paar Sachen wie private/public Unterscheidung, ist es sogar noch schwieriger oder unmöglich, da der Compiler nicht weiß, wie er sie durchsetzen soll. Der Grund, OOP-Möglichkeiten einer Sprache hinzuzufügen ist, hiervon eine Menge zu verstecken und mit einfacherer Syntax den Gebrauch zu versüßen oder transparenter zu gestalten, so wie die Art wie Properties als gewöhnliche Datelemente verwendet werden können, eher als Funktionen, obwohl das ist, was sie wirklich sind.

Hints for debugging/profiling

Beim Gebrauch des GDB oder anderer Debugger und des gprof Profiling-Tools wird die Information in 'C++ Syntax' angezeigt und alle Variablen-Namen und andere Symbole werden großgeschrieben, hier ist eine kurze Übersicht, die helfen soll, zu verstehen, was dirt angezeigt wird:

Dies ist ein Beispiel-Type:

Type bar
Declare Constructor()
Declare Constructor(Byref obj As bar)
Declare Constructor(Byval n As Integer)
Declare Destructor()
Declare Operator Cast() As Any Ptr
Declare Operator Let(Byval n As Integer)
Declare Property foo(Byval n As Integer)
Declare Property foo() As Integer
member As Any Ptr
End Type

Beim Gebrauch von GDB werden sie wie folgt angezeigt(Beachte in C++ wird :: benutzt, wo wir den '.' (dot) benutzen würden, '::' heißt auch 'scope resolution operator'):

BAR::BAR() - Der Standard-Constructor
BAR::BAR(BAR&) - Der Copy Constructor (& in C++ bedeutet eine Referenz, wie byRef)
BAR::BAR(int) - Der Constructor mit einem Integer-Argument(Beachte:es gibt kein spezielles Symbol zur Anzeige von ByVal, da es die Standardübergabe- Method in C/C++ ist)
BAR::~BAR() - Der Destructor
BAR::operator void*() - Eine Umwandlung zu Any Ptr (void ist vergleichbar mit Any, * bedeutet Pointer)
BAR::operator=(int) - Der Zuweisungs-Operator (Let), angegeben durch '=', in C/C++ '=' ist Zuweisung, '==' ist Test auf gleich.
BAR::FOO(int) - Property foo Setzer, mit einem Integer-Argument
BAR::FOO() - Property foo Holer

Element Sub/Funktionen werden wie Properties angezeigt, 'indexed properties' ebenfalls, nur mit dem Extra-Argument für den Index.

So werden die FB Datentypen angezeigt:

Any ptr - void *
ZString ptr - char *
String - FBSTRING
byte - signed char
ubyte - bool
short - short
ushort - unsigned short
integer - int
uinteger - unsigned int
longint - long long
ulongint - unsigned long long

Das sollte für den Anfang helfen, die Ausgaben von GDB/gprof zu verstehen..
..ein wenig Experimentieren hat noch nie geschadet ;)

More reading(but no more translating :D A.d.Ü)

http://www.freebasic.net/wiki/wikka.php?wakka=KeyPgNew ∞
http://www.freebasic.net/wiki/wikka.php?wakka=KeyPgDelete ∞
http://en.wikipedia.org/wiki/Copy_constructor ∞
http://en.wikipedia.org/wiki/Object_copy ∞
http://en.wikipedia.org/wiki/Name_mangling ∞

There are no comments on this page. [Add comment]

Page History :: 2006-12-30 21:05:23 XML :: Translator: ytwinky :: Search:
Valid XHTML 1.0 Transitional ∞ :: Valid CSS ∞ :: Powered by Wikka Wakka Wiki 1.1.6.0 ∞