3. Klassen, Strukturen, Schnittstellen
3.1. Das Objekt am Beispiel
3.1.1. Allgemeines
Wir werden uns nun anhand eines Beispiels mit der objektorientierten Programmierung befassen. Ein Objekt ist eine Einheit, die Daten enthält, die seinen Zustand definieren (diese werden als Eigenschaften bezeichnet), sowie Funktionen (diese werden als Methoden bezeichnet). Ein Objekt wird nach einem Modell erstellt, das als Klasse bezeichnet wird:
Public Class c1
' Attribute
Private p1 As type1
Private p2 As type2
....
' Methode
Public Sub m1(....)
...
End Sub
' Methode
Public Function m2(...)
....
End Function
End Class
Ausgehend von der oben genannten Klasse C1 lassen sich zahlreiche Objekte O1, O2, … erstellen Alle verfügen über die Eigenschaften p1, p2, … und die Methoden m3, m4, … Sie weisen jedoch unterschiedliche Werte für ihre Eigenschaften auf, sodass jedes Objekt pi einen eigenen Zustand besitzt. Analog dazu gilt die Deklaration
erzeugt zwei Objekte (der Begriff ist hier unzutreffend) vom Typ (Klasse) Integer. Ihre einzige Eigenschaft ist ihr Wert. Wenn O1 ein Objekt vom Typ C1 ist, bezeichnet O1.p1 die Eigenschaft p1 von O1 und O1.m1 die Methode m1 von O1. Betrachten wir ein erstes Objektmodell: die Klasse personne.
3.1.2. Definition der Klasse „Person“
Die Definition der Klasse personne lautet wie folgt:
Public Class personne
' Attribute
Private prenom As String
Private nom As String
Private age As Integer
' Methode
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' Methode
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Hier haben wir die Definition einer Klasse, also eines Datentyps. Wenn wir Variablen dieses Typs erstellen, werden wir sie Objekte oder Instanzen von Klassen nennen. Eine Klasse ist also eine Vorlage, anhand derer Objekte erstellt werden. Die Mitglieder oder Felder einer Klasse können Daten (Attribute), Methoden (Funktionen) oder Eigenschaften sein. Eigenschaften sind spezielle Methoden, die dazu dienen, den Wert von Attributen des Objekts abzurufen oder festzulegen. Diese Felder können mit einem der folgenden drei Schlüsselwörter versehen sein:
Ein privates Feld (private) ist ausschließlich über die internen Methoden der Klasse zugänglich | |
Ein öffentliches Feld (public) ist für jede Funktion zugänglich, unabhängig davon, ob sie innerhalb der Klasse definiert ist oder nicht | |
Ein geschütztes Feld (protected) ist ausschließlich über die internen Methoden der Klasse oder eines abgeleiteten Objekts zugänglich (siehe später das Konzept der Vererbung). |
Im Allgemeinen werden die Daten einer Klasse als privat deklariert, während ihre Methoden und Eigenschaften als öffentlich deklariert werden. Das bedeutet, dass der Benutzer eines Objekts (der Programmierer):
- keinen direkten Zugriff auf die privaten Daten des Objekts hat
- die öffentlichen Methoden des Objekts aufrufen kann, insbesondere diejenigen, die Zugriff auf dessen private Daten gewähren.
Die Syntax zur Deklaration einer Klasse lautet wie folgt:
public class classe
private donnée ou méthode ou propriété privée
public donnée ou méthode ou propriété publique
protected donnée ou méthode ou propriété protégée
end class
Die Reihenfolge der Deklaration der Attribute „private“, „protected“ und „public“ ist beliebig.
3.1.3. Die Methode initialisiert
Kehren wir zu unserer Klasse „[personne]“ zurück, die wie folgt deklariert ist:
Public Class personne
' Attribute
Private prenom As String
Private nom As String
Private age As Integer
' Methode
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' Methode
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Welche Funktion hat die Methode initialise? Da nom, prenom und age private Daten der Klasse personne sind, führen die Anweisungen:
sind unzulässig. Wir müssen ein Objekt vom Typ personne über eine öffentliche Methode initialisieren. Dies ist die Aufgabe der Methode initialise. Wir schreiben:
Die Schreibweise p1.initialise ist zulässig, da initialise öffentlich zugänglich ist.
3.1.4. Der Operator „new“
Die Anweisungssequenz
ist fehlerhaft. Die Anweisung
erklärt p1 als Verweis auf ein Objekt vom Typ personne. Dieses Objekt existiert noch nicht, daher ist p1 nicht initialisiert. Das ist so, als würde man schreiben:
wobei man mit dem Schlüsselwort nothing explizit angibt, dass die Variable p1 noch auf kein Objekt verweist. Wenn man anschließend schreibt
auf die Methode initialise des Objekts, auf das p1 verweist. Dieses Objekt existiert jedoch noch nicht, und der Compiler meldet den Fehler. Damit p1 auf ein Objekt verweist, muss man schreiben:
Dadurch wird ein noch nicht initialisiertes Objekt vom Typ personne erstellt: Die Attribute nom und prenom, bei denen es sich um Objektverweise vom Typ String handelt, erhalten den Wert nothing, und age den Wert 0. Es findet also eine Standardinitialisierung statt. Da p1 nun auf ein Objekt verweist, lautet die Initialisierungsanweisung für dieses Objekt
gültig.
3.1.5. Das Schlüsselwort „Me“
Sehen wir uns den Code der Methode initialise an:
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
Die Anweisung Me.prenom=P bedeutet, dass das Attribut prenom des aktuellen Objekts (Me) den Wert P erhält. Das Schlüsselwort Me bezeichnet das aktuelle Objekt: dasjenige, in dem sich die ausgeführte Methode befindet. Woher wissen wir das? Schauen wir uns an, wie die Initialisierung des Objekts erfolgt, auf das p1 im aufrufenden Programm verweist:
Es wird die Methode initialise des Objekts p1 aufgerufen. Wenn in dieser Methode auf das Objekt Me verwiesen wird, wird tatsächlich auf das Objekt p1 verwiesen. Die Methode initialise hätte auch wie folgt geschrieben werden können:
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
prenom = P
nom = N
Me.age = age
End Sub
Wenn eine Methode eines Objekts auf ein Attribut A dieses Objekts verweist, ist die Schreibweise Me.A implizit. Sie muss explizit verwendet werden, wenn ein Konflikt zwischen Bezeichnern vorliegt. Dies ist bei der folgenden Anweisung der Fall:
Me.age=age;
wobei age ein Attribut des aktuellen Objekts sowie den von der Methode empfangenen Parameter age bezeichnet. Die Mehrdeutigkeit muss daher beseitigt werden, indem das Attribut age als Me.age bezeichnet wird.
3.1.6. Ein Testprogramm
Hier ist ein kurzes Testprogramm:
Public Class personne
' Attribute
Private prenom As String
Private nom As String
Private age As Integer
' Methode
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' Methode
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
und die erzielten Ergebnisse:
dos>vbc personne1.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
3.1.7. Verwendung einer kompilierten Klassendatei (Assembly)
Beachten Sie, dass es im vorherigen Beispiel zwei Klassen in unserem Testprogramm gibt: die Klassen personne und test1. Es gibt noch eine andere Vorgehensweise:
-
Man kompiliert die Klasse „person“ in eine spezielle Datei, die als Assembly bezeichnet wird. Diese Datei hat die Erweiterung .dll
-
Die Klasse „test1“ wird kompiliert, wobei auf die Assembly verwiesen wird, die die Klasse „person“ enthält.
Die beiden Quelldateien sehen dann wie folgt aus:
test.vb | |
personne2.vb | |
Die Klasse personne wird durch die folgende Anweisung kompiliert:
dos>vbc /t:library personne2.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
24/02/2004 16:50 509 personne2.vb
24/02/2004 16:49 143 test.vb
24/02/2004 16:50 3 584 personne2.dll
Die Kompilierung hat eine Datei namens personne2.dll erzeugt. Es ist die Kompilierungsoption /t:library, die angibt, dass eine „Assembly“-Datei erzeugt werden soll. Kompilieren wir nun die Datei test.vb:
dos>vbc /r:personne2.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
24/02/2004 16:50 509 personne2.vb
24/02/2004 16:49 143 test.vb
24/02/2004 16:50 3 584 personne2.dll
24/02/2004 16:51 3 072 test.exe
Die Kompilierungsoption /r:personne2.dll teilt dem Compiler mit, dass er bestimmte Klassen in der Datei personne2.dll finden wird. Wenn er in der Quelldatei test.vb einen Verweis auf die Klasse personne findet – eine Klasse, die in der Quelldatei test.vb nicht deklariert ist –, sucht er nach der Klasse personne in den .dll-Dateien, auf die die Option /r verweist. Dort findet er die Klasse personne in der Assembly personne2.dll. Man hätte in diese Assembly auch andere Klassen aufnehmen können. Um bei der Kompilierung mehrere kompilierte Klassendateien zu verwenden, schreibt man:
Die Ausführung des Programms test1.exe liefert folgende Ergebnisse:
3.1.8. Eine weitere Methode initialisiert
Betrachten wir weiterhin die Klasse personne und fügen wir ihr die folgende Methode hinzu:
' Methode
Public Sub initialise(ByVal P As personne)
prenom = P.prenom
nom = P.nom
Me.age = P.age
End Sub
Wir haben nun zwei Methoden mit dem Namen initialise: Dies ist zulässig, solange sie unterschiedliche Parameter akzeptieren. Das ist hier der Fall. Der Parameter ist nun eine Referenz P auf eine Person. Die Attribute der Person P werden dann dem aktuellen Objekt (Me) zugewiesen. Es ist zu beachten, dass die Methode initialise direkten Zugriff auf die Attribute des Objekts P hat, obwohl diese vom Typ private sind. Dies gilt immer: Ein Objekt O1 einer Klasse C hat immer Zugriff auf die Attribute von Objekten derselben Klasse C. Hier ist ein Test der neuen Klasse personne, die, wie zuvor erläutert, in personne.dll kompiliert wurde:
' Optionen
Option Explicit On
Option Strict On
' Namensräume
Imports System
Module test1
Sub Main()
Dim p1 As New personne
p1.initialise("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
Dim p2 As New personne
p2.initialise(p1)
Console.Out.Write("p2=")
p2.identifie()
End Sub
End Module
und die Ergebnisse:
3.1.9. Konstruktoren der Klasse „Person“
Ein Konstruktor ist eine Prozedur mit dem Namen „New“, die bei der Erstellung des Objekts aufgerufen wird. Er wird in der Regel zur Initialisierung des Objekts verwendet. Wenn eine Klasse einen Konstruktor hat, der n Argumente akzeptiert (argi), können die Deklaration und Initialisierung eines Objekts dieser Klasse wie folgt erfolgen:
dim objet as classe =new classe(arg1,arg2, ... argn)
oder
dim objet as classe
…
objet=new classe(arg1,arg2, ... argn)
Wenn eine Klasse einen oder mehrere Konstruktoren hat, muss zwingend einer dieser Konstruktoren verwendet werden, um ein Objekt dieser Klasse zu erstellen. Wenn eine Klasse C keinen Konstruktor hat, verfügt sie über einen Standardkonstruktor, nämlich den Konstruktor ohne Parameter: public New(). Die Attribute des Objekts werden dann mit Standardwerten initialisiert. Genau das ist in den vorherigen Programmen geschehen, in denen wir geschrieben hatten:
Erstellen wir zwei Konstruktoren für unsere Klasse personne:
' Optionen
Option Explicit On
Option Strict On
' Namensräume
Imports System
' die Klasse „Person“
Public Class personne
' Attribute
Private prenom As String
Private nom As String
Private age As Integer
' Konstruktoren
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
initialise(P, N, age)
End Sub
Public Sub New(ByVal P As personne)
initialise(P)
End Sub
' Methoden zur Initialisierung des Objekts
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
Public Sub initialise(ByVal P As personne)
prenom = P.prenom
nom = P.nom
Me.age = P.age
End Sub
' Methode
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Unsere beiden Konstruktoren rufen lediglich die entsprechenden Methoden initialise auf. Zur Erinnerung: Wenn in einem Konstruktor beispielsweise die Notation initialise(P) vorkommt, übersetzt der Compiler dies in Me.initialise(P). Im Konstruktor wird also die Methode initialise aufgerufen, um das Objekt zu bearbeiten, auf das Me verweist, d. h. das aktuelle Objekt, das gerade erstellt wird. Hier ist ein kurzes Testprogramm:
' Optionen
Option Explicit On
Option Strict On
' Namensräume
Imports System
' Test-pg
Module test
Sub Main()
Dim p1 As New personne("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
Dim p2 As New personne(p1)
Console.Out.Write("p2=")
p2.identifie()
End Sub
End Module
und die erzielten Ergebnisse:
3.1.10. Objektreferenzen
Wir verwenden weiterhin dieselbe Klasse personne. Das Testprogramm sieht nun wie folgt aus:
' Optionen
Option Explicit On
Option Strict On
' Namensräume
Imports System
' Testseite
Module test
Sub Main()
' p1
Dim p1 As New personne("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
' p2 verweist auf dasselbe Objekt wie p1
Dim p2 As personne = p1
Console.Out.Write("p2=")
p2.identifie()
' p3 verweist auf ein Objekt, das eine Kopie des von p1 referenzierten Objekts sein wird
Dim p3 As New personne(p1)
Console.Out.Write("p3=")
p3.identifie()
' Der Zustand des von p1 referenzierten Objekts wird geändert
p1.initialise("Micheline", "Benoît", 67)
Console.Out.Write("p1=")
p1.identifie()
' Da p2 = p1 ist, muss sich der Zustand des von p2 referenzierten Objekts geändert haben
Console.Out.Write("p2=")
p2.identifie()
' Da p3 nicht auf dasselbe Objekt wie p1 verweist, muss sich das von p3 referenzierte Objekt nicht geändert haben
Console.Out.Write("p3=")
p3.identifie()
End Sub
End Module
Die erzielten Ergebnisse lauten wie folgt:
p1=Jean,Dupont,30
p2=Jean,Dupont,30
p3=Jean,Dupont,30
p1=Micheline,Benoît,67
p2=Micheline,Benoît,67
p3=Jean,Dupont,30
Wenn man die Variable p1 wie folgt deklariert:
verweist p1 auf das Objekt personne("Jean","Dupont",30), ist aber nicht das Objekt selbst. In C würde man sagen, dass es sich um einen Zeiger handelt, c.a.d, der auf die Adresse des erstellten Objekts verweist. Wenn man anschließend schreibt:
wird nicht das Objekt personne("Jean","Dupont",30) geändert, sondern die Referenz p1 ändert ihren Wert. Das Objekt personne("Jean","Dupont",30) geht „verloren“, wenn es von keiner anderen Variablen referenziert wird.
Wenn man schreibt:
wird der Zeiger p2 initialisiert: Er „zeigt“ auf dasselbe Objekt (er verweist auf dasselbe Objekt) wie der Zeiger p1. Wenn man also das Objekt ändert, auf das p1 „zeigt“ (oder auf das es verweist), ändert man damit auch das Objekt, auf das p2 verweist.
Wenn man schreibt:
wird ein neues Objekt erstellt, das eine Kopie des von p1 referenzierten Objekts ist. Dieses neue Objekt wird von p3 referenziert. Wenn man das von p1 „verwiesene“ (oder referenzierte) Objekt ändert, hat dies keinerlei Auswirkungen auf das von p3 referenzierte Objekt. Dies zeigen die erzielten Ergebnisse.
3.1.11. Temporäre Objekte
In einem Ausdruck kann der Konstruktor eines Objekts explizit aufgerufen werden: Das Objekt wird erstellt, aber wir haben keinen Zugriff darauf (um es beispielsweise zu ändern). Dieses temporäre Objekt wird für die Auswertung des Ausdrucks erstellt und anschließend verworfen. Der von ihm belegte Speicherplatz wird später automatisch von einem Programm namens „Garbage Collector“ freigegeben, dessen Aufgabe es ist, den Speicherplatz von Objekten zurückzugewinnen, auf die von den Programmdaten nicht mehr verwiesen wird. Betrachten wir das folgende neue Testprogramm:
' Optionen
Option Strict On
Option Explicit On
' Namensräume
Imports System
' Test-PG
Module test
Sub Main()
Dim p As New personne(New personne("Jean", "Dupont", 30))
p.identifie()
End Sub
End Module
und ändern wir die Konstruktoren der Klasse personne so, dass sie eine Meldung anzeigen:
' Konstruktoren
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
Console.Out.WriteLine("Constructeur personne(String, String, integer)")
initialise(P, N, age)
End Sub
Public Sub New(ByVal P As personne)
Console.Out.WriteLine("Constructeur personne(personne)")
initialise(P)
End Sub
Wir erhalten folgende Ergebnisse:
dos>test
Constructeur personne(String, String, integer)
Constructeur personne(personne)
Jean,Dupont,30
was die schrittweise Erstellung der beiden temporären Objekte zeigt.
3.1.12. Methoden zum Lesen und Schreiben privater Attribute
Wir fügen der Klasse personne die erforderlichen Methoden hinzu, um den Status der Attribute der Objekte zu lesen oder zu ändern:
Imports System
Public Class personne
' Attribute
Private prenom As [String]
Private nom As [String]
Private age As Integer
' Konstruktoren
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
Public Sub New(ByVal P As personne)
Me.prenom = P.prenom
Me.nom = P.nom
Me.age = P.age
End Sub
' Identifikatoren
Public Sub identifie()
Console.Out.WriteLine((prenom + "," + nom + "," + age))
End Sub
' Zugriffsmethoden
Public Function getPrenom() As [String]
Return prenom
End Function
Public Function getNom() As [String]
Return nom
End Function
Public Function getAge() As Integer
Return age
End Function
'Modifikatoren
Public Sub setPrenom(ByVal P As [String])
Me.prenom = P
End Sub
Public Sub setNom(ByVal N As [String])
Me.nom = N
End Sub
Public Sub setAge(ByVal age As Integer)
Me.age = age
End Sub
End Class
Wir testen die neue Klasse mit dem folgenden Programm:
' Optionen
Option Strict On
Option Explicit On
' Namensräume
Imports System
' Testseite
Public Module test
Sub Main()
Dim P As New personne("Jean", "Michelin", 34)
Console.Out.WriteLine(("P=(" & P.getPrenom() & "," & P.getNom() & "," & P.getAge() & ")"))
P.setAge(56)
Console.Out.WriteLine(("P=(" & P.getPrenom() & "," & P.getNom() & "," & P.getAge() & ")"))
End Sub
End Module
und erhalten folgende Ergebnisse:
3.1.13. Die Eigenschaften
Es gibt noch eine weitere Möglichkeit, auf die Attribute einer Klasse zuzugreifen: die Erstellung von Eigenschaften. Diese ermöglichen es uns, private Attribute so zu bearbeiten, als wären sie öffentlich. Betrachten wir die folgende Klasse personne, in der die zuvor genannten Accessoren und Modifikatoren durch Lese- und Schreibeigenschaften ersetzt wurden:
' Optionen
Option Explicit On
Option Strict On
' Namensräume
Imports System
' Personenklasse
Public Class personne
' Attribute
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' Konstruktoren
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
Me._prenom = P
Me._nom = N
Me._age = age
End Sub
Public Sub New(ByVal P As personne)
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
End Sub
' Identifikatoren
Public Sub identifie()
Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
End Sub
' Eigenschaften
Public Property prenom() As String
Get
Return _prenom
End Get
Set(ByVal Value As String)
_prenom = Value
End Set
End Property
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
' Gültiges Alter?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
End Class
Eine Eigenschaft (Property) ermöglicht es, den Wert eines Attributs auszulesen (get) oder festzulegen (set). In unserem Beispiel haben wir den Namen der Attribute mit dem Unterstrich „_“ vorangestellt, damit die Eigenschaften die Namen der primitiven Attribute tragen. Tatsächlich darf eine Eigenschaft nicht denselben Namen wie das Attribut tragen, das sie verwaltet, da es sonst zu einem Namenskonflikt in der Klasse kommt. Wir haben unsere Attribute daher _prenom, _nom und _age genannt und die Konstruktoren und Methoden entsprechend angepasst. Anschließend haben wir drei Eigenschaften mit den Namen nom, prenom und age angelegt. Eine Eigenschaft wird wie folgt deklariert:
Public Property nom() As Type
Get
...
End Get
Set(ByVal Value As Type)
...
End Set
End Property
wobei Type der Typ des Attributs sein muss, das von der Eigenschaft verwaltet wird. Sie kann zwei Methoden namens „get“ und „set“ haben. Die Methode „get“ ist in der Regel dafür zuständig, den Wert des von ihr verwalteten Attributs zurückzugeben (sie könnte auch etwas anderes zurückgeben, nichts spricht dagegen). Die „set“-Methode erhält einen Parameter namens „value“, den sie normalerweise dem von ihr verwalteten Attribut zuweist. Sie kann dabei die Gültigkeit des empfangenen Werts überprüfen und gegebenenfalls eine Ausnahme auslösen, falls sich der Wert als ungültig erweist. Genau das geschieht hier beim Alter.
Wie werden diese get- und set-Methoden aufgerufen? Betrachten wir das folgende Testprogramm:
' Optionen
Option Explicit On
Option Strict On
' Namensräume
Imports System
' Test-PG
Module test
Sub Main()
Dim P As New personne("Jean", "Michelin", 34)
Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")"))
P.age = 56
Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")"))
Try
P.age = -4
Catch ex As Exception
Console.Error.WriteLine(ex.Message)
End Try
End Sub
End Module
In der Anweisung
sollen die Werte der Eigenschaften prenom, nom und age der Person P abgerufen werden. Dabei wird die get-Methode dieser Eigenschaften aufgerufen, die den Wert des von ihnen verwalteten Attributs zurückgibt.
In der Anweisung
soll der Wert der Eigenschaft age festgelegt werden. Dabei wird die set-Methode dieser Eigenschaft aufgerufen. Sie erhält den Wert 56 als Parameter „value“.
Eine Eigenschaft P einer Klasse C, die nur die get-Methode definiert, wird als schreibgeschützt bezeichnet. Ist c ein Objekt der Klasse C, wird die Operation c.P=Wert vom Compiler abgelehnt.
Die Ausführung des vorstehenden Testprogramms liefert folgende Ergebnisse:
Eigenschaften ermöglichen es uns also, private Attribute so zu bearbeiten, als wären sie öffentlich.
3.1.14. Klassenmethoden und -attribute
Nehmen wir an, wir möchten die Anzahl der in einer Anwendung erstellten Objekte vom Typ [personne] zählen. Man könnte selbst einen Zähler verwalten, läuft dabei jedoch Gefahr, temporäre Objekte zu übersehen, die hier und da erstellt werden. Es erscheint sicherer, in die Konstruktoren der Klasse [personne] eine Anweisung einzufügen, die einen Zähler erhöht. Das Problem besteht darin, eine Referenz auf diesen Zähler zu übergeben, damit der Konstruktor ihn inkrementieren kann: Man muss ihnen einen neuen Parameter übergeben. Man kann den Zähler auch in die Definition der Klasse aufnehmen. Da es sich um ein Attribut der Klasse selbst und nicht um ein bestimmtes Objekt dieser Klasse handelt, deklariert man ihn anders mit dem Schlüsselwort Shared:
Um darauf zu verweisen, schreibt man personne._nbPersonnes, um zu zeigen, dass es sich um ein Attribut der Klasse personne selbst handelt. Hier haben wir ein privates Attribut erstellt, auf das man außerhalb der Klasse keinen direkten Zugriff hat. Daher erstellen wir eine öffentliche Eigenschaft, um Zugriff auf das Klassenattribut nbPersonnes zu gewähren. Um den Wert von nbPersonnes zu ermitteln, benötigt die Methode get dieser Eigenschaft kein bestimmtes personne-Objekt: denn _nbPersonnes ist nicht das Attribut eines bestimmten Objekts, sondern das Attribut einer ganzen Klasse. Daher benötigen wir eine ebenfalls deklarierte Eigenschaft Shared:
die von außen mit der Syntax personne.nbPersonnes aufgerufen wird. Die Eigenschaft wird als schreibgeschützt deklariert (ReadOnly), da sie keine Set-Methode bietet. Hier ein Beispiel. Die Klasse personne sieht nun wie folgt aus:
Option Explicit On
Option Strict On
' Namensräume
Imports System
' Klasse
Public Class personne
' Klassenattribute
Private Shared _nbPersonnes As Long = 0
' Instanzattribute
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' Konstruktoren
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
' eine weitere Person
_nbPersonnes += 1
Me._prenom = P
Me._nom = N
Me._age = age
End Sub
Public Sub New(ByVal P As personne)
' eine weitere Person
_nbPersonnes += 1
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
End Sub
' identifiziert
Public Sub identifie()
Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
End Sub
' Klassenattribut
Public Shared ReadOnly Property nbPersonnes() As Long
Get
Return _nbPersonnes
End Get
End Property
' Instanz-Eigenschaften
Public Property prenom() As String
Get
Return _prenom
End Get
Set(ByVal Value As String)
_prenom = Value
End Set
End Property
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
' Gültiges Alter?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
End Class
Mit dem folgenden Programm:
' Optionen
Option Explicit On
Option Strict On
' Namensräume
Imports System
' Test-PG
Module test
Sub Main()
Dim p1 As New personne("Jean", "Dupont", 30)
Dim p2 As New personne(p1)
Console.Out.WriteLine(("Nombre de personnes créées : " & personne.nbPersonnes))
End Sub
End Module
erhält man folgende Ergebnisse:
3.1.15. Übergabe eines Objekts an eine Funktion
Wir haben bereits erwähnt, dass VB.NET standardmäßig die tatsächlichen Parameter einer Funktion per Wert übergibt: Die Werte der tatsächlichen Parameter werden in die formalen Parameter kopiert. Im Falle eines Objekts darf man sich nicht von der systematischen sprachlichen Ungenauigkeit täuschen lassen, bei der von „Objekt“ statt von „Objektreferenz“ gesprochen wird. Ein Objekt wird ausschließlich über eine Referenz (einen Zeiger) auf dieses Objekt manipuliert. Was also an eine Funktion übergeben wird, ist nicht das Objekt selbst, sondern eine Referenz auf dieses Objekt. Es ist also der Wert der Referenz und nicht der Wert des Objekts selbst, der in den formalen Parameter kopiert wird: Es wird kein neues Objekt erstellt. Wird eine Objektreferenz R1 an eine Funktion übergeben, wird sie in den entsprechenden formalen Parameter R2 kopiert. Daher verweisen die Referenzen R2 und R1 auf dasselbe Objekt. Wenn die Funktion das Objekt ändert, auf das R2 verweist, ändert sie natürlich auch das Objekt, auf das R1 verweist, da es sich um dasselbe Objekt handelt.
![]() |
Dies zeigt das folgende Beispiel:
' Optionen
Option Explicit On
Option Strict On
' Namensräume
Imports System
' Testseite
Module test
Sub Main()
' eine Person p1
Dim p1 As New personne("Jean", "Dupont", 30)
' Anzeige p1
Console.Out.Write("Paramètre effectif avant modification : ")
p1.identifie()
' Änderung p1
modifie(p1)
' Anzeige p1
Console.Out.Write("Paramètre effectif après modification : ")
p1.identifie()
End Sub
Sub modifie(ByVal P As personne)
' Anzeige Person P
Console.Out.Write("Paramètre formel avant modification : ")
P.identifie()
' Änderung P
P.prenom = "Sylvie"
P.nom = "Vartan"
P.age = 52
' Anzeige P
Console.Out.Write("Paramètre formel après modification : ")
P.identifie()
End Sub
End Module
Die folgenden Ergebnisse wurden erzielt:
Paramètre effectif avant modification : Jean,Dupont,30
Paramètre formel avant modification : Jean,Dupont,30
Paramètre formel après modification : Sylvie,Vartan,52
Paramètre effectif après modification : Sylvie,Vartan,52
Es ist ersichtlich, dass nur ein Objekt erstellt wurde: nämlich das der Person p1 aus der Prozedur Main, und dass das Objekt tatsächlich durch die Funktion modifie geändert wurde.
3.1.16. Ein Array von Personen
Ein Objekt ist ein Datenelement wie jedes andere, und als solches können mehrere Objekte in einem Array zusammengefasst werden:
' Optionen
Option Explicit On
Option Strict On
' Namensräume
Imports System
' Testseite
Module test
Sub Main()
' eine Tabelle mit Personen
Dim amis(2) As personne
amis(0) = New personne("Jean", "Dupont", 30)
amis(1) = New personne("Sylvie", "Vartan", 52)
amis(2) = New personne("Neil", "Armstrong", 66)
' Anzeige
Console.Out.WriteLine("----------------")
Dim i As Integer
For i = 0 To amis.Length - 1
amis(i).identifie()
Next i
End Sub
End Module
Die Anweisung „Dim amis(2) As personne“ erstellt ein Array mit 3 Elementen vom Typ personne. Diese 3 Elemente werden hier mit den Werten nothing und c.a.d initialisiert, wobei sie auf kein Objekt verweisen. Auch hier spricht man – im weiteren Sinne – von einem Objektarray, obwohl es sich lediglich um ein Array von Objektverweisen handelt. Die Erstellung des Objektarrays, das selbst ein Objekt ist, erzeugt keine Objekte des Typs seiner Elemente: Dies muss anschließend erfolgen. Man erhält folgende Ergebnisse:
3.2. Vererbung am Beispiel
3.2.1. Allgemeines
Hier befassen wir uns mit dem Begriff der Vererbung. Das Ziel der Vererbung ist es, eine bestehende Klasse so „anzupassen“, dass sie unseren Anforderungen entspricht. Nehmen wir an, wir möchten eine Klasse enseignant erstellen: Ein Lehrer ist eine besondere Person. Er verfügt über Attribute, die eine andere Person nicht hat: zum Beispiel das Fach, das er unterrichtet. Er hat aber auch die Attribute jeder Person: Vorname, Nachname und Alter. Ein Lehrer gehört also vollständig zur Klasse personne, verfügt jedoch über zusätzliche Attribute. Anstatt eine Klasse enseignant von Grund auf neu zu definieren, würden wir lieber auf die bereits vorhandene Klasse personne zurückgreifen und diese an die besonderen Merkmale von Lehrern anpassen. Das Konzept der Vererbung ermöglicht uns dies. Um auszudrücken, dass die Klasse enseignant die Eigenschaften der Klasse personne erbt, schreibt man:
Public Class enseignant
Inherits personne
Beachten Sie die besondere Syntax über zwei Zeilen. Die Klasse personne wird als übergeordnete Klasse (oder Mutterklasse) bezeichnet, die Klasse enseignant als abgeleitete Klasse (oder Tochterklasse). Ein Objekt vom Typ enseignant verfügt über alle Eigenschaften eines Objekts vom Typ personne: Es hat dieselben Attribute und dieselben Methoden. Diese Attribute und Methoden der übergeordneten Klasse werden in der Definition der abgeleiteten Klasse nicht wiederholt: Es werden lediglich die Attribute und Methoden angegeben, die von der abgeleiteten Klasse hinzugefügt wurden. Wir nehmen an, dass die Klasse personne wie folgt definiert ist:
' Optionen
Option Strict On
Option Explicit On
' Namensräume
Imports System
' Klasse „Person“
Public Class personne
' Klassenattribute
Private Shared _nbPersonnes As Long = 0
' Instanzattribute
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' Konstruktoren
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
' eine weitere Person
_nbPersonnes += 1
' Konstruktion
Me._prenom = P
Me._nom = N
Me._age = age
' Nachverfolgung
Console.Out.WriteLine("Construction personne(string, string, int)")
End Sub
Public Sub New(ByVal P As personne)
' eine weitere Person
_nbPersonnes += 1
' Bau
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
' Nachverfolgung
Console.Out.WriteLine("Construction personne(string, string, int)")
End Sub
' Klassenattribut
Public Shared ReadOnly Property nbPersonnes() As Long
Get
Return _nbPersonnes
End Get
End Property
' Instanz-Eigenschaften
Public Property prenom() As String
Get
Return _prenom
End Get
Set(ByVal Value As String)
_prenom = Value
End Set
End Property
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
' Gültiges Alter?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
Public ReadOnly Property identite() As String
Get
Return "personne(" & _prenom & "," & _nom & "," & age & ")"
End Get
End Property
End Class
Die Methode identifie wurde durch die schreibgeschützte Eigenschaft identité ersetzt, die die Person identifiziert. Wir erstellen eine Klasse enseignant, die von der Klasse personne erbt:
' Optionen
Option Strict On
Option Explicit On
' Namensräume
Imports System
Public Class enseignant
Inherits personne
' Attribute
Private _section As Integer
' Konstruktor
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
MyBase.New(P, N, age)
Me._section = section
' Verfolgung
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
' Abschnittseigenschaft
Public Property section() As Integer
Get
Return _section
End Get
Set(ByVal Value As Integer)
_section = Value
End Set
End Property
End Class
Die Klasse enseignant ergänzt die Methoden und Attribute der Klasse personne um:
- ein Attribut section, das die Nummer der Sektion angibt, zu der der Lehrer im Lehrerkollegium gehört (grob gesagt eine Sektion pro Fach)
- einen neuen Konstruktor, mit dem alle Attribute eines Lehrers initialisiert werden können
Die Deklaration
Public Class enseignant
Inherits personne
gibt an, dass die Klasse enseignant von der Klasse personne abgeleitet ist.
3.2.2. Erstellung eines Lehrerkonstrukts
Der Konstruktor der Klasse enseignant lautet wie folgt:
' Konstruktor
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
MyBase.New(P, N, age)
Me._section = section
' Nachverfolgung
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
Die Erklärung
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
gibt an, dass der Konstruktor vier Parameter erhält: P, N, age, section. Sie muss drei davon, nämlich (P,N,age), an ihre Basisklasse weitergeben, in diesem Fall die Klasse personne. Es ist bekannt, dass diese Klasse einen Konstruktor „person(string, string, int)“ besitzt, der es ermöglicht, eine Person mit den übergebenen Parametern (P,N,age) zu erstellen. Die Klasse [enseignant] übergibt die Parameter (P, N, Alter) wie folgt an ihre Basisklasse:
MyBase.New(P, N, age)
Sobald die Konstruktion der Basisklasse abgeschlossen ist, wird die Konstruktion des Objekts enseignant durch die Ausführung des Konstruktor-Körpers fortgesetzt:
Me._section = section
Zusammenfassend lässt sich sagen, dass der Konstruktor einer abgeleiteten Klasse:
- übermittelt an seine Basisklasse die Parameter, die diese für ihre Erstellung benötigt
- verwendet die übrigen Parameter, um seine eigenen Attribute zu initialisieren
Man hätte auch folgendermaßen schreiben können:
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
Me._prenom = P
Me._nom = N
Me._age = age
Me._section = section
' Sendungsverfolgung
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
Das ist nicht möglich. Die Klasse personne hat ihre drei Felder _prenom, _nom und _age als privat (private) deklariert. Nur Objekte derselben Klasse haben direkten Zugriff auf diese Felder. Alle anderen Objekte, einschließlich untergeordneter Objekte wie in diesem Fall, müssen über öffentliche Methoden darauf zugreifen. Dies wäre anders gewesen, wenn die Klasse personne die drei Felder als geschützt (protected) deklariert hätte: In diesem Fall hätten abgeleitete Klassen direkten Zugriff auf die drei Felder gehabt. In unserem Beispiel war die Verwendung des Konstruktors der übergeordneten Klasse daher die richtige Lösung und entspricht der üblichen Vorgehensweise: Beim Erstellen eines untergeordneten Objekts wird zunächst der Konstruktor des übergeordneten Objekts aufgerufen und anschließend die für das untergeordnete Objekt (in unserem Beispiel section) spezifischen Initialisierungen vorgenommen.
Kompilieren wir die Klassen personne und enseignant in Assemblies:
dos>vbc /t:library personne.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>vbc /r:personne.dll /t:library enseignant.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
25/02/2004 10:08 1 828 personne.vb
25/02/2004 10:11 675 enseignant.vb
25/02/2004 10:12 223 test.vb
25/02/2004 10:16 4 096 personne.dll
25/02/2004 10:16 3 584 enseignant.dll
Es ist zu beachten, dass zum Kompilieren der untergeordneten Klasse enseignant die Datei personne.dll referenziert werden musste, die die Klasse personne enthält. Versuchen wir nun ein erstes Testprogramm:
' Optionen
Option Strict On
Option Explicit On
' Namensräume
Imports System
Module test
Sub Main()
Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
End Sub
End Module
Dieses Programm beschränkt sich darauf, ein Objekt enseignant zu erstellen (new) und es zu identifizieren. Die Klasse „enseignant“ verfügt über keine Methode „identité“, aber ihre übergeordnete Klasse hat eine solche Methode, die zudem öffentlich ist: Durch Vererbung wird sie zu einer öffentlichen Methode der Klasse „enseignant“. Die Ergebnisse lauten wie folgt:
dos>vbc /r:personne.dll /r:enseignant.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>test
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(Jean,Dupont,30)
Man sieht, dass:
- ein Objekt personne vor dem Objekt enseignant erstellt wurde
- die ermittelte Identität ist die des Objekts personne
3.2.3. Überladen einer Methode oder einer Eigenschaft
Im vorherigen Beispiel hatten wir die Identität des Teils personne des Lehrers, aber es fehlen bestimmte Informationen, die für die Klasse enseignant (die Klasse) spezifisch sind. Daher müssen wir eine Eigenschaft definieren, mit der der Lehrer identifiziert werden kann:
' Optionen
Option Strict On
Option Explicit On
' Namensräume
Imports System
Public Class enseignant
Inherits personne
' Attribute
Private _section As Integer
' Konstruktor
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
MyBase.New(P, N, age)
Me._section = section
' Verfolgung
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
' Abschnittseigenschaft
Public Property section() As Integer
Get
Return _section
End Get
Set(ByVal Value As Integer)
_section = Value
End Set
End Property
' Überladung der Eigenschaft „Identität“
Public Shadows ReadOnly Property identite() As String
Get
Return "enseignant(" & MyBase.identite & "," & _section & ")"
End Get
End Property
End Class
Die Methode identite der Klasse enseignant stützt sich auf die Methode identite ihrer übergeordneten Klasse (MyBase.identite) , um ihren Teil „personne“ anzuzeigen, und ergänzt diesen anschließend um das Feld _section, das spezifisch für die Klasse enseignant ist. Beachten Sie die Deklaration der Eigenschaft identite:
Public Shadows ReadOnly Property identite() As String
die angibt, dass die Eigenschaft „identite“ die gleichnamige Methode „verdeckt“, die in der übergeordneten Klasse vorhanden sein könnte. Nehmen wir ein Objekt enseignant E an. Dieses Objekt enthält ein Objekt personne:
![]() |
Die Eigenschaft „identity“ ist sowohl in der Klasse enseignant als auch in ihrer übergeordneten Klasse personne definiert. In der untergeordneten Klasse enseignant muss der Eigenschaft identite das Schlüsselwort „shadows“ vorangestellt werden, um anzugeben, dass eine neue Eigenschaft identite für die Klasse enseignant. neu definiert wird
Public Shadows ReadOnly Property identite() As String
Die Klasse enseignant verfügt nun über zwei Eigenschaften identite:
- die von der übergeordneten Klasse personne geerbte
- eine eigene
Wenn E ein Objekt vom Typ enseignant ist, bezeichnet E.identite die Methode identite der Klasse enseignant. Man sagt, dass die Eigenschaft identite der übergeordneten Klasse durch die Eigenschaft identite der untergeordneten Klasse „neu definiert“ wird. Allgemein gilt: Wenn O ein Objekt und M eine Methode ist, sucht das System zur Ausführung der Methode O.M nach einer Methode M in der folgenden Reihenfolge:
- in der Klasse des Objekts O
- in seiner übergeordneten Klasse, falls vorhanden
- in der übergeordneten Klasse der übergeordneten Klasse, sofern diese existiert
- usw.
Die Vererbung ermöglicht es also, in der Unterklasse Methoden/Eigenschaften mit demselben Namen wie in der Oberklasse neu zu definieren. Dadurch lässt sich die Unterklasse an ihre eigenen Anforderungen anpassen. In Verbindung mit dem Polymorphismus, auf den wir später noch eingehen werden, ist die Überladung von Methoden/Eigenschaften der Hauptvorteil der Vererbung. Betrachten wir dasselbe Beispiel wie zuvor:
' Optionen
Option Strict On
Option Explicit On
' Namensräume
Imports System
Module test
Sub Main()
Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
End Sub
End Module
Diesmal sind die Ergebnisse wie folgt:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Jean,Dupont,30),27)
3.2.4. Polymorphismus
Betrachten wir eine Klassenlinie: C0 C1 C2 … Cn, wobei Ci Cj angibt, dass die Klasse Cj von der Klasse Ci abgeleitet ist. Daraus folgt, dass die Klasse Cj alle Merkmale der Klasse Ci sowie weitere Merkmale aufweist. Seien Oi Objekte vom Typ Ci. Es ist zulässig zu schreiben:
Tatsächlich verfügt die Klasse Cj durch Vererbung über alle Merkmale der Klasse Ci sowie über weitere. Somit enthält ein Objekt Oj vom Typ Cj ein Objekt vom Typ Ci. Die Operation
bewirkt, dass Oi eine Referenz auf das im Objekt Oj enthaltene Objekt vom Typ Ci ist.
Die Tatsache, dass eine Variable Oi der Klasse Ci nicht nur auf ein Objekt der Klasse Ci verweisen kann, sondern tatsächlich auf jedes von der Klasse Ci abgeleitete Objekt, wird als Polymorphismus bezeichnet: die Fähigkeit einer Variablen, auf verschiedene Objekttypen zu verweisen. Nehmen wir ein Beispiel und betrachten wir die folgende klassenunabhängige Funktion:
Sub affiche(ByVal p As personne)
Man könnte ebenso gut schreiben
dass
Im letzteren Fall erhält der formale Parameter vom Typ personne der Funktion affiche einen Wert vom Typ enseignant. Da der Typ enseignant vom Typ personne abgeleitet ist, ist dies zulässig.
3.2.5. Neudefinition und Polymorphismus
Ergänzen wir unsere Prozedur affiche:
Sub affiche(ByVal p As personne)
' Identität von p anzeigen
Console.Out.WriteLine(p.identite)
End Sub
Die Methode p.identite gibt eine Zeichenkette zurück, die das Objekt personne identifiziert. Was passiert in unserem vorherigen Beispiel im Fall eines Objekts enseignant:
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
Betrachten wir das folgende Beispiel:
' Optionen
Option Strict On
Option Explicit On
' Namensräume
Imports System
Module test
Sub Main()
' ein Lehrer
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' eine Person
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' Anzeige
Sub affiche(ByVal p As personne)
' Identitätsplakat von p
Console.Out.WriteLine(p.identite)
End Sub
End Module
Die Ergebnisse lauten wie folgt:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(Lucile,Dumas,56)
Construction personne(string, string, int)
personne(Jean,Dupont,30)
Die Ausführung zeigt, dass die Anweisung p.identite jedes Mal die Eigenschaft identite einer personne ausgeführt hat, die in der enseignant* enthaltene Person e und anschließend die *personne* selbst. Sie hat sich nicht an das Objekt angepasst, das tatsächlich als Parameter an *affiche* übergeben wurde. Wir hätten es vorgezogen, die vollständige Identität des enseignant e zu haben. Dazu hätte die Notation p.identite auf die Eigenschaft identite des Objekts verweisen müssen, auf das p tatsächlich verweist, anstatt auf die Eigenschaft identite des Teils „personne“ des Objekts, auf das p tatsächlich verweist. Dieses Ergebnis lässt sich erzielen, indem man identite in der Basisklasse personne als überschreibbare Eigenschaft (overridable*) deklariert:
Public Overridable ReadOnly Property identite() As String
Get
Return "personne(" & _prenom & "," & _nom & "," & age & ")"
End Get
End Property
Das Schlüsselwort „overridable“ macht identite zu einer überschreibbaren oder virtuellen Eigenschaft. Dieses Schlüsselwort kann auch auf Methoden angewendet werden. Unterklassen, die eine virtuelle Eigenschaft oder Methode überschreiben, müssen daher das Schlüsselwort „overrides“ anstelle von „shadows“ verwenden, um ihre überschriebene Eigenschaft/Methode zu kennzeichnen. So ist in der Klasse enseignant die Eigenschaft identite wie folgt definiert:
' Überladung der Eigenschaft „Identität“
Public Overrides ReadOnly Property identite() As String
Get
Return "enseignant(" & MyBase.identite & "," & _section & ")"
End Get
End Property
Das Testprogramm:
' Optionen
Option Strict On
Option Explicit On
' Namensräume
Imports System
Module test
Sub Main()
' ein Lehrer
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' eine Person
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' Anzeige
Sub affiche(ByVal p As personne)
' Identitätsplakat von p
Console.Out.WriteLine(p.identite)
End Sub
End Module
liefert dann folgende Ergebnisse:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Lucile,Dumas,56),61)
Construction personne(string, string, int)
personne(Jean,Dupont,30)
Diesmal haben wir die vollständige Identität des Lehrers erhalten. Definieren wir nun eine Methode anstelle einer Eigenschaft neu. Die Klasse object ist die „Überklasse“ aller Klassen VB.NET. Wenn wir also schreiben:
schreibt man implizit:
Die Klasse object definiert eine virtuelle Methode ToString:

Die Methode ToString gibt den Namen der Klasse zurück, zu der das Objekt gehört, wie das folgende Beispiel zeigt:
' Optionen
Option Strict On
Option Explicit On
' Namensräume
Imports System
Module test2
Sub Main()
' ein Lehrer
Console.Out.WriteLine(New enseignant("Lucile", "Dumas", 56, 61).ToString())
' eine Person
Console.Out.WriteLine(New personne("Jean", "Dupont", 30).ToString())
End Sub
End Module
Die erzeugten Ergebnisse lauten wie folgt:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant
Construction personne(string, string, int)
personne
Es ist zu beachten, dass wir die Methode ToString zwar nicht in den Klassen personne und enseignant neu definiert haben, die Methode ToString der Klasse object dennoch in der Lage ist, den tatsächlichen Klassennamen des Objekts anzuzeigen. Definieren wir die Methode ToString in den Klassen personne und enseignant neu:
' ToString
Public Overrides Function ToString() As String
' die Eigenschaft „identite“ wird zugewiesen
Return identite
End Function
Die Definition ist in beiden Klassen identisch. Betrachten wir das folgende Testprogramm:
' Optionen
Option Strict On
Option Explicit On
' Namensräume
Imports System
Module test
Sub Main()
' ein Lehrer
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' eine Person
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' zeigt an
Sub affiche(ByVal p As personne)
' Identitätsplakat von p
Console.Out.WriteLine(p.identite)
End Sub
End Module
Die Ausführungsergebnisse lauten wie folgt:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Lucile,Dumas,56),61)
Construction personne(string, string, int)
personne(Jean,Dupont,30)
3.3. Einen Indexer für eine Klasse definieren
Betrachten wir die vordefinierte Klasse [ArrayList] in der Plattform .NET. Diese Klasse ermöglicht es, Objekte in einer Liste zu speichern. Sie gehört zum Namensraum [System.Collections]. Im folgenden Beispiel verwenden wir diese Klasse, um eine Liste von Personen (im weiteren Sinne) zu speichern:
' Optionen
Option Strict On
Option Explicit On
' Namensräume
Imports System
Imports System.Collections
' Klasse listeDePersonnes
Public Class listeDePersonnes
Inherits ArrayList
' zum Hinzufügen einer Person zur Liste
Public Overloads Sub Add(ByVal p As personne)
MyBase.Add(p)
End Sub
' einen Indexierer
Default Public Shadows Property Item(ByVal i As Integer) As personne
Get
Return CType(MyBase.Item(i), personne)
End Get
Set(ByVal Value As personne)
MyBase.Item(i) = Value
End Set
End Property
' ein weiterer Indexer
Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
Get
' Es wird nach der Person mit dem Namen N gesucht
Dim i As Integer
For i = 0 To Count - 1
If CType(Me(i), personne).nom = N Then
Return i
End If
Next i
Return -1
End Get
End Property
' toString
Public Overrides Function ToString() As String
' gibt (el1, el2, ..., eln) zurück
Dim liste As String = "("
Dim i As Integer
' Das dynamische Array wird durchlaufen
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' letztes Element
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
End Class
Hier einige Informationen zu bestimmten Eigenschaften und Methoden der Klasse [ArrayList]:
Attribut, das die Anzahl der Elemente in der Liste angibt | |
Methode zum Hinzufügen eines Objekts zur Liste | |
Methode, die das i-te Element der Liste zurückgibt |
Wir stellen fest, dass wir, um das Element Nr. i der Liste abzurufen, nicht [liste.Item(i)] geschrieben haben, sondern direkt [liste(i)], was auf den ersten Blick falsch erscheint. Dies ist jedoch möglich, da die Klasse [ArrayList] eine Standard-Eigenschaft [Item] gemäß einer Syntax definiert, die der folgenden ähnelt:
Default Public Property Item(ByVal i As Integer) As Object
Get
...
End Get
Set(ByVal Value As personne)
...
End Set
End Property
Wenn der Compiler auf die Notation [liste(i)] stößt, prüft er, ob die Klasse [ArrayList] eine Eigenschaft mit der folgenden Signatur definiert hat:
Default Public Property Proc(ByVal var As Integer) As Type
Hier findet er die Prozedur [Item]. Er wandelt dann die Schreibweise [liste(i)] in [liste.Item(i)] um. Die Eigenschaft [Item] wird als standardmäßig indizierte Eigenschaft der Klasse [ArrayList] bezeichnet. Die Ausführung des vorstehenden Programms liefert folgende Ergebnisse:
dos>vbc /r:personne.dll /r:enseignant.dll lstpersonnes1.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:34 3 584 lstpersonnes1.exe
12/03/2004 13:39 661 lstpersonnes1.vb
11/03/2004 18:26 4 096 personne.dll
dos>lstpersonnes1
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(paul,chenou,31)
personne(nicole,chenou,11)
enseignant(personne(jacques,sileau,33),61)
Erstellen wir eine Klasse namens [listeDePersonnes], die eine Liste von Personen darstellt, also eine spezielle Liste, die sich naheliegend aus der Klasse [ArrayList] ableiten lässt:
' Optionen
Option Strict On
Option Explicit On
' Namensräume
Imports System
Imports System.Collections
' Klasse listeDePersonnes
Public Class listeDePersonnes
Inherits ArrayList
' um eine Person zur Liste hinzuzufügen
Public Shadows Sub Add(ByVal p As Object)
' muss p eine Person sein
If Not TypeOf (p) Is personne Then
Throw New Exception("L'objet ajouté (" + p.ToString + ") n'est pas une personne")
Else
MyBase.Add(p)
End If
End Sub
' ein Indexierer
Default Public Shadows Property Index(ByVal i As Integer) As personne
Get
Return CType(MyBase.Item(i), personne)
End Get
Set(ByVal Value As personne)
MyBase.Item(i) = Value
End Set
End Property
' toString
Public Overrides Function ToString() As String
' gibt (el1, el2, ..., eln) zurück
Dim liste As String = "("
Dim i As Integer
' das dynamische Array wird durchlaufen
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' letztes Element
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
End Class
Die Klasse verfügt über die folgenden Methoden und Eigenschaften:
gibt eine Zeichenkette zurück, die den Inhalt der Liste „darstellt“ | |
Methode zum Hinzufügen einer Person zur Liste | |
Standardmäßig indizierte Eigenschaft, die die Person i aus der Liste angibt |
Betrachten wir die Neuerungen:
Es wird eine neue Methode [Add] angelegt.
' um eine Person zur Liste hinzuzufügen
Public Shadows Sub Add(ByVal p As Object)
' muss p eine Person sein
If Not TypeOf (p) Is personne Then
Throw New Exception("L'objet ajouté (" + p.ToString + ") n'est pas une personne")
Else
MyBase.Add(p)
End If
End Sub
Es gibt bereits eine in der übergeordneten Klasse [ArrayList] mit derselben Signatur, daher das Schlüsselwort [Shadows], um anzugeben, dass die neue Prozedur diejenige der übergeordneten Klasse ersetzt. Die Methode [Add] der untergeordneten Klasse prüft, ob das hinzugefügte Objekt tatsächlich vom Typ [personne] ist oder mit der Funktion [TypeOf] abgeleitet wurde. Ist dies nicht der Fall, wird mit der Anweisung [Throw] eine Ausnahme ausgelöst. Ist das hinzugefügte Objekt tatsächlich vom Typ [personne], wird die Methode [Add] der Basisklasse verwendet, um das Hinzufügen durchzuführen.
Es wird eine indizierte Eigenschaft erstellt:
' ein Indexer
Default Public Shadows Property Index(ByVal i As Integer) As personne
Get
Return CType(MyBase.Item(i), personne)
End Get
Set(ByVal Value As personne)
MyBase.Item(i) = Value
End Set
End Property
In der Basisklasse gibt es bereits eine Standard-Eigenschaft namens [Item] mit derselben Signatur. Daher muss das Schlüsselwort [Shadows] verwendet werden, um anzugeben, dass die neue indizierte Eigenschaft [Index] die Eigenschaft der Basisklasse [Item] „überdeckt“. Es ist zu beachten, dass dies auch dann gilt, wenn die beiden Eigenschaften nicht denselben Namen tragen. Die Eigenschaft [Index] ermöglicht es, auf die Person Nr. i in der Liste zu verweisen. Sie stützt sich auf die Eigenschaft [Item] der Basisklasse, um Zugriff auf das Element Nr. i des zugrunde liegenden Objekts [ArrayList] zu erhalten. Es werden Typänderungen vorgenommen, um zu berücksichtigen, dass die Eigenschaft [Item] mit Elementen vom Typ [Object] arbeitet, während die Eigenschaft [Index] mit Elementen vom Typ [personne] arbeitet.
Schließlich überschreiben wir (Overrides) die Methode [ToString] der Klasse [ArrayList]:
' toString
Public Overrides Function ToString() As String
' gibt (el1, el2, ..., eln) zurück
Dim liste As String = "("
Dim i As Integer
' das dynamische Array wird durchlaufen
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' letztes Element
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
Diese Methode gibt eine Zeichenkette der Form „(e1, e2, …, en)“ zurück, wobei ei die Elemente der Liste sind. Beachten Sie die Notation [Me(i)], die das Element Nr. i des aktuellen Objekts [Me] bezeichnet. Es wird die standardmäßige indizierte Eigenschaft verwendet. Somit ist [Me(i)] gleichbedeutend mit [Me.Index(i)].
Der Klassencode wird in die Datei [lstpersonnes2.vb] geschrieben und kompiliert:
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:45 970 lstpersonnes2.vb
11/03/2004 18:26 4 096 personne.dll
dos>vbc /r:personne.dll /r:enseignant.dll /t:library lstpersonnes2.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:50 3 584 lstpersonnes2.dll
12/03/2004 15:45 970 lstpersonnes2.vb
11/03/2004 18:26 4 096 personne.dll
Ein Testprogramm wird erstellt:
' Optionen
Option Explicit On
Option Strict On
' Namensräume
Imports System
Imports System.Collections
Module test
Sub Main()
' Erstellung einer leeren Personenliste
Dim liste As listeDePersonnes = New listeDePersonnes
' Erstellung von Personen
Dim p1 As personne = New personne("paul", "chenou", 31)
Dim p2 As personne = New personne("nicole", "chenou", 11)
Dim e1 As enseignant = New enseignant("jacques", "sileau", 33, 61)
' Liste füllen
liste.Add(p1)
liste.Add(p2)
liste.Add(e1)
' Anzeige der Liste
Console.Out.WriteLine(liste.ToString)
' Hinzufügen eines anderen Objekts als einer Person
Try
liste.Add(4)
Catch e As Exception
Console.Error.WriteLine(e.Message)
End Try
End Sub
End Module
Es wird kompiliert:
dos>vbc /r:personne.dll /r:enseignant.dll /r:lstpersonnes2.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:50 3 584 lstpersonnes2.dll
12/03/2004 15:45 970 lstpersonnes2.vb
11/03/2004 18:26 4 096 personne.dll
12/03/2004 15:50 3 584 test.exe
12/03/2004 15:49 623 test.vb
und anschließend ausgeführt:
dos>test
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
([personne(paul,chenou,31)],[personne(nicole,chenou,11)],[enseignant(personne(jacques,sileau,33),61)])
L'objet ajouté (4) n'est pas une personne
Man könnte schreiben
wobei l vom Typ [listeDePersonnes] wäre. Hier soll die Liste l nicht mehr nach einer Elementnummer, sondern nach einem Personennamen indiziert werden. Dazu definiert man eine neue, standardmäßig indizierte Eigenschaft:
' ein anderer Indexer
Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
Get
' Suche nach der Person mit dem Namen N
Dim i As Integer
For i = 0 To Count - 1
If CType(Me(i), personne).nom = N Then
Return i
End If
Next i
Return -1
End Get
End Property
Die erste Zeile
Default Public Shadows ReadOnly Property Index(ByVal N As String) As Integer
gibt an, dass erneut eine standardmäßig indizierte Eigenschaft erstellt wird. Alle Standardeigenschaften müssen denselben Namen tragen, in diesem Fall [Index]. Die neue Eigenschaft [Index] indiziert die Klasse listeDePersonnes anhand einer Zeichenkette N. Das Ergebnis von listeDePersonnes(N) ist eine Ganzzahl. Diese Ganzzahl gibt die Position der Person mit dem Namen N in der Liste an oder -1, falls diese Person nicht in der Liste enthalten ist. Es wird nur die Eigenschaft get definiert, wodurch die Schreibweise listeDePersonnes verhindert wird ("nom")=valeur hätte die Definition der Eigenschaft set erfordert). Daher das Schlüsselwort [ReadOnly]. Das Schlüsselwort [Shadows] ist erforderlich, um die Standard-Eigenschaft der Basisklasse auszublenden (obwohl sie nicht dieselbe Signatur hat).
Im Hauptteil von get wird die Liste der Personen nach dem als Parameter übergebenen Namen N durchsucht. Wird dieser an Position i gefunden, wird i zurückgegeben, andernfalls -1.
Ein neues Testprogramm könnte wie folgt aussehen:
' Optionen
Option Strict On
Option Explicit On
' Namensräume
Imports System
' Testseite
Module test
Sub Main()
' eine Liste von Personen
Dim l As New listeDePersonnes
' Personen hinzufügen
l.Add(New personne("jean", "dumornet", 10))
l.Add(New personne("pauline", "duchemin", 12))
' Anzeige
Console.Out.WriteLine(("l=" + l.ToString))
l.Add(New personne("jacques", "tartifume", 27))
Console.Out.WriteLine(("l=" + l.ToString))
' Element 1 ändern
l(1) = New personne("sylvie", "cachan", 5)
' Anzeige von Element 1
Console.Out.WriteLine(("l[1]=" + l(1).ToString))
' Anzeige der Liste l
Console.Out.WriteLine(("l=" + l.ToString))
' Personensuche
Dim noms() As String = New [String]() {"cachan", "inconnu"}
Dim i As Integer
For i = 0 To noms.Length - 1
Dim inom As Integer = l(noms(i))
If inom <> -1 Then
Console.Out.WriteLine(("personne(" & noms(i) & ")=" & l(inom).ToString))
Else
Console.Out.WriteLine(("personne(" + noms(i) + ") n'existe pas"))
End If
Next i
End Sub
End Module
Die Ausführung liefert folgende Ergebnisse:
personne(string, string, int)
Construction personne(string, string, int)
l=([personne(jean,dumornet,10)],[personne(pauline,duchemin,12)])
Construction personne(string, string, int)
l=([personne(jean,dumornet,10)],[personne(pauline,duchemin,12)],[personne(jacques,tartifume,27)])
Construction personne(string, string, int)
l[1]=personne(sylvie,cachan,5)
l=([personne(jean,dumornet,10)],[personne(sylvie,cachan,5)],[personne(jacques,tartifume,27)])
personne(cachan)=personne(sylvie,cachan,5)
personne(inconnu) n'existe pas
3.4. Strukturen
Die Struktur VB.NET leitet sich direkt aus der Struktur der Programmiersprache C ab und ähnelt stark einer Klasse. Eine Struktur wird wie folgt definiert:
Structure spersonne
' Attribute
...
' Eigenschaften
...
' Konstruktoren
...
' Methoden
End Structure
Trotz ähnlicher Syntax gibt es wesentliche Unterschiede zwischen Klassen und Strukturen. Der Begriff der Vererbung existiert beispielsweise bei Strukturen nicht. Wenn wir eine Klasse schreiben, die nicht abgeleitet werden soll, welche Unterschiede zwischen Strukturen und Klassen helfen uns dann bei der Entscheidung zwischen beiden? Schauen wir uns das folgende Beispiel an, um dies herauszufinden:
' Optionen
Option Strict On
Option Explicit On
' Namensräume
Imports System
' Struktur „spersonne“
Structure spersonne
Public nom As String
Public age As Integer
End Structure
' Klasse „Person“
Class cpersonne
Public nom As String
Public age As Integer
End Class
' ein Testmodul
Public Module test
Sub Main()
' eine Person p1
Dim sp1 As spersonne
sp1.nom = "paul"
sp1.age = 10
Console.Out.WriteLine(("sp1=spersonne(" & sp1.nom & "," & sp1.age & ")"))
' eine Person p2
Dim sp2 As spersonne = sp1
Console.Out.WriteLine(("sp2=spersonne(" & sp2.nom & "," & sp2.age & ")"))
' sp2 wird geändert
sp2.nom = "nicole"
sp2.age = 30
' Überprüfung von sp1 und sp2
Console.Out.WriteLine(("sp1=cpersonne(" & sp1.nom & "," & sp1.age & ")"))
Console.Out.WriteLine(("sp2=cpersonne(" & sp2.nom & "," & sp2.age & ")"))
' eine Person cp1
Dim cp1 As New cpersonne
cp1.nom = "paul"
cp1.age = 10
Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
' eine Person cp1 P2
Dim cp2 As cpersonne = cp1
Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
' P2 wurde geändert
cp2.nom = "nicole"
cp2.age = 30
' Überprüfung von P1 und P2
Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
End Sub
End Module
Wenn man dieses Programm ausführt, erhält man folgende Ergebnisse:
sp1=spersonne(paul,10)
sp2=spersonne(paul,10)
sp1=cpersonne(paul,10)
sp2=cpersonne(nicole,30)
cP1=cpersonne(paul,10)
cP2=cpersonne(paul,10)
cP1=cpersonne(nicole,30)
cP2=cpersonne(nicole,30)
Wo auf den vorherigen Seiten dieses Kapitels die Klasse personne verwendet wurde, verwenden wir nun die Struktur spersonne:
' Struktur „spersonne“
Structure spersonne
Public nom As String
Public age As Integer
End Structure
Die Erklärung
Dim sp1 As spersonne
erzeugt eine Struktur (name,alter), und der Wert von sp1 ist diese Struktur selbst.
Die Deklaration
Dim cp1 As New cpersonne
legt ein Objekt [cpersonne] an (grob gesagt das Äquivalent unserer Struktur), und cp1 ist dann die Adresse (die Referenz) dieses Objekts.
Fassen wir zusammen
- Im Fall der Struktur ist der Wert von sp1 die Struktur selbst
- Im Fall der Klasse ist der Wert von p1 die Adresse des erstellten Objekts
![]() |
![]() |
Wenn im Programm Folgendes geschrieben wird
Dim sp2 As spersonne = sp1
wird eine neue Struktur (Name, Alter) angelegt und mit dem Wert von p1 initialisiert, also mit der Struktur selbst.
![]() |
Die Struktur von sp1 wird somit in sp2 dupliziert. Es handelt sich um eine Wertkopie.
Die Anweisung
Dim cp2 As cpersonne = cp1
verhält sich anders. Der Wert von cp1 wird in cp2 kopiert, doch da es sich bei diesem Wert tatsächlich um die Adresse des Objekts handelt, wird dieses nicht dupliziert. Es gibt lediglich zwei Verweise darauf:
![]() |
Im Fall der Struktur wird durch eine Änderung des Werts von sp2 der Wert von sp1 nicht verändert, was das Programm zeigt. Im Fall des Objekts wird, wenn man das Objekt ändert, auf das cp2 verweist, auch das Objekt geändert, auf das cp1 verweist, da es sich um dasselbe Objekt handelt. Dies zeigen ebenfalls die Ergebnisse des Programms.
Aus diesen Erläuterungen lässt sich also folgern:
- Der Wert einer Variablen vom Typ „Struktur“ ist die Struktur selbst
- der Wert einer Variablen vom Typ „Objekt“ ist die Adresse des Objekts, auf das verwiesen wird
Sobald man diesen grundlegenden Unterschied verstanden hat, erweist sich die Struktur als der Klasse sehr ähnlich, wie das folgende neue Beispiel zeigt:
' Optionen
Option Strict On
Option Explicit On
' Namensräume
Imports System
' Personenstruktur
Structure personne
' Attribute
Private _nom As String
Private _age As Integer
' Eigenschaften
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
_age = value
End Set
End Property
' Konstruktor
Public Sub New(ByVal NOM As String, ByVal AGE As Integer)
_nom = NOM
_age = AGE
End Sub 'New
' TOSTRING
Public Overrides Function ToString() As String
Return "personne(" & nom & "," & age & ")"
End Function
End Structure
' ein Testmodul
Module test
Sub Main()
' eine Person p1
Dim p1 As New personne("paul", 10)
Console.Out.WriteLine(("p1=" & p1.ToString))
' eine Person p2
Dim p2 As personne = p1
Console.Out.WriteLine(("p2=" & p2.ToString))
' p2 wird geändert
p2.nom = "nicole"
p2.age = 30
' Überprüfung von p1 und p2
Console.Out.WriteLine(("p1=" & p1.ToString))
Console.Out.WriteLine(("p2=" & p2.ToString))
End Sub
End Module
Man erhält folgende Ausführungsergebnisse:
Der einzige nennenswerte Unterschied zwischen Struktur und Klasse besteht darin, dass bei einer Klasse die Objekte p1 und p2 am Ende des Programms denselben Wert gehabt hätten, nämlich den von p2.
3.5. Schnittstellen
Eine Schnittstelle ist eine Sammlung von Prototypen von Methoden oder Eigenschaften, die einen Vertrag bilden. Eine Klasse, die sich entscheidet, eine Schnittstelle zu implementieren, verpflichtet sich, eine Implementierung aller in der Schnittstelle definierten Methoden bereitzustellen. Der Compiler überprüft diese Implementierung. Hier ist beispielsweise die Definition der Schnittstelle Istats:
Jede Klasse, die diese Schnittstelle implementiert, wird wie folgt deklariert:
public class C
Implements Istats
...
function moyenne() as Double Implements Istats.moyenne
...
end function
function écartType () as Double Implements Istats. écartType
...
end function
end class
Die Methoden [moyenne] und [écartType] müssen in der Klasse C definiert werden. Betrachten wir den folgenden Code:
' Optionen
Option Strict On
Option Explicit On
' Namensräume
Imports System
' Struktur
Public Structure élève
Public _nom As String
Public _note As Double
' Konstruktor
Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
Me._nom = NOM
Me._note = NOTE
End Sub
End Structure
' Klasse „Notes“
Public Class notes
' Attribut
Protected _matière As String
Protected _élèves() As élève
' Konstruktor
Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
' Speicherung von Schülern und Fächern
Me._matière = MATIERE
Me._élèves = ELEVES
End Sub
' ToString
Public Overrides Function ToString() As String
Dim valeur As String = "matière=" + _matière + ", notes=("
Dim i As Integer
' alle Noten werden verkettet
For i = 0 To (_élèves.Length - 1) - 1
valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "],"
Next i
'letzte Note
If _élèves.Length <> 0 Then
valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "]"
End If
valeur += ")"
' Ende
Return valeur
End Function
End Class
Die Klasse notes fasst die Noten einer Klasse in einem Fach zusammen:
Public Class notes
' Attribut
Protected _matière As String
Protected _élèves() As élève
Die Attribute werden als protected deklariert, damit sie von einer abgeleiteten Klasse aus zugänglich sind. Der Typ élève ist eine Struktur, die den Namen des Schülers und seine Note in dem Fach speichert:
Public Structure élève
Public _nom As String
Public _note As Double
' Konstruktor
Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
Me._nom = NOM
Me._note = NOTE
End Sub
End Structure
Wir beschließen, diese Klasse notes in eine Klasse notesStats abzuleiten, die zwei zusätzliche Attribute haben soll: den Mittelwert und die Standardabweichung der Noten:
Public Class notesStats
Inherits notes
Implements Istats
' Attribute
Private _moyenne As Double
Private _écartType As Double
Die Klasse notesStats implementiert die folgende Schnittstelle Istats:
Das bedeutet, dass die Klasse notesStats zwei Methoden namens moyenne und écartType mit der in der Schnittstelle Istats angegebenen Signatur enthalten muss. Die Klasse notesStats sieht wie folgt aus:
' Optionen
Option Strict On
Option Explicit On
' Namensräume
Imports System
Public Class notesStats
Inherits notes
Implements Istats
' Attribute
Private _moyenne As Double
Private _écartType As Double
' Konstruktor
Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
MyBase.New(MATIERE, ELEVES)
' Berechnung des Notendurchschnitts
Dim somme As Double = 0
Dim i As Integer
For i = 0 To ELEVES.Length - 1
somme += ELEVES(i)._note
Next i
If ELEVES.Length <> 0 Then
_moyenne = somme / ELEVES.Length
Else
_moyenne = -1
End If
' Standardabweichung
Dim carrés As Double = 0
For i = 0 To ELEVES.Length - 1
carrés += Math.Pow(ELEVES(i)._note - _moyenne, 2)
Next i
If ELEVES.Length <> 0 Then
_écartType = Math.Sqrt((carrés / ELEVES.Length))
Else
_écartType = -1
End If
End Sub
' ToString
Public Overrides Function ToString() As String
Return MyBase.ToString() & ",moyenne=" & _moyenne & ",écart-type=" & _écartType
End Function 'ToString
' Methoden der Istats-Schnittstelle
Public Function moyenne() As Double Implements Istats.moyenne
' berechnet den Notendurchschnitt
Return _moyenne
End Function
Public Function écartType() As Double Implements Istats.écartType
' gibt die Standardabweichung zurück
Return _écartType
End Function
End Class
Der Mittelwert _moyenne und die Standardabweichung _ecartType werden bereits bei der Erstellung des Objekts berechnet. Daher müssen die Methoden moyenne und écartType lediglich den Wert der Attribute _moyenne und _ecartType zurückgeben. Beide Methoden geben -1 zurück, wenn das Schülerarray leer ist.
Die folgende Testklasse:
' Optionen
Option Strict On
Option Explicit On
' Namensräume
Imports System
Module test
Sub Main()
' Schüler und Noten
Dim ELEVES() As élève = {New élève("paul", 14), New élève("nicole", 16), New élève("jacques", 18)}
' die in einem Notenobjekt gespeichert werden
Dim anglais As New notes("anglais", ELEVES)
' und die angezeigt werden
Console.Out.WriteLine((anglais.ToString))
' dasselbe mit Durchschnittswert und Standardabweichung
anglais = New notesStats("anglais", ELEVES)
Console.Out.WriteLine((anglais.ToString))
End Sub
End Module
liefert folgende Ergebnisse:
matière=anglais, notes=([paul,14],[nicole,16],[jacques,18])
matière=anglais, notes=([paul,14],[nicole,16],[jacques,18]),moyenne=16,écart-type=1,63299316185545
Die Klasse notesStats hätte die Methoden moyenne und écartType sehr wohl für sich selbst implementieren können, ohne anzugeben, dass sie die Schnittstelle Istats implementiert. Wozu dienen Schnittstellen? Der Sinn liegt darin, dass eine Funktion als formalen Parameter einen Wert vom Typ einer Schnittstelle I annehmen kann. Jedes Objekt einer Klasse C, das die Schnittstelle I implementiert, kann dann als tatsächlicher Parameter dieser Funktion dienen. Betrachten wir das folgende Beispiel:
' Optionen
Option Strict On
Option Explicit On
' Namensräume
Imports System
' eine Schnittstelle „Iexample“
Public Interface Iexemple
Function ajouter(ByVal i As Integer, ByVal j As Integer) As Integer
Function soustraire(ByVal i As Integer, ByVal j As Integer) As Integer
End Interface
' eine erste Klasse
Public Class classe1
Implements Iexemple
Public Function ajouter(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.ajouter
Return a + b + 10
End Function
Public Function soustraire(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.soustraire
Return a - b + 20
End Function
End Class
'eine zweite Klasse
Public Class classe2
Implements Iexemple
Public Function ajouter(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.ajouter
Return a + b + 100
End Function
Public Function soustraire(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.soustraire
Return a - b + 200
End Function
End Class
Die Schnittstelle Iexemple definiert zwei Methoden: ajouter und soustraire. Die Klassen classe1 und classe2 implementieren diese Schnittstelle. Es ist zu beachten, dass diese Klassen aus Gründen der Vereinfachung des Beispiels nichts weiter tun. Betrachten wir nun das folgende Beispiel:
' Optionen
Option Strict On
Option Explicit On
' Namensräume
Imports System
' Testklasse
Module test
'berechnen
Sub calculer(ByVal i As Integer, ByVal j As Integer, ByVal inter As Iexemple)
Console.Out.WriteLine(inter.ajouter(i, j))
Console.Out.WriteLine(inter.soustraire(i, j))
End Sub
' die Funktion „Main“
Sub Main()
' Erstellung von zwei Objekten „Klasse1“ und „Klasse2“
Dim c1 As New classe1
Dim c2 As New classe2
' Aufrufe der statischen Funktion „berechnen“
calculer(4, 3, c1)
calculer(14, 13, c2)
End Sub
End Module
Die Funktion calculer akzeptiert als Parameter ein Element vom Typ Iexemple. Sie kann für diesen Parameter also sowohl ein Objekt vom Typ classe1 als auch vom Typ classe2 entgegennehmen. Dies geschieht in der Prozedur Main mit folgenden Ergebnissen:
Man sieht also, dass es sich hier um eine Eigenschaft handelt, die dem bei Klassen betrachteten Polymorphismus ähnelt. Wenn eine Gruppe von Klassen Ci, die nicht durch Vererbung miteinander verbunden sind (sodass der Polymorphismus der Vererbung nicht genutzt werden kann), eine Reihe von Methoden mit derselben Signatur aufweist, kann es sinnvoll sein, diese Methoden in einer Schnittstelle I zusammenzufassen, von der alle betroffenen Klassen erben würden. Instanzen dieser Klassen Ci können dann als Parameter von Funktionen verwendet werden, die einen Parameter vom Typ I akzeptieren, c.a.d. Dabei handelt es sich um Funktionen, die ausschließlich die in der Schnittstelle I definierten Methoden der Objekte Ci nutzen und nicht die spezifischen Attribute und Methoden der einzelnen Klassen Ci. Schließlich sei angemerkt, dass die Vererbung von Schnittstellen mehrfach erfolgen kann, c.a.d. Man kann also schreiben
Public Class classe
Implements I1,I2,...
wobei die Ij Schnittstellen sind.
3.6. Namensräume
Um eine Zeile auf dem Bildschirm auszugeben, verwenden wir die Anweisung
Wenn wir uns die Definition der Klasse Console ansehen
Namespace: System
Assembly: Mscorlib (in Mscorlib.dll)
stellen wir fest, dass sie zum Namensraum System gehört. Das bedeutet, dass die Klasse Console als System.Console bezeichnet werden sollte und man eigentlich schreiben müsste:
Dies lässt sich durch die Verwendung einer Klausel imports vermeiden:
Es wird angenommen, dass der Namensraum System mit der Klausel imports importiert wird. Wenn der Compiler auf den Namen einer Klasse stößt (hier Console), sucht er diese in den verschiedenen Namensräumen, die durch die Klauseln imports importiert wurden. Hier findet er die Klasse Console im Namensraum System. Beachten wir nun die zweite Information, die mit der Klasse Console verbunden ist:
Assembly: Mscorlib (in Mscorlib.dll)
Diese Zeile gibt an, in welcher „Assembly“ sich die Definition der Klasse Console befindet. Wenn man außerhalb von Visual Studio.NET kompiliert und die Referenzen der verschiedenen dll angeben muss, die die zu verwendenden Klassen enthalten, kann diese Information nützlich sein. Zur Erinnerung: Um die für die Kompilierung einer Klasse erforderlichen dll zu referenzieren, schreibt man:
Wenn man eine Klasse erstellt, kann man diese innerhalb eines Namensraums anlegen. Der Zweck dieser Namensräume besteht darin, Namenskonflikte zwischen Klassen zu vermeiden, wenn diese beispielsweise verkauft werden. Betrachten wir zwei Unternehmen, E1 und E2, die Klassen vertreiben, die jeweils in den dll, E1.dll und E2.dll vertreiben. Nehmen wir an, ein Kunde C kauft diese beiden Klassensätze, in denen beide Unternehmen eine Klasse personne definiert haben. Der Kunde C kompiliert ein Programm wie folgt:
Wenn die Quelldatei prog.vb die Klasse personne verwendet, weiß der Compiler nicht, ob er die Klasse personne aus E1.dll oder die aus E2.dll verwenden soll. Er meldet einen Fehler. Wenn das Unternehmen E1 darauf achtet, seine Klassen in einem Namensraum namens E1 anzulegen, und das Unternehmen E2 in einem Namensraum namens E2, dann heißen die beiden Klassen personne E1.personne und E2.personne. Der Kunde muss in seinen Klassen entweder E1.personne oder E2.personne verwenden, jedoch nicht personne. Der Namensraum ermöglicht es, diese Mehrdeutigkeit zu beseitigen. Um eine Klasse in einem Namensraum anzulegen, schreibt man:
Namespace istia.st
Public Class personne
' Definition der Klasse
...
end Class
end Namespace
Erstellen wir zum Beispiel in einem Namensraum unsere zuvor untersuchte Klasse personne. Wir wählen istia.st als Namensraum. Die Klasse personne lautet nun:
' Optionen
Option Strict On
Option Explicit On
' Namensräume
Imports System
' Erstellung des Namensraums istia.st
Namespace istia.st
Public Class personne
' Attribute
Private prenom As String
Private nom As String
Private age As Integer
' Methode
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' Methode
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
End Namespace
Diese Klasse wird zu personne.dll kompiliert:
dos>vbc /t:library personne.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
Verwenden wir nun die Klasse personne in einer Testklasse:
' Optionen
Option Strict On
Option Explicit On
' Namensräume
Imports System
Imports istia.st
' Testseite
Public Module test
Sub Main()
Dim p1 As New personne
p1.initialise("Jean", "Dupont", 30)
p1.identifie()
End Sub
End Module
Um zu vermeiden, dass wir
Dim p1 As New istia.st.personne
haben wir den Namensraum istia.st mit einer imports-Klausel importiert:
Imports istia.st
Nun kompilieren wir das Testprogramm:
dos>dir
12/03/2004 18:06 3 584 personne.dll
11/03/2004 18:27 610 personne.vb
12/03/2004 18:05 254 test.vb
dos>vbc /r:personne.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
12/03/2004 18:06 3 584 personne.dll
11/03/2004 18:27 610 personne.vb
12/03/2004 18:08 3 072 test.exe
12/03/2004 18:05 254 test.vb
Dadurch wird eine Datei namens „test.exe“ erstellt, deren Ausführung folgende Ergebnisse liefert:
3.7. Das Beispiel IMPOTS
Wir greifen die bereits im vorigen Kapitel behandelte Steuerberechnung wieder auf und bearbeiten sie mithilfe einer Klasse. Zur Erinnerung:
Wir betrachten den vereinfachten Fall eines Steuerpflichtigen, der nur sein Gehalt anzugeben hat:
- Wir berechnen die Anzahl der Anteile des Arbeitnehmers: nbParts = nbEnfants/2 + 1, wenn er unverheiratet ist, nbEnfants/2 + 2, wenn er verheiratet ist, wobei nbEnfants die Anzahl seiner Kinder ist.
- Wenn er mindestens drei Kinder hat, erhält er einen halben Anteil zusätzlich
- sein steuerpflichtiges Einkommen R wird berechnet als R = 0,72 * S, wobei S sein Jahresgehalt ist
- man berechnet seinen Familienkoeffizienten QF = R / nbParts
- Man berechnet seine Steuer I. Betrachten wir die folgende Tabelle:
12620,0 | 0 | 0 |
13.190 | 0,05 | 631 |
15640 | 0,1 | 1290,5 |
24.740 | 0,15 | 2072,5 |
31.810 | 0,2 | 3309,5 |
39.970 | 0,25 | 4900 |
48360 | 0,3 | 6898,5 |
55.790 | 0,35 | 9316,5 |
92.970 | 0,4 | 12106 |
127.860 | 0,45 | 16.754,5 |
151250 | 0,50 | 23.147,5 |
172.040 | 0,55 | 30.710 |
195.000 | 0,60 | 39312 |
0 | 0,65 | 49062 |
Jede Zeile enthält 3 Felder. Um die Steuer I zu berechnen, wird die erste Zeile gesucht, in der QF <= Feld 1 gilt. Wenn beispielsweise QF = 23000 ist, wird die Zeile
Die Steuer I beträgt dann 0,15*R – 2072,5*nbParts. Wenn QF so ist, dass die Beziehung QF <= Feld1 niemals erfüllt ist, werden die Koeffizienten der letzten Zeile verwendet. Hier:
was die Steuer I = 0,65 * R – 49062 * nbParts ergibt.
Die Steuerklasse „impot“ wird wie folgt definiert:
' Optionen
Option Strict On
Option Explicit On
' Namensräume
Imports System
Public Class impot
' die für die Steuerberechnung erforderlichen Daten
' stammen aus einer externen Quelle
Private limites(), coeffR(), coeffN() As Decimal
' Hersteller
Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
' Es wird überprüft, ob alle drei Tabellen dieselbe Größe haben
Dim OK As Boolean = LIMITES.Length = COEFFR.Length And LIMITES.Length = COEFFN.Length
If Not OK Then
Throw New Exception("Les 3 tableaux fournis n'ont pas la même taille(" & LIMITES.Length & "," & COEFFR.Length & "," & COEFFN.Length & ")")
End If
' alles in Ordnung
Me.limites = LIMITES
Me.coeffR = COEFFR
Me.coeffN = COEFFN
End Sub
' Berechnung der Steuer
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
' Berechnung der Anzahl der Anteile
Dim nbParts As Decimal
If marié Then
nbParts = CDec(nbEnfants) / 2 + 2
Else
nbParts = CDec(nbEnfants) / 2 + 1
End If
If nbEnfants >= 3 Then
nbParts += 0.5D
End If
' Berechnung des zu versteuernden Einkommens und des Familienquotienten
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' Steuerberechnung
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
End Function
End Class
Es wird ein Steuerobjekt mit den Daten angelegt, die die Berechnung der Steuer eines Steuerpflichtigen ermöglichen. Dies ist der feste Teil des Objekts. Sobald dieses Objekt angelegt ist, kann man wiederholt seine Methode „berechnen“ aufrufen, die die Steuer des Steuerpflichtigen anhand seines Familienstands (verheiratet oder nicht), der Anzahl seiner Kinder und seines Jahresgehalts berechnet. Ein Testprogramm könnte wie folgt aussehen:
' Optionen
Option Strict On
Option Explicit On
' Namensräume
Imports System
Imports Microsoft.VisualBasic
Module test
Sub Main()
' Interaktives Programm zur Steuerberechnung
' Der Benutzer gibt drei Daten über die Tastatur ein: verheiratet nbEnfants Gehalt
' Das Programm zeigt daraufhin die zu zahlende Steuer an
Const syntaxe As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' für die Steuerberechnung erforderliche Datentabellen
Dim limites() As Decimal = {12620D, 13190D, 15640D, 24740D, 31810D, 39970D, 48360D, 55790D, 92970D, 127860D, 151250D, 172040D, 195000D, 0D}
Dim coeffR() As Decimal = {0D, 0.05D, 0.1D, 0.15D, 0.2D, 0.25D, 0.3D, 0.35D, 0.4D, 0.45D, 0.5D, 0.55D, 0.6D, 0.65D}
Dim coeffN() As Decimal = {0D, 631D, 1290.5D, 2072.5D, 3309.5D, 4900D, 6898.5D, 9316.5D, 12106D, 16754.5D, 23147.5D, 30710D, 39312D, 49062D}
' Anlegen eines Steuerobjekts
Dim objImpôt As impot = Nothing
Try
objImpôt = New impot(limites, coeffR, coeffN)
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(1)
End Try
' Endlosschleife
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Long
While True
' Die Parameter für die Steuerberechnung werden abgefragt
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' Was ist zu tun?
If paramètres Is Nothing OrElse paramètres = "" Then
Exit While
End If
' Überprüfung der Anzahl der Argumente in der eingegebenen Zeile
Dim erreur As Boolean = False
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe)
erreur = True
End If
' Überprüfung der Gültigkeit der Parameter
If Not erreur Then
' verheiratet
marié = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
erreur = True
End If
' nbEnfants
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
' Gehalt
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
End If
' Wenn die Parameter korrekt sind, wird die Steuer berechnet
If Not erreur Then
Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire) & " F"))
Else
Console.Error.WriteLine(syntaxe)
End If
End While
End Sub
End Module
Hier ist ein Beispiel für die Ausführung des vorherigen Programms:
dos>vbc /t:library impots.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dir>dir
12/03/2004 18:24 4 096 impots.dll
12/03/2004 18:20 1 483 impots.vb
12/03/2004 18:21 2 805 test.vb
dos>vbc /r:impots.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
12/03/2004 18:24 4 096 impots.dll
12/03/2004 18:20 1 483 impots.vb
12/03/2004 18:26 6 144 test.exe
12/03/2004 18:21 2 805 test.vb
dos>test
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :x x x
syntaxe : marié nbEnfants salaire
marié : o pour marié, n pour non marié
nbEnfants : nombre d'enfants
salaire : salaire annuel en F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22504 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :





