Visual Basic .NET/ Druck Auto
Visual Basic ist eine Sprache mit einer bewegten Vergangenheit, die im Jahre 1964 mit der Entwicklung des direkten Vorgängers BASIC begann. BASIC ist die Abkürzung für „Beginner's All-purpose Symbolic Instruction Code“, das heißt in etwa „symbolische Allzweck-Programmiersprache für Anfänger“. Schon der Name verrät, dass diese Sprache vor allem auf Programmieranfänger ausgelegt war. Sie zeichnete sich durch eine einfache Syntax aus, d.h., der Quelltext, den der Programmierer verfasst, ist sehr leicht verständlich. Außerdem beinhaltete die Sprache im Gegensatz zu anderen damaligen Programmiersprachen wie Fortran sehr wenige und einfache Anweisungen, wodurch BASIC schnell eine große Verbreitung an Schulen oder sonstigen Lehreinrichtungen fand, vor allem als Erstsprache, mit denen Anfängern grundlegende Konzepte der Programmierung vermittelt werden sollten.
Rückblickend war die größte Stärke von BASIC, die Simplizität, gleichzeitig ihre größte Schwäche. Die Sprache war für professionelle Anwendungen schlicht zu einfach, sie enthielt zu wenig Funktionen, um den Anforderungen der Firmen gerecht zu werden. Außerdem verfügte BASIC über so gut wie keine Kontrollstrukturen (bestimmte Befehle, die den Quelltext strukturieren, damit er kürzer und verständlicher wird), sodass der Quelltext einfach viel zu schwer zu lesen war. In diesem Zusammenhang wird oft der Begriff Spaghetti-Code (Code = Quelltext) verwendet, der ausdrückt, dass sich der Code ungefähr so einfach nachvollziehen lässt wie die Lage der einzelnen Nudeln auf einem Teller Spaghetti.
Eines der ersten Produkte von Microsoft war ein Interpreter für BASIC, d.h. ein Programm, das BASIC-Code in ein ausführbares Programm umsetzt. Daher ist es nicht verwunderlich, dass Microsoft den Versuch startete, diese Sprache an die Anforderungen der heutigen Zeit anzupassen. Ergebnis war 1991 „Microsoft Visual Basic 1.0“, das neue Maßstäbe in der Entwicklung von Programmen setzte. Es war mit einer sogenannten IDE ausgestattet. IDE ist die Abkürzung für „Integrated Development Environment“, zu deutsch „Integrierte Entwicklungsumgebung“. Diese Umgebung vereinigt einen Texteditor, in dem der Code geschrieben wird, mit der Möglichkeit, aus dem Code ein Programm zu erstellen und dieses auszuführen. Eine völlige Neuheit war der Fensterdesigner, in dem die Fenster erstellt werden konnten, die bei der Ausführung eines Programmes auf dem Bildschirm erscheinen. Bis dahin musste man solche Fenster im Code erstellen, was äußerst mühsam war. Mit Visual Basic war es möglich, in erstaunlich kurzer Zeit erstaunlich professionell wirkende Anwendungen zu erstellen. Leider waren diese Anwendungen sehr langsam, und die Funktionalität von Visual Basic war bei weitem noch nicht so groß wie etwa die von C++, sodass Visual-Basic-Programme in professionellen Kreisen fast ausschließlich als Prototypen für Programme dienten, die später etwa in C++ implementiert wurden.
Der nächste bedeutende Schritt war „Visual Basic 5.0“, das durch neue Funktionen versuchte, zu C++ aufzuschließen. So wurden hier erste „objektorientierte“ Konzepte verwirklicht. Das heißt, dass bestimmte Funktionalitäten des Programms in Objekte „verpackt“ werden, was die Übersichtlichkeit erhöht. Außerdem konnten solche Objekte von anderen Projekten wiederverwendet werden. Ein Beispiel: In einer Firma arbeitet jemand an einem Malprogramm und schreibt dafür ein Objekt, das Funktionalitäten bietet, um auf eine Bildfläche zu zeichnen. Sein Kollege nutzt dieses Objekt in einem anderen Projekt, in dem es z.B. um die Darstellung von mathematischen Kurven und Funktionsgraphen geht. Man sieht, dass objektorientiertes Programmieren die Arbeit erleichtern kann. Leider verfügt Visual Basic 5 nur über rudimentäre Fähigkeiten auf diesem Gebiet, genau wie sein Nachfolger Visual Basic 6.
Neben der objektorientierten Programmierung bot Visual Basic 5 eine weitere, viel wichtigere Neuerung: Die Programme wurden nun in echte Maschinencode-Programme übersetzt. (Maschinencode ist die kodierte Information, die vom Prozessor direkt verstanden wird.) Bis Version 4 konnte Visual Basic nur Programme in einem Zwischencode erstellen, der umständlich in Maschinencode übersetzt werden musste, sobald das Programm startete. Die Verwendung von Maschinencode bedeutete einen enormen Geschwindigkeitszuwachs, jedoch blieb Visual Basic weiter hinter C++ und anderen Programmiersprachen.
2002 erfolgte der Umbruch in der Visual-Basic-Historie. In diesem Jahr veröffentlichte Microsoft die .NET-Architektur. Visual Basic wurde als „Visual Basic .NET“ in diese Architektur eingegliedert. Im Prinzip bedeutet .NET einen Rückschritt, denn anstatt Maschinencode wird nun wieder ein Zwischencode erstellt, der erst in Maschinencode übersetzt wird, wenn das Programm startet. Doch mit .NET sollte alles anders werden, denn das .NET-Framework, das für diese Übersetzung zuständig ist, arbeitet viel effizienter als die frühen Visual-Basic-Programme. Zum Beispiel wird das Programm erst komplett übersetzt und dann ausgeführt, während Visual Basic 1 bis 4 die Programme nach Bedarf häppchenweise übersetzten und die Häppchen sofort ausführten. Außerdem kann das .NET-Framework den Maschinencode während der Übersetzung an die vorhandene Hardware anpassen, z.B. bestimmte Zusatzfunktionen des Prozessors nutzen oder, wenn eine schnelle Grafikkarte vorhanden ist, die Berechnung von Bildern darauf auslagern.
Da nicht nur Visual Basic, sondern auch C++, die Microsoft-Variante von Java namens J# und das von Microsoft für .NET neu entwickelte C# in die .NET-Architektur eingebunden wurden, werden VB.NET-Programme genauso schnell ausgeführt wie C++.NET-Programme. Für den Programmierer bietet .NET den Vorteil, dass nun endlich die objektorientierte Programmierung konsequent unterstützt wird. Außerdem stellt Microsoft mit dem .NET-Framework Tausende von Objekten zur Verfügung, die alltägliche Aufgaben vereinfachen, etwa das Rechnen mit komplexen mathematischen Funktionen oder das Lesen und Schreiben von Dateien.
Warum Visual Basic .NET?
Mit der Umstellung auf .NET hat die Visual-Basic-Sprache tiefgreifende Veränderungen erfahren. Es heißt, dass die Sprache dadurch schwieriger geworden ist. Auf jeden Fall ist sie komplexer geworden, doch erstens ist Visual Basic dadurch um einiges mächtiger geworden, und zweitens ist Visual Basic immer noch eine der am einfachsten erlernbaren Programmiersprachen, abgesehen von einigen Exoten, mit denen sich aber meist nicht einmal ein Fenster erstellen oder eine Datei schreiben lässt. Visual Basic .NET bietet einen guten Kompromiss zwischen Leistungsfähigkeit und Beherrschbarkeit. Außerdem gibt es für Visual Basic kostenlose und sehr leistungsstarke Entwicklungsumgebungen (siehe nächstes Kapitel).
Entwicklungsumgebungen
Wollen Sie mit Visual Basic .NET Programme schreiben, brauchen Sie zuerst eine Entwicklungsumgebung. Sicherlich könnten Sie den Code auch in einem Texteditor (Editor oder WordPad) schreiben, doch das wäre äußerst mühsam. Eine Entwicklungsumgebung steht Ihnen mit vielen Hilfsmitteln zur Seite, vor allem einer grafischen Oberfläche und einer Referenz zu den Funktionen von Visual Basic .NET (enthält Tausende von Objekten mit jeweils Dutzenden von Funktionen)Siehe Downloads (Microsoft Visual Basic).
Microsoft hat Visual Basic ursprünglich entwickelt. So ist es nicht verwunderlich, dass die am weitesten verbreitete Entwicklungsumgebung für Visual Basic von Microsoft selber kommt. Microsoft Visual Basic ist sowohl einzeln als auch als Teil des Microsoft Visual Studio erhältlich, das verschiedene Programmiersprachen, z.B. auch C++ und C#, beherrscht, und sich in seinem Funktionsumfang vor allem an professionelle Anwender richtet. Die aktuelle Version von Visual Basic ist Visual Basic 2008. Mit der Einführung von .NET hat Microsoft die bis Version 6 übliche Nummerierung (Visual Basic 1.0 bis Visual Basic 6.0) aufgegeben, die Produktnamen beziehen sich auf das Veröffentlichungsjahr (obwohl z.B. Visual Basic 2005 erst im Februar 2006 erschienen ist).
Für Einsteiger und Heimanwender zugeschnitten ist die sogenannte Visual Basic 2008 Express Edition, ein hochtrabender Name für eine eingeschränkte, aber dafür kostenlos nutzbare Version von Visual Basic 2008, die viele wichtige Funktionen der normalen Version beinhaltet, vor allem die weitentwickelte und mächtige Entwicklungsumgebung. Für Umsteiger von Visual Basic 5 und 6 bietet Visual Basic 2008 eine Funktion zum Konvertieren ihrer Projekte in .NET-Projekte (wobei die alten Projekte in einer Koexistenz erhalten bleiben).
Die Visual Basic 2008 Express Edition steht gegenwärtig (August 2008) bei Microsoft zum Download bereit. Das Programm kann ohne zeitliche Einschränkung weiterbenutzt werden. Sollten Sie keine Gelegenheit zum Download gefunden haben oder sollte der Download für Sie nicht möglich sein (vor allem wenn Sie versuchen, die über 400 Megabyte große Installationsdatei über eine Modemverbindung herunterzuladen), gibt es eine freie Alternative: SharpDevelop (kurz: #develop). Frei bedeutet in diesem Kontext nicht nur kostenlos, sondern quelloffen, das heißt, der Quelltext kann frei heruntergeladen und verändert werden. Somit gibt es die Garantie, dass SharpDevelop immer frei verfügbar ist.
Mittlerweile ist SharpDevelop 2.2 (Version 3.0 befindet sich zur Zeit (August 2008) noch in Entwicklung) verfügbar, das die gleichen Funktionen unterstützt wie Visual Basic 2005. Es ist anzunehmen, dass die in Vorbereitung befindliche Version 3.0 sich an Visual Basic 2008 orientiert. Obwohl das SharpDevelop-Projekt erst ein paar Jahre läuft (zum Vergleich: Microsoft Visual Basic 1.0 wurde 1991 veröffentlicht), gleichen sich SharpDevelop und Microsoft Visual Basic in Sachen Funktionalität und Bequemlichkeit fast vollkommen.
SharpDevelop gibt es nur für Windows. Für Unix-artige Betriebssysteme, zum Beispiel Linux oder BSD, kann man MonoDevelop für die freie .NET-Umgebung Mono benutzen. MonoDevelop ist aus SharpDevelop entstanden, jedoch mittlerweile eigenständig. Eine aktuelle Mono-Version kommt mittlerweile mit den großen Linux-Distributionen mit, ansonsten kann man die Pakete von der untengenannten Seite herunterladen.
Downloads
- Microsoft Visual Basic 2010 Express Edition (deutsch)
- SharpDevelop (offizielle Internetpräsenz, englisch)
- Mono-Projekt (offizielle Internetpräsenz, englisch)
Einstieg in Microsoft Visual Basic
Eine komfortable IDE
Microsoft Visual Studio (auch als VS abgekürzt) ist eine vollständige IDE (Integrated Development Environment, also Integrierte Entwicklungsumgebung) und bietet alles, was das Programmieren erleichtert:
- Syntax-Hervorhebung
- Code-Vervollständigung
- Zusammenfassung mehrerer Quelltexte zu Projekten
- schneller Wechsel zum Compiler mit Debugger
- integrierte Hilfe zum .NET Framework
Visual Studio steht als professionelles Werkzeug (mit unterschiedlichem Umfang) zur Verfügung. Es ist auch als kostenlose Community-Version erhältlich.
Installation und Einrichtung
Visual Studio glänzt durch eine relativ autonome Installationsroutine, die, soweit noch nicht vorhanden, auch den nötigen Unterbau installiert, vor allem das .NET-Framework, auf Wunsch aber auch nützliche Zusätze wie z.B. die Microsoft SQL Server 2008 Express Edition, die für Datenbankprogrammierung unerlässlich ist.
Achtung: Wenn Sie planen, Visual Studio und SharpDevelop parallel zu installieren, empfehlen wir Ihnen dringend, zuerst das für Sie "unwichtigere" Programm zu installieren. Durch die folgende Installation werden unter Umständen die Dateizuordnungen der ersten Installation beschädigt. Bei einer Installation beider IDE gilt deshalb: immer erst die Zweitumgebung und dann die Hauptentwicklungsumgebung installieren.
Einstellungen
Wie jedes andere Programm muss auch VS nach dem ersten Start an die persönlichen Bedürfnisse angepasst werden, um ein zügiges Arbeiten zu ermöglichen. Dazu öffnen Sie den Optionsdialog im Menü Extras. Wählen Sie zunächst (links unten) Alle Einstellungen anzeigen und legen fest:
- Unter Projekte und Projektmappen legen Sie ein Standardverzeichnis für Ihre Projekte fest und steuern, ob ein neues Projekt sofort zu speichern ist.
- Auch unter Erstellen und Ausführen sollten Sie "Vor Erstellen: Alle Änderungen speichern" wählen.
- Unter Umgebung > Start steuern Sie, ob die zuletzt bearbeitete Projektmappe stets aktiviert werden soll oder ob stattdessen die Startseite anzuzeigen ist.
- Unter Umgebung > Hilfe > Online regeln Sie, wo und wie Hilfe-Informationen gesucht werden sollen.
- Unter Text-Editor > Alle Sprachen sollten unter Anzeigen die Zeilennummern aktiviert werden.
Alle anderen Optionen können Sie sich einmal ansehen und später bei Bedarf anpassen. Bestätigen Sie die Festlegungen mit OK.
Die Oberfläche
Der größte Teil des Bildschirms wird vom Hauptfenster eingenommen. Es enthält ggf. die Startseite mit den zuletzt bearbeiteten Projekten und während der Laufzeit die geöffneten Dokumente. Dieses Hauptfenster wird eingerahmt von verschiedenen zusätzlichen Informationen und Fenstern zur Auswahl:
- Links finden Sie:
- die Toolbox (Auswahl an Werkzeugen), die bei WinForms-Anwendungen auch die Controls anbietet
- den Datenbank-Explorer mit der Liste der Datenverbindungen
- Rechts finden Sie:
- die Übersicht der Projektmappen mit allen ihren Informationen
- die Übersicht der Klassen einer Projektmappe
- die Eigenschaften, vor allem wenn ein Formular in der Designer-Ansicht geöffnet ist
- Unten finden Sie:
- Fehlermeldungen und Warnungen
- Aufgaben, die in verschiedenen Programmteilen zu erledigen sind
- Ausgabe während des Programmablaufs
- Lokale Variable zum Debuggen während des Programmablaufs
Diese Fenster kann man bei Bedarf ein- und ausblenden. Im ausgefahrenen Zustand findet sich im Titel eines solchen Fensters neben dem obligatorischen X zum Schließen eine Pinnnadel. Ein Klick stellt sie auf und verhindert, dass das Fenster automatisch ausgeblendet wird; ein erneuter Klick hebt diese Verankerung auf. Im Menü Ansicht können Sie diese Fenster nach Bedarf öffnen.
Im Untermenü Ansicht > Symbolleisten können Sie Symbolleisten auswählen. Sehr nützlich sind neben Standard die Symbolleisten Erstellen und Debuggen. Wie in Microsoft Office können die Symbolleisten verschoben und in mehreren Reihen angeordnet werden.
Ein erstes Projekt
Hello World
Das einfachste Programm ist ein Hello World-Programm, das lediglich diesen Text ausgibt. Ein solches Programm dient seit Urzeiten in Lehrbüchern einmal als Test für den Lernenden, um sich mit der Programmierumgebung vertraut zu machen und zu prüfen, ob alles funktioniert. Andererseits zeigen Sie die notwendige Grundstruktur eines Programms auf. Ein solches Programm enthält eigentlich nur die folgenden Zeilen:
System::Console::WriteLine("Hello World");
System::Console::WriteLine("Ende mit beliebiger Taste");
System::Console::ReadKey();
System.Console.WriteLine("Hello World");
System.Console.WriteLine("Ende mit beliebiger Taste");
System.Console.ReadKey();
System.Console.WriteLine('Hello World');
System.Console.WriteLine('Ende mit beliebiger Taste');
System.Console.ReadKey();
System.Console.WriteLine("Hello World")
System.Console.WriteLine("Ende mit beliebiger Taste")
System.Console.ReadKey()
"Mein neues Auto" als Einstieg
Wir wollen ein etwas größeres Projekt vorbereiten, das später ausführlicher besprochen werden soll.
Eine Projektmappe (Englisch solution, also Problemlösung) enthält mehrere, in der Regel inhaltlich zusammengehörende Projekte. Ein Projekt (project) enthält alles, was zum Erstellen einer einzelnen Assembly (exe- oder dll-Datei) unter .NET notwendig ist.
Das folgende Programm wird auch im Kapitel "Mein neues Auto" als erstes Programm bei jeder Programmiersprache besprochen. Einzelheiten des Codes werden deshalb hier überhaupt nicht erklärt; jetzt geht es uns nur um die Möglichkeiten der IDE mit der Eingabe des Codes, dem Erstellen des Programms und der Fehlersuche.
Ein Projekt hinzufügen
Für ein neues Projekt gibt es diese Möglichkeiten:
- Auf der Startseite klicken Sie unter Zuletzt geöffnete Projekte auf Erstellen: Projekt....
- Im Menü Datei wählen Sie Neues Projekt.
- Rechtsklick auf die Projektmappe im Projektmappen-Explorer und Hinzufügen > Neues Projekt.
Es öffnet sich ein Dialog für die grundlegenden Einstellungen des neuen Projekts. Geben Sie dazu ein: den Namen des neuen Projekts, z.B. "Mein neues Auto", den allgemeinen Speicherort, z.B. "E:\Eigene Dateien\NET Projects\Wikibooks" sowie den Namen der Projektmappe, z.B. "Einstieg" (nachdem Sie Projektmappenverzeichnis erstellen aktiviert haben). Je nachdem, wie das neue Projekt gewählt wurde, ist die Projektmappe bereits fest vorgegeben oder kann ausgewählt werden.
Bestätigen Sie die Eingaben durch Klicken auf OK. Die IDE erstellt ein Gerüst mit allen Dateien, die für das neue Projekt nötig sind.
- Solange es uns um einzelne Funktionalitäten der Arbeit mit .NET oder einer Programmiersprache geht, werden wir uns vorwiegend mit Konsolenanwendungen befassen. Andere Anwendungen werden in den Themen-Büchern behandelt.
Der Quelltext-Editor
Je nach Situation wird das Hauptprogramm bereits angezeigt; oder Sie müssen es auswählen: Öffnen Sie unter Projekte, bis die Datei Program (je nachdem mit der Extension cs, vb, c o.a.) angezeigt wird. Doppelklick darauf zeigt sie an.
Damit wir die Arbeitsweise von Visual Studio demonstrieren können, ändern und ergänzen Sie den vorgegebenen Code, sodass der untenstehende Code zu sehen ist.
- Löschen Sie zunächst die nicht vorgesehenen Zeilen am Anfang.
- Bei Visual Basic ergänzen Sie am Anfang und Ende die Zeilen mit Namespace, bei C# ändern Sie den Text.
- Schreiben Sie den untenstehenden Code, und zwar bis einschließlich der ersten Zeile mit WriteLine. Beenden Sie den Code mit den drei Schlusszeilen.
- Beachten Sie, dass Einrückungen automatisch erfolgen.
- Schreiben Sie langsam und machen Sie vor allem nach einem Punkt und einer öffnenden Klammer eine kurze Pause.
- Schreiben Sie nach einem Punkt erst einmal nur einen Buchstaben und warten Sie wieder.
Achten Sie dabei darauf, was die IDE vorschlägt oder sozusagen vorschreibt (im Sinne von "voraus schreibt") und als Erläuterungen anzeigt. Hier bietet Visual Studio Möglichkeiten an, die Zeile logisch zu beenden. Wenn weitere Informationen angeboten werden (durch einen Text wie "2 von 19"), dann blättern Sie mit den Pfeiltasten weiter.
Es ist nicht erforderlich, dass Sie alle Informationen verstehen. Es geht jetzt nur darum, dass diese Hilfestellung angeboten wird, damit Sie bei der eigenen Programmierung darauf achten und dies nutzen.
using System;
namespace Wikibooks.CSharp.Mein_neues_Auto
{
class Program
{
public static void Main(string[] args)
{
string input;
if (DateTime.Now.Hour < 12) {
Console.Write("Guten Morgen, ");
} else if (DateTime.Now.Hour < 18) {
Console.Write("Grüß Gott, ");
} else
Console.Write("Guten Abend, ");
Console.WriteLine(Environment.UserName);
// Typ abfragen
Console.WriteLine("Welchen Typ Auto wollen Sie sich zulegen?");
input = Console.ReadLine();
Car mycar = new Car(input, "998cm³ 44kW", 3);
Console.WriteLine(mycar.ToString());
// beschleunigen
mycar.Accelerate(40);
mycar.ShowValues();
// rechts blinken
mycar.SetRightSignal(true);
mycar.ShowValues();
// etwas bremsen
mycar.Delay(20);
mycar.ShowValues();
// Blinker aus
mycar.SetRightSignal(false);
mycar.ShowValues();
// wieder beschleunigen
mycar.Accelerate(30);
mycar.ShowValues();
// Warnblinker ein
mycar.SetBothSignals(true);
mycar.ShowValues();
// bremsen bis auf 0
mycar.Delay(mycar.Speed);
mycar.ShowValues();
// Warnblinker aus
mycar.SetBothSignals(false);
mycar.ShowValues();
Console.Write("Press any key to continue . . . ");
Console.ReadKey(true);
}
}
}
Imports System
Namespace Wikibooks.VBNet.Mein_neues_Auto
Module Program
Sub Main(ByVal args As String())
Dim input As String
If DateTime.Now.Hour < 12 Then
Console.Write("Guten Morgen, ")
ElseIf DateTime.Now.Hour < 18 Then
Console.Write("Grüß Gott, ")
Else
Console.Write("Guten Abend, ")
End If
Console.WriteLine(Environment.UserName)
' Typ abfragen
Console.WriteLine("Welchen Typ Auto wollen Sie sich zulegen?")
input = Console.ReadLine()
Dim mycar As New Car(input, "998cm³ 44kW", 3)
Console.WriteLine(mycar.ToString())
' beschleunigen
mycar.Accelerate(40)
mycar.ShowValues()
' rechts blinken
mycar.SetRightSignal(True)
mycar.ShowValues()
' etwas bremsen
mycar.Delay(20)
mycar.ShowValues()
' Blinker aus
mycar.SetRightSignal(False)
mycar.ShowValues()
' wieder beschleunigen
mycar.Accelerate(30)
mycar.ShowValues()
' Warnblinker ein
mycar.SetBothSignals(True)
mycar.ShowValues()
' bremsen bis auf 0
mycar.Delay(mycar.Speed)
mycar.ShowValues()
' Warnblinker aus
mycar.SetBothSignals(False)
mycar.ShowValues()
Console.Write("Press any key to continue . . . ")
Console.ReadKey(True)
End Sub
End Module
End Namespace
Diese Art, wie Code-Teile vorgeschlagen werden, wird von Microsoft als IntelliSense bezeichnet; sie ist allgemein auch als Code-Completion (Code-Vervollständigung) bekannt.
Beachten Sie nun die folgenden Besonderheiten der Anzeige:
- Bestimmte Abschnitte des Codes sind farbig. Diese Funktion heißt Syntaxhervorhebung. Blaue Wörter sind zum Beispiel Begriffe, die im Code eine besondere Bedeutung haben, sogenannte Schlüsselwörter (dazu später mehr).
- Die Wörter in Anführungszeichen sind Zeichenketten, die im Programm erscheinen, wie wir gleich sehen.
- Mit der TODO-Anweisung können Sie sich selbst darauf hinweisen, was hier noch zu erledigen ist; über die Aufgabenliste können Sie dorthin springen und so manche Programmteile auf später verschieben.
Mit diesem Code soll nun die IDE arbeiten.
Dateien speichern
Vor dem nächsten Schritt speichert VS die betroffenen Quelldateien automatisch, sofern Sie dies als Option gewählt haben. Wenn Sie während umfangreicher Arbeiten nicht kompilieren, sondern die IDE beenden, können Sie die geänderten Dateien gezielt speichern. (Beim Schließen einer Projektmappe fragt VS auch ausdrücklich nach.) Welche Dateien seit der letzten Speicherung geändert wurden, ersehen Sie aus dem Sternchen Program.cs * beim Dateinamen. Sie können so speichern:
- mit einem Klick auf das Diskettensymbol oder Strg + S die aktuelle Datei
- mit einem Klick auf den Diskettenstapel oder Strg + Shift + S alle geöffneten Dateien.
Das Kompilieren
Vorbereitung nur für C# und C++: Fügen Sie hinter das letzte else eine öffnende Klammer { ein.
Zunächst muss das Programm erstellt, also kompiliert werden. Das bedeutet, dass der ursprüngliche (von Ihnen geschriebene) Code in den .NET-Zwischencode übersetzt werden muss. Dies geschieht auf einem dieser Wege:
- durch F5, wenn das Programm sofort mit Debugger gestartet werden soll
- durch Shift + F6
- über die Symbolleiste Erstellen mit einem Klick auf das Icon mit zwei kleinen Pfeilen
- über das Projektmappen-Fenster durch Rechtsclick auf den Projekt-Namen "Hello World" und Erstellen
Je nach Situation ist es nützlich, die gesamte Projektmappe zu erstellen:
- durch F6
- über die Symbolleiste Erstellen mit einem Klick auf das Icon mit drei kleinen Pfeilen
- über das Projektmappen-Fenster durch Rechtsclick auf die Projektmappe und Projektmappe erstellen
Starten Sie also das Kompilieren. Leider enthält das Programm Fehler, die uns der Compiler mitteilt:
// bei C# und C++ Program.cs(33,2) : Fehler CS1513: } erwartet. ' bei VB Program.vb(17,0) : Fehler BC30081: "If" muss mit einem zugehörigen "End If" abgeschlossen werden.
VB ist in diesem Fall genauer, weil die richtige Stelle angegeben wird. Das letzte Else wurde mit End abgeschlossen, es muss aber – wie die Fehlermeldung feststellt – mit End If beendet werden.
Bei den C-Varianten wird nur festgestellt, dass die Klammerpaare {...} nicht zusammenpassen: Es gibt eine öffnende Klammer ohne dazugehörige schließende Klammer. In diesem Fall ist es einfach: Zu der unter Vorbereitung eingegebenen Klammer fehlt der Partner; machen Sie dazu nach dem nächsten Write-Befehl eine neue Zeile und schreiben Sie die fehlende Klammer } hin. Sie werden sofort sehen, zu welchem Partner diese gehört. Probieren Sie das Schreiben dieser Klammern aus. In der Regel ist es am sichersten, nach einer öffnenden Klammer sofort die schließende Klammer zu schreiben und den folgenden Code dazwischenzusetzen.
Speichern Sie die geänderte Datei.
Kompilieren Sie das Programm erneut. Es sollte keine Fehler mehr aufweisen, liefert aber eine Warnung:
// bei C# und C++ Die Variable input ist deklariert, wird aber nicht verwendet. ' bei VB Nicht verwendete lokale Variable: input.
Warnungen können theoretisch ignoriert werden; das Programm kann trotzdem ablaufen. In aller Regel ist damit aber eine Unsauberkeit im Code enthalten; deshalb sollten Sie sich immer um die Beseitigung von Warnungen kümmern. Hier wurde sie provoziert: Kopieren Sie den gesamten noch fehlenden Programmtext und speichern Sie die geänderte Datei.
Kompilieren Sie erneut. Nun gibt es eine Menge gleichartiger Fehlermeldungen:
Der Typ- oder Namespacename Car konnte nicht gefunden werden. // bei C++, C# (Fehlt eine using-Direktive oder ein Assemblyverweis?) ' bei VB (Fehlt eine Imports-Direktive oder ein Assemblyverweis?)
Nun ja, die Definition der Klasse Car fehlt noch völlig. Also erstellen wir eine weitere Quelldatei:
- Für C# mit Rechtsklick auf Car.cs mit der Auswahl "Ziel speichern unter" dem Namen Car.cs im Quellverzeichnis
- Für VB mit Rechtsklick auf Car.vb mit der Auswahl "Ziel speichern unter" dem Namen Car.vb im Quellverzeichnis
Diese Quelldatei fügen wir unserem Projekt hinzu: im Projekt-Explorer nach Rechtsklick auf das Projekt über Hinzufügen > Vorhandene Datei die gerade gespeicherte Datei auswählen. Sofern diese Datei nicht sofort angezeigt wird, können Sie sie mit Doppelklick öffnen und überprüfen.
Um eine weitere Fehlermeldung zu vermeiden, die wir erst später besprechen wollen, gehen Sie im Projekt-Explorer mit Rechtsklick auf Referenzen und wählen über Hinzufügen im Bereich "GAC" die Assembly System.Drawing (Doppelklick oder "Wählen") aus und bestätigen mit OK.
Damit können wir erneut und endgültig kompilieren; es sollten keine Meldungen bleiben.
Das Ausführen
Jetzt können wir das Programm ablaufen lassen; es gibt wieder mehrere Möglichkeiten:
- durch F5 mit Debugger
- über den Menü-Punkt Debuggen > Debuggen starten
- durch Strg + F5 ohne Debugger
- über den Menü-Punkt Debuggen > Starten ohne Debuggen
- über den Projektmappen-Explorer durch Rechtsklick auf den Projektnamen und Debuggen > Neue Instanz starten
- über die Symbolleiste durch Klick auf den grünen Pfeil
Der letzte Weg führt nicht immer direkt zum Ziel: Wenn die Projektmappe mehrere Projekte enthält, werden zunächst alle Projekte neu kompiliert; es wird diejenige Anwendung gestartet, die als Startprojekt festgelegt wurde.
Ein fertiges Programm kann auch "normal" gestartet werden – wie jedes installierte Programm auch:
- in der Eingabeaufforderung direkt "Mein neues Auto" aufrufen (wegen des Leerzeichens sind die Gänsefüßchen notwendig)
- durch Doppelklick im Inhaltsverzeichnis z.B. des Windows-Explorers oder eines Commanders
Der Programmablauf öffnet ein Konsolenfenster:
'"`UNIQ--syntaxhighlight-0000000E-QINU`"'
Darin werden Ausgabezeilen angezeigt, nämlich alles, was mit Write und WriteLine angegeben wurde, und es enthält verschiedene Eingaben, nämlich einmal ReadLine und einmal ReadKey. Vor allem der letzte Befehl, der auf die Eingabe einer beliebigen Taste wartet, ist bei einer Console-Anwendung nützlich; wenn er fehlt, wird das Programm beendet, ohne dass Sie die letzte Ausgabe sehen oder gar lesen können.
Fehlersuche mit Debugger
Dank der guten Vorarbeit (und der wenigen Arbeit, die erledigt wird) enthält unser Programm keine Fehler. Aber für den Fall der Fälle können wir es untersuchen. Sorgen Sie also dafür, dass das Programm nicht einfach durchläuft
- Setzen Sie einen Haltepunkt (Breakpoint), indem Sie in eine bestimmte Zeile gehen, z.B. in die Zeile mit ReadLine:
- Klicken Sie in dieser Zeile in den Fensterrahmen links von der Zeilennummer.
- Oder klicken Sie in dieser Zeile an eine beliebige Stelle und drücken dann F9.
- Starten Sie dann wieder Kompilieren und Ausführen (wie oben).
Das Programm wird zunächst mit der Console beginnen und dann in die IDE zurückspringen und an der betreffenden Stelle anhalten. Der Arbeitsablauf kann mit den Befehlen im Menü Debuggen bzw. mit F10 und F11 fortgesetzt werden. Gehen Sie dabei wie folgt vor:
- Im Fenster Lokal sehen Sie, dass für input noch kein Wert vorgegeben ist.
- Drücken Sie einmal F11. Das Programm springt in die Console, weil es dort auf eine Eingabe wartet.
- Schreiben Sie den gewünschten Eingabetext und drücken Return.
- Das Programm springt automatisch in die IDE zurück und steht jetzt auf der nächsten Zeile.
- Im Fenster Lokal sehen Sie jetzt den von Ihnen eingegebenen Wert.
Mit F5 kann der Programmablauf bis zum nächsten Haltepunkt fortgesetzt werden. Bei uns kommt kein neuer Haltepunkt, aber das Programm wartet in der Console auf eine beliebige Taste; deshalb landen Sie dort und können es freigeben.
Bei späteren Programmen sollten Sie das Debuggen intensiv mit Haltepunkten, den Funktionstasten und den Variablen ausprobieren.
Versionen
Die verschiedenen Versionen von Visual Studio arbeiten mit den .NET-Versionen so zusammen:
- VS 2003 gehört zu .NET 1.1.
- VS 2005 gehört zu .NET 2.0.
- VS 2008 gehört zu .NET 3.5.
- VS 2010 gehört zu .NET 4.0. Über Projekt > Projektoptionen kann unter Kompilieren auch .NET 2.0, 3.0 oder 3.5 festgelegt werden.
Zusammenfassung
Mit Visual Studio steht eine professionelle IDE zur Verfügung:
- Es können verschiedene Programmiersprachen verarbeitet werden.
- Der Code-Editor berücksichtigt die Anforderungen von Programmierern.
- Der passende .NET-Compiler und ein Debugger sind integriert.
Damit können wir umfangreiche .NET-Anwendungen erstellen.
Einstieg in SharpDevelop
Eine komfortable IDE
SharpDevelop (auch als #D bezeichnet) ist eine vollständige IDE (Integrated Development Environment, also Integrierte Entwicklungsumgebung) und bietet alles, was das Programmieren erleichtert:
- Syntax-Hervorhebung
- Code-Vervollständigung
- Zusammenfassung mehrerer Quelltexte zu Projekten
- schneller Wechsel zum Compiler mit Debugger
- integrierte Hilfe zum .NET Framework
SharpDevelop steht kostenlos zur Verfügung und kann auch auf einem USB-Stick lauffähig installiert werden, siehe Weblinks.
Installation und Einrichtung
#D verfügt über eine Installationsroutine. Voraussetzung ist aber, dass das .NET Framework in einer passenden Version bereits installiert ist, und zwar sowohl mit der Laufzeitumgebung als auch mit dem SDK. Einzelheiten dazu siehe .NET installieren.
Achtung: Wenn Sie planen, SharpDevelop und Microsoft Visual Studio parallel zu installieren, empfehlen wir Ihnen dringend, zuerst das für Sie "unwichtigere" Programm zu installieren. Durch die folgende Installation werden unter Umständen die Dateizuordnungen der ersten Installation beschädigt. Bei einer Installation beider IDE gilt deshalb: immer erst die Zweitumgebung und dann die Hauptentwicklungsumgebung installieren.
Einstellungen
Nach dem Start wartet, wie bei jedem komplexen Programm, die Einrichtung der Oberfläche. Dazu öffnen Sie den Optionsdialog im Menü Extras. Legen Sie dort zunächst folgende Einstellungen fest:
- Unter SharpDevelop Optionen – UI wählen Sie die Sprache für das Programm.
- Unter SharpDevelop Optionen – Visueller Stil wählen Sie die bevorzugte Programmiersprache.
- Unter SharpDevelop Optionen – Projekte und Projektmappen legen Sie ein Standardverzeichnis für Ihre Projekte fest und steuern, ob die zuletzt bearbeitete Projektmappe stets aktiviert werden soll oder ob stattdessen die Startseite anzuzeigen ist.
- Unter Tools – Help-System wählen Sie eine der Varianten zur .NET-Dokumentation aus; Dadurch steht die äußerst umfangreiche MSDN-Library auch unter SharpDevelop zur Verfügung.
- Unter Windows Forms Designer – Rastereinstellungen wählen Sie den Ausrichtungsmodus Am Gitter einrasten und aktivieren, falls nicht schon vorgegeben, die beiden Kontrollkästchen weiter unten.
Alle anderen Optionen können Sie sich einmal ansehen und später bei Bedarf anpassen. Wir weisen vor allem auf die Einstellungen zum Text-Editor und auf die Code-Schablonen hin. Bestätigen Sie die Festlegungen mit OK.
Die Oberfläche
SharpDevelop orientiert sich dabei sehr stark am Microsoft-Vorbild. Dadurch fällt es relativ leicht, zwischen den beiden IDE zu wechseln; lediglich die Verteilung der Zusatzfenster unterscheidet sich.
Der größte Teil des Bildschirms wird vom Hauptfenster eingenommen. Es enthält ggf. die Startseite mit den zuletzt bearbeiteten Projekten und während der Laufzeit die geöffneten Dokumente. Dieses Hauptfenster wird eingerahmt von verschiedenen zusätzlichen Informationen und Fenstern zur Auswahl:
- Links finden Sie:
- die Übersicht der Projekte einer Projektmappe mit allen ihren Informationen
- die Übersicht von Tools (Werkzeugen), die bei WinForms-Anwendungen auch die Controls anbietet
- Rechts finden Sie:
- die Übersicht der Klassen einer Projektmappe
- die Eigenschaften, vor allem wenn ein Formular in der Designer-Ansicht geöffnet ist
- Unten finden Sie:
- Fehlermeldungen und Warnungen
- Aufgaben, die in verschiedenen Programmteilen zu erledigen sind
- Ausgabe während des Programmablaufs
- Lokale Variable zum Debuggen während des Programmablaufs
Diese Fenster kann man bei Bedarf ein- und ausblenden. Im ausgefahrenen Zustand findet sich im Titel eines solchen Fensters neben dem obligatorischen X zum Schließen eine Pinnnadel. Ein Klick stellt sie auf und verhindert, dass das Fenster automatisch ausgeblendet wird; ein erneuter Klick hebt diese Verankerung auf. Im Menü Ansicht können Sie diese Fenster nach Bedarf öffnen.
Die Bedienung
Das Programm kann selbstverständlich über die Menü-Befehle als auch über die Schaltflächen bedient werden. Sehr viele Punkte sind auch über die Tastatur zu erreichen.
Ein erstes Projekt
Hello World
Das einfachste Programm ist ein Hello World-Programm, das lediglich diesen Text ausgibt. Ein solches Programm dient seit Urzeiten in Lehrbüchern einmal als Test für den Lernenden, um sich mit der Programmierumgebung vertraut zu machen und zu prüfen, ob alles funktioniert. Andererseits zeigt es die notwendige Grundstruktur eines Programms auf. Ein solches Programm enthält eigentlich nur die folgenden Zeilen:
System::Console::WriteLine("Hello World");
System::Console::WriteLine("Ende mit beliebiger Taste");
System::Console::ReadKey();
System.Console.WriteLine("Hello World");
System.Console.WriteLine("Ende mit beliebiger Taste");
System.Console.ReadKey();
System.Console.WriteLine('Hello World');
System.Console.WriteLine('Ende mit beliebiger Taste');
System.Console.ReadKey();
System.Console.WriteLine("Hello World")
System.Console.WriteLine("Ende mit beliebiger Taste")
System.Console.ReadKey()
"Mein neues Auto" als Einstieg
Wir wollen ein etwas größeres Projekt vorbereiten, das später ausführlicher besprochen werden soll.
Eine Projektmappe (Englisch solution, also Problemlösung) enthält mehrere, in der Regel inhaltlich zusammengehörende Projekte. Ein Projekt (project) enthält alles, was zum Erstellen einer einzelnen Assembly (exe- oder dll-Datei) unter .NET notwendig ist.
Das folgende Programm wird auch im Kapitel "Mein neues Auto" als erstes Programm bei jeder Programmiersprache besprochen. Einzelheiten des Codes werden deshalb hier überhaupt nicht erklärt; jetzt geht es uns nur um die Möglichkeiten der IDE mit der Eingabe des Codes, dem Erstellen des Programms und der Fehlersuche.
Ein Projekt hinzufügen
Für eine neue Projektmappe gibt es diese Möglichkeiten:
- Klicken Sie auf der Startseite auf Neue Solution.
- Wählen Sie im Menü Datei > Neu > Projektmappe.
Für ein neues Projekt gibt es diese Möglichkeit:
- Rechtsklick auf die Projektmappe im Projekte-Fenster und Hinzufügen > Neues Projekt.
Es öffnet sich ein Dialog für die grundlegenden Einstellungen des neuen Projekts. Wählen Sie in der linken Liste Ihre Programmiersprache und dann Windowsanwendungen aus und rechts Konsolenanwendung. Geben Sie ein Verzeichnis für die Projektmappe an, z.B. "E:\Eigene Dateien\NET Projects\Wikibooks\Einstieg", und einen Namen für das Projekt, z.B. "Mein neues Auto". Bestätigen Sie die Eingaben durch Klicken auf Erstellen. Die IDE erstellt ein Gerüst mit allen Dateien, die für das neue Projekt nötig sind.
- Solange es uns um einzelne Funktionalitäten der Arbeit mit .NET oder einer Programmiersprache geht, werden wir uns vorwiegend mit Konsolenanwendungen befassen. Andere Anwendungen werden in den Themen-Büchern behandelt.
Der Quelltext-Editor
Je nach Situation wird das Hauptprogramm bereits angezeigt; oder Sie müssen es auswählen: Öffnen Sie unter Projekte, bis die Datei Program (je nachdem mit der Extension cs, vb, c o.a.) angezeigt wird. Doppelklick darauf zeigt sie an.
Dreist, wie SharpDevelop ist, hat es für uns bereits ein komplettes Hello-World-Programm geschrieben. Damit wir die Arbeitsweise von SharpDevelop demonstrieren können, ändern Sie den vorgegebenen Code, sodass der untenstehende Code zu sehen ist.
- Löschen Sie zunächst die Zeilen mit Hello World und Todo.
- Bei Visual Basic ergänzen Sie am Anfang und Ende die Zeilen mit Namespace.
- Schreiben Sie den untenstehenden Code, und zwar bis einschließlich der ersten Zeile mit WriteLine. Beenden Sie den Code mit den drei Schlusszeilen.
- Beachten Sie, dass Einrückungen automatisch erfolgen.
- Schreiben Sie langsam und machen Sie vor allem nach einem Punkt und einer öffnenden Klammer eine kurze Pause.
- Schreiben Sie nach einem Punkt erst einmal nur einen Buchstaben und warten Sie wieder.
Achten Sie dabei darauf, was die IDE sozusagen vorschreibt (im Sinne von "voraus schreibt") und als Erläuterungen anzeigt. Hier bietet SharpDevelop Möglichkeiten an, die Zeile logisch zu beenden. Wenn weitere Informationen angeboten werden (durch einen Text wie "2 von 19"), dann blättern Sie mit den Pfeiltasten weiter.
Es ist nicht erforderlich, dass Sie alle Informationen sofort verstehen. Es geht jetzt nur darum, dass diese Hilfestellung angeboten wird, damit Sie bei der eigenen Programmierung darauf achten und dies nutzen.
using System;
namespace Wikibooks.CSharp.Mein_neues_Auto
{
class Program
{
public static void Main(string[] args)
{
string input;
if (DateTime.Now.Hour < 12) {
Console.Write("Guten Morgen, ");
} else if (DateTime.Now.Hour < 18) {
Console.Write("Grüß Gott, ");
} else
Console.Write("Guten Abend, ");
Console.WriteLine(Environment.UserName);
// Typ abfragen
Console.WriteLine("Welchen Typ Auto wollen Sie sich zulegen?");
input = Console.ReadLine();
Car mycar = new Car(input, "998cm³ 44kW", 3);
Console.WriteLine(mycar.ToString());
// beschleunigen
mycar.Accelerate(40);
mycar.ShowValues();
// rechts blinken
mycar.SetRightSignal(true);
mycar.ShowValues();
// etwas bremsen
mycar.Delay(20);
mycar.ShowValues();
// Blinker aus
mycar.SetRightSignal(false);
mycar.ShowValues();
// wieder beschleunigen
mycar.Accelerate(30);
mycar.ShowValues();
// Warnblinker ein
mycar.SetBothSignals(true);
mycar.ShowValues();
// bremsen bis auf 0
mycar.Delay(mycar.Speed);
mycar.ShowValues();
// Warnblinker aus
mycar.SetBothSignals(false);
mycar.ShowValues();
Console.Write("Press any key to continue . . . ");
Console.ReadKey(true);
}
}
}
Imports System
Namespace Wikibooks.VBNet.Mein_neues_Auto
Module Program
Sub Main(ByVal args As String())
Dim input As String
If DateTime.Now.Hour < 12 Then
Console.Write("Guten Morgen, ")
ElseIf DateTime.Now.Hour < 18 Then
Console.Write("Grüß Gott, ")
Else
Console.Write("Guten Abend, ")
End If
Console.WriteLine(Environment.UserName)
' Typ abfragen
Console.WriteLine("Welchen Typ Auto wollen Sie sich zulegen?")
input = Console.ReadLine()
Dim mycar As New Car(input, "998cm³ 44kW", 3)
Console.WriteLine(mycar.ToString())
' beschleunigen
mycar.Accelerate(40)
mycar.ShowValues()
' rechts blinken
mycar.SetRightSignal(True)
mycar.ShowValues()
' etwas bremsen
mycar.Delay(20)
mycar.ShowValues()
' Blinker aus
mycar.SetRightSignal(False)
mycar.ShowValues()
' wieder beschleunigen
mycar.Accelerate(30)
mycar.ShowValues()
' Warnblinker ein
mycar.SetBothSignals(True)
mycar.ShowValues()
' bremsen bis auf 0
mycar.Delay(mycar.Speed)
mycar.ShowValues()
' Warnblinker aus
mycar.SetBothSignals(False)
mycar.ShowValues()
Console.Write("Press any key to continue . . . ")
Console.ReadKey(True)
End Sub
End Module
End Namespace
Diese Art, wie Code-Teile vorgeschlagen werden, wird als Code-Completion (Code-Vervollständigung) bezeichnet. Der ebenfalls verwendete Begriff IntelliSense ist für Microsoft Visual Studio reserviert.
Beachten Sie die folgenden Besonderheiten der Anzeige:
- Bestimmte Abschnitte des Codes sind farbig. Diese Funktion heißt Syntaxhervorhebung. Blaue Wörter sind zum Beispiel Begriffe, die im Code eine besondere Bedeutung haben, sogenannte Schlüsselwörter (dazu später mehr).
- Die Wörter in Anführungszeichen sind Zeichenketten, die im Programm erscheinen, wie wir gleich sehen. SharpDevelop hebt diese bei VB, anders als z.B. Microsoft Visual Basic oder dieses Wikibook, nicht farblich hervor.
- Mit der TODO-Anweisung können Sie sich selbst darauf hinweisen, was hier noch zu erledigen ist; über die Liste der Aufgaben können Sie dorthin springen und so manche Programmteile auf später verschieben.
Mit diesem Code soll nun die IDE arbeiten.
Dateien speichern
Vor dem nächsten Schritt speichert SharpDevelop die betroffenen Quelldateien automatisch. Wenn Sie während umfangreicher Arbeiten nicht kompilieren, sondern die IDE beenden, können Sie die geänderten Dateien gezielt speichern. Welche Dateien seit der letzten Speicherung geändert wurden, ersehen Sie aus dem Sternchen Program.cs * beim Dateinamen. Sie können so speichern:
- mit einem Klick auf das Diskettensymbol oder Strg + S die aktuelle Datei
- mit einem Klick auf den Diskettenstapel oder Strg + Shift + S alle geöffneten Dateien.
Das Kompilieren
Vorbereitung nur für C# und C++: Fügen Sie hinter das letzte else eine öffnende Klammer { ein.
Zunächst muss das Programm erstellt, also kompiliert werden. Das bedeutet, dass der ursprüngliche (von Ihnen geschriebene) Code in den .NET-Zwischencode übersetzt werden muss. Dies geschieht auf einem dieser Wege:
- durch F9
- über den Menü-Punkt Erstellen > Erstelle Mein neues Auto
- über das Projekte-Fenster durch Rechtsclick auf den Projekt-Namen "Mein neues Auto" und Erstellen
Starten Sie also das Kompilieren. Leider enthält das Programm Fehler, die uns der Compiler mitteilt:
// bei C# und C++ Program.cs(33,2) : Fehler CS1513: } erwartet. ' bei VB Program.vb(17,0) : Fehler BC30081: "If" muss mit einem zugehörigen "End If" abgeschlossen werden.
VB ist in diesem Fall genauer, weil die richtige Stelle angegeben wird. Das letzte Else wurde mit End abgeschlossen, es muss aber – wie die Fehlermeldung feststellt – mit End If beendet werden.
Bei den C-Varianten wird nur festgestellt, dass die Klammerpaare {...} nicht zusammenpassen: Es gibt eine öffnende Klammer ohne dazugehörige schließende Klammer. In diesem Fall ist es einfach: Zu der unter Vorbereitung eingegebenen Klammer fehlt der Partner; machen Sie dazu nach dem nächsten Write-Befehl eine neue Zeile und schreiben Sie die fehlende Klammer } hin. Sie werden sofort sehen, zu welchem Partner diese gehört. Probieren Sie das Schreiben dieser Klammern aus; in der Regel bekommen Sie zu einer öffnenden Klammer sofort den Partner und werden den folgenden Code dazwischen schreiben.
Wenn Sie „spaßeshalber“ einmal die öffnende Klammer entfernen, werden Sie mit vielen Fehlermeldungen bombardiert. Das untersuchen wir in späteren Kapiteln.
Speichern Sie die geänderte Datei.
Kompilieren Sie das Programm erneut. Es sollte keine Fehler mehr aufweisen, liefert aber eine Warnung:
// bei C# und C++ Die Variable input ist deklariert, wird aber nicht verwendet. ' bei VB Nicht verwendete lokale Variable: input.
Warnungen können theoretisch ignoriert werden; das Programm kann trotzdem ablaufen. In aller Regel ist damit aber eine Unsauberkeit im Code enthalten; deshalb sollten Sie sich immer um die Beseitigung von Warnungen kümmern. Hier wurde sie provoziert: Kopieren Sie den gesamten noch fehlenden Programmtext und speichern Sie die geänderte Datei.
Kompilieren Sie erneut. Nun gibt es eine Menge gleichartiger Fehlermeldungen:
Der Typ- oder Namespacename Car konnte nicht gefunden werden. // bei C++, C# (Fehlt eine using-Direktive oder ein Assemblyverweis?) ' bei VB (Fehlt eine Imports-Direktive oder ein Assemblyverweis?)
Nun ja, die Definition der Klasse Car fehlt noch völlig. Also erstellen wir eine weitere Quelldatei:
- Für C# mit Rechtsklick auf Car.cs mit der Auswahl "Ziel speichern unter" dem Namen Car.cs im Quellverzeichnis
- Für VB mit Rechtsklick auf Car.vb mit der Auswahl "Ziel speichern unter" dem Namen Car.vb im Quellverzeichnis
Diese Quelldatei fügen wir unserem Projekt hinzu: im Projekte-Fenster nach Rechtsklick auf den Projektnamen über Hinzufügen > Vorhandene Datei die gerade gespeicherte Datei auswählen. Sofern diese Datei nicht sofort angezeigt wird, können Sie sie mit Doppelklick öffnen und überprüfen.
Um eine weitere Fehlermeldung zu vermeiden, die wir erst später besprechen wollen, gehen Sie im Projekt-Fenster mit Rechtsklick auf Referenzen und wählen über Hinzufügen im Bereich "GAC" die Assembly System.Drawing (mit Doppelklick oder "Wählen") aus und bestätigen mit OK.
Damit können wir erneut und endgültig kompilieren; es sollten keine Meldungen bleiben.
Das Ausführen
Jetzt können wir das Programm ablaufen lassen; es gibt wieder mehrere Möglichkeiten:
- durch F5
- über den Menü-Punkt Projekt > Projekt starten
- über den Menü-Punkt Debuggen > Ausführen
- über das Projekte-Fenster durch Rechtsclick auf den Projekt-Namen und Projekt starten
- über die Symbolleiste durch Klick auf den grünen Pfeil
Der letzte Weg führt nicht immer direkt zum Ziel: Wenn die Projektmappe mehrere Projekte enthält, werden zunächst alle Projekte neu kompiliert; es wird diejenige Anwendung gestartet, die als Startprojekt festgelegt wurde.
Ein fertiges Programm kann auch "normal" gestartet werden – wie jedes installierte Programm auch:
- in der Eingabeaufforderung direkt "Mein neues Auto" aufrufen (wegen des Leerzeichens sind die Gänsefüßchen notwendig)
- durch Doppelklick im Inhaltsverzeichnis z.B. des Windows-Explorers oder eines Commanders
Der Programmablauf öffnet ein Konsolenfenster:
'"`UNIQ--syntaxhighlight-0000001E-QINU`"'
Darin werden Ausgabezeilen angezeigt, nämlich alles, was mit Write und WriteLine angegeben wurde, und es enthält verschiedene Eingaben, nämlich einmal ReadLine und einmal ReadKey. Der letzte Befehl, der auf die Eingabe einer beliebigen Taste wartet, hat mit dem Programm selbst nichts zu tun. Er ist bei einer Console-Anwendung nützlich; wenn er fehlt, wird das Programm beendet, ohne dass Sie die letzte Ausgabe sehen oder gar lesen können.
Fehlersuche mit Debugger
Dank der guten Vorarbeit (und der wenigen Arbeit, die erledigt wird) enthält unser Programm keine Fehler. Aber für den Fall der Fälle können wir es untersuchen. Sorgen Sie also dafür, dass das Programm nicht einfach durchläuft
- Setzen Sie einen Haltepunkt (Breakpoint), indem Sie in eine bestimmte Zeile gehen, z.B. in die Zeile mit ReadLine:
- Klicken Sie in dieser Zeile in den Fensterrahmen links von der Zeilennummer.
- Oder klicken Sie in dieser Zeile an eine beliebige Stelle und drücken dann F7.
- Starten Sie dann wieder Kompilieren und Ausführen (wie oben).
Das Programm wird zunächst mit der Console beginnen und dann in die IDE zurückspringen und an der betreffenden Stelle anhalten. Der Arbeitsablauf kann mit den Befehlen im Menü Debuggen bzw. mit F10 und F11 fortgesetzt werden. Gehen Sie dabei wie folgt vor:
- Im Fenster Lokale Variablen sehen Sie, dass für input noch kein Wert vorgegeben ist.
- Drücken Sie einmal F11. Das Programm geht nicht weiter, weil es auf eine Eingabe wartet.
- Wechseln Sie über die Taskleiste zum Programm, schreiben den gewünschten Eingabetext und drücken Return.
- Das Programm springt automatisch in die IDE zurück und steht jetzt auf der nächsten Zeile.
- Im Fenster Lokale Variablen sehen Sie jetzt den von Ihnen eingegebenen Wert.
Mit F6 kann der Programmablauf bis zum nächsten Haltepunkt fortgesetzt werden. Bei uns kommt kein neuer Haltepunkt, aber das Programm wartet in der Console auf eine beliebige Taste. Wechseln Sie also dorthin und geben es frei.
Bei späteren Programmen sollten Sie das Debuggen intensiv mit Haltepunkten, den Funktionstasten und den Variablen ausprobieren.
Versionen
Die verschiedenen Versionen von SharpDevelop arbeiten mit den .NET-Versionen so zusammen:
- Zu #D 1.x gehört .NET 1.1.
- Zu #D 2.x gehört .NET 2.0.
- Zu #D 3.x gehört .NET 3.5.
- Zu #D 4.x gehört .NET 4.0.
In allen neueren Versionen kann über Projekt > Projektoptionen unter Kompilieren auch eine andere (frühere) .NET-Version festgelegt werden.
Zusammenfassung
Mit SharpDevelop steht eine vollwertige IDE zur Verfügung:
- Es können verschiedene Programmiersprachen verarbeitet werden.
- Der Code-Editor berücksichtigt die Anforderungen von Programmierern.
- Der passende .NET-Compiler und ein Debugger sind integriert.
Damit können wir umfangreiche .NET-Anwendungen erstellen.
Hello World - Das erste Programm
Nachdem wir nun erfolgreich unser erstes Projekt kompiliert haben, wollen wir einen Blick auf den Code an sich werfen.
Module Module1
Sub Main()
System.Console.WriteLine("Hello World.")
System.Console.ReadLine()
End
End Sub
End Module
Strukturelemente
Wir definieren hier ein sogenanntes Modul, eine Zusammenfassung von Funktionen eines Programmes. Das Modul hier heißt Module1, bei SharpDevelop wäre der Name Main. Damit bekannt ist, welcher Code zu diesem Modul gehört, muss dieser Code in Module Module1 und End Module eingeschlossen werden. Das wollen wir jetzt so akzeptieren. Zunächst wollen wir uns auch damit begnügen, nur ein Modul zu verwenden und allen Code in den Definitionsbereich dieses Moduls, also zwischen Module Module1 und End Module zu notieren.
Programme bestehen aus bestimmten Abschnitten, sogenannten Funktionen, die voneinander weitgehend unabhängig sind. Diese Aufteilung dient dazu, bestimmte Aktionen einzeln auszuführen. Zum Beispiel wird beim Druck auf den "OK"-Button eines Dialogfeldes eine andere Aktion ausgeführt als beim Druck auf den „Abbrechen“-Button. Diese beiden Aktionen sind in unterschiedlichen Funktionen definiert. Allgemein wird eine Funktion in Visual Basic von Sub und End Sub umschlossen. Im obigen Code wird eine Funktion namens Main definiert. (Das Klammerpaar hinter dem Namen ist für uns erst einmal uninteressant.) Jedes Programm darf nur eine Funktion mit dem Namen Main haben, sie wird aufgerufen, wenn das Programm gestartet wird. Sobald diese Funktion abgearbeitet wurde, also der komplette Code darin ausgeführt wurde, wird das Programm beendet.
Befehlselemente
Die Funktion Main enthält drei weitere Zeilen Code.
Bei diesen zwei Zeilen handelt es sich um Befehle. Dieser Code unterscheidet sich von dem, den wir bisher kennengelernt haben. Während der vorherige Code nur das Verhalten bestimmter Codebereiche bestimmt, legen Befehle konkrete Aktionen fest, die vom Computer ausgeführt werden.
Die ersten zwei Befehle sind Funktionsaufrufe. Das heißt, dem Computer wird mitgeteilt, eine bestimmte Funktion auszuführen, die zum Programm gehört. In den Klammern werden nach einem bestimmten Schema weitere Informationen übermittelt, die für die Ausführung der Funktion wichtig sind. (Hier handelt es sich einmal um eine Zeichenkette und einmal um nichts, d.h. die zweite Funktion braucht keine Zusatzinformationen.)
Der Teil vor der Klammer ist der Name der aufzurufenden Funktion. Sie wundern sich vielleicht, wie das Programm eine Funktion namens System.Console.WriteLine ausführen kann, wo doch schließlich nur Main definiert wurde. Dazu muss man wissen, dass .NET, wie bereits erklärt, viele Tausende von Objekten bereitstellt, die alltägliche Aufgaben vereinfachen. Diese sind in sogenannten Namensräumen geordnet, so wie Dateien in Ordnern einsortiert werden. Dabei können Namensräume auch Unternamensräume enthalten, so wie Ordner Unterordner enthalten können. Der Stammnamensraum von .NET, in dem alle anderen Objekte und Namensräume enthalten sind, heißt schlicht und einfach System. Der System-Namensraum enthält das Objekt Console. Diese Relation wird in Visual Basic durch die Schreibweise System.Console
gekennzeichnet.
Das Objekt System.Console stellt Funktionalitäten zur Benutzung eines Konsolenfensters bereit. Objekte können sogenannte Methoden enthalten, das sind Funktionen, die zum Zwecke der Ordnung dem Objekt zugeordnet sind. Das System.Console-Objekt enthält nur eine Methode namens WriteLine. Diese Methode wird als Funktion über den Bezeichner System.Console.WriteLine aufgerufen. (Analog die Methode ReadLine). Die Funktion WriteLine gibt einen Text aus, gefolgt von einem Zeilenumbruch.
In den Klammern werden nach einem vorgegebenen Schema zusätzliche Informationen übermittelt, die für die Ausführung der Funktion relevant sind. Diese Informationen heißen Parameter, seltener auch Argumente. Eine Entwicklungsumgebung unterstützt Sie beim Aufruf von Funktionen durch die Anzeige der möglichen Schemata, sobald Sie den Cursor in die Klammer setzen oder nach einem Funktionsnamen eine öffnende Klammer eingeben.
Im Falle von System.Console.WriteLine sollte in dem Fenster, in dem diese Informationen erscheinen, oben links auch eine Angabe der Art „1 von 18“, umrandet von 2 Knöpfen mit nach oben und unten zeigenden Pfeilen, zu sehen sein. Dies bedeutet, dass für die Parameter mehrere Schemata existieren, in diesem Fall 18. Man sagt, die Funktion ist überladen. Mit den 2 Knöpfen oder den Pfeiltasten können Sie durch die Schemata blättern. (In Microsoft Visual Basic 2008 verwenden wir im vorliegenden Code etwa das 14. Schema.) Bis jetzt sehen die Schemata noch sehr kryptisch aus, das wird sich jedoch bald ändern.
Der zweite Befehl ist ein sehr interessantes Beispiel, wie man Sachen, die eigentlich für etwas anderes gedacht waren, für die eigenen Zwecke missbrauchen kann. Unser Problem wäre, dass ein Programm, das nur aus einem WriteLine-Befehl besteht, sofort beendet werden würde, da nichts mehr zu tun ist. Dann würde das Konsolenfenster zusammen mit der Ausgabe sofort verschwinden. (Probieren Sie es ruhig noch einmal aus, indem Sie den ReadLine-Befehl löschen.)
Die ReadLine-Methode soll eigentlich der Eingabe eines Wertes durch den Benutzer dienen. Diese Eingabe wird durch [Enter] abgeschlossen und erst, wenn die Eingabe erfolgt ist, wird das Programm fortgesetzt. In unserem Fall interessiert uns die Eingabe nicht (wir werden später sehen, wie man mit Eingaben umgeht), stattdessen ist für uns nur wichtig, dass das Programm angehalten ist, bis [Enter] gedrückt wurde. In dieser Zeit kann die Ausgabe in Ruhe gelesen werden.
Jetzt schauen wir uns noch den dritten Befehl an. Dieser Befehl enthält keine Klammern und besteht, wie die Syntaxhervorhebung offenbart, nur aus einem Schlüsselwort, nämlich End. Der Befehl folgt offensichtlich nicht der Klammersyntax für Funktionen (die übrigens auch gilt, wenn gar keine Parameter benötigt werden; es erscheint dann nur ein leeres Klammernpaar, siehe z.B. ReadLine). Die Erklärung ist denkbar einfach: Dieser Befehl ist keine Funktion, sondern eine Anweisung. Während Funktionen nur einem bestimmten formalen Schema folgen und konkrete Funktionen nicht explizit zur Sprache Visual Basic gehören (es handelt sich lediglich um Ergänzungen), sind bestimmte Anweisungen per Definition in Visual Basic enthalten, man kann auch keine neuen Anweisungen definieren.
Die End-Anweisung ist eine der einfachsten, sie beendet das Programm sofort. In diesem Fall ist das eigentlich gar nicht nötig, da das Programm sowieso beendet wird, wenn die Befehle in der Funktion Main abgearbeitet wurden. Deshalb diente uns die End-Anweisung hier auch nur als Beispiel für Anweisungen und wird im Folgenden erstmal nicht mehr verwendet. Dafür werden wir gleich einige andere Anweisungen kennen lernen.
Aufgabe
Versuchen Sie zunächst, das Programm um eine Meldung zu ergänzen, die erklärt, dass ein Druck auf [Enter] das Programm beendet. So sieht das dann beispielsweise aus:
Module Module1
Sub Main()
System.Console.WriteLine("Hello World.")
System.Console.WriteLine()
System.Console.Write("Drücken Sie [Enter], um das Programm zu beenden.")
System.Console.ReadLine()
End
End Sub
End Module
Hier habe ich noch durch einen WriteLine-Befehl ohne Parameter eine zusätzliche Leerzeile eingefügt. Das können Sie jederzeit machen, um die Ausgabe Ihrer Programme übersichtlicher zu gestalten. Außerdem steht hier bei der Aufforderung, [Enter] zu drücken, nicht WriteLine, sondern nur Write. Diese Methode gibt lediglich den gewünschten Text aus, ohne einen Zeilenumbruch danach einzufügen. Dadurch erscheint der Cursor nicht unter, sondern direkt hinter der Aufforderung. Testen Sie den Unterschied zwischen Write und WriteLine einmal selber, indem Sie in dem obigen Programm Aufrufe der einen durch Aufrufe der anderen Methode ersetzen oder ein völlig eigenes Beispiel schreiben. Beachten Sie vor allem, dass Zeichenketten immer von Anführungszeichen umschlossen sein müssen.
In der Folge werden wir uns mit nützlichen und häufig wiederkehrenden Programmiertechniken beschäftigen. Dazu werden wir immer nur den Inhalt von Main verändern. Sie können die Beispiele in den folgenden Lektionen nachvollziehen, indem Sie die 2 Befehle in der Funktion Main durch den dort angegebenen Inhalt ersetzen. Alternativ können Sie auch neue Projekte anlegen, so wie es in der Einführung beschrieben wurde.
Vorher werden wir jedoch schnell ein nützliches Feature kennenlernen, das nur dem Programmierer dient und am eigentlichen Programm gar nichts ändert. Werfen Sie einen Blick auf die folgenden zwei Quelltexte, die exakt dasselbe Programm beschreiben.
Module Module1 Sub Main() System.Console.WriteLine("Hello world.") System.Console.ReadLine() End Sub End Class
Dieser Code ist – was das Programm selbst betrifft – durchaus korrekt. Aber im Vergleich mit dem folgenden Code ist er schlechter verständlich und deshalb ungünstig:
Module Module1 Sub Main() 'Ein klassisches Hello-World-Programm. System.Console.WriteLine("Hello world.") 'gibt die Nachricht aus System.Console.ReadLine() 'verhindert, dass die Konsole sofort geschlossen wird End Sub End Class
Der zweite Code wurde um Kommentare erweitert. Diese erscheinen nur im Quelltext und nicht im fertigen Programm. Alles, was in einer Zeile hinter einem Apostroph (auf der Tastatur über die Rautetaste bei gedrückter Umschalttaste erreichbar) steht, ist auskommentiert. Dies kann zwei Gründe haben: Einerseits dienen Kommentare der Dokumentation, indem sie wie oben den Code erklären. Dies ist bei größeren Projekten hilfreich, oder wenn Sie sich mit einem Programm lange nicht mehr befasst haben. Kommentare helfen dann, den Code schneller zu verstehen, um effektiv weiterzuarbeiten.
Dabei kommt es vor allem auf die Qualität der Kommentare an: Gute Kommentare erklären nicht, was der Code macht, sondern warum er es macht. Das heißt auch, dass die Kommentare im obigen Beispiel schlecht sind, da sie nur das Offensichtliche erklären, nämlich was der Code macht. (Schließlich dienten diese Kommentare auch nur Demonstrationszwecken.) Eine Ausnahme ist lediglich der Kommentar hinter dem ReadLine-Befehl.
Die zweite Einsatzmöglichkeit für Kommentare besteht darin, Code auszukommentieren. Wenn Sie z.B. einen Fehler suchen und nicht wissen, an welchem Befehl es liegt, müssen Sie die Befehle nicht einzeln löschen und wiederherstellen, sondern können die zweifelhaften Befehle zu Versuchszwecken auskommentieren, d.h. Apostrophe davorsetzen. Sie können auch ganze Funktionen auskommentieren, wenn diese gerade nicht gebraucht werden, Sie sich aber nicht sicher sind, ob Sie die Funktionen oder Teile davon nicht wiederverwenden können, oder wenn Sie eine Funktion neuschreiben wollen, die alte Version aber zum Vergleich behalten wollen.
Die Codebeispiele in diesem Buch werden meistens keine Kommentare enthalten, da sie sowieso ausführlich erklärt werden.
Variablen
Eine wichtige Funktion von Computern ist die Speicherung von Daten. Langfristig geschieht dies mit Dateien, kurzfristig wäre die Speicherung auf der Festplatte viel zu aufwändig. Das Mittel der Wahl ist der Arbeitsspeicher, auf den sehr schnell zugegriffen werden kann. Das Problem: Der Arbeitsspeicher ist groß. Außerdem haben wir meist viele verschiedene Arten von Daten, die zu speichern sind. Variablen sind die Lösung für diese Probleme: Dazu wird ein Teil des Speichers reserviert und mit einem Namen versehen. (Dieser Prozess heißt Deklaration.) Dann können Werte auf einer Variable gespeichert werden. Dann können die Variablen wie deren Werte verwendet werden. (Das hört sich jetzt vielleicht etwas unverständlich an, aber das wird Ihnen gleich klar werden.)
Ein grundlegendes Prinzip der Variablen ist, dass auf bestimmten Variablen nur bestimmte Arten von Daten gespeichert werden dürfen, z.B. nur Kommazahlen. Dadurch weiß Visual Basic immer, was es in einer bestimmten Situation mit einer Variable tun soll. Um festzulegen, was in einer Variable enthalten sein darf, wird ihr bei der Deklaration ein sogenannter Datentyp zugewiesen. Es gibt z.B. Integer für Ganzzahlen oder String für Zeichenketten.
Deklaration
Für die Deklaration von Variablen stellt Visual Basic eine Anweisung namens Dim zur Verfügung. Der Name kommt vom englischen dimension, zu deutsch Ausdehnung. Die Deklaration legt nämlich auch die Ausdehnung, also die Größe der Variable im Speicher, fest. Die Größe wird durch den Datentyp bestimmt, üblich sind Größen wie vier oder acht Byte pro Variable.
Nach dem Schlüsselwort Dim muss in der Dim-Anweisung der Name der Variable stehen. Den dürfen Sie frei wählen, bis auf ein paar Einschränkungen: Der Name darf nur aus Buchstaben, Unterstrichen und Ziffern bestehen, am Anfang muss ein Buchstabe stehen, und der Name darf kein Schlüsselwort sein. Fehler merken Sie meistens anhand der Syntaxhervorhebung oder der Fehlerkennzeichnung in Ihrer Entwicklungsumgebung.
Nach dem Variablenname muss das Schlüsselwort As stehen, gefolgt von dem Datentyp. (Auch hier bietet die Entwicklungsumgebung eine Liste gültiger Datentypen an.) So sieht also eine gültige Dim-Anweisung aus.
Integer-Variablen können Ganzzahlen speichern, zum Beispiel 3 oder -5000, jedoch nicht etwa Kommazahlen oder Zeichenketten. Sie können auch mehrere Variablen auf einmal deklarieren, indem Sie diese durch Kommata trennen. Dabei darf das einleitende Dim aber nur einmal stehen. Die Variablen dürfen auch verschiedene Typen haben.
Zuweisungen
Nun haben wir mit der Variable die Möglichkeit, einen Wert zu speichern, wie das folgende Beispiel demonstriert.
Das Gleichheitszeichen ist ein sogenannter Operator. Allgemein dienen Operatoren dazu, bestimmte Aktionen mit einem oder zwei Werten durchzuführen. (Wir werden im nächsten Kapitel einen genaueren Blick auf Operatoren werfen.) Das Gleichheitszeichen als Operator weist der Variable auf der linken Seite (hier die Ganzzahlvariable "Test") einen Wert (hier 3) zu. Man kann Zuweisungen auch schon in der Dim-Anweisung vornehmen, wie die folgenden Beispiele zeigen. (Man nennt das erste Zuweisen eines Wertes an eine Variable den Startwert.)
Damit offenbart sich auch, dass der Datentyp String für Zeichenketten gedacht ist und Single-Variablen Kommazahlen aufnehmen. Natürlich kann man auch Variablen einen Wert zuweisen, die bereits einen enthalten. Der alte Wert wird dabei unwiderruflich gelöscht.
Verwendung
Einmal deklariert und initialisiert können die Variablen wie die direkt im Code definierten Werte (die übrigens Literale heißen) verwendet werden. Die folgende Hello-World-Variante verwendet Variablen.
Dim Nachricht As String = "Hello world." System.Console.WriteLine(Nachricht) System.Console.ReadLine()
Wie man sieht, können geschickt gewählte Variablennamen die Lesbarkeit von Code erhöhen. Der Zeichenkettenwert wird, anstatt direkt verwendet zu werden, erst in einer Variable gespeichert, dann wird die Variable verwendet. Das zeigt die Vielgesichtigkeit von Variablen: Bei Zuweisungen stehen sie für den Speicher, auf dem der zuzuweisende Wert abgelegt wird, bei Verwendung stehen sie für ihre Werte.
Datentypen
Um effektiv mit Variablen zu arbeiten, ist es von großer Bedeutung, die wichtigsten Datentypen zu kennen. Die folgende Tabelle enthält diese elementaren Datentypen sowie die Wertebereiche, d.h. die Werte, die der Variable zugewiesen werden dürfen. Im Alltag werden Ihnen vor allem die Datentypen begegnen, deren Namen in der Tabelle fett dargestellt sind.
Wird eine Variable deklariert, erhält sie einen vorläufigen Standardwert. So verhindert Visual Basic, dass man einen Wert sucht, wo keiner ist. Wenn man die Variable noch in der Dim-Anweisung mit einem Startwert initialisiert, wird auf der Variable kein Standardwert gespeichert.
Datentyp | Wertebereich | Beschreibung | Standardwert |
---|---|---|---|
Vorzeichenbehaftete Ganzzahlen | |||
SByte | -128 bis 127 | 8-Bit-Ganzzahl | 0 |
Short | -32.768 bis 32.767 | 16-Bit-Ganzzahl | 0 |
Integer | -2.147.483.648 bis 2.147.483.647 | 32-Bit-Ganzzahl | 0 |
Long | -9.223.372.036.854.775.808 bis 9.223.372.036.854.775.807 | 64-Bit-Ganzzahl | 0 |
Vorzeichenlose Ganzzahlen | |||
Byte | 0 bis 255 | vorzeichenlose 8-Bit-Ganzzahl | 0 |
UShort | 0 bis 65.535 | vorzeichenlose 16-Bit-Ganzzahl | 0 |
UInteger | 0 bis 4.294.967.295 | vorzeichenlose 32-Bit-Ganzzahl | 0 |
ULong | 0 bis 18.446.744.073.709.551.615 | vorzeichenlose 64-Bit-Ganzzahl | 0 |
Gleitkommazahlen | |||
Single | -3,402823E38 bis 3,402823E38 | Gleitkommazahl einfacher Genauigkeit | 0.0 |
Double | -1,79769313486231E208 bis 1,79769313486231E208 | Gleitkommazahl doppelter Genauigkeit | 0.0 |
Zeichen und Zeichenketten | |||
Char | Unicode | einzelnes Zeichen | Nullzeichen / Nothing |
String | Unicode | Zeichenkette (bis zu 2 Mrd. Zeichen) | Nullzeichen / Nothing |
Andere Datentypen | |||
Boolean | True (wahr) oder False (falsch) | Wahrheitswerte | False |
Date | 1. Januar 1 bis 31. Dezember 9999 | Datum- und Uhrzeitangabe in einem | 1. Januar 1, Mitternacht |
Bei Ganzzahlen bedeuten die Begriffe vorzeichenlos und vorzeichenbehaftet, ob in der Variable auch ein Vorzeichen gespeichert wird. Wenn nicht, können in der Variable nur positive Werte gespeichert werden.
Ein Wort zu Gleitkommazahlen: Diese können nur bis zu einer bestimmten Anzahl von Nachkommastellen gespeichert werden, Überschüssiges wird abgeschnitten. Als Nachkommastelle zählt alles außer der ersten Ziffer, das Komma wird, wie in wissenschaftlicher Notation üblich, durch Zehnerpotenzen nach links oder rechts verschoben.
Zum Beispiel wird die Zahl 4500 nicht als 4500 gespeichert, wie es bei einer Ganzzahlvariable der Fall wäre. Stattdessen wird das Komma verschoben, dass nur eine Vorkommastelle übrig bleibt. Um den Wert zu erhalten, wird eine Zehnerpotenz angehängt.
Für Zahlen zwischen 0 und 1 sowie negative Zahlen ergibt sich analog:
Die Schreibweise in Visual-Basic-Code sieht für die letzte Ziffer so aus: -3.723005E2. Beachten Sie vor allem den Dezimalpunkt. Die Datentypen für Gleitkommazahlen, Single und Double, unterscheiden sich nicht nur in der Größe der Zehnerpotenzen (E-38 bis E38 bei Single und E-208 bis E208 bei Double), sondern auch in der Anzahl von Nachkommastellen (15 bei Single, 31 bei Double). Die Werte E-38 und E-208 sind die Grenzwerte bei der Annäherung an Null, kleinere Werte werden auf Null gerundet.
Welchen Datentyp soll ich nehmen?
Diese Frage stellen sich Programmieranfänger und erfahrene Programmierer bei Zahlenwerten immer wieder. Die wichtigste Empfehlung hier ist, sich ein System zu entwickeln und das dann konsequent durchzusetzen.
Ich empfehle für Ganzzahlen den Integer- und für Gleitkommazahlen den Double-Datentyp. Beide Typen haben den Vorteil, dass sie fast immer groß genug sind und auch von den meisten Funktionen des .NET-Frameworks verwendet werden. Für Integer spricht zudem, dass eine Integer-Variable 32 Bit groß ist und normale Rechner mit 32-Bit-Zahlen am schnellsten rechnen.
Manchmal wird dazu geraten, möglichst kleine Variablen zu verwenden, damit die Anwendung wenig Speicher belegt. Ich rate davon aber aus zwei Gründen ab:
- Für die Maßstäbe der Anwendungsprogrammierung lohnt sich eine solche Erbsenzählerei nicht, die zudem eine häufige Fehlerquelle darstellt.
- Wie oben angedeutet rechnen normale Computer mit 32-Bit-Zahlen (Integer) am besten. Die Speicherplatzersparnis kleiner Zahlentypen wird durch die verminderte Rechengeschwindigkeit aufgewogen. Hier muss man also abwägen, ob man lieber schneller oder ressourcenschonender arbeiten möchte.
Operatoren
Wie schon im vorherigen Kapitel angeschnitten wurde, dienen Operatoren dazu, bestimmte Aktionen mit einem oder zwei Werten durchzuführen. Dieses Kapitel und die zugehörigen Unterkapitel stellen die vielfältigen Operatoren genauer vor. Ein Tipp vorab: Sie können sich die Ergebnisse der dargestellten Codezeilen anzeigen lassen, indem Sie die bereits bekannte System.Windows.Forms.MessageBox.Show-Funktion verwenden. Geben Sie in den Klammern nur den Namen einer Variable an. Der Wert der Variable wird dann, unabhängig vom Typ, bei der Ausführung des Programmes in einem Hinweisfeld angezeigt. In den Codebeispielen der folgenden Kapitel werden zur Kontrolle auch die ausgegebenen Variablenwerte angegeben, so wie hier.
Außerdem verfügt die Visual-Basic-Sprache über eine nützliche Funktion zur Verkürzung des Codes. Statt System.Console.WriteLine kann man auch nur Console.WriteLine schreiben. Bestimmte Namensräume werden nämlich standardmäßig importiert, d.h. sie können benutzt werden, ohne sie explizit zu notieren. Der Visual-Basic-Compiler erkennt dann automatisch, welche Funktion gemeint ist. Einige Namensräume werden standardmäßig importiert, für uns von Belang ist darunter der System-Namensraum. Wäre dies nicht der Fall, müsste man statt Integer z.B. immer System.Integer schreiben, denn Integer ist ein Objekt des System-Namensraumes.
Unter der Ausnutzung importierter Namensräume verkürzt sich dieser Code ohne Beeinträchtigung der Lesbarkeit.
Mathematische Operatoren
Mathematische Operatoren sind der denkbar beste Einstieg in die Thematik der Operatoren. Erstens sind sie (größtenteils) aus dem Alltagsleben vertraut, zweitens kann man an ihnen wichtige Grundbegriffe erklären, die für das Verständnis wichtig sind.
Die 5 Grundrechenarten
Nach dem Gleichheits- oder Zuweisungsoperator = ist der Additionsoperator + einer der wichtigsten Operatoren. Ihnen ist das "Plus-Zeichen" sicher aus der Schulzeit oder dem Berufsleben bekannt. Er dient dazu, zwei Werte zu addieren. 3 + 5 ergibt 8. Dazu ein Codebeispiel:
Dieses Programm führt Additionen aus. Die erste Zeile deklariert zwei Variablen, denen im folgenden Werte zugewiesen werden. Bei der ersten Zuweisung wird man intuitiv feststellen, dass der Variable a der Wert 10 zugewiesen wird. Das ist so korrekt, jedoch möchte ich, um das Verständnis für spätere, komplexere Zusammenhänge zu wahren, etwas genauer auf die Vorgänge in dieser Codezeile eingehen.
Ein Operator führt eine bestimmte Aktion durch. Anders als die uns bisher bekannten Funktionen entsteht dabei aber ein Wert, im Beispiel die Summe von 8 und 2, also 10. Dieser Wert heißt Rückgabewert. (Wir werden später sehen, dass auch Funktionen Rückgabewerte haben können.) Der Operator funktioniert ganz ähnlich wie eine Funktion: Er führt eine Aktion mit seinen Operanden (den Werten links und rechts des Operators) aus, so wie eine Funktion eine Aktion mit den ihr übergebenen Parametern ausführt. Operatoren sind jedoch weit tiefer in die Sprache integriert, man kann, anders als bei Funktionen, keine eigenen Operatoren erschaffen. Visual Basic stellt uns jedoch ein reichhaltiges Operatorenangebot zur Verfügung, das mächtige Manipulationsmöglichkeiten für die Werte der Standarddatentypen eröffnet.
Man kann sich eine Operation in etwa folgendermaßen vorstellen: Der Operator führt seine Aktion durch und ersetzt sich und seine Operanden sinnbildlich mit seinem Rückgabewert. Aus a = 8 + 2 wird durch Ausführen der Additionsoperation a = 10. Die Zuweisungsoperation weist a dann das Ergebnis der Addition, nämlich 10, zu. Beachten Sie, dass der Zuweisungsoperator keinen Rückgabewert hat. Nachdem alle Operationen erfolgreich abgeschlossen wurden, fährt die Ausführung mit der nächsten Zeile fort.
Die dritte Zeile b = 10 + a betont noch einmal die Vielgesichtigkeit von Variablen. Während b als die Speicherzelle interpretiert wird, in der das Ergebnis der Zuweisungsoperation gespeichert wird, wird a als der Wert interpretiert, mit dem gerechnet werden soll. Dieser Wert wird (um die oben erfolgreich eingesetzte Vorstellungskraft erneut zu bemühen) zuerst für a eingesetzt, bevor die Operation ausgeführt wird. In diesem Fall sieht die Abfolge im Computer also so aus:
b = 10 + a
wird durch Abrufen eines Wertes aus dem Speicher zub = 10 + 10
.b = 10 + 10
wird durch Ausführen einer Additionsoperation zub = 20
.b = 20
wird durch Ausführen der Zuweisungsoperation erfolgreich gelöst.
Auf den ersten Blick erscheint die vierte Zeile unlogisch, schließlich ist a niemals gleich a+1. Diese Interpretationsweise haftet aber noch aus der Mathematik an. Hier muss man erneut sehr scharf zwischen der mathematischen Logik, die es für unmöglich erklärt, dass eine Zahl gleich ihrem Nachfolger ist, und der Informatik, für die dieser Code eine Zuweisung darstellt, trennen. Zuerst wird wieder der Wert von a, zurzeit 10, aus dem Speicher abgerufen und rechts eingesetzt. Dann wird die Addition durchgeführt, die 11 ergibt, dann wird dieses Ergebnis auf a gespeichert.
Operationen können auch bei der Initialisierung innerhalb der Dim-Anweisung verwendet werden. Natürlich können in einer Codezeile auch mehrere Operationen stehen.
Im Falle mehrerer gleicher Operationen, hier der zwei Additionen in der ersten Zeile, wird von links nach rechts vorgegangen. Das heißt, dass erst 5 + 3 gerechnet wird und dann 8 + 2 (8 ist das Ergebnis der ersten Addition). Das Ergebnis 10 wird dann auf a gespeichert.
In der zweiten Zeile vermuten Sie wahrscheinlich einen Fehler. Schließlich wird mit b gerechnet, bevor dieser Variable ein Wert zugewiesen wird. Das ist falsch. Wird eine Variable deklariert, wird ihr immer zuerst der Standardwert zugewiesen. Zum Beispiel wird a zuerst mit dem Standardwert 0 und danach mit dem Startwert 10 belegt. Die Variable b wird zunächst mit 0 belegt, dann ergibt die Operation a + b
den Wert 10, der auf b gespeichert wird.
Vielleicht wundern Sie sich, warum dieses Kapitel mit Die 5 Grundrechenarten überschrieben ist, wo es doch nur vier gibt. Ich habe jedoch die (im Gegensatz zu anderen Sprachen) in Visual Basic vorhandene und komfortable Potenzoperation dazu gerechnet.
Nach der Addition kommt logischerweise die Subtraktion. Der Operator ist das Minuszeichen bzw. der Bindestrich -. Die Verwendung erfolgt analog zur Addition. Beachten Sie die Links-Vor-Rechts-Regel bei der Reihenfolge von Operationen.
Aufgrund der Links-Vor-Rechts-Regel erhält s den Wert 1 und nicht den Wert 3, der sich ergeben würde, wenn man erst die rechte Subtraktion ausführt.
Multiplikation und Division erfolgen über die entsprechenden Operatoren, das Sternchen * für Multiplikationen und den Schrägstrich / für Divisionen.
Dieser Code verursacht einen Fehler, da die Multiplikationsoperation den Wert 40.000 zurückgibt, dieser Wert jedoch für Byte zu groß ist. Indem man den Datentyp der Variablen in Integer ändert, umgeht man das Problem.
Die Potenzoperation ist die fünfte der vier Grundrechenarten (daher auch die Überschrift). Der Potenzoperator ^ (auf der Tastatur zwischen der Tabulatortaste und der Escape-Taste) akzeptiert für die Potenz insbesondere auch Kommazahlen und negative Werte.
p2 entspricht der Quadratwurzel von 5. ( )
Ausdruck
Oft wird Ihnen im Zusammenhang mit Operationen der Begriff Ausdruck begegnen. Einfach ausgedrückt ist alles rechts vom Gleichheitszeichen einer Zuweisungsoperation ein Ausdruck. Zum Beispiel ist 8 + 3
ein Ausdruck. Auch a
ist ein gültiger Ausdruck, wenn die Variable a deklariert wurde.
Priorität
Bestimmt ist Ihnen die Regel "Punkt vor Strich" bekannt. Anscheinend kennt auch Visual Basic die Punkt-vor-Strich-Regel. Um genau zu sein, verfügt Visual Basic über ein viel allgemeineres System zur Bestimmung der Reihenfolge von Operationen. Der zentrale Begriff ist der der Priorität. Operationen mit einer höheren Priorität werden zuerst ausgeführt, dann Operationen mit einer niedrigeren Priorität. Der Potenzoperator hat von allen Operatoren die höchste Priorität, Multiplikation und Division haben eine etwas geringere Priorität, Addition und Subtraktion haben von den bisher eingeführten Operationen die niedrigste Priorität. Eine detaillierte Liste mit der Priorität aller Operatoren ist in diesem Wikibook verfügbar.
Zum Glück gibt es eine Möglichkeit, die Prioritäten auszutricksen. Sicherlich kennen Sie Klammern schon aus der Mathematik, wo eine geklammerte Addition vor einer Multiplikation ausgeführt wird. So ist 5 * 2 + 3 = 13
, aber 5 * (2 + 3) = 25
. Klammern dürfen auch in Visual Basic jederzeit eingesetzt werden, um die Priorität umzuverteilen. Davon ausgenommen ist der Zuweisungsoperator =, nur Ausdrücke rechts davon dürfen geklammert werden. (Nebenbei bemerkt darf auch nur eine Zuweisungsoperation pro Zeile stehen.)
Nachfolgend ein paar Beispiele zur Priorität, der Umgehung derselben durch Klammern, und über den Sinn und Unsinn von Klammerwäldern.
Dim OhneKlammern As Integer = 5 + 3 ^ 2 Dim MitKlammern As Integer = (5 + 3) ^ 2 Dim SovielKlammernWieMöglich As Integer = (((5) + (3)) ^ (2))
OhneKlammern = 14 MitKlammern = 64 SovielKlammernWieMöglich = 64
Die letzte Zeile zeigt einerseits, dass auch um einzelne Werte Klammern gesetzt werden darf, und andererseits, dass Klammern die Lesbarkeit sehr verringern. Setzen Sie Klammern deshalb nur mit Bedacht ein. (Übrigens: In der letzten Codezeile sind eigentlich noch mehr Klammern möglich, zum Beispiel könnte man statt (5) auch (((5))) schreiben können, aber warum sollte man?)
Mit der obengenannten Regel für Ausdrücke (alles rechts vom Gleichheitszeichen der Zuweisungsoperation ist ein Ausdruck) ergibt sich, dass Ausdrücke neben Werten, Operatoren und Variablennamen auch Klammern enthalten dürfen. Zum Beispiel ist 3 * (5 + Temp) ein gültiger Ausdruck, wenn es eine Variable Temp gibt.
Ganzzahlige Divisionen
Auf den ersten Blick mag es unsinnig erscheinen, noch eine Art der Division einzuführen. Es wird sich aber zeigen, dass ganzzahlige Divisionen sehr interessante Möglichkeiten der schnellen, einfachen und ressourcenschonenden Programmierung ermöglichen.
Was sind nun ganzzahlige Divisionen? Erinnern wir uns dabei an die Grundschule zurück, in der man noch keine Kommazahlen kannte. Rechnete man damals „19 durch 5“, so war das Ergebnis nicht „3,8“, sondern „3 mit Rest 4“. Genau diese Ergebnisse liefert die ganzzahlige Division, die in Visual Basic auf zwei Operatoren aufgeteilt ist. Der Operator \ liefert das eigentliche Ergebnis, der Operator Mod den Rest. Das folgende Beispiel setzt die ganzzahlige Division „19 durch 5“ in Visual Basic in Visual Basic .NET um.
Auf den ersten Blick erscheint der Operator Mod seltsam, schließlich handelt es sich nicht um ein Sonderzeichen, sondern um ein Wort (genauer die Kurzform von Modulo). Es wird sich aber schon im nächsten Kapitel offenbaren, dass Wortoperatoren fast öfter vorkommen als Zeichenoperatoren.
Wozu taugen nun ganzzahlige Divisionen? Der Operator \ wird für Divisionen verwendet, bei denen der Wert nicht bekannt ist (etwa bei Benutzereingaben), der Rückgabewert aber auf jeden Fall ganzzahlig sein muss. Der Operator Mod wird z.B. verwendet, wenn auf Teiler oder Vielfache von Zahlen getestet werden soll. Im folgenden Beispiel ist y gleich 0, wenn der Wert der Variable x ein Vielfaches von 3 ist. Sie können das Beispiel ausprobieren, indem Sie y ausgeben lassen und x im Code ändern. Deshalb steht x hier auch nicht unter „Output“.
Vergleichsoperatoren
In der Datentypentabelle im Kapitel über Variablen ist auch der Boolean-Typ aufgeführt, der Wahrheitswerte speichert, sprich wahr oder falsch bzw. in Visual Basic True oder False. Dieser Datentyp wird später im Verborgenen eine große Rolle spielen, deshalb ist das ausgiebige Studium seiner Operatoren von elementarer Bedeutung.
Mit „seine Operatoren“ sind die Vergleichsoperatoren in diesem Kapitel und die Bool'schen Operatoren im nächsten Kapitel gemeint. Insgesamt sind dies 16 Stück, was die Bedeutung dieses Datentyps klarmacht. Diese Operatoren akzeptieren alle Datentypen als Eingabe und geben immer Boolean-Werte zurück.
Wenn Sie die Beispiele dieses Kapitels nachvollziehen und die Ausgabe der Boolean-Variablen über die System.Windows.Forms.MessageBox.Show-Methode nicht arbeitet, ersetzen Sie die Boolean-Variable im Aufruf der Funktion durch einen Aufruf der IIf-Funktion, wie im folgenden Beispiel. Dabei steht MeineVariable für die auszugebende Variable. Im Hinweisfeld erscheint dann True oder False, je nach dem Wert der Variablen.
Der Gleichheitsoperator =
Moment mal, ist = nicht der Zuweisungsoperator? Stimmt, aber eben auch der Gleichheitsoperator. Die Gleichheitsoperation überprüft, ob seine Operanden gleich sind, also die durch die Operanden dargestellten Werte gleich sind. Sind diese Werte gleich, gibt die Operation True zurück, sonst False. Visual Basic erkennt den Unterschied zwischen Zuweisungs- und Gleichheitsoperator.
Die letzte Zeile zeigt, dass es der gute Stil doch einmal erlaubt, eine an für sich unnötige Klammer zu setzen. Die folgende, absolut äquivalente Zeile zeigt, was hier eine Zuweisungs- und was eine Gleichheitsoperation ist.
Die Gleichheitsoperation vergleicht a (mit dem Wert 3) und 3. Das Ergebnis ist True, da beide Werte gleich sind. Dieses Ergebnis wird dann auf b gespeichert. Wäre a = 4 gewesen, so wäre das Ergebnis der Gleichheitsoperation False gewesen. Der Gleichheitsoperator gilt übrigens auch für Zeichenketten, wie das folgende Beispiel zeigt.
Dim s1 As String = "Test", s2 As String = "Test" Dim s3 As Boolean = (s1 = s2)
s3 = True
Der Ungleichheitsoperator <>
Der Ungleichheitsoperator ist das genaue Gegenteil des Gleichheitsoperators (wie der Name schon vermuten lässt). Er gibt True zurück, wenn seine Operanden ungleich sind, sonst False. Wir wollen auf ein weiteres Beispiel verzichten. Probieren Sie den Operator aus, indem Sie in den obigen Beispielen den Gleichheits- durch den Ungleichheitsoperator ersetzen.
Die Relationsoperatoren <, <=, > und >=
Wenn Sie erst einmal die Funktionsweise der beiden obigen Operatoren verstanden haben, sind diese Operatoren relativ leicht zu verstehen. Die folgende Übersicht fasst die Operatoren zusammen:
Operator | Gibt True zurück, wenn... | Gibt False zurück, wenn... |
---|---|---|
< | ...der erste Operand kleiner als der zweite Operand ist. | ...der erste Operand größer als der zweite Operand ist oder die Werte beider Operanden gleich sind. |
<= | ...der erste Operand kleiner als der zweite Operand oder gleich dem zweiten Operand ist. | ...der erste Operand größer als der zweite Operand ist. |
> | ...der erste Operand größer als der zweite Operand ist. | ...der erste Operand kleiner als der zweite Operand ist oder die Werte beider Operanden gleich sind. |
>= | ...der erste Operand größer als der zweite Operand oder gleich dem zweiten Operand ist. | ...der erste Operand kleiner als der zweite Operand ist. |
Diese Operationen erscheinen für Zahlen jeglicher Art sofort logisch, bei Zeichenketten ist die Sache nicht so offensichtlich. Hier wird alphabetisch sortiert. Man kann sich das wie ein Lexikon vorstellen, in dem die Einträge alphabetisch sortiert werden. So ist „Maier“ kleiner als „Meier“, da „a“ vor „e“ steht. Die genaue Sortierreihenfolge wird nicht nur vom Alphabet bestimmt, da auch Sonderzeichen berücksichtigt werden müssen, doch für die alltägliche Arbeit reichen diese Faustregeln. Nachfolgend einige Beispiele zu dieser Operatorenfamilie:
Dim a As Boolean = (3 < 4) Dim b As Boolean = (3 < 3) Dim c As Boolean = (3 <= 3) Dim d As Boolean = ("Test" < "Tasse") Dim e As Boolean = ("dass" < "das")
a = True b = False c = True d = False e = False
Zu d: "Ta" steht im Alphabet vor "Te", also ist "Tasse" kleiner als "Test".
Zu e: Verhilft das Alphabet nicht zu einer Sortierung, ist die kürzere Zeichenkette (hier "das") kleiner als die längere (hier "dass").
Zusammenfassung
Die folgende Tabelle fasst alle Vergleichsoperatoren kurz zusammen. Dabei ist links der Operator und oben das tatsächliche Verhältnis der Operanden zueinander angegeben. Der linke Operand ist a, der rechte Operand ist b.
a < b | a = b | a > b | |
---|---|---|---|
= | False | True | False |
<> | True | False | True |
< | True | False | False |
<= | True | True | False |
> | False | False | True |
>= | False | True | True |
Bool'sche Operatoren
Die im vorherigen Unterkapitel besprochenen Vergleichsoperatoren verarbeiten alle uns bis jetzt bekannten Datentypen und geben Boolean-Werte zurück. Mit den Bool'schen Operatoren lernen wir eine weitere Gruppe von Operatoren kennen, die mit den Vergleichsoperatoren den Rückgabetyp, nämlich Boolean gemein hat. Anders als letztere fordern die Bool'schen Operatoren aber auch als Operanden Boolean-Werte. Damit eignen sie sich in der Praxis hervorragend, um die Ergebnisse mehrerer Vergleichsoperationen zu verknüpfen.
Die Bool'schen Operationen heben sich von der großen Masse der Operationen auch dadurch ab, dass ihre Operatoren aus Worten bestehen, nicht aus Sonderzeichen. In dieser Hinsicht sind sie dem Operator Mod sehr ähnlich, der offensichtlich auch aus einem Wort besteht.
Negation
Bisher haben wir nur Operatoren kennengelernt, die zwei Operanden benötigen. Diese heißen binäre Operatoren (griech. bi- = zwei). Der Negationsoperator Not benötigt nur einen Operanden. Deshalb ist er ein sogenannter unärer Operator (griech. uni- = eins). Not gibt das Gegenteil des Wertes des Operanden zurück. Ist der Operand True, gibt die Operation False zurück, ist der Operand False, gibt die Operation True zurück. Ein Beispiel:
Die erste Zeile zeigt die Verwendung von Not. Der Operator wird immer vor dem Operanden notiert. Diese Schreibweise heißt Präfix-Notation (griech. prä- = vorher; der Operator kommt vorher), das Gegenteil (Operator nach dem Operand) heißt Postfix-Notation. Letztere tritt in Visual Basic nie auf, deshalb sind diese Begriffe zwar nett zu wissen, aber in Visual Basic nicht von Belang.
In der ersten Zeile zeigt sich erstmals richtig ein Phänomen von Visual Basic, dass in anderen Programmiersprachen kaum deutlich wird. Not True heißt übersetzt nicht wahr, also unwahr oder falsch, sprich False. Diese Erscheinung eines im wahrsten Sinne des Wortes „lesbaren“ Codes wird durch die hohe Schlüsselwortdichte in Visual Basic begünstigt, wohingegen andere Sprachen wie C viele Schlüsselworte durch Sonderzeichen ersetzt haben. Die zweite Zeile erscheint auf den ersten Blick sonderbar, eine geschickt gesetzte Klammer enthüllt jedoch die Bedeutung.
Der jetzt in Klammern gesetzte Ausdruck wird erst zu False aufgelöst, dann wird der entstandene Ausdruck Not False zu True aufgelöst und b wird mit diesem Ergebnis initialisiert.
Konjunktion
Die Konjunktionsoperation verknüpft zwei Boolean-Werte zu einem Boolean-Wert. Dabei wird True zurückgegeben, wenn beide Operanden True sind, sonst False. Der Operator ist And, wiederum also ein Wortoperator. Der Name leitet sich vom englischen „Und“ ab, da der 1. und der 2. Operand True sein müssen.
Dim a As Boolean = True And True Dim b As Boolean = True And False Dim c As Boolean = False And False Dim d As Boolean = Not False And Not False
a = True b = False c = False d = True
Beachten Sie, dass Negationen eine höhere Priorität haben als Konjunktionen. Die letzte Zeile ist also äquivalent zu der folgenden Zeile. Diese wird zu a = True And True, dann zu a = True aufgelöst.
Neben dem Operator And gibt es auch den Operator AndAlso. Das Ergebnis ist das gleiche, die Ausführung eine andere. Dazu müssen Sie sich in Erinnerung rufen, dass Operationen nacheinander ausgeführt werden. Der AndAlso-Operator erzwingt eine Neuverteilung der Prioritäten: Zuerst wird der linke Operand aufgelöst. Ist er True, wird auch der rechte Operand aufgelöst und mit And verknüpft. Ist der linke Operand jedoch False, so ist der rechte Operand egal, er wird nicht aufgelöst und die Konjunktion gibt sofort False zurück. Dies bringt statistisch gesehen in 50% aller Fälle einen Geschwindigkeitsvorteil.
Der AndAlso-Operator kann also für zeitkritische Anwendungen verwandt werden, d.h. für Anwendungen, die innerhalb kürzester Zeit zu einem Ergebnis kommen müssen, etwa weil sie viele Aufgaben hintereinander in einer adäquaten Geschwindigkeit ausführen müssen oder weil andere Anwendungen auf ihr Ergebnis warten. Allerdings muss man beim Einsatz des AndAlso-Operators auf die Verwendung von Funktionen im rechten Operanden achten, die evtl. gar nicht ausgeführt werden. (Mit Funktionen werden wir uns später noch genauer befassen.) Für die meisten, nicht zeitkritischen Anwendungen, reicht der And-Operator zur Konjunktion insgesamt voll aus, die Verwendung von AndAlso ist nur minimal schneller.
Adjunktion und Disjunktion
Die Adjunktionsoperation Or und die Disjunktionsoperation Xor ähneln der Konjunktion in der Hinsicht, dass auch sie zwei Boolean-Werte zu einem verknüpfen. Die Or-Operation gibt True zurück, wenn mindestens einer seiner Operanden True ist. Er gibt also nur dann False zurück, wenn beide Operanden False sind. Der Name Or kommt vom englischen Wort für „oder“, da der linke oder der rechte Operand True sein muss, damit True zurückgegeben wird. Den Unterschied zwischen Or und And demonstriert dieses Beispiel, das dem Beispiel zur Konjunktion bis auf den Operator gleicht. Beachten Sie das Ergebnis der zweiten Adjunktion.
Dim a As Boolean = True Or True 'gibt True zurück Dim b As Boolean = True Or False 'gibt True zurück Dim c As Boolean = False Or False 'gibt False zurück
a = True b = True c = False
Der AndAlso-Operator findet in der Adjunktion sein Äquivalent im OrElse-Operator. Hier gilt, dass der rechte Operand nicht ausgewertet wird, wenn der linke Operand True ist, da in diesem Fall das Ergebnis auf jeden Fall True ist. Für den OrElse-Operator gelten die gleichen praktischen Einschränkungen, die für den AndAlso-Operator oben schon dargelegt wurden.
Der Xor-Operator gibt True zurück, wenn genau einer seiner zwei Operanden True ist. Er gibt False zurück, wenn keiner der Operanden oder beide Operanden True ist. Der Name des Operators ist die Abkürzung für „exclusive or“, zu deutsch „ausschließendes Oder“, da ein wahrer (True) Operand ausschließt, dass der andere Operand True ist, wenn das Ergebnis True sein soll. Wieder werden dieselben Beispiele wie bei And und Or bemüht, um einen Vergleich (besonders zu Or) zu ermöglichen.
Dim a As Boolean = True Xor True Dim b As Boolean = True Xor False Dim c As Boolean = False Xor False
a = False b = True c = False
Vielleicht fällt Ihnen auf, dass a Xor b dasselbe ergibt wie <t>a <> b. Bei Boolean-Operanden kann man dieses Wissen manchmal in einen minimalen Geschwindigkeitsvorteil umsetzen. Muss man aber nicht.
Zusammenfassung
Die nachfolgenden Wertetabellen fassen die Bool'schen Operationen zusammen. Links oben steht der Operator, rechts davon in der oberen Zeile findet sich der Wert des 1. Operands, an der linken Seite (bei binären Operatoren) findet sich der Operand. An der Kreuzung des durch die Operanden markierten Paares aus Zeile und Spalte findet sich der Rückgabewert der Operation.
|
|
Zeichenkettenoperatoren
Die bisherigen Operatoren konnten nur mit Zahlen oder Boolean-Werten umgehen, es gibt jedoch auch zwei Operatoren für Zeichen und Zeichenketten.
Verkettung
Der Verkettungsoperator & verlangt zwei Parameter vom Typ Char oder String. Diese beiden Zeichen(ketten) werden aneinandergehängt und das Ergebnis zurückgegeben. Der Rückgabewert ist immer vom Typ String.
Dim a As String = "abc", b As String = "def", c1 As String, c2 As String, c3 As String c1 = a & b 'gibt "abcdef" zurück c2 = b & a 'gibt "defabc" zurück c3 = a & "g" & b 'gibt "abcgdef" zurück
c1 = "abcdef" c2 = "defabc" c3 = "abcgdef"
In der ersten Zeile werden drei String-Variablen deklariert, von denen zwei mit einfachen Werten initialisiert werden. Die Variable c dient als Zwischenspeicher für die Ergebnisse der Verkettungsoperationen. In der zweiten Zeile werden a und b verkettet. Das Ergebnis besteht also aus den Zeichenketten „abc“ und „def“, die zu „abcdef“ verkettet werden.
Die zweite Zeile offenbart, dass die Verkettung von a und b von der Verkettung von b und a verschieden ist. Während im ersten Fall die Zeichenkette von a im Ergebnis vor der Zeichenkette von b auftaucht, sind sie im zweiten Fall genau vertauscht. Das Ergebnis ist also nicht „abcdef“, sondern „defabc“. Der rechte Operand wird also immer an den linken Operanden angehängt und das Ergebnis zurückgegeben.
Die dritte Zeile zeigt, dass man, wie bei allen anderen Operationen auch, mehrere Operationen gleichzeitig ausgeführt werden können, und, dass man wie immer auch Werte verwenden kann, die nicht in Variablen gespeichert sind, sondern direkt im Code angegeben sind.
Vergleich
Sicherlich kennen Sie die Suchfunktion von Windows, mit der nach Dateien mit bestimmten Namen gesucht werden kann. Dabei kann man, wenn man den genauen Namen der Datei nicht weiß, bestimmte Platzhalter verwenden. Etwa bedeutet ein Sternchen im Suchbegriff, dass im eigentlichen Dateinamen an der entsprechenden Stelle beliebig viele andere Zeichen stehen können. So findet man mit dem Suchmuster „*.doc“ Dateien wie „Brief.doc“ und „Lebenslauf.doc“. Eine ähnliche Funktionalität bietet auch Visual Basic mit dem Like-Operator. Dieser Operator verlangt zwei String-Operanden, der linke entspricht dem Dateinamen in der Windows-Suche, der rechte entspricht dem Suchmuster.
Einfach gesprochen vergleicht Like also zwei Zeichenfolgen. Geht der Vergleich positiv aus, also entspricht die Zeichenfolge dem Muster, so wird True zurückgegeben, sonst False. Dabei können verschiedene Platzhalter, sogenannte Wildcards, verwendet werden. Ein Beispiel ist das Sternchen, dass für beliebig viele beliebige Zeichen oder auch für kein Zeichen stehen kann.
Dim a As Boolean = "ab" Like "ab" Dim b As Boolean = "ab" Like "ac" Dim c As Boolean = "aaa" Like "a*a" 'liefert "True", da ein Sternchen rechts für beliebig viele Zeichen links stehen kann Dim d As Boolean = "aa" Like "a*a" 'liefert "True", da ein Sternchen rechts auch für keine Zeichen links stehen kann Dim e As Boolean = "a" Like "a*a" 'liefert "False", da links mindestens die rechts enthaltenen zwei "a" vorhanden sein müssen
a = True b = False c = True d = True e = False
Zu a: Die Zeichenkette links entspricht dem Muster rechts.
Zu b: Das Muster rechts entspricht nicht der Zeichenfolge links. ("ac" entspricht nicht "ab".)
Zu c: Ein Sternchen rechts kann für beliebig viele Zeichen links stehen. (Hier steht das Sternchen für ein "a".)
Zu d: Das Sternchen kann auch für kein Zeichen stehen, so wie hier.
Zu e: Die hintere "a" im Muster finden keine Entsprechung in der Zeichenfolge links.
Es gibt noch zwei weitere Platzhalter. Ein Fragezeichen kann immer nur für genau ein Zeichen stehen, nicht mehr und nicht weniger. Eine Raute (#) steht für genau eine Ziffer von 0 bis 9.
Dim a As Boolean = "a" Like "?*" Dim b As Boolean = "a" Like "??*" Dim c As Boolean = "12345" Like "##?##" Dim d As Boolean = "abcde" Like "??#??"
a = True b = False c = True d = False
Zu a: Das Fragezeichen repräsentiert das "a" und das Sternchen nichts.
Zu b: Die zwei Fragezeichen müssen für zwei Zeichen links stehen, dort ist aber nur eines.
Zu c: Jede der Wildcards findet ihre Entsprechung in einem der linken Zeichen.
Zu d: "c" ist keine Ziffer.
Schließt man im Muster eine Menge von Zeichen in eckige Klammern ein, so repräsentiert dieses Muster genau ein Zeichen aus der angegebenen Menge von Zeichen. So repräsentiert „[AaZz]“ entweder ein großes oder ein kleines A oder ein großes oder ein kleines Z. Setzt man vor die Zeichenmenge, direkt hinter der öffnenden eckigen Klammer, ein Ausrufezeichen, wird die Zeichenmenge umgekehrt. Das Muster steht dann nicht mehr für eines der angegebenen Zeichen, sondern für ein beliebiges Zeichen, das nicht angegeben wurde. So repräsentiert „[!ABC]“ zum Beispiel Buchstaben wie „D“ oder „Z“ oder auch Sonderzeichen und Zahlen, aber keinen der Buchstaben „A“, „B“ oder „C“.
Soll ein Zeichen aus einem bestimmten Zeichenbereich vorkommen, so kann der Zeichenbereich in eckigen Klammern eingeschlossen werden. Innerhalb der Klammern steht das erste und das letzte Zeichen des Bereiches, getrennt durch einen Bindestrich. Das Muster „[A-Z]“ repräsentiert z.B. genau einen Großbuchstaben, das Muster „[A-C]“ repräsentiert nur entweder „A“, „B“ oder „C“. Wie man sieht, ist also das Muster „#“ zu „[0-9]“ äquivalent. Auch bei Buchstabenbereichen funktioniert das Ausrufezeichen zur Umkehr. Während „[A-Z]“ einen Großbuchstaben repräsentiert, steht „[!A-Z]“ für ein beliebiges Zeichen, das kein Großbuchstabe ist.
Soll ein Fragezeichen, eine Raute oder ein Sternchen so in der linken Zeichenfolge vorkommen, muss das jew. Zeichen im Muster in eckige Klammern eingeschlossen werden. Dann wird es nicht als Wildcard interpretiert. Das gleiche gilt für die öffnende eckige Klammer, die in eckige Klammern eingeschlossen werden muss, um ihre spezielle Funktion zu verlieren. Das Muster „[[]“ steht also für genau eine öffnende eckige Klammer in der linken Zeichenfolge. Die schließende eckige Klammer kann aus offensichtlichen Gründen im Muster nicht innerhalb von eckigen Klammern stehen, um sich selbst in der linken Zeichenfolge zu repräsentieren. Es kann jedoch außerhalb von eckigen Klammern verwendet werden, um für sich selbst zu stehen.
Dim a As Boolean = "a" Like "?" Dim b As Boolean = "a" Like "[?]" Dim c As Boolean = "[]" Like "[[]]"
a = True b = False c = True
Zu a: Das Fragezeichen findet seine Entsprechung in dem "a".
Zu b: Dieses Fragezeichen ist in eckige Klammern eingeschlossen und somit keine Wildcard mehr. Es kann nur für sich selbst stehen.
Zu c: Die öffnende eckige Klammer links passt zu "[[]" rechts und die schließende Klammer links passt zur schließenden Klammer rechts.
Zusammengesetzte Operatoren
Oftmals werden Ihnen Befehle begegnen, die den folgenden sehr ähneln (auf die Deklarationen wurde verzichtet).
Mit dem Wert einer Variable wird eine Operation durchgeführt, dann wird das Ergebnis wieder auf derselben Variable gespeichert. Bei der doppelten Angabe der Variable handelt es sich um eine vor allem lästige Redundanz, vor allem bei langen Variablennamen. Seit .NET kennt Visual Basic deshalb wie viele andere Hochsprachen auch sogenannte zusammengesetzte Operatoren. Die folgenden drei Codezeilen sind zu den oberen äquivalent.
Hier wurden die Zuweisungsoperation und die arithmetische bzw. Zeichenkettenoperation zu einer einzelnen Operation zusammengefasst. Diese Methode hat zwei Vorteile: Einerseits ist der Code ein bisschen übersichtlicher, andererseits werden solche Anweisungen schneller ausgeführt, da sich die Methode des gleichzeitigen Rechnens und Speicherns eher an die eigentlichen Vorgänge im Computer annähert. Dieser Geschwindigkeitsvorteil wird jedoch meist erst bei mehreren Operationen, sprich bei einigen Tausend bis Millionen Operationen, deutlich und ist nur in zeitkritischen Anwendungen wirklich von Belang.
Bitweise Operatoren
Informationen zum Thema „Bitweise Operatoren“ sind leider noch nicht vorhanden, da noch niemand die Zeit hatte, sich mit diesem Teil des Buches zu befassen. Wenn Du dich mit diesem Thema auskennst, hilf bitte mit, dieses Kapitel zu vervollständigen.
Liste der Operatorenprioritäten
Diese Liste stellt die Prioritäten aller Operatoren in Visual Basic einander gegenüber. Je höher ein Operator in der Liste erscheint, umso höher seine Priorität. Die Originalliste findet sich in der englischen MSDN Library.
Wundern Sie sich nicht, dass in dieser Liste einige Operatoren auftauchen, die Ihnen noch nicht bekannt sind, namentlich Is, IsNot, und TypeOf...Is. Diese stehen im Zusammenhang mit objektorientierter Programmierung.
- Arithmetische und Verkettungsoperatoren
- Potenzierung (^)
- Unäre Negation (-) 1
- Multiplikation (*) und Division (/)
- Ganzzahlige Division (\)
- Modulo (Mod)
- Addition (+) und Subtraktion (-)
- Zeichenfolgenverkettung (&)
- Vergleichsoperatoren (=, <>, <, <=, >, >=, Like, Is, IsNot, TypeOf...Is)
- Logische Operatoren
- Negation (Not)
- Konjunktion (And, AndAlso)
- Disjunktion (Or, OrElse, Xor)
- Zuweisungsoperator (=) und zusammengesetzte Operatoren
1 Bei der unären Negationsoperation handelt es sich um das Minus vor negativen Zahlen.
Konstanten
Der lateinische Wortursprung der Variable (lat. varius = verschieden) betont ihre Veränderbarkeit, also die Fähigkeit, ihren Wert zu ändern. Daneben gibt es die Konstanten, deren Wert immer konstant ist. Dieses Kapitel behandelt die zwei verschiedenen Arten von Konstanten in Visual Basic, konstante Variablen und Literale.
Konstante Variablen
Konstante Variablen sind relativ einfach zu verstehen. Es handelt sich um Variablen, deren Wert konstant ist. Ihnen darf nur ein einziges Mal ein Wert zugewiesen werden, nämlich bei der Initialisierung. Während Variablen auch ohne Wert deklariert werden dürfen und nachher durch eine Zuweisungsoperation einen Wert erhalten können, ist beides bei Konstanten nicht möglich. Ihnen muss während der Deklaration auch ein Wert zugewiesen werden. Die Deklaration von Konstanten unterscheidet sich kaum von der Deklaration von Variablen. Das Schlüsselwort Dim muss lediglich durch Const ersetzt werden.
Nach der Deklaration können Konstanten wie Variablen verwendet werden, mit der Ausnahme, dass Zuweisungen nicht möglich sind. Die folgenden Beispiele stellen gültige und fehlerhafte Anweisungen und Befehle gegenüber. Gute Entwicklungsumgebungen weisen Sie auch darauf hin, wenn Sie versuchen, einer Konstante einen Wert zuzuweisen.
Const a As Integer = 1 'OK Dim b As Integer = 3 * a 'OK (Der Wert der Konstanten wird nicht verändert.) a = 2 'Fehler! (Zuweisung zu einer Konstanten nicht erlaubt) Const c As Integer 'Fehler! (Konstante wird ohne Wert deklariert)
Sie können Konstanten auch ohne Datentyp deklarieren. Visual Basic wählt dann den Standarddatentyp Object aus. Davon rate ich Ihnen jedoch dringend ab, da diese Praxis einerseits die Unübersichtlichkeit des Codes erhöht und andererseits wichtige Regulierungsinstrumente aus der Hand des Programmierers und des Compilers nimmt.
Der Nutzen von Konstanten besteht in drei Punkten: Erstens verhindern Konstanten auf eine werkseitig implementierte und sehr komfortable Art und Weise das Ändern von Werten, die nicht geändert werden sollten, z.B. sollte die Kreiszahl Pi nicht geändert werden, es handelt sich ja auch um eine Naturkonstante. Außerdem erhöhen Konstanten die Wartbarkeit und Lesbarkeit von Code. Dazu das folgende Beispiel, das absolute Werte verwendet. Problem: Was soll die 20?
Durch eine Konstante wird die Unklarheit beseitigt.
Const AnzahlSchülerProKlasse As Integer = 20 AnzahlSchüler = AnzahlKlassen * AnzahlSchülerProKlasse
Das dritte Argument für Konstanten: Oft wird ein Wert mehrfach für eine ähnliche Berechnung verwendet. Im vorigen Beispiel würde man wahrscheinlich mehrmals die Anzahl der Schüler berechnen (z.B. für die jeweiligen Klassen und Klassenstufen). Hier hat die Verwendung einer Konstante auch den Vorteil, dass man den Wert im Code nur einmal aktualisieren muss. Angenommen, die Anzahl der Schüler pro Klasse ändert sich auf 22. Hätte man absolute Werte und nicht Konstanten verwendet, müsste man jeden dieser Werte im Code aufsuchen und ändern, wobei man sich womöglich noch vertippt oder ein Vorkommnis übersieht. Bei Verwendung einer Konstanten muss man nur einen Wert verändern, der Rest aktualisiert sich dann automatisch.
Literale
Betrachten wir ein einfaches Programmbeispiel.
Wird ein Programm erstellt, so müssen in der entsprechenden ausführbare Datei alle Informationen enthalten sein, die das Programm zu seiner Ausführung benötigt. Der vorliegende Code enthält z.B. folgende Informationen:
- Lege eine Variable x vom Typ Byte an.
- Initialisiere x mit dem Wert 3.
- Rufe die MessageBox.Show-Funktion mit dem Parameter x auf.
Uns interessiert jetzt der zweite Punkt: Wie wird der Wert 3 im Programm gespeichert? In einem Programm werden Werte, die im Quellcode direkt angegeben sind, sogenannte Literale, wie Variablen gespeichert. Das heißt, dass die 3
wie eine Variable gespeichert wird, auf die man aber im Programm nur ein einziges Mal zugreifen kann, nämlich dort, wo der Literal im eigentlichen Code drinsteht.
Wenn ein Literal im Programm eine Variable ist, kommt unweigerlich die Frage nach dem Datentyp auf. Es gibt einige Standarddatentypen: Es handelt sich hier um Integer oder Long für Ganzzahlen (Long nur dann, wenn der Wert für Integer zu groß ist), Double für Gleitkommazahlen, String für Zeichen und Zeichenketten und Boolean für Wahrheitswerte. Das heißt, dass z.B. der Literal 3
im obigen Beispiel ein Integer-Literal wäre, ein Literal „3.0
“ wäre jedoch ein Double-Literal.
Aus diesen Standarddatentypen erwachsen zwei Probleme, die sich aber mit einem einfachen Mittel lösen lassen.
Problem 1: Wertebereiche
Zu dieser Problematik habe ich aus der MSDN Library folgendes Beispiel (leicht abgewandelt) entnommen.
Versuchen Sie, diesen Code zu kompilieren, wird ein Fehler in der 2. Zeile angezeigt. Das erscheint verwunderlich, da Double Werte bis etwa 10^208 akzeptiert. Doch ist der Literal ein Long-Literal, da es sich um eine Ganzzahl handelt. Der Long-Datentyp akzeptiert nur Werte bis 9223372036854775807, was etwa ein Hundertstel des obigen Wertes ist. Das Problem lässt sich wie folgt umgehen.
Der Wert des Literals bleibt der gleiche, doch handelt es sich jetzt um einen Double-Literal, der den entsprechenden Wert ohne Probleme akzeptiert.
Problem 2: Umwandlungen
In diesem Code wird das erste Problem nicht auftauchen, denn sowohl Long, der Typ der Variable, als auch Integer, der Typ des Literals, akzeptieren den Wert 2. Es bleibt aber noch ein Problem: Wie kann man einen Integer-Wert in einer Long-Variable speichern? Zum Glück (und zum Unglück) wandelt Visual Basic in solchen Fällen Werte eines Datentyps in Werte eines anderen Wertes um. (Natürlich mit Grenzen, z.B. kann eine Zeichenkette nicht immer in eine Zahl umgewandelt werden.) Doch diese Umwandlung kostet Zeit, was vor allem bei zeitkritischen Anwendungen oder sehr häufigen Wertzuweisungen von Bedeutung ist. Man kann die Umwandlungen umgehen, indem man die Typen von Literalen explizit angibt. Im folgenden Beispiel sehen Sie einen Long-Literal.
Durch das große L direkt nach der Zahl wird dem Compiler mitgeteilt, dass es sich bei diesem Literal um einen Long-Literal handelt. So wird die zeitraubende Umwandlung verhindert, denn ein Long-Wert muss zur Speicherung in einer Long-Variable nicht umgewandelt werden.
Wie man sieht, muss man nur einen bestimmten Buchstaben an den Literal anhängen, um den Typ festzulegen. Die folgende Tabelle enthält alle möglichen Suffixe (lat. für Endungen). Beachten Sie, dass man statt dem angegebenen Großbuchstaben auch die entsprechenden Kleinbuchstaben verwendet werden können. Neben Buchstaben sind manchmal auch Symbole möglich, die ein Relikt aus BASIC-Zeiten darstellen. „5!
“ ist also ein Single-Literal.
Datentyp | Buchstabensuffix | Symbolsuffix |
---|---|---|
Short | S | |
Integer | I | % |
Long | L | & |
UShort | US | |
UInteger | UI | |
ULong | UL | |
Single | F | ! |
Double | R | # |
Char | C |
Bei Char muss das C nach den Anführungszeichen gesetzt werden. Im folgenden sind einige Literale und die entsprechenden Typen beispielhaft aufgelistet:
Literal | Datentyp |
---|---|
5F
|
Single |
25ul
|
ULong |
"f"C
|
Char |
Kontrollstrukturen
Bis jetzt liefen unsere Visual-Basic-.NET-Programme immer sehr linear, sprich eine Anweisung nach der anderen wurde streng von oben nach unten abgearbeitet. In diesem Kapitel ändern wir das: Wir werden manche Anweisungen überspringen und andere sogar mehrmals ausführen können. Unser Werkzeug sind sogenannte Kontrollstrukturen, spezielle Anweisungen, mit denen wir den Programmfluss kontrollieren können. Dazu zählen Entscheidungen (auch „bedingte Anweisungen“ genannt) und Schleifen.
Vorher möchte ich jedoch noch einmal auf die ReadLine-Methode eingehen. Wie ich am Anfang erwähnte, kann der Benutzer bei einem Aufruf der ReadLine-Methode einen Wert eingeben. Dabei verwenden wir den InputBox-Befehl wie einen Zahlenwert oder eine Operation und weisen die Eingabe des Benutzers einer Variablen zu.
In dem folgenden Beispiel stellt sich der Computer schwerhörig und fragt nach einer Eingabe, die (in diesem trivialen Fall) gleich wieder ausgegeben wird. (Hier und im Folgenden werden Benutzereingaben in den Ausgabeprotokollen, die natürlich nur beispielhaft sein können, fett dargestellt.)
Console.Write("Was haben Sie gesagt? ") Dim Text As String = Console.ReadLine() Console.WriteLine("Ach so. Sie haben " & Text & " gesagt.")
Was haben Sie gesagt? Hallo Ach so. Sie haben Hallo gesagt.
Mit der Console.Write-Methode wird die Frage gestellt. Da nur Write und nicht WriteLine benutzt wird, bleibt der Cursor in derselben Zeile, der User kann seine Eingabe während des Aufrufes der ReadLine-Methode in ebendieser Zeile machen. (Probieren Sie diesen Effekt aus, indem Sie Write durch WriteLine ersetzen. Es ist allerdings auch ein Stück weit Geschmacksache, ob man vor Eingaben einen Zeilenumbruch einfügt oder nicht.)
Im Beispiel wird eine String-Variable Text mit der Eingabe des Benutzers initialisiert. Bei so einer Zuweisung ist der Typ der Zielvariablen eigentlich egal. Allerdings sollten Sie bei Zahlenvariablen aufpassen, ob der Benutzer auch wirklich passende Zahlen eingibt. Sonst bricht das Programm mit einem Laufzeitfehler ab. Wie man das verhindern kann, dazu später mehr.
Am obigen Beispiel wäre auszusetzen, dass der Computer nicht korrekt zitiert. Er sagt etwa „Ach so. Sie haben Test gesagt.“, obwohl „Ach so. Sie haben "Test" gesagt.“ eigentlich korrekt wäre. Problem: Wie soll man die Gänsefüßchen in Visual Basic .NET notieren? Wenn man in eine Zeichenkette einfach ein Anführungszeichen setzt, denkt Visual Basic, dass die Zeichenkette dort zu Ende ist.
Diese Verständigungsschwierigkeiten lassen sich zum Glück durch eine sogenannte Escape-Zeichenfolge lösen, mit der wir der üblichen Notation „entkommen“ (engl. escape). Dazu schreiben wir statt einem Gänsefüßchen zwei direkt hintereinander. Innerhalb einer Zeichenkette in Visual-Basic-Code werden zwei Gänsefüßchen direkt hintereinander als ein Gänsefüßchen in der Zeichenkette aufgefasst, nicht als Ende der Zeichenkette.
Console.Write("Was haben Sie gesagt? ") Dim Text As String = Console.ReadLine() Console.WriteLine("Ach so. Sie haben """ & Text & """ gesagt.")
Was haben Sie gesagt? Hallo Ach so. Sie haben "Hallo" gesagt. (Falls Sie in die InputBox "Hallo" eingegeben haben.)
Hier haben wir zweimal drei Gänsefüßchen hintereinander. Beim ersten Mal werden die ersten zwei Gänsefüßchen als Escape-Zeichenfolge für ein Gänsefüßchen aufgefasst, das dritte beendet die Zeichenkette. Analog beim zweiten Vorkommen: Das erste Gänsefüßchen startet die Zeichenkette, darauf folgt die Escape-Zeichenfolge.
Zweiseitige Entscheidungen
Oft sind Aktionen eines Programms an bestimmte Bedingungen geknüpft. Zum Beispiel ruft ein E-Mail-Programm nur dann E-Mails ab, wenn ein gültiges Passwort eingegeben wurde. In einem Zeichenprogramm wird nur dann ein Punkt auf das Bild gesetzt, wenn Sie das Stiftwerkzeug ausgewählt haben und dann auf das Bild klicken. In Visual Basic werden solche Bedingungen mit der If-Anweisung realisiert. Wie bei Anweisungen üblich, unterscheidet sich die Syntax des If-Befehls gänzlich von der normaler Befehle.
Console.Write("Zahl: ") Dim a As Integer = Console.ReadLine() If a = 3 Then a += 2 End If Console.WriteLine(a)
Zahl: 3 5
Zahl: 4 4
If und Then sind Schlüsselwörter, die übersetzt „Wenn“ und „Dann“ heißen. Bei dem = in der zweiten Zeile handelt es sich nicht um einen Zuweisungs-, sondern um einen Gleichheitsoperator. Diese Gleichheitsoperation liefert einen Boolean-Wert zurück. Zwischen If und Then muss immer ein Boolean-Wert stehen. (Daran erkennt Visual Basic, dass es sich um eine Gleichheitsoperation handelt.) Nur wenn dieser Boolean-Wert True ist, werden die folgenden Befehle ausgeführt. In diesem Fall handelt es sich dabei nur um den Befehl a = a + 2. Der Bereich der Befehle und Anweisungen, die nur ausgeführt werden, wenn der Boolean-Wert True ist, endet an der Anweisung End If. Zwischen If/Then und End If können auch mehrere Anweisungen stehen, die nur ausgeführt werden, wenn der Boolean-Wert in der If-Anweisung True ist.
Hinweis: So wie hier werde ich im folgenden mehrere Ausgabeprotokolle angeben, um das Verhalten in den verschiedenen Fällen zu verdeutlichen.
Vielleicht ist Ihnen aufgefallen, dass die zweite Zeile ein bisschen eingerückt wurde. Dies dient der Übersichtlichkeit, da dadurch die besondere Stellung dieses Befehls, der ja nur unter einer bestimmten Bedingung ausgeführt wird, signalisiert wird.
Hier sieht man auch den Sinn der vielen Vergleichsoperatoren und Bool'schen Operatoren. Damit lassen sich nämlich teils sehr komplexe Operationskonstrukte basteln, ohne den Wert irgendwo zwischenzuspeichern. Ein einfaches Beispiel: Sie haben auf den Integer-Variablen Hoehe und Breite die Höhe und Breite eines Rechteckes gespeichert und wollen jetzt eine Fehlermeldung ausgeben, falls die Höhe oder die Breite null ist, wodurch es ja kein Rechteck mehr wäre, sondern nur noch eine Linie oder vielleicht sogar nur ein Punkt. Ein erster Entwurf würde vielleicht exzessiven Gebrauch von Boolean-Variablen machen.
Console.WriteLine("Bitte geben Sie die Größe Ihres Rechteckes in Metern an!") Console.Write("Höhe: ") Dim Hoehe As Double = Console.ReadLine() Console.Write("Breite: ") Dim Breite As Double = Console.ReadLine() Dim HoeheGleichNull As Boolean = (Hoehe = 0) Dim BreiteGleichNull As Boolean = (Breite = 0) Dim KeinRechteck As Boolean = HoeheGleichNull Or BreiteGleichNull If KeinRechteck Then Console.WriteLine("Die angegebenen Werte für Höhe und Breite sind ungültig!") End If
Bitte geben Sie die Größe Ihres Rechteckes in Metern an! Höhe: 5 Breite: 6
Bitte geben Sie die Größe Ihres Rechteckes in Metern an! Höhe: 9 Breite: 0 Die angegebenen Werte für Höhe und Breite sind ungültig!
Dieses Beispiel ist zwar korrekt, zeigt aber einen schlechten Programmierstil und wirkt unübersichtlich. Die Zeilen 6 bis 9 können auf eine einzige Zeile komprimiert werden. Der folgende Code, in dem auf überschüssige Zwischenergebnisse verzichtet wurde, erzeugt das gleiche Programm wie der obere, jedoch mit einer höheren Lesbarkeit des Codes und mit einer (minimal) höheren Ausführungsgeschwindigkeit.
Console.WriteLine("Bitte geben Sie die Größe Ihres Rechteckes in Metern an!") Console.Write("Höhe: ") Dim Hoehe As Double = Console.ReadLine() Console.Write("Breite: ") Dim Breite As Double = Console.ReadLine() If (Hoehe = 0) Or (Breite = 0) Then Console.WriteLine("Die angegebenen Werte für Höhe und Breite sind ungültig!") End If
Bitte geben Sie die Größe Ihres Rechteckes in Metern an! Höhe: 5 Breite: 6
Bitte geben Sie die Größe Ihres Rechteckes in Metern an! Höhe: 9 Breite: 0 Die angegebenen Werte für Höhe und Breite sind ungültig!
Faustregel: Verwenden Sie niemals eine Variable, wenn ein Wert oder ein Ergebnis einer Operation nur einmal gebraucht wird, außer wenn durch die direkte Verwendung des Wertes die Übersichtlichkeit zu stark leiden würde.
Man kann das obige Beispiel noch weiter verkürzen, wenn man sich eine besondere Form der If-Anweisung zunutze macht. Gibt es zwischen Then und End If nur einen weiteren Befehl, so kann man das End If auch weglassen und den Befehl direkt hinter dem Then notieren. Unser Beispiel verkürzt sich damit von anfänglich elf Zeilen auf sechs Zeilen und zeigt sehr eindrucksvoll, wie ein guter Programmierstil die Übersichtlichkeit steigert.
Console.WriteLine("Bitte geben Sie die Größe Ihres Rechteckes in Metern an!") Console.Write("Höhe: ") Dim Hoehe As Double = Console.ReadLine() Console.Write("Breite: ") Dim Breite As Double = Console.ReadLine() If (Hoehe = 0) Or (Breite = 0) Then Console.WriteLine("Die angegebenen Werte für Höhe und Breite sind ungültig!")
Bitte geben Sie die Größe Ihres Rechteckes in Metern an! Höhe: 5 Breite: 6
Bitte geben Sie die Größe Ihres Rechteckes in Metern an! Höhe: 9 Breite: 0 Die angegebenen Werte für Höhe und Breite sind ungültig!
Zum guten Programmierstil gehört ja, wie schon früher erwähnt, auch der sparsame Einsatz von Klammern. In diesem Fall jedoch sind die Klammern um die Gleichheitsoperationen, wenn auch eigentlich unnötig, zur Übersichtlichkeit sehr nützlich.
Von der Einseitigkeit zur Zweiseitigkeit
Manchmal soll ein Programm auch dann Funktionen ausführen, wenn die Bedingung in einer If-Anweisung nicht eintritt. Zum Beispiel verbindet sich ein E-Mail-Programm mit dem Internet, wenn das eingegebene Passwort richtig ist, sonst gibt es eine Fehlermeldung aus. Nehmen wir einmal an, wir wollen auch nur eine Meldung ausgeben, ob die Eingabe richtig ist oder nicht, um das Beispiel zu vereinfachen. Versuchen Sie einmal, den Code zu notieren, der diese Meldung ausgibt. Das Passwort sollte dabei einen konstanten Wert haben, der Benutzer soll es dann raten.
Const Passwort As String = "VB.NET" Console.Write("Passwort: ") Dim Eingabe As String = Console.ReadLine() If Passwort = Eingabe Then Console.WriteLine("Das eingegebene Passwort ist korrekt.") End If If Passwort <> Eingabe Then Console.WriteLine("Das eingegebene Passwort ist falsch.") End If
Passwort: VB.NET Das eingegebene Passwort ist richtig.
Passwort: Hallo Welt! Das eingegebene Passwort ist falsch.
Dieser Code ist richtig, daran besteht nach einigen Testläufen mit verschiedenen Werten für Passwort und Eingabe kein Zweifel. Doch ist das nicht der effektivste Weg. Wie man sieht, werden nämlich zwei Vergleichsoperationen ausgeführt: „Sind Passwort und Eingabe gleich?“ und „Sind Passwort und Eingabe ungleich?“. Vielleicht haben Sie auch statt Passwort <> Eingabe als zweite Bedingung Not (Passwort = Eingabe) eingegeben und damit geprüft: „Sind Passwort und Eingabe nicht gleich?“ (Man beachte den kleinen, aber feinen Unterschied.) Haben Sie diese Bedingung notiert, sind Sie schon ganz nah an der Lösung, denn Sie haben ausgenutzt, dass die zweite Bedingung genau das Gegenteil der ersten Bedingung ist.
Nun heißt dieses Kapitel „Zweiseitige Entscheidungen“, bis jetzt jedoch hatten wir nur einseitige Entscheidungen, d.h. Entscheidungen, bei denen etwas ausgeführt wird, wenn die Bedingung True ist. Zweiseitige Entscheidungen zeichnen sich dadurch aus, dass etwas ausgeführt wird, wenn die Bedingung True ist, und dass etwas anderes ausgeführt wird, wenn die Bedingung nicht True ist. Die Bedingung wird dabei nur einmal geprüft anstatt, wie im Beispiel oben, zweimal.
Die einseitige Entscheidung oben markiert zwischen Then und End If eine Gruppe von Anweisungen und Befehlen, die ausgeführt wird, wenn die Bedingung True ist. Die zweiseitige Entscheidung markiert zwei Gruppen von Befehlen; die erste wird ausgeführt, wenn die Bedingung True ist, sonst wird die andere Gruppe von Befehlen ausgeführt. Beide Gruppen stehen wiederum zwischen Then und End If, werden aber durch das Schlüsselwort Else getrennt, dass die zweite Befehlsgruppe einleitet und damit die erste Befehlsgruppe abschließt.
Const Passwort As String = "VB.NET" Console.Write("Passwort:") Dim Eingabe As String = Console.ReadLine() If Passwort = Eingabe Then Console.WriteLine("Das eingegebene Passwort ist korrekt.") Else Console.WriteLine("Das eingegebene Passwort ist falsch.") End If
Passwort: VB.NET Das eingegebene Passwort ist richtig.
Passwort: Hallo Welt! Das eingegebene Passwort ist falsch.
Das Schlüsselwort Else ist dabei im Gegensatz zu den Anweisungsgruppen nicht eingerückt, um die Zugehörigkeit zur If-Anweisung zu verdeutlichen. Die drei Zeilen If ... Then, Else und End If bilden nämlich alle zusammen die If-Anweisung. Man sieht, dass Anweisungen auch mehrere Teilstücke haben können, zwischen denen auch anderer Code stehen kann oder sogar muss.
Kaskadierte Kontrollstrukturen
Man kann If-Anweisungen auch verschachteln (kaskadieren), d.h. innerhalb der Befehlsgruppen einer If-Anweisung dürfen wiederum If-Anweisungen stehen. Das gilt allgemein für alle Kontrollstrukturen; alle dürfen innerhalb der Befehlsgruppen beliebiger anderer Kontrollstrukturen vorkommen. Dazu möchte ich noch ein praktisches Beispiel geben. Angenommen, der Benutzer soll zwei Zahlen eingeben. Es soll geprüft werden, ob a geteilt durch b fünf ergibt. (Das wäre zum Beispiel der Fall, wenn a zehn ist und b zwei.) Ein erster Entwurf sieht so aus.
Console.Write("Ganzzahl a = ") Dim a As Integer = Console.ReadLine() Console.Write("Ganzzahl b = ") Dim b As Integer = Console.ReadLine() If a / b = 5 Then Console.WriteLine("Die Bedingung ist erfüllt.") Else Console.WriteLine("Die Bedingung ist nicht erfüllt.") End If
Ganzzahl a = 60 Ganzzahl b = 12 Die Bedingung ist erfüllt.
Ganzzahl a = 59 Ganzzahl b = -1 Die Bedingung ist nicht erfüllt.
Das sieht gut aus und funktioniert anscheinend auch immer. Doch muss man als Programmierer immer vom sogenannten DAU ausgehen, dem Dümmsten Anzunehmenden User. Ein DAU soll auf niemanden beleidigend oder diskriminierend wirken, er soll nur dazu dienen, Programme gegen alle eventuellen Eingaben des Benutzers abzusichern. Was bringt uns der DAU nun? Sie als der Programmierer würden niemals versuchen, b mit Null zu belegen, da Sie (hoffentlich) wissen, dass sich Divisionen durch Null nicht besonders positiv auf die Funktionsweise des PC auswirken. Doch der DAU weiß das nicht. Er würde vielleicht für b Null eingeben. (Manche User machen sowas sogar mit Absicht.) Wir müssen also dafür sorgen, dass nur durch b dividiert wird, wenn feststeht, dass b nicht Null ist. Probieren Sie das aus, indem Sie in einem Testlauf für b bewusst Null eingeben.
Console.Write("Ganzzahl a = ") Dim a As Integer = Console.ReadLine() Console.Write("Ganzzahl b = ") Dim b As Integer = Console.ReadLine() If b <> 0 Then If a / b = 5 Then Console.WriteLine("Die Bedingung ist erfüllt.") Else Console.WriteLine("Die Bedingung ist nicht erfüllt.") End If Else Console.WriteLine("Die Bedingung ist nicht erfüllt.") End If
Ganzzahl a = 60 Ganzzahl b = 12 Die Bedingung ist erfüllt.
Ganzzahl a = 59 Ganzzahl b = -1 Die Bedingung ist nicht erfüllt.
Jetzt haben wir zwei If-Anweisungen verschachtelt. Wenn b gleich Null ist, kann a durch b niemals 5 sein, deshalb die negative Ausgabe im Else-Teil ganz unten. Ist b nicht gleich Null, so wird weiter geprüft, ob a durch b 5 ist und eine entsprechende Meldung ausgegeben. Beachten Sie, dass diese Bedingung überhaupt nicht beachtet wird, wenn b gleich Null ist.
Am obigen, kaskadierten Code hätte man vielleicht noch auszusetzen, dass derselbe Befehl (die negative Rückmeldung) zweimal vorkommt und entsprechend doppelt soviel Speicherplatz belegt. Das ist zwar nicht so schlimm, dennoch gibt es einige Programmierer, die versuchen, die Ausführungsgeschwindigkeit und den Platzverbrauch ihrer Programme mit allen Mitteln zu optimieren. Generell ist Optimierung auf solche Faktoren nichts Schlechtes. Optimierung brauchen Sie manchmal, etwa wenn Sie ein Programm schreiben, das ein paar Tausende bis Millionen gleichartiger Daten sortieren muss. (Sowas kommt vor, wenn auch selten.) In diesem kleinen Rahmen und auch für die meisten normalen Softwareprojekte ist solche Optimierung jedoch überflüssig.
Angenommen, Sie wollen eine If-Anweisung einsparen und alles auf eine If-Anweisung reduzieren. Das ist möglich, so wie hier zu sehen aber nicht.
Console.Write("Ganzzahl a = ") Dim a As Integer = Console.ReadLine() Console.Write("Ganzzahl b = ") Dim b As Integer = Console.ReadLine() If (b <> 0) And (a / b = 5) Then Console.WriteLine("Die Bedingung ist erfüllt.") Else Console.WriteLine("Die Bedingung ist nicht erfüllt.") End If
Ganzzahl a = 60 Ganzzahl b = 12 Die Bedingung ist erfüllt.
Ganzzahl a = 59 Ganzzahl b = -1 Die Bedingung ist nicht erfüllt.
Auf den ersten Blick sieht diese Sache gut aus, doch im Detail offenbart sich der Fehler. Die Prioritätenliste sagt uns, dass von den beteiligten Operatoren die Division zuerst ausgeführt wird, noch bevor die Ungleichheit vorne geprüft wurde. Wenn b gleich Null ist, würde also schon die erste Operation einen Fehler verursachen. Scheinbar ist es also nicht möglich, alles in eine Bedingung zu packen, da immer die Division zuerst ausgeführt wird. Das ist jedoch so nicht ganz richtig. Manch aufmerksamem Leser, der sich an das Kapitel Bool'sche Operatoren zurückerinnert, wird jetzt der AndAlso-Operator einfallen, der das Problem löst.
Console.Write("Ganzzahl a = ") Dim a As Integer = Console.ReadLine() Console.Write("Ganzzahl b = ") Dim b As Integer = Console.ReadLine() If (b <> 0) AndAlso (a / b = 5) Then Console.WriteLine("Die Bedingung ist erfüllt.") Else Console.WriteLine("Die Bedingung ist nicht erfüllt.") End If
Ganzzahl a = 60 Ganzzahl b = 12 Die Bedingung ist erfüllt.
Ganzzahl a = 59 Ganzzahl b = -1 Die Bedingung ist nicht erfüllt.
Der AndAlso-Operator ändert nämlich die Priorität. Die Operatoren in seinem linken Operanden bekommen die höchste Priorität, er selber eine mittlere Priorität, und die Operatoren im rechten Operanden bekommen die niedrigste Priorität. Nun wird zuerst der linke Operand b <> 0 aufgelöst. Nur wenn dieser Ausdruck True ist, wird auch der rechte Operand a / b = 5 aufgelöst, wonach der AndAlso-Operator dann wie ein normaler And-Operator aktiv wird. Ist der linke Operand False, springt die Ausführung sofort in den Else-Teil. Das Verhalten entspricht damit genau dem des Beispiels mit kaskadierten If-Anweisungen.
Solche eleganten Lösungen bleiben jedoch leider oft die Ausnahme und sind meist auch nicht nötig, da sie nur die Lesbarkeit erschweren und manche Missverständnisse auslösen. Viele Programmierer kennen nämlich den AndAlso-Operator nicht und denken, Sie hätten die Division durch Null übersehen. In solchen Fällen hilft jedoch oft auch ein Kommentar, evtl. auch mit Verweis auf den Eintrag in der MSDN Library zu diesem Thema.
Mehrseitige Entscheidungen
Oft sollen nicht nur einfache Ja/Nein- bzw. True/False-Entscheidungen getroffen werden, sondern mehrere Wege unterschieden werden. In Visual Basic .NET gibt es für solche mehrseitige Entscheidungen gleich zwei Lösungswege: einen bekannten, flexiblen und einen neuen und komfortablen.
Als Beispiel soll uns dieses Mal ein Würfelspiel dienen. Es werden zwei Würfel geworfen und die Augensumme gebildet. Es gilt folgender Spielplan:
Augensumme | Resultat |
---|---|
12 | 1. Preis |
10, 11 | 2. Preis |
7 - 9 | 3. Preis |
6 | Trostpreis |
2 - 5 | Niete |
Das Würfeln geschieht im Computer durch Zufallszahlen. Visual Basic .NET verfügt dazu über einen Zufallszahlengenerator auf Basis einer mathematischen Iterationsformel. Um den Generator zu initialisieren, genügt ein Aufruf der Funktion Randomize ohne Parameter. Dann gibt der Ausdruck Int(6 * Rnd() + 1)
eine Zufallszahl von 1 bis 6 zurück, der auf einer Variable gespeichert werden kann. Das Würfeln wird also wie folgt durchgeführt (wobei Meldungen über die Augenzahlen ausgegeben werden).
Randomize() Dim ErsterWuerfel As Integer = Int(6 * Rnd() + 1) Console.WriteLine("Der erste Würfel zeigt " & ErsterWuerfel & " Augen.") Dim ZweiterWuerfel As Integer = Int(6 * Rnd() + 1) Console.WriteLine("Der zweite Würfel zeigt " & ZweiterWuerfel & " Augen.") Dim Augensumme As Integer = ErsterWuerfel + ZweiterWuerfel Console.WriteLine("Die Augensumme beträgt " & Augensumme & ".")
(Beispiel, Zahlenwerte sind zufällig) Der erste Würfel zeigt 2 Augen. Der zweite Würfel zeigt 5 Augen. Die Augensumme beträgt 7.
Beachten Sie, dass der Ausdruck Int(6 * Rnd() + 1)
anders als die bisherigen Ausdrücke mit Werten und Operatoren nicht immer das gleiche zurückgibt. Das liegt an der Zufallsfunktion Rnd. Bei den Console.WriteLine-Aufrufen ist zu beachten, dass die Integer-Variablen für die Verkettungsoperation automatisch in Zeichenketten umgewandelt werden.
ElseIf
Was uns nun noch fehlt, ist eine Meldung über das Resultat, sprich was man gewonnen hat (wenn auch nur Ruhm und Ehre verlost werden). Versuchen Sie einmal selbst, mit dem bisherigen Wissen einen Code zu schreiben, der das Resultat feststellt und ausgibt. Wahrscheinlich werden Sie (mit leichten Abwandlungen) eine der folgenden zwei Möglichkeiten notieren. (Ich verzichte auf langatmige Ausgaben und gebe nur das direkte Resultat aus.)
If Augensumme = 12 Then Console.WriteLine("1. Preis") If Augensumme = 10 Or Augensumme = 11 Then Console.WriteLine("2. Preis") If Augensumme >= 7 And Augensumme <= 9 Then Console.WriteLine("3. Preis") If Augensumme = 6 Then Console.WriteLine("Trostpreis") If Augensumme < 6 Then Console.WriteLine("Niete")
3. Preis (für die Augensumme 7)
If Augensumme = 12 Then Console.WriteLine("1. Preis") Else If Augensumme >= 10 Then Console.WriteLine("2. Preis") Else If Augensumme >= 7 Then Console.WriteLine("3. Preis") Else If Augensumme = 6 Then Console.WriteLine("Trostpreis") Else Console.WriteLine("Niete") End If End If End If End If
3. Preis (für die Augensumme 7)
Im ersten Beispiel habe ich beim 3. Preis die Anzahl an Operationen reduziert, indem ich nicht Augensumme = 7 Or Augensumme = 8 Or Augensumme = 9
prüfe, sondern mir zunutze mache, dass die drei Werte einen zusammenhängenden Bereich bilden, sodass ich den aktuellen Wert nur mit der Obergrenze und mit der Untergrenze vergleichen muss. Ein Nachteil dieser Methode ist, dass unnötige Vergleiche durchgeführt werden. Angenommen, die Augensumme ist 12. Beim ersten Vergleich ist die Bedingung schon True, eine Meldung wird ausgegeben. Die anderen Vergleiche müssten jetzt eigentlich nicht mehr durchgeführt werden.
Das zweite Beispiel trägt dieser Problematik Rechnung. Es werden nur solange Vergleiche durchgeführt, bis ein Erfolg eintritt und eine Meldung ausgegeben wird. Dieses Beispiel ist vom Konzept her auch schon um einiges näher an der beabsichtigten Lösung, sieht nur sehr unübersichtlich aus. So sieht es richtig aus:
If Augensumme = 12 Then Console.WriteLine("1. Preis") ElseIf Augensumme >= 10 Then Console.WriteLine("2. Preis") ElseIf Augensumme >= 7 Then Console.WriteLine("3. Preis") ElseIf Augensumme = 6 Then Console.WriteLine("Trostpreis") Else Console.WriteLine("Niete") End If
3. Preis (für die Augensumme 7)
Das Schlüsselwort ElseIf leitet eine weitere Befehlsgruppe ein. Wenn die erste Bedingung nicht True ist, wird, sofern vorhanden, nicht in die Else-Gruppe, sondern zur ersten ElseIf-Bedingung gewechselt und diese ausgewertet. Ist diese Bedingung True, so wird die dazugehörige Befehlsgruppe ausgeführt. Ist die Bedingung False, wird sofern vorhanden, zur wiederum nächsten ElseIf-Bedingung gewechselt und diese ausgewertet. So geht das Verfahren immer weiter. Sobald eine Bedingung True ist, wird in die entsprechende Befehlsgruppe gewechselt und diese ausgeführt, dann wird ans Ende der If-Anweisung, hinter End If, gesprungen. Die verbleibenden, noch nicht geprüften Bedingungen, bleiben dann unberücksichtigt. Wurden alle Bedingungen negativ geprüft, wird in die Else-Gruppe gesprungen. Beachten Sie, dass die Else-Gruppe auch weggelassen werden kann, sie ist in einer If-Anweisung genauso optional wie die ElseIf-Gruppen.
Select
Für mehrseitige Anweisungen gibt es neben der erweiterten Form der If-Anweisung auch die sogenannte Select-Anweisung. Verwendet man anstatt der If-Anweisung die Select-Anweisung, ergibt sich für die Auswahl des Preises folgender Code. Beachten Sie, dass das Case-Schlüsselwort in der ersten Zeile optional ist, also auch weggelassen werden kann.
Select Case Augensumme Case 12 Console.WriteLine("1. Preis") Case 10, 11 Console.WriteLine("2. Preis") Case 7 To 9 Console.WriteLine("3. Preis") Case 6 Console.WriteLine("Trostpreis") Case Else Console.WriteLine("Niete") End Select
3. Preis (für die Augensumme 7)
Der Select-Anweisung wird in der ersten Zeile eine Variable zugeordnet, hier die Variable Augensumme. Im folgenden wird nur der Wert dieser Variablen mit bestimmten Werten verglichen. Die Bedingungen werden mit dem Schlüsselwort Case eingeleitet. Dann stehen die Werte, mit denen der Wert der Variablen verglichen werden soll. Geht einer dieser Vergleiche positiv aus, so werden die Befehle in der Befehlsgruppe, die auf die Bedingung folgt, ausgeführt, danach wird zur End Select-Anweisung gesprungen, welche die Select-Anweisung beschließt.
Die Syntax, die formale Notationsweise der mit Case eingeleiteten Bedingungen unterscheidet sich grundlegend von denen normaler Ausdrücke mit Bool'schen und Vergleichsoperatoren. Das Beispiel zeigt einige einfache Varianten.
In der ersten Bedingung Case 12
wird geprüft, ob der Wert der Variablen Augensumme gleich 12 ist. Das ergibt sich, so denke ich, auch aus dem Kontext. Man sieht, dass ein einfacher Vergleich durchgeführt wird, wenn nach Case nur ein Wert stehen. Die Select-Bedingung Case 12
entspricht der If-Bedingung Augensumme = 12
.
Die zweite Bedingung ähnelt der ersten, listet aber, durch Komma getrennt, mehrere Werte auf. Wenn der Wert der Variable Augensumme gleich einem dieser Werte ist, wird die zugeordnete Befehlsgruppe ausgeführt. Die Select-Bedingung Case 10, 11
entspricht der If-Bedingung (Augensumme = 10) Or (Augensumme = 11)
. Bei der Auflistung der Werte in der Select-Bedingung spielt die Reihenfolge keine Rolle.
In der dritten Bedingung Case 7 To 9
wird keine Liste von Werten definiert, sondern ein Bereich von Werten. Liegt der Wert der Variablen Augensumme innerhalb dieses Bereiches (wobei die Grenzen zum Bereich gehören), wird die der Select-Bedingung zugeordnete Befehlsgruppe ausgeführt. Diese Bedingung Case 7 To 9
entspricht also der If-Bedingung (Augensumme >= 7) And (Augensumme <= 9)
. Beachten Sie bei der Angabe von Bereichen, dass immer der kleinere Wert vor dem To stehen muss.
Sie können in einer Select-Bedingung auch mehrere Bereiche notieren und sie mit konkreten Werten verbinden. Diese einzelnen Elemente müssen lediglich durch Komma getrennt werden. Unten sehen Sie eine Beispielbedingung sowie die entsprechende If-Bedingung. Diese Codefragmente haben nichts mit dem Beispiel zu tun und dienen nur der Illustration. An diesem Beispiel sieht man etwa, dass Select-Bedingungen vor allem bei komplexen Vergleichen übersichtlicher sind.
Die vierte Select-Bedingung Case 6
entspricht inhaltlich offensichtlicherweise der ersten Bedingung.
Zuletzt finden wir mit Case Else
die Entsprechung zum Else-Schlüsselwort der If-Anweisung. Nur wenn vorher kein Vergleich positiv ausgegangen ist, wird die Case-Else-Befehlsgruppe ausgeführt.
Sie können das Beispiel auch kürzer schreiben, wenn Sie nach der jeweiligen Bedingung einen Doppelpunkt schreiben und dann in der gleichen Zeile den ersten Befehl der entsprechenden Befehlsgruppe notieren. Obgleich theoretisch immer einsetzbar, eignet sich diese Form in der Praxis aber aus Übersichtlichkeitsgründen nur für Befehlsgruppen, die nur aus einzelnen Befehlen bestehen, so wie in unserem Beispiel:
Select Augensumme Case 12 : Console.WriteLine("1. Preis") Case 10, 11 : Console.WriteLine("2. Preis") Case 7 To 9 : Console.WriteLine("3. Preis") Case 6 : Console.WriteLine("Trostpreis") Case Else : Console.WriteLine("Niete") End Select
3. Preis (für die Augensumme 7)
Verändern wir das Beispiel dahingehend, dass nur ausgegeben werden soll, ob ein Preis oder eine Niete aufgetreten ist.
Select Augensumme Case Is >= 6 : Console.WriteLine("Preis") Case Else : Console.WriteLine("Niete") End Select
Preis (für die Augensumme 7)
Die erste Bedingung hier enthält neben Werten und Bereichen die dritte Möglichkeit, den Wert zu vergleichen. Nach Case Is steht ein Vergleichsoperator und dessen rechter Operand, für den linken Operand wird der Wert von Augensumme eingesetzt. Die Select-Bedingung Case Is >= 6
entspricht also der If-Bedingung Augensumme >= 6
. Theoretisch ließen sich sämtliche Select-Bedingungen aus Is-Konstrukten zusammenbauen, wie die folgende Abwandlung der Preisauswahl zeigt:
Select Augensumme Case Is = 12 : Console.WriteLine("1. Preis") Case Is = 10, Is = 11 : Console.WriteLine("2. Preis") Case Is >= 7, Is <= 9 : Console.WriteLine("3. Preis") Case Is = 6 : Console.WriteLine("Trostpreis") Case Else : Console.WriteLine("Niete") End Select
3. Preis (für die Augensumme 7)
Ein solcher exzessiver Einsatz von Is-Konstrukten erzeugt allerdings einen schwer lesbaren Code und gehört dadurch nicht zum guten Programmierstil.
Die Select-Anweisung kann mit allen einfachen Datentypen, also mit allen Kommazahlen und mit Zeichenketten umgehen. Für Bereiche von Zeichenketten gelten dabei ähnliche Regeln wie für den Umgang von Vergleichsoperatoren mit Zeichenketten: Zuerst wird die Länge verglichen, dann werden gleichlange Zeichenketten alphabetisch sortiert und entsprechende Vergleiche durchgeführt.
Einfache Schleifen
Vielfach soll ein Programm eine bestimmte Aufgabe nicht nur einmal, sondern mehrmals hintereinander ausführen, bis eine bestimmte Bedingung eintritt. Zum Beispiel wird ein Browser immer wieder versuchen, sich mit einem Webserver zu verbinden, bis dieser die Verbindung annimmt und die gewünschten Daten sendet. Ein Mediaplayer wird, wenn entsprechend eingestellt, eine Wiedergabe solange wiederholen, wie der Benutzer nicht auf den Stoppknopf drückt.
Auch Visual Basic .NET verfügt über eine Möglichkeit, bestimmte Befehle oder Befehlsgruppen zu wiederholen, die Schleifen. Diese unterteilen sich in kopfgesteuerte und fußgesteuerte Schleifen. Beide Arten werden jedoch von einer Anweisung erzeugt, der Do-Loop-Anweisung.
Als Beispiel soll uns dieses Mal ein wirklich praktisches Problem dienen. Nehmen Sie an, Sie haben 1000 € gespart und möchten diese jetzt für 5 Jahre anlegen. Der Berater bei der Bank hat Ihnen folgende zwei Angebote gemacht: ein Sparbuch mit einer Laufzeit von 5 Jahren und einem Jahreszins von 3 % oder eines mit der gleichen Laufzeit, aber einem Monatszins von 0,25 %. Auf den ersten Blick scheinen die Angebote aufs Gleiche hinauszulaufen, denn 0.25 * 12 = 3, doch soll ein Programm klären, was besser ist. Die Tabelle unten stellt die Konditionen noch einmal genauer dar. (Eine Anmerkung: Ich weiß, dass Sparbücher heute veraltet sind und es viel bessere Anlagen gibt, außerdem ist der Zinssatz utopisch, aber das ist schließlich nur ein Beispiel.)
Angebot | Art | Laufzeit | Zins | Verzinsung |
---|---|---|---|---|
1 | Sparbuch | 5 Jahre | 3 % | jährlich |
2 | 0,25 % | monatlich |
Fußgesteuerte Schleifen
Eine Do-Loop-Anweisung zur Erstellung einer Schleife besteht aus einer Befehlsgruppe, die zwischen den Schlüsselwörtern Do und Loop notiert wird. Das folgende Beispiel gibt fortwährende Meldungen aus. (Ich empfehle nicht, dieses Beispiel praktisch auszuprobieren.)
Sollten Sie doch so wagemutig sein und das Beispiel ausprobieren, wird Ihnen das Problem mit dieser Variante klar werden. Die Schleife sorgt dafür, dass unendlich viele Meldungen ausgegeben werden, da ja die Befehlsgruppe in der Schleife unendlich oft wiederholt wird. (Zumindest theoretisch, für unendlich viele Ausführungen fehlt uns aber jetzt die Zeit.) Wir haben es hier mit einer Endlosschleife zu tun. (Sie werden in der Praxis feststellen, dass Endlosschleifen manchmal ganz nützlich sind.) Das Problem ist: Wie kriegen wir unsere Schleife dazu, nach einer bestimmten Zeit abzubrechen, zum Beispiel nach 10 Meldungen? Das geht mit einer Bedingung, so wie wir sie in der If-Anweisung verwendet haben. Dazu wird hinter dem Loop das Schlüsselwort Until notiert, gefolgt von der Bedingung. Die Bedingung heißt Abbruchbedingung, denn wenn sie True ist, wird die Ausführung der Schleife abgebrochen.
Damit wird ein neues Problem deutlich: Wie können wir jetzt die 10 Meldungen ausgeben und danach abbrechen? Die Lösung ist eine Zählvariable. Jedes Mal, wenn eine Meldung ausgegeben wird, wird der Zähler um eins erhöht. Sobald der Zähler den Wert 10 erreicht hat, wird die Schleife abgebrochen. Die Implementation (die programmiererische Umsetzung dieses Konzepts) sieht so aus:
Dim Zähler As Integer = 0 Do Console.WriteLine("Schleife") Zähler += 1 Loop Until Zähler = 10
Schleife (zehnmal hintereinander)
Am Anfang wird der Zähler erzeugt und mit 0 initialisiert. In der Schleife wird jedes Mal, wenn eine Meldung ausgegeben wird, der Zähler um eins erhöht. Die Abbruchbedingung lautet Zähler = 10
, d.h. wenn der Zähler den Wert 10 hat, wird die Ausführung der Schleife abgebrochen und mit den evtl. auf die Schleife folgenden Befehlen fortgesetzt.
Mit dem nun erworbenen Wissen können wir auch die Zinsrechnung implementieren. Auf einer Variable speichern wir zunächst das Startkapital von 1000 €. (Wir verwenden wegen der Centbeträge Gleitkommazahlen.) Nun wird ein Zähler angelegt. Jedes Mal, wenn eine Zinszahlung erfolgt (in diesem Fall simuliert wird), wird der Zähler erhöht. Da wir bereits wissen, wie oft verzinst wird (beim Angebot 1 fünfmal, beim Angebot 2 sechzigmal, denn 60 = 5 * 12), können wir mit einer Gleichheitsoperation eine einfache Abbruchbedingung formulieren. Nachfolgend der Code für die Berechnung des 1. Angebotes:
Dim Kapital As Single = 1000.0 'Startkapital Dim AnzahlVerzinsungen As Integer = 0 'Zähler mit beschreibendem Namen Do Kapital += 0.03 * Kapital AnzahlVerzinsungen += 1 Loop Until AnzahlVerzinsungen = 5 Console.WriteLine("Am Ende haben Sie " & Kapital & " Euro.")
Am Ende haben Sie 1159.2740743 Euro.
Die eigentliche Berechnung läuft in der Zeile Kapital += 0.03 * Kapital
ab. Der Ausdruck rechts des Kompositoperatoren += ist der Zins, nämlich 3 % (= 3/100 = 0,03) des aktuellen Kapitals. Dieser Zins wird dem Kapital hinzugerechnet, also auf das Sparbuchkonto gutgeschrieben. Beachten Sie nach der Schleife außerdem den Aufruf der Console.WriteLine-Funktion. Für die Verkettungsoperation wird die Gleitkommazahlvariable Kapital in eine String-Variable umgewandelt.
Sie sollten als Ergebnis etwa 1159 Euro erhalten. Wenn Sie die vielen Nachkommastellen in der Ausgabe stören, ersetzen Sie in der letzten Zeile das Kapital
zwischen den zwei Verkettungsoperatoren durch Math.Round(Kapital,2)
. Dieser Aufruf der Round-Funktion des Math-Objektes rundet die Zahl auf zwei Nachkommastellen.
Als Übung sollten Sie jetzt versuchen, ausgehend von dieser Beispielimplementierung des 1. Angebotes eine Rechnung für das 2. Angebot durchzuführen. Beachten Sie den veränderten Zinssatz von 0,25 % sowie die größere Anzahl von Verzinsungen, da nicht 5 Jahre lang, sondern 60 Monate lang verzinst wird. Zur Kontrolle: Mit 1162 Euro ist das 2. Angebot minimal besser als das erste. Das liegt daran, dass die Zinsen öfter mitverzinst werden.
Man kann Schleifen auch nicht nur mit Zählern verwenden, sondern auch komplexere Sachverhalte sehr einfach und ressourcensparend implementieren. Ein Beispiel: Sie haben sich für das 1. Angebot entschieden, also die jährliche Verzinsung mit 3%, und wollen jetzt wissen, wie lange Sie warten müssen, bis sich 100 Euro Zinsen angesammelt haben.
Den Zähler werden wir in unserem Entwurf weiterverwenden, allerdings in einem etwas anderem Kontext. Jetzt wird er nicht mehr für die Abbruchbedingung, sondern für die Feststellung des Ergebnisses, nämlich wieviele Jahre das Ansparen von 100 Euro dauert, verwendet. Der Abbruch der Schleife erfolgt, sobald das gewünschte Sparziel von 100 Euro erreicht ist, also das Kapital über 1100 Euro gewachsen ist. Die Abbruchbedingung ist also Kapital >= 1100.0
.
Versuchen Sie einmal, anhand dieser Überlegungen das entsprechende Programm zu implementieren. Die Ausgabe sollte die Wartezeit sowie die Endsumme beinhalten. (Diese beiden Fakten können Sie auch auf zwei Hinweisfelder aufteilen.) Die Lösung sehen Sie unten.
Dim Kapital As Single = 1000.0 'Startkapital Dim Jahreszahl As Integer = 0 Do Kapital += 0.03 * Kapital Jahreszahl += 1 Loop Until Kapital >= 1100.0 Console.Write("Sie müssen " & Jahreszahl & " Jahre warten. ") Console.WriteLine("Am Ende haben Sie " & Kapital & " Euro.")
Ausgabe: Sie müssen 4 Jahre warten. Am Ende haben Sie 1125,509 Euro.
Kopfgesteuerte Schleifen
Mit Until kann man nach der Ausführung der Schleife eine Bedingung prüfen und die Schleife eventuell wiederholen. Da die Bedingung am Fuß der Schleife sitzt, heißt diese Schleifenart „fußgesteuerte Schleife“.
Das genaue Gegenteil ist die „kopfgesteuerte Schleife“. Hier steht die Bedingung vor der Schleife. Nur wenn die Bedingung True ist, wird die Schleife ausgeführt. Wenn nicht, springt die Ausführung an das Ende der Schleife.
Eine kopfgesteuerte Schleife wird in Visual Basic .NET über das Schlüsselwort While erzeugt. Es wird nach Do notiert, gefolgt von der Fortsetzungsbedingung, dem Gegenstück zur Abbruchbedingung. Schon der Name zeigt, wie gegensätzlich sich beide Bedingungen verhalten. Die kopfgesteuerte Schleife wird erneut ausgeführt, wenn die Fortsetzungsbedingung True ist, die fußgesteuerte Schleife wird erneut ausgeführt, wenn die Abbruchbedingung False ist. Darauf müssen Sie auch achten, wenn sie eine fuß- durch eine kopfgesteuerte Schleife ersetzen oder umgekehrt. Das folgende Beispiel ist zum letzten Code äquivalent. Achten Sie besonders auf die Fortsetzungsbedingung.
Dim Kapital As Single = 1000.0 'Startkapital Dim Jahreszahl As Integer = 0 Do While Kapital < 1100.0 Kapital += 0.03 * Kapital Jahreszahl += 1 Loop Console.Write("Sie müssen " & Jahreszahl & " Jahre warten. ") Console.WriteLine("Am Ende haben Sie " & Kapital & " Euro.")
Ausgabe: Sie müssen 4 Jahre warten. Am Ende haben Sie 1125,509 Euro.
Beachten Sie, dass sich die While- und Until-Konstrukte gegenseitig ausschließen. Das heißt, dass eine Schleife entweder kopf- oder fußgesteuert ist.
Grundsätzlich stellt sich nun die Frage, wann eine kopfgesteuerte Schleife und wann eine fußgesteuerte Schleife eingesetzt werden sollte. Dazu muss man sich folgendes klarmachen: Eine fußgesteuerte Schleife wird mindestens einmal ausgeführt, da die Bedingung erst nach der ersten Ausführung getestet wird. Bei einer kopfgesteuerten Schleife kann es sein, dass, wenn die Fortsetzungsbedingung das erste Mal nicht erfüllt ist, die Schleife nie ausgeführt wird.
Daher: Verwenden Sie fußgesteuerte Schleifen, wenn Sie sicherstellen müssen, dass die Schleife mindestens einmal ausgeführt wird. Verwenden Sie kopfgesteuerte Schleifen, wenn Sie sich nicht sicher sind, ob die Schleife überhaupt ausgeführt werden muss.
Sollte diese Faustregel keine Entscheidung liefern, rate ich Ihnen zur fußgesteuerten Schleife, da diese ein bisschen schneller ausgeführt wird.
Exit Do und Continue Do
Eben sagte ich, dass eine Schleife entweder kopf- oder fußgesteuert ist. Das stimmt so nicht. Eine Schleife kann auch weder kopf- noch fußgesteuert sein. Es handelt sich dann um eine der bereits oben angesprochenen Endlosschleifen.
Doch auch diese Schleifen werden nicht notwendigerweise unendlich oft ausgeführt. Der Grund ist die Exit-Do-Anweisung. Diese Anweisung, die innerhalb jeder Do-Schleife vorkommen kann, sorgt dafür, dass die Schleife sofort verlassen wird. Dabei werden auch die Bedingungen der While- und Until-Konstrukte übergangen. Die Schleife wird nach der Exit-Do-Anweisung nicht mehr ausgeführt.
Wie If-Anweisungen kann man Do-Schleifen verschachteln. Führt man in einer Verschachtelung von Do-Schleifen die Exit-Do-Anweisung aus, so wird nur die innerste Schleife verlassen. Im folgenden Beispiel springt die Ausführung nach der Exit-Do-Anweisung zur Anweisung Console.WriteLine("3")
.
Dim Zähler As Integer = 0 Do Console.WriteLine("1") Do Exit Do Console.WriteLine("2") Loop Console.WriteLine("3") Zähler += 1 Loop Until Zähler = 2
1 3 1 3
Des Weiteren gibt es die Continue-Do-Anweisung. Sie wirkt ähnlich wie Exit Do, jedoch springt sie an den Anfang der Schleife. Ist die Schleife Kopfgesteuert, wird auch die Bedingung geprüft, bevor die Ausführung weitergeht.
Optimierungen
Ich möchte noch einmal auf das erste Beispiel Bezug nehmen. Die eigentliche Berechnung ist hier nämlich ziemlich unvorteilhaft implementiert. In den folgenden Zeilen wird der Befehl schrittweise umgeformt, um am Ende einen effektiveren Befehl, der weniger Operationen enthält, zu erhalten. Jede einzelne Codezeile tut dabei das gleiche, nur auf unterschiedliche Arten.
Kapital += 0.03 * Kapital 'Ausgangssituation: 2 Operationen Kapital = Kapital + 0.03 * Kapital Kapital = 1 * Kapital + 0.03 * Kapital Kapital = (1 + 0.03) * Kapital Kapital = 1.03 * Kapital Kapital *= 1.03 'Endsituation: 1 Operation
Das ist ein schönes Beispiel, wie mit einfachen Überlegungen die Ausführungsgeschwindigkeit und der Speicherverbrauch eines Programmes reduziert werden können. Dies macht sich vor allem bei zeitkritischen Anwendungen und hohen Wiederholungsraten bemerkbar (etwa, wenn Sie einmal probieren, was auf dem Sparbuch nach 100.000 Jahren theoretisch passiert wäre), ist aber auch ein Zeichen eines eleganten, umsichtigen Programmierstils.
Dim Kapital As Single = 1000.0 'Startkapital Dim AnzahlVerzinsungen As Integer = 0 'Zähler mit beschreibendem Namen Do Kapital *= 1.03 AnzahlVerzinsungen += 1 Loop Until AnzahlVerzinsungen = 5 Console.WriteLine("Am Ende haben Sie " & Kapital & " Euro.")
Man kann das Beispiel noch weiter vereinfachen, wenn man bedenkt, dass die Potenzierung nur eine Verkettung von Multiplikationen ist. In diesem Fall wird das Kapital fünfmal hintereinander mit 1.03 multipliziert, was einer Multiplikation mit 1.03 hoch 5 gleichkommt. Die Schleife und die Variable Kapital sind nun nicht mehr nötig, das Beispiel verkürzt sich in der Gesamtheit von sieben Zeilen auf eine:
Zählschleifen
Neben den Do-Schleifen gibt es noch eine andere, etwas statischere Art von Schleifen, die Zählschleifen oder auch Iterationsschleifen.
Im ersten Beispiel des vorhergehenden Kapitels wurde ein Zähler verwendet, um eine Befehlsgruppe eine vorher bekannte Anzahl mal zu wiederholen. Dafür kann auch eine Zählschleife verwendet werden. Hier wird eine bestimmte Variable als Zählvariable ausgezeichnet, die bei jeder Wiederholung um einen bestimmten Wert (standardmäßig 1) erhöht wird. Die Ausführung der Schleife wird abgebrochen, sobald ein bestimmter Grenzwert überschritten.
Unten sehen Sie zuerst eines der Beispiele für die Do-Schleife aus dem vorhergehenden Kapitel und die äquivalente Umsetzung mit einer For-Zählschleife.
Dim Kapital As Single = 1000.0 'Startkapital Dim AnzahlVerzinsungen As Integer = 0 'Zähler mit beschreibendem Namen Do Kapital += 0.03 * Kapital AnzahlVerzinsungen += 1 Loop Until AnzahlVerzinsungen = 10 Console.WriteLine("Am Ende haben Sie " & Kapital & " Euro.")
Dim Kapital As Single = 1000.0 'Startkapital Dim AnzahlVerzinsungen As Integer 'Zähler muss nicht initialisiert werden For AnzahlVerzinsungen = 1 To 10 Kapital += 0.03 * Kapital Next Console.WriteLine("Am Ende haben Sie " & Kapital & " Euro.")
Die eigentliche Schleife beginnt in Zeile 3 mit dem Schlüsselwort For. Dahinter muss die Zählvariable notiert werden, hier handelt es sich um die Variable AnzahlVerzinsungen. In der Praxis werden Sie für solche Variablen oft Namen wie „Count...“, „Cnt...“ oder (für Deutschfetischisten) „Zähler...“ verwenden. Nach dem Namen weisen Sie der Zählvariable mit dem Zuweisungsoperator einen Startwert zu. Nach dem To folgt der Grenzwert. Die Schleife wird nach unten hin vom Schlüsselwort Next begrenzt.
Was passiert nun? Nach jeder Ausführung der Befehle in der Schleife (hier nur des Befehls Kapital += 0.03 * Kapital
) wird die Zählvariable um eins erhöht. Sobald der Wert der Zählvariable den Grenzwert überschritten hat (also Zähler > Grenzwert, nicht Zähler >= Grenzwert!), wird die Schleife abgebrochen.
Beachten Sie, dass als Zählvariablen nur Variablen folgender Datentypen verwendet werden dürfen: Byte, Short, Integer, Long, Single und Double.
Schrittweite
Standardmäßig wird der Zähler mit jedem Schleifendurchlauf um eins erhöht. Diese Schrittweite kann man aber auch ändern, indem man in der ersten Zeile der Schleife nach dem Grenzwert das Schlüsselwort Step notiert, gefolgt von der gewünschten Schrittweite. Die folgenden Beispiele sind äquivalent zu den vorhergehenden.
Dim Kapital As Single = 1000.0 Dim AnzahlVerzinsungen As Single For AnzahlVerzinsungen = 1 To 5 Step 0.5 Kapital += 0.03 * Kapital Next Console.WriteLine("Am Ende haben Sie " & Kapital & " Euro.")
Dim Kapital As Single = 1000.0 Dim AnzahlVerzinsungen As Integer For AnzahlVerzinsungen = -10 To 10 Step 2 Kapital += 0.03 * Kapital Next Console.WriteLine("Am Ende haben Sie " & Kapital & " Euro.")
Wenn man Step auf einen negativen Wert setzt läuft die Schleife rückwärts. Eine Rückwärtslaufende Funktion mit obigen Beispiel würde wie folgt lauten können.
Dim Kapital As Single = 1000.0 Dim AnzahlVerzinsungen As Integer For AnzahlVerzinsungen = 10 To 1 Step -1 Kapital += 0.03 * Kapital Next Console.WriteLine("Am Ende haben Sie " & Kapital & " Euro.")
Integrierte Deklarationen
Meist braucht man eine Zählvariable nur für eine Zählschleife. Um diese Verbindung noch deutlicher werden zu lassen und Fehler mit einer versehentlichen späteren Verwendung von Zählern zu vermeiden. Nachfolgend wurde die oberste For-Schleife entsprechend modifiziert:
Dim Kapital As Single = 1000.0 For AnzahlVerzinsungen As Integer = 1 To 10 Kapital += 0.03 * Kapital Next Console.WriteLine("Am Ende haben Sie " & Kapital & " Euro.")
Die Deklaration der Zählvariable AnzahlVerzinsungen wurde in die For-Anweisung integriert. Nach dem Namen der zu deklarierenden Zählvariable folgt das bekannte As-Schlüsselwort und der Datentyp. Auch hier gilt die Begrenzung auf die oben benannten Datentypen. Der Vorteil an dieser Methode ist, dass die Variable nach Beendigung der Schleife gelöscht wird. Versucht man, auf die Variable zuzugreifen, wird ein Fehler ausgelöst. Moderne Entwicklungsumgebungen weisen Sie auf solche Fehler auch im vornherein hin. Der folgende Code verdeutlicht noch einmal das Problem.
Dim Count1 As Integer For Count1 = 1 To 5 Console.WriteLine("Schleife 1") Next For Count2 As Integer = 1 To 3 Console.WriteLine("Schleife 2") Next Count1 = 0 'zulässig, aber meist nicht gewollt Count2 = 0 'Fehler! - nach der Schleife gibt es die Variable nicht mehr
Schleife 1 (fünfmalige Ausgabe) Schleife 2 (dreimalige Ausgabe)
Felder
Oft ist es von Vorteil, bestimmte Variablen thematisch zu gruppieren, um sie einfacher verarbeiten zu können. Zum Beispiel nehmen Simulationen Messreihen auf oder Mediaplayer stellen einen Film als viele Bildpunkte dar. In beiden Fällen erscheint es schwachsinnig, für jeden Wert eine einzelne Variable anzulegen. Das Mittel der Wahl sind hier Felder. Felder (nach dem englischen Begriff auch Arrays genannt) sind spezielle Variablen, die mehrere Werte eines bestimmten Datentypes aufnehmen können. Die Implementation ist in Visual Basic .NET gut gelungen, offenbart jedoch auch eine Schwachstelle.
Eindimensionale Felder
In einem Feld wird jeder Wert durch eine Zahl, den sogenannten Index, repräsentiert. Da jeder Index innerhalb eines Feldes nur einmal vergeben wird, bestimmt ein Paar aus Feldname und Index einen Wert eindeutig. Das heißt, wenn Sie immer den gleichen Feldnamen und den gleichen Index angeben, werden Sie immer den gleichen Wert erhalten, genauso wie wenn Sie eine Variable über den immer gleichen Namen ansprechen.
Die Indizes der Feldwerte werden automatisch vergeben, es handelt sich hier immer um einen zusammenhängenden Bereich von Ganzzahlen. Dieser Bereich beginnt immer bei 0, man sagt auch, das Feld ist nullbasiert. Die Obergrenze (auch Feldgrenze genannt) bestimmt der Programmierer bei der Deklaration selbst.
Die Deklaration eines Feldes ähnelt der einer normalen Variable, nur wird direkt hinter dem Feldnamen in Klammern die Feldgrenze notiert. Die Elemente des Feldes spricht man auf die gleiche Weise an: Hinter dem Feldnamen wird in Klammern der Index des gewünschten Wertes notiert. Bei diesem Wert kann es sich auch um einen Operatorenausdruck handeln.
Dim Feld(3) As Integer Feld(0) = 0 Feld(1) = 1 Feld(2) = Feld(0) + Feld(1) Feld(3) = Feld(1) + Feld(2) Console.WriteLine(Feld(0)) Console.WriteLine(Feld(1)) Console.WriteLine(Feld(2)) Console.WriteLine(Feld(3))
(hier der Übersicht halber alles in einer Zeile) 0 1 1 2
Ganz besondere Schlaumeier haben es sofort erkannt: Dieses Beispiel berechnet die ersten 4 Glieder der Fibonacci-Folge. Das ist aber gar nicht so wichtig, das Programm schon. In der ersten Zeile wird das Feld deklariert. Es enthält 4 (nicht 3!) Elemente mit den Indizes 0, 1, 2 und 3. Diese Feldelemente werden im folgenden initialisiert. Den ersten zwei Elementen mit den Indizes 0 und 1 werden die Werte 0 und 1 zugewiesen, die letzten zwei Elemente mit den Indizes 2 und 3 werden mit der Summe der jeweils vorhergehenden Werte initialisiert.
Es gibt noch eine zweite, für kleine Felder mit bekannten Werten meist komfortablere Form der Initialisierung:
Dim Feld() As Integer = {0, 1, 1, 2} Console.WriteLine(Feld(0)) Console.WriteLine(Feld(1)) Console.WriteLine(Feld(2)) Console.WriteLine(Feld(3))
(hier der Übersicht halber alles in einer Zeile) 0 1 1 2
Das Feld erhält hier die gleichen Werte wie oben. In den geschweiften Klammern stehen die Werte, die im Feld gespeichert werden sollen. Der Wert ganz links (1) wird in dem Element mit dem Index 0 gespeichert, der Wert rechts davon (1) in dem Element mit dem Index 1, der Wert rechts davon (2) in dem Element mit dem Index 2, und so weiter. Beachten Sie, dass bei dieser Art der Belegung keine Feldgrenze angegeben werden darf. Sie ergibt sich aus der Zahl der bereitgestellten Werte. So verhindert Visual Basic .NET einen Widerspruch, also dass für ein angelegtes Feld zu wenige oder (schlimmer noch) zu viele Werte bereitstehen.
Schon während dieses einfachen Beispiels zeigt sich ein Problem bei der Arbeit mit Feldern: die untere Feldgrenze 0. Sie sorgt dafür, dass ein Feld mit der Obergrenze 3 vier Elemente enthält, was nicht nur vielen Einsteigern spanisch vorkommt, sondern vor allem vielen Umsteigern, die von früheren Visual-Basic-Versionen gewohnt sind, neben der oberen auch die untere Feldgrenze festlegen zu können. Viele Visual-Basic-Programmierer deklarieren ein Feld mit der Obergrenze 3 und verwenden dann nur die Elemente 1 bis 3, damit es wieder 3 Elemente sind. Das ist allerdings unvorteilhaft, da das Index-0-Element nicht verwendet wird, aber unnötigerweise Speicherplatz belegt. Stattdessen sollte man die Obergrenze um eins verkleinern und dann bei allen Indizes konsequent eins abziehen.
Mehrdimensionale Felder
Stellen Sie sich ein Schachbrett vor. Sollten Sie die Zustände der 64 Felder speichern wollen, wäre ein Feld ideal. Wahrscheinlich würden Sie das entsprechende Feld so deklarieren:
Zur Zustandsspeicherung verwende ich hier String-Variablen, ein leerer String ("") bedeutet also ein leeres Feld, ein String wie "wBauer" könnte einen weißen Bauer darstellen. Später lernen wir mit Enumerationen und Typen zwei objektorientierte Möglichkeiten kennen, die Speicherung solcher Daten effizienter zu gestalten.
Das Problem bei der Darstellung mit einem solchen Feld ist die ziemlich umständliche Nutzung. Wüssten Sie etwa auf Anhieb, wo das Feld 50 liegt oder wieweit es vom Feld 40 entfernt ist? Unten sehen Sie links die jetzige Darstellung des Schachfeldes mit einem Index pro Feldelement, rechts eine viel pragmatischere und sinnvollere Darstellung mithilfe von zwei Indizes pro Feldelement. Ein entsprechendes Feld heißt aufgrund der zwei Indizes zweidimensional. Entsprechend ist ein Feld, in dem jedes Element über einen Index angesprochen wird, eindimensional. Ein Feld mit mehr als einem Index heißt allgemein mehrdimensional.
|
|
Ein mehrdimensionales Feld wird deklariert, indem in der Klammer, in dem beim eindimensionalen Feld die Feldgrenze steht, die Feldgrenzen von Kommata getrennt geschrieben werden.
Dim Schachbrett(7,7) As String
Wollte man statt acht nur sechs Reihen im Schachbrett haben, müsste man statt (7,7)
die Feldgrenzen mit (5,7)
festlegen. Sollte jede der acht Reihen nur vier Felder enthalten, würden die Feldgrenzen (7,3)
heißen. Der Zugriff auf die Elemente erfolgt analog zur Deklaration, wie das folgende Beispiel zeigt, in der nicht alle Feldelemente mit Werten belegt werden (das würde den Rahmen sprengen).
Dim Schachbrett(7,7) As String Schachbrett(0,0) = "" 'linkes oberes Feld leer Schachbrett(1,0) = "wBauer" 'auf dem Feld darunter ein weißer Bauer If Schachbrett(0,0) = "" Then 'wenn das linke obere Feld leer ist... Schachbrett(0,7) = Schachbrett(0,0) '... ist das rechte obere Feld auch leer End If
Die einseitige Entscheidung am Ende ist ziemlich unsinnig, zeigt aber, dass Feldelemente überall verwendet werden dürfen, wo auch normale Variablen verwendet werden dürfen.
Wie für eindimensionale Felder gibt es auch für mehrdimensionale Felder die Möglichkeit, sie bei der Deklaration gleich zu initialisieren. Im folgenden Beispiel erstelle ich dazu ein Feld mit 3x2 Elementen, also den Obergrenzen 2 und 1.
Die Anzahl der Kommas in den runden Klammern bestimmt die Anzahl von Dimensionen. Es müssen soviel Kommas sein, wie auch bei der Deklaration des Feldes ohne Initialisierung verwendet werden würden. Die Anzahl der Kommas ist also um eins geringer als die Anzahl der Felddimensionen. Das zweidimensionale Feld wird in dieser Initialisierung als eindimensionales Feld von eindimensionalen Feldern angesehen. Jede untergeordnete geschweifte Klammer definiert eine Gruppe von Elementen, die den ersten Index gemeinsam haben. Die Elemente, in denen die Werte 1 und 2 gespeichert sind, haben beide 0 als ersten Index. Innerhalb der Klammer wird der zweite Index hochgezählt. So hat das Element mit dem Wert 2 als ersten Index 0 und als zweiten Index 1, d.h. MeinFeld(0,1) = 2
. (Das Gleichheitszeichen ist hier als Gleichheitsoperator zu verstehen.) Die obige Initialisierung des Feldes MeinFeld ist äquivalent zu der folgenden „manuellen“ Belegung der Feldelemente.
Dim MeinFeld(2,1) As Integer MeinFeld(0,0) = 1 MeinFeld(0,1) = 2 MeinFeld(1,0) = 3 MeinFeld(1,1) = 4 MeinFeld(2,0) = 5 MeinFeld(2,1) = 6
Redimensionierungen
Oftmals ist es vonnöten, Feldgrenzen zu ändern, etwa wenn in ein Feld von Messwerten ein neuer Messwert eingetragen werden sollen, oder wenn ein Browser eine aufgerufene Adresse in seinem Verlauf speichern will. In Visual Basic .NET gibt es für solche Redimensionierungen (Neufestlegungen der Feldgrenzen) die ReDim-Anweisung.
Dim Feld(2) As Integer Feld(0) = 1 Feld(1) = 2 Feld(2) = 3 ReDim Feld(3) Feld(3) = 4 Console.WriteLine(Feld(0))
0
In diesem Beispiel wird zunächst ein Feld mit der Feldgrenze 2, also mit 3 Elementen deklariert, die danach belegt werden. Danach wird das Feld mit ReDim vergrößert. Da der Datentyp nicht geändert werden kann, muss er auch nicht angegeben werden. Ansonsten ähnelt die Syntax der der Deklaration des Feldes, ausgenommen das ReDim-Schlüsselwort. Außerdem lässt die ReDim-Anweisung keine Initialisierung zu. Im Beispiel wird die Feldgrenze auf 3 festgelegt, also das Feld um 1 auf 4 Elemente vergrößert. In der folgenden Zeile wird das neu geschaffene Element belegt.
Allgemein gelten für Redimensionierungen aus meist offensichtlichen Gründen die folgenden Regeln:
- Die Dimensionszahl darf sich nicht ändern. Ein eindimensionales Feld kann nicht plötzlich zweidimensional werden, da dadurch Aufrufe von Feldelementen mit nur einem Index fehlerhaft würden.
- Der Datentyp kann aus dem selben Grund nicht geändert werden. Würde man ein Integer-Datenfeld als Byte-Datenfeld redimensionieren (wobei „redeklarieren“ ), würden Befehle wie
Feld(0) = 300
fehlerhaft, da eine Bereichsüberschreitung vorliegt (kurz: der Wert passt nicht in das Feldelement).
In der letzten Zeile wird der Wert eines Feldelementes abgerufen. Wenn Sie das Beispiel ausführen, wird nicht, wie erwartet, 1 ausgegeben, sondern 0. Woran liegt das? Um diese Frage zu klären, werfen wir einen kleinen Blick in den Speicher. Wenn eine Variable deklariert wird, fordert das Programm beim Betriebssystem (in unserem Fall ja meist Windows) Speicher für die Variable an. Windows sucht im Arbeitsspeicher ein passendes, freies Stück heraus, reserviert es und meldet dem Programm die Adresse des reservierten Speichers, auf dem das Programm die Variable anlegen darf. (In Wahrheit ist das noch viel komplizierter, aber das ist gerade nicht so wichtig.) Wichtig ist, dass der Speicherplatz für die Variable nur so groß ist, dass die Variable gerade hineinpasst. Wenn wir nun unser Feld (welches im Prinzip nur eine etwas größere Variable mit mehreren Werten ist) redimensionieren, kann (und wird) der Fall eintreten, dass der Speicherplatz für das Feld nicht mehr ausreicht. Um Fehler zu vermeiden, fordert das Programm völlig neuen Speicher in der passenden Größe an und erstellt dort das Feld neu. Dabei werden die zuvor im Feld gespeicherten Werte aber nicht übernommen, alle Feldelemente werden nur mit den Standardwerten initialisiert. Deshalb sehen wir beim Abrufen eines Feldelementes nicht den vor der Redimensionierung gespeicherten Wert, sondern den Standardwert 0.
Die ReDim-Anweisung besitzt zum Glück eine Erweiterung, mit der sich dieses Problem beheben lässt. Dazu muss zwischen ReDim und den Feldnamen das Schlüsselwort Preserve notiert werden.
Dim Feld(2) As Integer Feld(0) = 1 Feld(1) = 2 Feld(2) = 3 ReDim Preserve Feld(3) Feld(3) = 4 Console.WriteLine(Feld(0))
1
Wenn das Schlüsselwort Preserve angegeben ist, wird erst das neue Feld angelegt und alle Werte des alten Feldes in das neue Feld kopiert, bevor das alte Feld gelöscht wird. Alle Elemente des neuen Feldes, die durch die Vergrößerung entstanden und für die es deshalb im alten Feld noch keine Werte gab, werden mit dem Standardwert, hier 0, initialisiert. Im Beispiel wird also erst das neue Feld mit 4 Elementen erstellt, dann die 3 Elemente des alten Feldes in das neue Feld kopiert, und dann das alte Feld gelöscht. Damit immer alle Elemente des alten Feldes kopiert werden können und keines verloren geht, muss das neue Feld immer größer oder gleich groß sein (wobei letzteres praktisch nicht von Bedeutung ist, da dann die ganze Redimensionierung schwachsinnig wäre).
Leider hat die Redimensionierung einen extremen Nachteil: Wenn man ein Feld mehrere Male vergrößert, zum Beispiel weil man 100 Datensätze einliest und das Feld 100-mal um eins vergrößert, dann werden auch einhundertmal Werte kopiert. Stellen Sie sich mal vor, Sie müssten sich 100 Notizen machen und jedesmal alle alten Notizen erst wieder auf einen etwas größeren Zettel schreiben. Das dauert natürlich ewig, genauso beim andauernden Redimensionieren.
Sie haben jetzt zwei Optionen: Entweder, Sie kriegen vorher irgendwie raus, wie viel Platz Sie brauchen, um dann einen entsprechend großen Zettel zu nehmen bzw. ein ausreichend großes Feld zu deklarieren. Das Problem hier ist, dass sich die erforderliche Kapazität in echten Szenarien fast nie abschätzen lässt. Mit einem Riesenfeld, dessen Werte nach Bedarf belegt werden, sollte man auch nicht arbeiten, da man dann aufwendig zwischen belegten und nicht belegten Feldern unterscheiden müsste. Der zweite Ausweg wäre, an den vollen Zettel einen neuen unten anzukleben. Dieses Konzept ist in Visual Basic in der Objektorientierten Programmierung als sogenannte Collection (engl. Sammlung, auch „verkettete Liste“) verwirklicht. An so eine Liste können nach Bedarf neue Elemente hinzugefügt werden. Solche fortgeschrittenen objektorientierten Themen wollen wir in diesem Abschnitt aber noch nicht behandeln.
Feldfunktionen
Wahrscheinlich ist Ihnen jetzt von dem ganzen Deklarieren, Initialisieren und Redimensionieren so schwindlig geworden, dass Ihnen vor lauter Aufregung die Feldgrenzen Ihrer Felder entfallen sind. Holen Sie sich erstmal ein Glas Wasser und eine Aspirin-Tablette, dann erkläre ich Ihnen, wie Sie Ihre Feldgrenzen programmgesteuert wiederfinden.
Dazu muss man wissen, dass in Visual Basic .NET alles ein Objekt ist. Bis jetzt kennen Sie vor allem das System.Windows.Forms.MessageBox-Objekt, von dem es nur eins gibt. (Das stimmt zwar so nicht, aber das soll später in der objektorientierten Programmierung Thema sein.) Jede Variable ist ein Objekt, vor allem sind auch Felder Objekte. Wie das MessageBox-Objekt mit seiner Show-Methode hat auch ein Feld Methoden. Die wahrscheinlich wichtigste ist GetUpperBound. Das Get kommt in vielen Methodennamen und steht dafür, dass diese Methode eine Eigenschaft des jeweiligen Objektes abruft, hier die obere Feldgrenze (engl. upper bound). (Analog zu Get ist Set, dass Methoden zum Einstellen einer Eigenschaft kennzeichnet. Zum Beispiel könnte ReDim auch SetUpperBound heißen.)
GetUpperBound ist eine Methode des Feldobjektes. Heißt das Feld z.B. MeinFeld, wird die Methode mit der bekannten Punktsyntax über die Syntax MeinFeld.GetUpperBound aufgerufen. So wie bei der Console.WriteLine-Methode in Klammern der anzuzeigende Text eingetragen werden muss, muss bei der GetUpperBound-Methode in Klammern eine Ganzzahl stehen. Diese Zahl gibt an, welche Feldgrenze zurückgegeben werden soll, da es ja bei mehrdimensionalen Feldern mehrere geben kann. Die erste Feldgrenze wird durch 0 repräsentiert. Wie man sieht, fängt wie bei Feldelementen auch hier die Zählung bei 0 an. Bei eindimensionalen Feldern müssen Sie also immer 0 angeben. Bei jeder darauffolgenden Feldgrenze erhöht sich die Zahl um 1. Ich möchte das mit einem Beispiel illustrieren.
Dim MeinFeld(5,9,3) As String, Feldgrenzen(2) As Integer Feldgrenzen(0) = MeinFeld.GetUpperBound(0) Feldgrenzen(1) = MeinFeld.GetUpperBound(1) Feldgrenzen(2) = MeinFeld.GetUpperBound(2) Console.WriteLine(Feldgrenzen(1))
9
Da die GetUpperBound-Methode einen Wert ermittelt, hier die entsprechende Feldgrenze, kann man diesen Wert wie einen Literal einer Variable zuweisen. Auch die Ausgabe mit der Console.WriteLine-Methode ist denkbar. Viele Methoden ermitteln einen Wert, den Sie zurückgeben, deshalb heißen diese Werte Rückgabewerte. Im vorliegenden Beispiel geben die Aufrufe der MeinFeld.GetUpperBound-Methode die entsprechenden Feldgrenzen 5, 9 und 3 zurück, das Feld Feldgrenzen wird mit diesen Werten belegt. Das folgende Beispiel ist zum obigen äquivalent und zeigt noch einmal, dass Rückgabewerte überall verwendet werden können, wo auch literale Werte übergeben werden können.
Dim MeinFeld(5,9,3) As String Dim Feldgrenzen() As Integer = {MeinFeld.GetUpperBound(0), MeinFeld.GetUpperBound(1), MeinFeld.GetUpperBound(2)} Console.WriteLine(Feldgrenzen(1))
9
Analog zu GetUpperBound funktioniert GetLowerBound, dass die untere Feldgrenze bestimmt. Da in unserem Fall die Indizes immer bei 0 beginnen, gibt diese Methode immer 0 zurück. Für unsere Zwecke ist die GetLowerBound-Methode uninteressant, in den allermeisten Fällen kommt man auch ohne eine andere Felduntergrenze aus.
Die Length-Eigenschaft eines Feldes gibt die Anzahl von Elementen zurück.
Verwenden Sie MeinFeld.Length, wenn Sie die Anzahl der Elemente in der Feldvariable MeinFeld bestimmen wollen. Sie können diesen Ausdruck wie eine Variable verwenden, dürfen ihm aber keinen Wert zuweisen (eine weitere Eigenheit der OOP).
Eine Feldgrenze kann auch -1 sein. Wenn mindestens einer der Indizes eines Feldes -1 ist, bedeutet das, dass das Feld zwar leer ist, aber existiert. (In Fachtermini: Es ist Empty, aber nicht Nothing.) Jeder Versuch, auf ein solches leeres Feld zuzugreifen, schlägt fehl. Leere Felder können sehr nützlich sein, wenn die Anzahl der Elemente des Feldes bei der Deklaration nicht klar ist, etwa weil die Feldelemente in einer Datei gespeichert sind, sodass man das Feld erst nach und nach füllen kann. Dazu später mehr.
Eine weitere Feldfunktion ist die Sort-Methode, die, wie der Name schon sagt, das Feld sortiert. Der Bezeichner für diese Methode ist allerdings nicht MeinFeld.Sort, sondern Array.Sort (engl. array = Feld). Sort ist nämlich keine Methode des Feldes, sondern eine Methode des Array-Objektes, das dem MessageBox-Objekt ähnelt. In Klammern muss das Feld an die Methode übergeben werden, damit die Methode weiß, welches Feld zu sortieren ist. Außerdem ist zu beachten, dass Sort nur eindimensionale Felder sortieren kann.
Dim MeinFeld() As Integer = {1, 5, 8, 3, 8, 4, 7} Array.Sort(MeinFeld) Console.WriteLine(MeinFeld(0)) Console.WriteLine(MeinFeld(1)) Console.WriteLine(MeinFeld(2)) Console.WriteLine(MeinFeld(3)) Console.WriteLine(MeinFeld(4)) Console.WriteLine(MeinFeld(5)) Console.WriteLine(MeinFeld(6))
Dieses Beispiel erstellt ein Feld mit unsortierten Zahlenwerten, sortiert es dann und gibt die Werte in Reihenfolge aus. Wen es interessiert: Die Sort-Methode verwendet den Quicksort-Algorithmus, der sich durch eine selbst im schlimmsten Fall noch durchschnittlich schnelle Ausführung auszeichnet.
Eine andere zum Sortieren nützliche Methode ist die Reverse-Methode, die das komplette Feld umdreht. Im folgenden Beispiel wird der Aufruf der Array.Sort-Methode durch einen Aufruf der Array.Reverse-Methode ergänzt. Für die Array.Reverse-Methode gilt dieselbe Einschränkung wie für Array.Sort: Es können nur eindimensionale Felder umgekehrt werden.
Dim MeinFeld() As Integer = {1, 5, 8, 3, 8, 4, 7} Array.Sort(MeinFeld) Array.Reverse(MeinFeld) Console.WriteLine(MeinFeld(0)) Console.WriteLine(MeinFeld(1)) Console.WriteLine(MeinFeld(2)) Console.WriteLine(MeinFeld(3)) Console.WriteLine(MeinFeld(4)) Console.WriteLine(MeinFeld(5)) Console.WriteLine(MeinFeld(6))
(hier der Übersicht halber alles in einer Zeile) 8 8 7 5 4 3 1
Kaskadierte Felder
Es gibt noch eine zweite Möglichkeit, mehrdimensionale Felder zu realisieren, nämlich als kaskadiertes (verschachteltes) Feld im Feld. Werfen Sie einen Blick auf die folgenden Deklarationen:
Dim Feld1(5,6) As Integer Dim Feld2(5)() As Integer ReDim Feld2(0)(2) ReDim Feld2(1)(6) ReDim Feld2(2)(5) ReDim Feld2(3)(1) ReDim Feld2(4)(4) ReDim Feld2(5)(3) Dim Feld3()() As Integer = {New Integer() {1, 2}, New Integer() {3, 4, 5} }
In der ersten Zeile entsteht ein normales mehrdimensionales Feld mit 42 Elementen (nicht 30!). In der zweiten Zeile wird ein eindimensionales Feld deklariert, dessen 6 Werte nicht einfache Variablen, sondern jeweils wieder Felder sind. Auch wenn wir in diesem Beispiel keine Initialisierung vornehmen, müssen die zweiten Feldgrenzen offen bleiben. In den nächsten 6 Zeilen werden nämlich die 6 Einzelfelder durch Redimensionierung erstellt. Beachten Sie hierbei vor allem, dass die Feldgrenzen der einzelnen Felder voneinander unabhängig sind, während die 2. Dimension eines normalen 2-dimensionalen Feldes einheitlich sein muss. Damit eignen sich kaskadierte Felder vor allem für dynamische Inhalte, in denen die Zahl von Elementen in einem Teilfeld variiert und sich mit der Zeit verändert. Man kann auch noch mehr Dimensionen zu einer Feldkaskade hinzufügen, allerdings leidet dann die Übersicht.
In der letzten Zeile haben wir noch ein Beispiel für die Initialisierung von kaskadierten Feldern. Hierbei kann das Teilfeld nicht so komfortabel wie bei einem mehrdimensionalen Feld initialisiert werden. Stattdessen müssen wir das Feld im Speicher erstellen und dann als Wert im Feld speichern. Das hört sich jetzt sehr komplex an (und wenn man sich die Vorgänge im Speicher anschaut, ist es das auch, aber das ist ja nicht unser Thema). Deshalb nur so viel: Mit der New-Anweisung wird ein Feld im Speicher erstellt und sofort initialisiert. (Das Deklarieren nur mit Feldgrenze ist hier nicht möglich.) Dabei folgt auf das New-Schlüsselwort der Datentyp der Feldelemente und dann der uns mittlerweile gut bekannte Klammerwust zum Festlegen der Feldstruktur. Dann kommen in geschweiften Klammern die Werte. Alles wie gehabt also, nur dass die Klammern nach und nicht vor dem Datentyp stehen und dass kein Feldname vergeben wird.
Wie man leicht sieht, eignen sich kaskadierte Felder nicht für Felder mit vielen Werten, die von Hand eingetippt werden. Dafür ist ein konventionelles mehrdimensionales Feld besser geeignet. Die Stärken von kaskadierten Feldern liegen in dynamischen Inhalten oder solchen, die vom Computer nach einer einzelnen Formel berechnet werden können. Wie das geht, sehen Sie im nächsten Kapitel.
Feldschleifen
Am häufigsten werden Felder in der Praxis in Zusammenhang mit Schleifen verwendet, besonders mit Zählschleifen. Mit Zählschleifen wird die Zählvariable von 0 bis zur Obergrenze hochgezählt, diese Variable wird dann als Index für das Feld verwendet. Mit jedem Schleifendurchlauf wird so eines der Feldelemente bearbeitet, diese Methodik dient dazu, an allen Elementen des Feldes eine ähnliche Operation vorzunehmen. Das folgende Beispiel zeigt ein elementares Beispiel. Ein Feld wird mit Werten initialisiert. Zuerst die Initialisierung mit der klassischen Methode:
Vor allem bei komplexeren Werten, die sich aus Berechnungen ergeben, ist diese Variante sehr schreibaufwändig und meist auch redundant, da viele Werte auf die gleiche Art und Weise berechnet werden. Im folgenden wird das Feld mit denselben Werten belegt:
Das Feld wird zunächst ohne Initialisierung deklariert, die Wertzuweisung erfolgt in der Schleife. In jedem Schleifendurchlauf wird der Zähler um eins erhöht, im ersten Schleifendurchlauf ist der Zähler null, im letzten zehn. Die dritte Zeile liest sich so: Dem Feldelement mit dem Index, der dem im Zähler gespeicherten Wert entspricht (Feld(Zähler)
), wird der Wert des Zählers zugewiesen. Im ersten Schleifendurchlauf lautet dieser Befehl also Feld(0) = 0
, im zweiten Durchlauf Feld(1) = 1
, im dritten Durchlauf Feld(2) = 2
, und so weiter.
Ein anderes Beispiel: Vorhin hatte ich die Fibonaccifolge erwähnt. Diese wird wie folgt gebildet: Das nullte Element ist 0, das erste Element ist 1. Jedes weitere Element ist die Summe der beiden vorhergehenden Elemente. Das zweite Element ist also die Summe des nullten und des ersten Elementes, also 1. Das dritte Element ist die Summe des ersten (1) und des zweiten Elementes (1), also 2. Versuchen Sie einmal, den Code zu entwickeln, der die ersten 20 Elemente der Fibonaccifolge in ein Feld schreibt und dann alle Werte ausgibt. Die Lösung sehen Sie unten.
'Felddeklaration Dim Fibonacci(20) As Integer 'Belegung der Feldelemente Fibonacci(0) = 0 Fibonacci(1) = 1 For Count1 As Integer = 2 To 20 Fibonacci(Count1) = Fibonacci(Count1 - 1) + Fibonacci(Count1 - 2) Next 'Ausgabe For Count2 As Integer = 0 To 20 Console.WriteLine("Das " & Count2 & ". Element ist: " & Fibonacci(Count2)) Next
Das 0. Element ist: 0 Das 1. Element ist: 1 Das 2. Element ist: 1 Das 3. Element ist: 2 Das 4. Element ist: 3 Das 5. Element ist: 5 Das 6. Element ist: 8 Das 7. Element ist: 13 ...
Ich habe dieses Beispiel mit einigen Kommentaren versehen, die deutlich machen, wofür die einzelnen Befehle stehen. Zuerst wird das passende Feld deklariert. Dann werden die beiden vorgegebenen Werte im Feld gespeichert. Im folgenden errechnet eine Schleife die restlichen Werte rekursiv. Zuerst wird das dritte Element belegt, dann das vierte, dann das fünfte, und so weiter. Da nur auf die vorhergehenden Elemente zugegriffen wird, ist gesichert, dass nicht fälschlicherweise auf ein Element zugegriffen wird, dass noch nicht belegt wurde. Bei der Ausgabe wird für das Hinweisfeld ein Satz mithilfe der Verkettungsoperation erstellt. Dazu wandelt Visual Basic die Integer-Werte zunächst in String-Werte um, da die Verkettungsoperation nur für String-Operanden definiert ist.
Da die Werte der Fibonaccifolge in einem Feld vorliegen, können wir auch noch andere Sachen damit anstellen. Der folgende Code berechnet das Verhältnis zweier jeweils aufeinanderfolgender Werte. Wenn Sie diesen Code an den vorhergehenden Code anhängen oder die Ausgabeschleife (mit Count2) durch diesen Code ersetzen, wird Ihnen auffallen, dass sich die Verhältnisse bei steigenden Indizes immer weiter an 1,618... annähern. Dies ist eine spezifische Eigenschaft der Fibonaccifolge.
'Ausgabe der Verhältnisse For Count3 As Integer = 2 To 20 'mit dem nullten Element können wir nicht rechnen - Gefahr der Division durch Null 'deshalb müssen wir das Verhältnis von 0 und 1 auslassen und fangen mit dem von 1 und 2 an Console.WriteLine(Fibonacci(Count3) & "/" & Fibonacci(Count3 - 1) & " ergibt: " & (Fibonacci(Count3) / Fibonacci(Count3 - 1))) 'was für ein Klammernhaufen... Next
1/1 ergibt: 1 2/1 ergibt: 2 3/2 ergibt: 1.5 5/3 ergibt: 1.6666667 8/5 ergibt: 1.6 13/8 ergibt: 1.625 21/13 ergibt: 1.6153846
Die Einsatzmöglichkeiten von Feldern mit Schleifen sind vielfältig. Bis jetzt sind wir noch nicht so weit, als dass ich alle Möglichkeiten vorführen könnte. Vieles wird man sich auch erst im Alltag ausdenken.
For-Each-Schleifen
Neben den klassischen Schleifen und Zählschleifen gibt es in Visual Basic eine besondere Schleifenart, die Feldschleife. Sie durchläuft nicht einen bestimmten Wertebereich, sondern die Elemente eines Feldes vom ersten bis zum letzten. Das klappt auch mit mehrdimensionalen Feldern. Auch hier muss man eine Zählvariable angeben, die vom gleichen Typ sein muss wie die Elemente des Feldes. Bei jedem Schleifendurchlauf wird der Wert von jeweils einem Schleifenelement in die Zählvariable kopiert, sodass man den Wert bearbeiten kann. Nach dem Schleifendurchlauf wird der Wert wieder in das aktuelle Feldelement zurückgespeichert und ein neuer Durchlauf mit dem nächsten Feldelement gestartet.
Dim Feld() As Integer = {1, 2, 3, 4} Dim Wert As Integer 'die Zählvariable 'Berechnung For Each Wert In Feld Wert *= 5 Next 'Ausgabe Dim Text As String For Each Wert In Feld Text &= Wert & " " Next Console.WriteLine(Text)
5 10 15 20
Hier sehen wir zwei typische Einsatzbereiche für Zählschleifen. Zählschleifen eignen sich dafür, an allen Feldelementen die gleiche Operation durchzuführen. In der ersten Zählschleife wird etwa jeder der Werte im Feld verfünffacht. Ein weiteres Einsatzgebiet ist die geordnete Behandlung von einzelnen Feldelementen nacheinander. Hier werden die Werte in einer Zeichenkette zusammengefügt und dann zusammenausgegeben. (Außerdem wird jeweils ein Leerzeichen eingefügt, damit man die Werte voneinander unterscheiden kann. Sonst wäre die Ausgabe nämlich „5101520“.)
Die Feldschleife hat einen großen Vorteil gegenüber der normalen Schleife: Man braucht nicht immer den aktuellen Index anzugeben, dadurch wird der Code kürzer. Andererseits hat sie auch einige Nachteile: Man weiß den aktuellen Index nicht und kann nicht etwa auf benachbarte Feldelemente zugreifen. Deshalb wird die Feldschleife vor allem bei Auflistungen von voneinander unabhängigen Elementen eingesetzt, was vor allem beim Programmieren mit Objekten sehr häufig vorkommt.
Ein Tipp: Wie bei der Zählschleife können Sie auch bei der Feldschleife die Zählvariable am Schleifenanfang deklarieren. Nach Ende der Schleife gibt es die Variable dann nicht mehr, wodurch die Übersichtlichkeit erhöht wird und Fehler von vornherein verhindern werden können.
Funktionen
Oft gibt es in einem Programm wiederkehrende Aufgaben, für die man denselben Code verwenden kann. Stellen Sie sich einen Webbrowser vor, der immer wieder Dateien aus dem Internet herunterladen muss, seien es Webseiten, Bilder oder Archive. Intutitiv würde man den entsprechenden Code immer wieder verwenden. Nun stellen Sie sich vor, Sie haben im Download-Code einen Fehler gefunden. Nun müssten Sie jedes Vorkommnis des Fehlers suchen und ausmerzen, was anstrengend und langweilig ist und eine hohe Gefahr von Fehlern mit sich bringt, etwa dass Sie ein Vorkommen übersehen oder sich vertippen.
Zum Glück bietet Visual Basic eine Möglichkeit, wiederkehrende Aufgaben nur einmal zu definieren und eine Schnittstelle bereitzustellen, die unendlich oft wiederverwendet werden kann, um diese Aufgabe anhand des vorhandenen Codes zu lösen. Diese Möglichkeit heißt Funktion. Schauen Sie sich das folgende Beispiel an. Ihnen wird auffallen, dass jetzt wieder der komplette Code notiert wird, da wir nicht mehr nur Main() bearbeiten.
Module Beispiel
Sub Main()
Console.Write("Geben Sie eine Zahl ein: ")
Dim a As Integer = Console.ReadLine()
Do
a = 2 * a
If a = 4 Then End
Loop Until a > 100
End
End Sub
End Module
Probieren Sie einmal verschiedene Werte wie 5 oder 6 aus. (In solchen Fällen ist es oft eine gute Idee, sich vorher zu überlegen, was bei welchen Werten von a passieren müsste.) Beachten Sie, dass in diesem Fall keine Gültigkeitsprüfung für den eingegebenen Wert implementiert werde. Das heißt, dass nicht geprüft wird, ob überhaupt eine positive Zahl eingegeben wurde.
In diesem Code wird zweimal die End-Anweisung verwendet. Das ist soweit nicht schlimm. Stellen Sie sich nun vor, Sie wollen den Wert der Variablen a ausgeben, sobald man versucht, das Programm zu schließen. Sie müssten nun alle Vorkommnisse der End-Anweisung um einen solchen Befehl erweitern.
Module Beispiel
Sub Main()
Console.Write("Geben Sie eine Zahl ein: ")
Dim a As Integer = Console.ReadLine()
Do
a = 2 * a
If a = 4 Then
Console.WriteLine(a)
Console.ReadLine()
End
End If
Loop Until a > 100
Console.WriteLine(a)
Console.ReadLine()
End
End Sub
End Module
4 (für a = 1) 4 (für a = 2) 192 (für a = 3) 128 (für a = 4)
Auf den ersten Blick lassen sich der Code und insbesondere die Console.Writeline-Aufrufe nicht mehr so leicht überblicken, die ganze Sache wird unübersichtlich. Diese Unübersichtlichkeit verstärkt sich noch, wenn Sie mehrere solche Befehle hinzufügen, sodass das Beenden letzlich viele Zeilen umfasst. Nun werfen Sie einen Blick auf den folgenden Code, der genau das gleiche bewirkt:
Module Beispiel
Dim a As Integer
Sub Main()
Console.Write("Geben Sie eine Zahl ein: ")
a = Console.ReadLine()
Do
a = 2 * a
If a = 4 Then
beendeDasProgramm()
End If
Loop Until a > 100
beendeDasProgramm()
End Sub
Sub beendeDasProgramm()
Console.WriteLine(a)
Console.ReadLine()
End
End Sub
End Module
4 (für a = 1) 4 (für a = 2) 192 (für a = 3) 128 (für a = 4)
Nun wurde eine Funktion namens „BeendeDasProgramm“ definiert, ein wörtlich zu nehmender Befehl an den Computer, das Programm zu beenden. Zwischen Sub BeendeDasProgramm()
und End Sub
finden sich die Anweisungen, die das Programm korrekt beenden. Diese Anweisungen sind aus der Main()-Funktion verschwunden, stattdessen sehen wir BeendeDasProgramm()
. Dieser Befehl ruft die Funktion auf, d.h. es werden die Befehle und Anweisungen in der Funktion ausgeführt, dann setzt das Programm am Ursprungsort des Aufrufs fort.
Vielleicht ist Ihnen schon aufgefallen, dass die Deklaration der Variable hier nicht innerhalb der Funktion Main() passiert, sondern schon davor. Das hängt damit zusammen, dass die Variable a, wenn Sie in der Funktion Main() deklariert wird, auch nur da verfügbar ist. Sie werden dann innerhalb einer anderen Funktion, hier BeendeDasProgramm, nicht auf die Variable zugreifen können. In diesem Fall ist die Variable lokal, denn sie ist auf eine bestimmte Lokalität, hier die Funktion Main() beschränkt. Wenn eine Variable in mehreren Funktionen verfügbar sein soll, muss sie global deklariert werden. Das bedeutet, dass die Variable nicht an eine bestimmte Funktion gebunden ist, sondern in allen Funktionen verfügbar ist. Um eine Variable global zu deklarieren, notieren Sie die Dim-Anweisung zwischen Module Beispiel
und End Module
, aber nicht innerhalb einer Funktion. Der Übersicht halber werden die Dim-Anweisungen für globale Variablen meist ganz oben in die Klassendefinition, also direkt nach Module ...
, gestellt. Sie können die Deklarationen aber auch zwischen zwei Funktionen platzieren. Wir werden uns später noch einmal genauer und allgemeiner mit globalen und lokalen Variablen befassen.
Arbeit mit Funktionen
Das Beispiel zeigt, wie Funktionen definiert werden: Die erste Zeile der Funktion beginnt mit Sub
, gefolgt vom Funktionsnamen und einem leeren Klammerpaar. (Wir werden dieses Paar bald mit Leben füllen.) Für Funktionsnamen gelten die gleichen Regeln wie für Variablennamen: Erlaubt sind nur Buchstaben, Ziffern und Unterstriche, das erste Zeichen muss ein Buchstabe sein. Diese einleitende Zeile heißt Funktionskopf. Sie charakterisiert die Schnittstelle, über die die Funktion verwendet wird. Da dieses Verhalten ähnlich zur Variablendeklaration ist, die die Variable charakterisiert (über den Datentyp und evtl. die Feldgrenzen), heißt der Funktionskopf auch Funktionsdeklaration.
Nach dieser einleitenden Zeile folgt der sogenannte Funktionsrumpf. Er enthält alle Anweisungen und Befehle, die von der Funktion ausgeführt werden, sobald sie aufgerufen wird. Abschließend begrenzt die Anweisung End Sub
die Funktion nach unten hin. Da dieser Bereich das Verhalten der Funktion definiert, heißt der Funktionsrumpf auch Funktionsdefinition.
Der Inhalt von Main
Alle Funktionsaufrufe sind Befehle. Auch der Befehl System.Console.WriteLine
ist ein Funktionsaufruf. Als aufmerksamer Leser sollte sich Ihnen jetzt auffallen, dass Funktionsnamen keine Punkte enthalten dürfen, dieser es aber offensichtlicherweise tut. Das ist richtig und falsch. Die Funktion, die wir hier aufrufen, heißt nicht System.Console.WriteLine, sondern nur WriteLine. Ich habe ja eben bewusst gesagt, dass der Funktionsaufruf einen Namen enthält, „mit dem die Funktion eindeutig identifiziert wird“. Dieser Name heißt auch Funktionsbezeichner.
Denken Sie zur Erklärung an eines der ersten Kapitel, in dem es um das „Hello World“-Programm ging. .NET stellt uns tausende von Objekten mit zahlreichen Funktionen (im wahrsten Sinne des Wortes) zur Verfügung. Die Objekte sind in Namensräumen geordnet, die wiederum in Namensräumen geordnet sein können. Ähnlich ist es mit Dateien (Objekten), die in Ordnern (Namensräumen) geordnet werden, die wiederum in Ordnern geordnet werden können.
Dieser Ordnung gerecht werdend gehört auch die Funktion BeendeDasProgramm zum Objekt Beispiel. Vielleicht denken Sie jetzt, dass der Aufruf über den Bezeichner Beispiel.BeendeDasProgramm erfolgen müsste. Wenn Sie allein zu dieser Überlegung gelangt sind, haben Sie schon eines der wesentlichen Konzepte der Objektorientierten Programmierung (OOP) verstanden, dass jedoch erst viel später genauer ausgeführt wird. Dort werden Sie auch sehen, dass Beispiel.BeendeDasProgramm falsch ist; es müsste Me.BeendeDasProgramm heißen. Doch dafür muss man sich genauer mit OOP beschäftigen, was an dieser Stelle noch nicht das Thema sein soll. Für jetzt nur soviel: Da Main() zum selben Objekt wie BeendeDasProgramm gehört, nämlich zu Beispiel, reicht es, wenn man einfach nur den Funktionsnamen als Bezeichner verwendet, also die Funktion nur über den Bezeichner BeendeDasProgramm aufruft. Das wird bis zur Einführung in die OOP immer so sein, denn erst dann werden wir mehrere Objekte verwenden.
Parameter
Hier noch einmal der Code des Beispiels aus dem vorherigen Kapitel:
Module Beispiel
Dim a As Integer
Sub Main()
System.Console.Write("Geben Sie eine Zahl ein: ")
a = Console.ReadLine()
Do
a = 2 * a
If a = 4 Then
beendeDasProgramm()
End If
Loop Until a > 100
beendeDasProgramm()
End Sub
Sub beendeDasProgramm()
Console.WriteLine(a)
Console.ReadLine()
End
End Sub
End Module
4 (für a = 1) 4 (für a = 2) 192 (für a = 3) 128 (für a = 4)
Die Funktion BeendeDasProgramm erledigt eine ganz spezifische Aufgabe, nämlich die Ausgabe der Variable a, gefolgt von der Beendigung des Programms. Manchmal aber sollen Funktionen flexibel sein und auf bestimmte Situationen reagieren. Sie denken jetzt vielleicht an Bedingungen, doch ich meine etwas anderes. Denken Sie zum Beispiel an die Funktion MessageBox.Show. Das Meldungsfeld soll nicht immer den gleichen Text ausgeben, sondern jedesmal einen anderen Inhalt haben. Sie wissen im Prinzip auch schon die Lösung, nämlich, dass man den gewünschten Text im Klammernpaar hinter dem Funktionsbezeichner MessageBox.Show eintragen muss. Dieser Text heißt Parameter.
Eine Funktion kann aber nur Parameter annehmen, auf die Sie durch eine entsprechende Funktionsdeklaration und -definition vorbereitet ist. Wenn Sie im obigen Beispiel statt BeendeDasProgramm()
den Befehl BeendeDasProgramm(5)
eintragen, wird Ihnen Ihre Entwicklungsumgebung einen Fehler melden. Die Funktion weiß nämlich nichts mit dem Parameter anzufangen. Soll vielleicht 5 ausgegeben werden, oder der Wert von a fünfmal ausgegeben werden?
Es ist aber sehr einfach möglich, Parameter in die Funktionsdeklaration einzuschließen. Dazu benötigen wir aber einiges Vorwissen über das Wesen von Parametern. Im Funktionsaufruf sind Parameter wie Werte. Sie können als Literal angegeben werden, also z.B. als direkter Zahlenwert oder direkte Zeichenkette. Dieser Wert kann aber auch ein Ausdruck sein, also eine Kombination aus literalen Werten, Operatoren, Variablennamen und Klammern.
In der Funktion verhalten sich Parameter wie Variablen. Die Variablen werden beim Funktionsaufruf erstellt und mit den im Befehlsaufruf angegebenen Werten initialisiert. Man sagt, die Werte werden an die Funktion übergeben.
Das hört sich jetzt alles sehr theoretisch an. Deshalb möchte ich die ganze Sache jetzt mit einem Beispiel veranschaulichen. Dazu soll das obige Programm erweitert werden. Die Funktion BeendeDasProgramm soll einen Parameter erhalten, der den Endwert enthält, also den Wert, der ausgegeben werden soll. Dazu müssen wir zuerst die Funktionsdeklaration erweitern:
Der Parameter wird im Klammernpaar notiert. Die Schreibweise ähnelt der Dim-Anweisung, nur ohne Dim.
In der Klammer steht der Parametername, gefolgt von As und dem Datentyp. Alles wie gehabt. Der Parametername ist außerhalb der Funktion unerheblich und dient hier nur als Hilfe, damit man, wenn man den Funktionsaufruf aufschreibt, noch weiß, was der Parameter bedeutet. Innerhalb der Funktion verhält sich der Parameter dann wie eine Variable. Der Parametername wird zum Variablennamen. Man kann die Parametervariable wie eine normale Variable verwenden, auch Zuweisungen sind möglich.
So sieht dann die ganze Funktion aus. Anstatt der globalen Variable a wird nun der Parameter Endwert verwendet. Dies hat in der Praxis den Vorteil, dass die Funktion auch in einem anderen Kontext wiederverwendet werden kann. Sie erfordert jetzt nämlich nichts mehr, was sie nicht selbst deklariert. Hätte man die Funktion in der Fassung ohne Parameter in ein anderes Programm übernommen, wäre wahrscheinlich ein Fehler aufgetreten, da die Variable a nicht mit übernommen wurde oder, schlimmer noch, eine andere Variable a würde zweckentfremdet.
Natürlich erfordert das geänderte Verhalten der Funktion BeendeDasProgramm auch andere Funktionsaufrufe. So sieht der komplette Code des Programms aus.
Public Class Form1 Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load Dim a As Integer = InputBox("Bitte Zahl eingeben:") Do a = 2 * a If a = 4 Then BeendeDasProgramm(a) Loop Until a > 100 BeendeDasProgramm(a) End Sub Private Sub BeendeDasProgramm(Endwert As Integer) MessageBox.Show(Endwert) End End Sub End Class
4 (für a = 1) 4 (für a = 2) 192 (für a = 3) 128 (für a = 4)
Wie Ihnen sicher auffällt, ist a jetzt wieder eine lokale Variable von Form1_Load. Man sollte Variablen immer lokal und nur in notwendigen Fällen global definieren. Dies erfolgt aus bestimmten Aspekten heraus: Erstens ist (wie oben schon erläutert) die Funktion selbstständig und kann auch in andere Projekte übernommen werden, ohne dass Schwierigkeiten bei der Portierung auftreten. Zweitens bannt eine lokale Variable die Gefahr, dass eine andere Funktion auf etwas zugreift, auf dass sie nicht zugreifen soll. (Das ist Teil des Kapselungsprinzips, eines grundlegenden Merkmals der OOP.)
Eine Funktion kann natürlich mehrere Parameter enthalten. Die einzelnen Parameterdeklarationen werden durch Kommata separiert. Ein Beispiel liefert die Funktionsdeklaration von Form1_Load, die zwei Parameter deklariert.
ByVal und ByRef
Vielleicht hat ihre Entwicklungsumgebung beim Eintippen der Funktionsdeklaration von BeendeDasProgramm vor Endwert das Schlüsselwort ByVal hinzugefügt. ByVal ist bei Parameterdeklarationen optional. Manche IDE fügt es automatisch an, da ein Fehlen eines entsprechenden Schlüsselwortes dazu führt, dass der Standard ByVal angenommen wird. Das automatische Einfügen dient der Übersichtlichkeit, da man dann immer weiß, was für einen Parameter man hat. Neben ByVal gibt es auch das ByRef-Verhalten. Diese Schlüsselwörter legen fest, wie aus dem übergebenen Wert ein Parameter erstellt wird.
Um zu verstehen, was ByVal und ByRef bedeuten, muss man wissen, was Variablen sind. Variablen sind Zeiger. Sie enthalten die Adresse einer bestimmten Speicherzelle. Sie zeigen auf diese Zelle. Die Zelle wiederum enthält den Wert, der in der Variablen gespeichert ist. Im Bild rechts ist dies in der zweiten Zeile veranschaulicht.
Ist ein Parameter als ByVal gekennzeichnet, bedeutet dies, dass vom übergebenen Wert eine exakte Kopie im Speicher angelegt wird. Die Parametervariable zeigt auf diese Kopie. Wenn man innerhalb der Funktion die Parametervariable mit einem neuen Wert belegt, wirkt sich das nur auf die Kopie aus. Der eigentlich übergebene Wert wird nicht geändert.
Anders bei ByRef: Eine solche Parametervariable zeigt auf den ursprünglich übergebenen Wert. Wird in der Funktion der Wert der Parametervariable geändert, wirkt sich diese Änderung unmittelbar auch auf den eigentlich übergebenen Wert in der aufgerufenen Funktion aus.
Was muss man davon jetzt wissen? Nur soviel: ByVal-Parameter kann man beliebig ändern, ohne dass die aufrufende Funktion etwas davon merkt. Änderungen an ByRef-Parametern übertragen sich jedoch auf den aufrufenden Wert. Ein Beispiel soll das verdeutlichen.
Public Class Form1 Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load Dim a As Integer = 5 Funktion_Mit_ByVal(a) MessageBox.Show("In Form1_Load nach ByVal: " & a) Funktion_Mit_ByRef(a) MessageBox.Show("In Form1_Load nach ByRef: " & a) End Sub Private Sub Funktion_Mit_ByVal(ByVal Wert As Integer) MessageBox.Show("In ByVal vor Erhöhung: " & Wert) Wert += 1 MessageBox.Show("In ByVal nach Erhöhung: " & Wert) End Sub Private Sub Funktion_Mit_ByRef(ByRef Wert As Integer) MessageBox.Show("In ByRef vor Erhöhung: " & Wert) Wert += 1 MessageBox.Show("In ByRef nach Erhöhung: " & Wert) End Sub End Class
In ByVal vor Erhöhung: 5 In ByVal nach Erhöhung: 6 In Form1_Load nach ByVal: 5 In ByRef vor Erhöhung: 5 In ByRef nach Erhöhung: 6 In Form1_Load nach ByRef: 6
Was ist passiert? Nachdem a mit 5 belegt wurde, wird die ByVal-Funktion aufgerufen. Dabei wird eine lokale Kopie des Wertes von a erstellt. Der Wert dieser Kopie wird dann um 1 erhöht. (Meldungen 1 und 2) Auf die Variable a wirkt sich die Erhöhung nicht aus, da die Erhöhung an einer Kopie vorgenommen wurde. a ist gleich geblieben (Meldung 3). Nun wird die ByRef-Funktion aufgerufen. Die Parametervariable verwendet keine Kopie des Wertes von a, sondern den Wert von a selber. Deshalb wirkt sich die Erhöhung in der ByRef-Funktion (Meldungen 4 und 5) auch auf die Variable a aus, denn sowohl a als auch die ByRef-Parametervariable benutzen dieselbe Speicherzelle.
Felder als Parameter
Manchmal (meist sind diese Fälle sehr selten) möchte man auch ein Feld als Parameter übergeben. Das ist möglich, unter einer Einschränkung: Sie dürfen keine Feldgrenzen angeben. Außerdem verschiebt sich das Klammerpaar, das bei Felddeklarationen für gewöhnlich hinter dem Feldnamen steht, hinter den Datentyp. Das folgende Beispiel zeigt zwei mögliche Funktionsdeklarationen. Beachten Sie, dass innerhalb der aufgerufenen Funktion die Größe des Feldes zunächst unbekannt ist. Mithilfe der GetLowerBound- und GetUpperBound-Funktion kann die Feldgröße jedoch bestimmt werden.
Private Sub Feldfunktion(ByVal EindimensionalesFeld As Integer(), ByVal ZusatzInformation As Integer) Private Sub Mehrdimensional(ByVal DreidimensionalesFeld As Integer(,,))
Rückgabewerte
Funktionen dienen oft der Berechnung eines Wertes, zum Beispiel verfügt das .NET-Framework im Namespace System.Math über viele mathematische Funktionen für Logarithmen, Trigonometrie, etc. Diese Funktionen benötigen nicht nur bestimmte Werte, sondern liefern auch einen Wert zurück. Wie bei Operatoren auch heißt dieser Wert Rückgabewert.
An dieser Stelle geht Visual Basic einen anderen Weg als viele andere Programmiersprachen. In C etwa heißt eine Funktion immer „Funktion“. In Visual Basic heißt eine Funktion nur dann „Funktion“, wenn sie einen Rückgabewert hat. Eine Funktion ohne Rückgabewert heißt „Prozedur“. Eine Prozedur erkennt man am Schlüsselwort Sub nach Private in der ersten Zeile und nach End in der letzten Zeile. In Funktionen steht nicht Sub, sondern Function. (Sub kommt übrigens vom englischen „subroutine“, was „Unterprogramm“ bedeutet.)
Allerdings werden, auch von Programmierern, sowohl Funktionen als auch Prozeduren als „Funktion“ bezeichnet, vor allem da auch andere Programmiersprachen da keinen Unterschied machen. Deshalb wird auch in diesem Buch der Begriff „Funktion“ auch für Prozeduren verwendet.
Ist eine Funktion erst einmal eine Function, muss auch ein Rückgabetyp benannt werden. Der Rückgabetyp ist der Datentyp, den der Rückgabewert haben muss. Durch die Festlegung eines Rückgabetyps wird sichergestellt, dass die aufrufende Funktion mit dem Rückgabewert fehlerfrei operieren kann. Um einen Rückgabetyp festzulegen, hängen Sie an die Funktionsdeklaration einfach As und den Datentyp an. Ein Beispiel: Im Folgenden sehen Sie eine Prozedur mit zwei Parametern, darunter eine Funktion mit den gleichen Parametern, aber (da es eine Funktion ist) außerdem einem Rückgabetyp.
Private Sub MeineProzedur(ByRef Daten As Integer(), ByVal Operation As String) Private Function MeineFunktion(ByRef Daten As Integer(), ByVal Operation As String) As Boolean
Sehr häufig bei selbstgeschriebenen Funktionen sind (wie im obigen Beispiel) Boolean-Rückgabetypen. Dabei gibt der Rückgabewert an, ob die Operationen, die die Funktion durchführt, erfolgreich verlaufen sind. (Das ist nicht immer so klar, wie es jetzt scheint, vor allem wenn Sie mit so instabilen Objekten wie Dateien oder Netzwerkverbindungen arbeiten.) Ist alles glatt gelaufen, wird True zurückgegeben, sonst False.
Rückgabe
Damit wären wir auch gleich beim nächsten Problem. Wie kann man nun einen Wert zurückgeben? Zum Glück geht das ganz einfach mit der Return-Anweisung. Jetzt ist der Zeitpunkt für ein praktisches Beispiel gekommen. Eine Funktion soll einen Wert verdoppeln und das Ergebnis zurückgeben. Der folgende Code löst dieses Problem.
Public Class Form1 Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load Dim a As Integer = 2 Dim b As Integer = Verdoppeln(a) MessageBox.Show(b) End Sub Private Function Verdoppeln(ByVal Zahl As Integer) As Integer Dim Doppel As Integer = 2 * Zahl Return Doppel End Function End Class
4
Die Funktion Verdoppeln wird von Form1_Load aus aufgerufen, für den Parameter Zahl wird der Wert von a, 2, übergeben. In der Funktion wird zunächst die Verdopplung durchgeführt und das Ergebnis, in diesem Falle 4, in der Variablen Doppel zwischengespeichert. Dann kommt das eigentlich Interessante, die Return-Anweisung. Die besteht, wie man leicht sieht, aus dem Schlüsselwort Return und dem Rückgabewert, hier der Wert von Doppel, also in diesem Falle 4.
Jeder mögliche Ausdruck kann mit Return zurückgegeben werden, also etwa ein Literal, ein Variablenwert (wie oben) oder auch das Ergebnis einer oder mehrerer Operationen. Daher kann man die Funktion Verdoppeln auch kürzer schreiben, indem man das Ergebnis der Multiplikationsoperation direkt zurückgibt.
Rückgabewerte können wie Variablenwerte oder Literale verwendet werden, z.B. kann der Rückgabewert von Verdoppeln direkt als Parameter an die MessageBox.Show-Funktion verwendet werden, sodass sich die Funktion Form1_Load wie folgt verkürzt:
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load MessageBox.Show(Verdoppeln(2)) End Sub
4
Manchmal ist es vonnöten, an die aufrufende Funktion mehrere Werte zurückzugeben. Eine entsprechende Möglichkeit über mehrere Rückgabewerte ist in Visual Basic .NET wie auch in anderen Programmiersprachen nicht vorgesehen, man kann sie jedoch über ByRef-Parameter in einer Prozedur nachbauen, z.B.:
Private Sub ZweiRückgabewerte(ByVal Wert1 As Integer, ByVal Wert2 As Integer, ByVal Daten As String, ByRef Ergebnis1 As Boolean, ByRef Ergebnis2 As Integer)
Hier werden drei Datenparameter übergeben, die die für die Funktion erforderlichen Daten enthalten. Sie heißen Wert1, Wert2 und Daten. Die Rückgabeparameter Ergebnis1 und Ergebnis2 geben die Ergebnisse an die aufrufende Funktion zurück. Die Rückgabewerte werden auf den Variablen durch einfache Zuweisungsoperationen gespeichert.
Noch ein Tipp: Man muss den Rückgabewert von Funktionen nicht innerhalb der aufrufenden Funktion verwerten. Man kann eine Funktion auch wie eine Prozedur aufrufen. Zum Beispiel hätte in der Form1_Load-Funktion oben auch folgender Befehl stehen können:
Der Rückgabewert verschwindet dabei auf Nimmerwiedersehen. In diesem Fall ist das natürlich sinnlos, denn die einzige Aufgabe der Funktion ist die Berechnung des Rückgabewertes. Allerdings gibt es auch Funktionen, in denen der Rückgabewert nicht immer von Belang ist. Ein Beispiel ist die MessageBox.Show-Funktion, deren Funktionsdeklaration wie folgt aussieht:
Wundern Sie sich nicht über die zwei Schlüsselwörter am Anfang. Public ist eine andere Option neben Private. Diese Optionen regeln die Sichtbarkeit von Funktionen aus anderen Objekten heraus. Bis jetzt verwenden wir nur ein Objekt, nämlich Form1. Daher ist für uns die Funktionssichtbarkeit unerheblich, wir verwenden aus Einfachheitsgründen immer Private. Das Shared regelt die Zugehörigkeit innerhalb des Objektes. Auf diesen und den Aspekt der Funktionssichtbarkeit gehen wir später genauer ein.
Interessant ist jetzt nur die Tatsache, dass die MessageBox.Show-Funktion einen Rückgabewert hat, nämlich ein System.Windows.Forms.DialogResult-Objekt. (Hier nur als DialogResult, da sich das MessageBox-Objekt im selben Namensraum befindet.) DialogResult fällt bei der Hauptaufgabe der MessageBox.Show-Funktion, der Darstellung eines Hinweisfeldes, als Nebenprodukt ab. Es enthält Informationen, welcher Knopf auf dem Hinweisfeld gedrückt wurde. Es gibt nämlich neben dem Standardfeld mit dem einzelnen Knopf OK, dessen Einsamkeit weitere Ergebnisprüfungen obsolet macht, noch andere, Ihnen aus der Arbeit mit Windows wahrscheinlich wohlbekannte Varianten wie Ja/Nein, Ja/Nein/Abbrechen oder Abbrechen/Wiederholen/Ignorieren. In Kürze werden wir auf diese Varianten genauer eingehen.
Frühzeitiges Verlassen
Manchmal möchte man nicht, dass alle Befehle in einer Funktion ausgeführt werden. Dann kann man die Funktion vorzeitig verlassen. Dafür gibt es zum Glück nicht noch eine extra Anweisung. Das übernimmt ebenfalls die Return-Anweisung. Ein Aufruf der Return-Anweisung sorgt dafür, dass die Funktion sofort verlassen wird. Bis jetzt ist uns das noch nicht aufgefallen, weil die Return-Anweisung immer der letzte Befehl in unseren Funktionen war.
Funktionen erfordern, dass jeder Aufruf irgendwann in einer Return-Anweisung endet. Schließlich brauchen wir ja einen Rückgabewert. Bei Prozeduren ist eine Return-Anweisung eigentlich nicht nötig (wie die Form1_Load-Prozedur beweist), allerdings ist auch hier die Benutzung der Return-Anweisung möglich. Dann steht das Return aber alleine, einen Rückgabewert hat eine Prozedur schließlich nicht. (Analogie: Return-Anweisungen in einer Funktion müssen einen Rückgabewert haben.)
Ein kleines Beispiel soll die Auswirkungen der Return-Anweisung verdeutlichen.
Public Class Form1 Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load Testprozedur() End Sub Private Sub Testprozedur() MessageBox.Show("Hallo") Return MessageBox.Show("Welt!") End Sub End Class
Hallo
Hier wird die Testprozedur aufgerufen. Nach der Ausgabe "Hallo" wird die Return-Anweisung aufgerufen und damit die Prozedur verlassen. Deshalb wird der zweite Aufruf der MessageBox.Show-Funktion nicht mehr ausgeführt.
Drei Anmerkungen noch: Erstens können natürlich innerhalb einer Funktion beliebig viele Return-Anweisungen stehen, vor allem bei verzweigten Bedingungsbäumen. Drittens gibt es noch eine zweite Möglichkeit, Funktionen zu verlassen und Rückgabewerte zu speichern. Statt Return schreibt man Exit Function bzw. Exit Sub. Den Rückgabewert muss man vorher einer Pseudovariable zuweisen, die genauso heißt wie die Funktion. (Also zum Beispiel MeineFunktion = 5.)
Überladung
Erneut wollen wir das Beispiel des vorigen Kapitels um einen weiteren Aspekt erweitern. Jetzt wird die Funktion überladen.
Public Class Form1 Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load MessageBox.Show(Verdoppeln(2)) MessageBox.Show(Verdoppeln(2.4)) End Sub Private Function Verdoppeln(ByVal Zahl As Integer) As Integer Return 2 * Zahl End Function End Class
4 4
Wahrscheinlich erwarten Sie jetzt die Ausgaben 4 und 4,8, doch in Wahrheit wird zweimal 4 ausgegeben. Der Parameter der Funktion Verdoppeln muss vom Typ Integer sein, 2.4 ist jedoch ein Double-Literal. Damit kein Fehler erzeugt wird, wird der Double-Wert in einen Integer-Wert umgewandelt, also eine Ganzzahl. Aus 2.4 wird 2. Dieser Wert wird an die Funktion übergeben, welche diesen korrekt verdoppelt (mit dem Ergebnis 4). Trotzdem ist das Ergebnis falsch, denn wir beabsichtigten nicht, 2 zu verdoppeln, sondern 2,4.
Die offensichtliche Lösung für dieses Problem ist, in der Funktionsdeklaration „Integer“ durch „Double“ zu ersetzen. Das löst das Problem: Beide Funktionsaufrufe im folgenden Beispiel arbeiten korrekt.
Public Class Form1 Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load MessageBox.Show(Verdoppeln(2)) MessageBox.Show(Verdoppeln(2.4)) End Sub Private Function Verdoppeln(ByVal Zahl As Double) As Double Return 2.0 * Zahl End Function End Class
4,0 4,8
In der Funktion Verdoppeln habe ich die 2 (Typ Integer) durch eine 2.0 (Typ Double) ersetzt, da Operationen mit zwei gleichen Datentypen generell schneller sind. Die Lösung mit Double-Parameter und -Rückgabetyp ist allerdings nicht die feine Art, da sie die Sache unnötig verkompliziert. Wenn man nämlich wie beim ersten Funktionsaufruf einen Integer-Literal übergibt, muss dieser erst aufwändig in Double umgewandelt werden. Will man das Ergebnis dann noch auf einer Integer-Variable speichern oder an einen Integer-Parameter übergeben, wird Double nochmals zurück konvertiert.
Zusammengefasst: Integer-Parameter und -Rückgabetyp bedeuten weniger Rechenzeit, dafür oft falsche Ergebnisse. Double-Parameter und -Rückgabetyp bedeuten immer richtige Ergebnisse, aber unnötigen Rechenaufwand. Wie kann man nun das Beste beider Varianten miteinander verbinden? So:
Public Class Form1 Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load MessageBox.Show(Verdoppeln(2)) MessageBox.Show(Verdoppeln(2.4)) End Sub Private Function Verdoppeln(ByVal Zahl As Integer) As Integer Return 2 * Zahl End Function Private Function Verdoppeln(ByVal Zahl As Double) As Double Return 2.0 * Zahl End Function End Class
4 4,8
Wenn Sie jetzt doppelt sehen, sehen Sie absolut richtig. Jetzt haben wir nämlich zwei Funktionsdeklarationen und -definitionen. Es handelt sich aber um dieselbe Funktion. Jetzt haben wir die Funktion überladen. Das bedeutet, dass mehrere Varianten von Parametern zur Verfügung stehen. Diese Varianten heißen ebenfalls Überladungen. In der ersten Überladung wird hier ein Integer-Parameter übergeben und ein Integer-Wert zurückgegeben, die zweite Überladung benutzt Double-Werte. Der Visual-Basic-Compiler erkennt beim Erstellen des Programmes automatisch, welche der Überladungen für welchen Funktionsaufruf benutzt werden soll. Manchmal weiß aber auch der Compiler nicht, welche Überladung die richtige ist. Falls Sie ein Beispiel wollen, ersetzen Sie in der zweiten Überladungen „Double“ durch „Single“. Der zweite Funktionsaufruf stellt den Computer dann vor ein Dilemma: Soll er von Double zu Single oder von Double zu Integer umwandeln (vor allem weil in beiden Fällen der Wertebereich reduziert wird)?
Alternative: Optionale Parameter
Auch in den von .NET mitgelieferten Funktionen wird Überladung gerne und häufig eingesetzt. Ein Beispiel ist die MessageBox.Show-Funktion, die über nicht weniger als 21 Überladungen verfügt. Sie kennen bis jetzt die folgenden beiden Überladungen:
Show(text As String) As DialogResult Show(text As String, caption As String) As DialogResult
Hier sieht man eine weitere nützliche Eigenschaft von Überladungen: Sie können unterschiedlich viele Parameter haben. Eine Überladung kann auch ohne Parameter auskommen. (Natürlich darf es pro Funktion nur höchstens eine Überladung ohne Parameter geben, wegen der Eindeutigkeit.)
Oft werden Überladungen deshalb für zusätzliche optionale Parameter verwendet. Oben ist der Parameter caption optional. Das heißt, man kann die Funktion unter Angabe dieses Parameters aufrufen, kann den Parameter aber weglassen. Dann wird meist ein Standardwert verwendet (in diesem Fall wird für caption, den Titel des Hinweisfensters, der Name des aufrufenden Programms benützt). In schlechten Programmen (und davon gibt es leider ziemlich viele) wird in der Überladung ohne den optionalen Parameter nur die andere Überladung aufgerufen, wobei für den optionalen Parameter der Standardwert eingesetzt wird. Zum Beispiel würde eine Implementation eines Hinweisfeldes dann so aussehen (hier ist der Standardwert für den optionalen Parameter ""
, also eine leere Zeichenkette):
Private Sub Hinweisfeld(ByVal Text As String) Hinweisfeld(Text, "") End Sub Private Sub Hinweisfeld(ByVal Text As String, ByVal Titel As String) MessageBox.Show(Text, Titel) End Sub
Solche optionalen Parameter kann man aber auch einfacher haben. Mit Optional-Parametern (Optional ist ein Schlüsselwort) gab es in Visual Basic nämlich lange vor Überladungen eine einfache Implementationsmöglichkeit, die man sich auch hier zu Nutze machen kann:
Private Sub Hinweisfeld(ByVal Text As String, Optional ByVal Titel As String = "") MessageBox.Show(Text, Titel) End Sub
Hier ist der Parameter Titel als Optional deklariert. Wenn für ihn kein Wert übergeben wird, wird der Standardwert verwendet. Der Standardwert (hier ""
) wird dem Parameter, wie oben zu sehen, quasi zugewiesen. Das hat jedoch nichts mit der Zuweisungsoperation zu tun, sondern ist Teil der Visual-Basic-Sprachsyntax.
Eine Funktion kann beliebig viele Optional-Parameter haben, jedoch darf nach einem solchen optionalen Parameter kein weiterer nicht-optionaler Parameter folgen. Kurz: Alle optionalen Parameter müssen hinter allen nicht-optionalen stehen. Theoretisch kann man auch Überladungen und optionale Parameter mischen, dies sollten Sie aber tunlichst vermeiden, da sich beide Konzepte gegenseitig verkomplizieren.
Rekursion und Iteration
Vor allem bei mathematischen Funktionen unterscheidet man zwischen zwei grundlegenden Arbeitsweisen: Iteration und Rekursion. Dieses Kapitel soll eine Einführung in Vor- und Nachteile beider Wege sein.
So wie dieses Buch mit dem obligatorischen „Hello World“-Beispiel begonnen hat, so werden wir auch hier ein obligatorisches Beispiel bemühen: die Fakultätsfunktion. In der Mathematik ist die Fakultät einer positiven, ganzen Zahl n (geschrieben „n!“) die Multiplikation aller Zahlen von 1 bis n miteinander. Zum Beispiel:
Rekursion
Visual Basic .NET besitzt wie fast alle modernen Programmiersprachen die erstaunliche Möglichkeit, aus einer Funktion heraus die gleiche Funktion nochmals aufzurufen. Zum Beispiel kann sich innerhalb einer Funktion Test ein Befehl Test()
befinden. Das machen wir uns mit Rekursion zunutze.
Der Ansatz der Rekursion ist nämlich, ein vorliegendes Problem (hier: „Was ist die Fakultät von n?“) auf ein einfacheres Problem zu reduzieren. Das nächsteinfachere Problem ist hier: „Was ist die Fakultät von n-1?“ Bei einem Blick auf die obigen Beispiele fällt auf, dass die Fakultät von 6 genau das sechsfache der Fakultät von 5 ist. Das ist logisch, schließlich fehlt bei 5! genau der Faktor 6. Allgemein ist also:
Damit haben wir das erreicht, was unser Ziel war. Wir haben das Ausgangsproblem auf ein einfacheres Problem reduziert. Dieser Vorgang heißt Rekursionsschritt. So sieht der Rekursionsschritt in Code aus:
Der Rückgabewert der Funktion FakRek (also die Fakultät von n) ist das n-Fache des Rückgabewertes der Funktion FakRek mit dem Parameter „n - 1“ (also die Fakultät von n - 1). Versuchen Sie nun, diese Funktion in einem Ihrer Programme aufzurufen, zum Beispiel mit FakRek(6)
. Problem: Das Programm wird sich festfahren und irgendwann abgebrochen, weil der Speicher alle ist oder ein Überlauf auftritt.
Was ist passiert? Wenn Sie FakRek(6)
aufrufen, wird der Ausdruck hinter Return ausgewertet. Dazu wird FakRek(5)
aufgerufen. Damit dieser Aufruf seinen Rückgabewert berechnen kann, wird FakRek(4)
aufgerufen. Damit dieser Aufruf seinen Rückgabewert berechnen kann, wird FakRek(3)
aufgerufen. Damit dieser Aufruf seinen Rückgabewert berechnen kann, wird FakRek(2)
aufgerufen... Ich glaube, Sie verstehen, worauf ich hinaus will. Beim Versuch, die Return-Ausdrücke auszuwerten, verzettelt sich Visual Basic in Funktionsaufrufen, die immer neue Funktionsaufrufe starten. Wir haben es also mit einer Endlosschleife zu tun. Da auch Funktionsaufrufe Speicherplatz (in einem speziellen Speicherbereich) benötigen, ist der irgendwann voll. Alternativ kann es schon vorher passieren, dass der Parameter so negativ wird, dass er nicht mehr auf Integer gespeichert werden kann, es kommt zum erwähnten Überlauf.
Abhilfe schafft ein Rekursionsanfang. Den benutzen wir, sobald das Problem auf das einfachste denkbare Problem reduziert wurde, um die Rekursion abzubrechen. Im Falle der Fakultät ist das einfachste Problem die Fakultät von 1, die ist 1. Ein zweites Kriterium für den Rekursionsanfang ist, dass sich alle Probleme durch Rekursionsschritte auf diesen Anfang reduzieren lassen. Auch das ist gegeben, da wir nur positive Zahlen benutzen. Wenn die kleiner werden, kommen wir garantiert irgendwann bei 1 an. So sieht das ganze dann aus:
Private Function FakRek(ByVal n As Integer) As Integer If n = 1 Then Return 1 'Rekursionsanfang Else Return n * FakRek(n - 1) 'Rekursionsschritt End If End Function
Wenn n gleich 1 ist, befinden wir uns am Rekursionsanfang, der Rückgabewert wird als absoluter Wert (Literal) angegeben. Ansonsten wird der Rekursionsschritt benutzt, um das bestehende Problem zu vereinfachen.
Man kann das Beispiel auch kürzer schreiben, indem man die einzeilige Fassung der If-Anweisung verwendet. Dabei nutzen wir aus, dass die Return-Anweisung die Ausführung der Funktion beendet, nachfolgende Befehle werden nicht ausgewertet. Im Falle des Rekursionsanfangs wird hier also der Rekursionsschritt nicht ausgeführt:
Private Function FakRek(ByVal n As Integer) As Integer If n = 1 Then Return 1 'Rekursionsanfang Return n * FakRek(n - 1) 'Rekursionsschritt End Function
Ein verbleibendes Problem ist der für die Funktion zu große Wertebereich. Bei Werten von 0 und kleiner verpassen wir den Rekursionsanfang und landen wieder in der Endlosschleife. Indem Sie den Parameterdatentyp in UInteger ändern, lässt sich dieses Problem umgehen. Die Fakultät von 0 wird allgemein auch mit 1 definiert. Verschieben Sie den Rekursionsanfang von 1 nach 0, lässt sich auch diese letzte Lücke schließen.
Private Function FakRek(ByVal n As UInteger) As UInteger If n = 0 Then Return 1 'Rekursionsanfang Return n * FakRek(n - 1) 'Rekursionsschritt End Function
Iteration
Warum erzähle ich das alles? Weil es noch ein alternatives Modell gibt: die Iteration. Während die Rekursion komplexe Probleme zu einfachen Problemen auflöst, löst Iteration zuerst das einfachste Problem und löst, darauf bauend, komplexe Probleme. Es handelt also in aller Hinsicht um den totalen Gegenentwurf.
Als Ausgangspunkt verwenden wir bei der Iteration das einfachste Problem, nämlich die Fakultät von Null (= 1). Nun gehen wir nach und nach zum nächstkomplexeren Problem über, also erst zur Fakultät von 1, dann zu der von 2, und so weiter. Das setzen wir fort, bis das konkrete Problem erreicht ist und die Lösung vorliegt. So sieht der Code für die iterative Implementation der Fakultätsfunktion aus:
Private Function FakIter(ByVal n As Integer) As Integer Dim Temp As Integer = 1 'die Fakultät von 0 als Ausgangspunkt Dim Crnt As Integer = 0 'zurzeit berechnen wir die Fakultät von 0 (Crnt = Current = zurzeit) Do While Crnt < n 'die gewünschte Fakultät ist noch nicht erreicht Crnt += 1 'wir berechnen die nächsthöhere Fakultät Temp *= Crnt 'Berechnung der nächsthöheren Fakultät 'Temp enthält die aktuelle Fakultät (i-1)!, Crnt enthält i Loop Return Temp 'nach der Schleife liegt auf Temp die gewünschte Fak. vor End Function
Vergleich
Rekursion und Iteration haben verschiedene Vorteile, die in ihrem Ausmaß nicht zu unterschätzen sind. Die Rekursion ist, wie auch aus den obigen Beispielen ersichtlich wird, sehr einfach und intuitiv implementierbar. Negativ fallen die lange Laufzeit und der größere Speicherbedarf auf. Dies ergibt sich durch die andauernden Funktionsaufrufe. Die Iteration kennt diese Probleme nicht. Mit ihr lassen sich auch komplexe Probleme in kurzer Zeit berechnen. Das ist vor allem für zeitkritische oder aufwändige Berechnungen von Bedeutung. Der Nachteil dieser Methode ist die komplexere Implementierung.
Alles in allem ist Iteration empfehlenswerter als Rekursion. Sie erfordert zwar mehr Arbeit, die investierte Zeit zahlt sich jedoch aus.
Übung
Fibonacci-Folge
Ein anderes, etwas komplexeres Beispiel für Rekursion und Iteration ist die Fibonacci-Folge. Sie berechnet sich wie folgt: Die ersten zwei Glieder (Index 1 und 2) sind 1, jedes andere Glied ergibt sich als Summe der zwei vorhergehenden Glieder.
Index | Wert |
---|---|
1 | 1 |
2 | 1 |
3 | 1 + 1 = 2 |
4 | 1 + 2 = 3 |
5 | 2 + 3 = 5 |
6 | 3 + 5 = 8 |
... |
Ihre Aufgabe ist es nun, die Fibonacci-Folge rekursiv und iterativ zu implementieren. Eine Lösung mit Erklärungen steht für Sie bereit.
Lösung der Aufgabe zu Rekursion und Iteration
Private Function FiboRek(ByVal Value As Integer) As Integer If Value = 1 Or Value = 2 Then Return 1 Return FiboRek(Value - 1) + FiboRek(Value - 2) End Function Private Function FiboIter(ByVal Value As Integer) As Integer If Value = 1 Or Value = 2 Then Return 1 Dim Werte(Value) As Integer Werte(1) = 1 Werte(2) = 1 For Zähler As Integer = 3 To Value Werte(Zähler) = Werte(Zähler - 1) + Werte(Zähler - 2) Next Return Werte(Value) End Function
Erklärung
Die rekursive Implementierung ist auch hier die einfachere. Zuerst definieren wir mit den gegebenen Basiswerten den Rekursionsanfang: Das erste und zweite Glied der Fibonaccifolge sind 1. Davon ausgehende Werte errechnen sich als die Summe der beiden vorhergehenden Werte; das n-te Glied der Fibonaccifolge ist die Summe des (n-1)-ten Gliedes und des (n-2)-ten Gliedes.
Die iterative Implementierung ist da schon komplexer. Auch hier die beiden Elemente 1 und 2 sofort zurückzugeben, hat sich für die folgende Implementation als am zweckmäßigsten erwiesen. Da, anders als bei der Fakultät, ein Wert zweimal gebraucht wird, nämlich für die Berechnung der zwei nächsten Werte, ist es nicht möglich, eine einzelne Variable als Rechenraum zu nutzen. Ich habe mich hier deshalb für ein Feld entschieden. Das Element 0 des Feldes wird nicht genutzt, um den Code einfach zu halten. Element 1 und 2 werden mit den geg. Werten belegt. Die folgenden Felder werden mit einer Schleife berechnet, die schrittweise von den einfacheren zu den komplexeren Problemen übergeht. In der Schleife werden dann die einzelnen Werte so berechnet, dass das Element n des Feldes immer das n-te Glied der Fibonaccifolge ist. Zum Schluss wird dann das gesuchte Element zurückgegeben.
Typumwandlungen
Nicht immer ist man mit dem zufrieden, was man hat. Man hat einen Integer, braucht die Zahl aber als String, um sie in eine Datei schreiben zu können. Man hat von einer DLL-Funktion einen Boolean zurückbekommen, der angibt, ob alles glatt gelaufen ist, man will statt False und True jedoch 0 und 1 in eine Byte-Variable speichern. Manchmal ist man aber doch zufrieden mit dem, was man hat. Mit Visual Basic .NET können Sie demnach zufrieden sein, denn mit Visual Basic .NET haben Sie Typumwandlungen, explizite und implizite.
Typumwandlungen wandeln einen Typ in den anderen um. Das heißt, dass sie mit Typumwandlungen Ihren Integer in einen String und Ihren Boolean in ein Byte umwandeln können. Doch schon mit der Begrifflichkeit fangen die Probleme an: Die Typumwandlung wird auch „Konvertierung“ (lat. convertere = umwenden, verwandeln) genannt, entsprechend spricht man vom „Umwandeln“ und „Konvertieren“. Aus der C- und C++-Gemeinde kommt noch der Begriff „Cast“ für Umwandlung. Das Verb „casten“ wird nicht nur wie „umwandeln“ mit der Präposition „in“ (Umwandlung von Integer in String), sondern auch mit „auf“ verwendet (Casten von Integer auf String). Auch „nach“ kommt vor (Konvertierung von Integer nach String).
Man unterscheidet zwischen impliziter und expliziter Typumwandlung. Implizite Typumwandlung ist, wenn man selber nicht dran denkt und der Compiler die Typumwandlung einfügen muss. Explizite Typumwandlung ist, wenn der Computer nicht weiß, was er umwandeln soll oder dass er überhaupt umwandeln soll. Offensichtlicherweise ist implizite Umwandlung einfacher, da man da nichts tun muss, so wie in dem folgenden Beispiel.
Die MessageBox.Show-Funktion akzeptiert als ersten Parameter nur String-Variablen. Da 5 aber ein Integer-Literal ist, tritt die implizite Typumwandlung in Kraft. 5 wird (zur Laufzeit) von Integer (5) in String ("5") umgewandelt. Dieser Mechanismus klappt relativ gut, so dass Sie nur dann eingreifen sollten, wenn die automatische (implizite) Typumwandlung nicht funktioniert oder wenn sich ein Problem durch explizite Typumwandlung eleganter lösen lässt.
Die C-Funktionen
Sollten Sie sich zu expliziter Typumwandlung entschließen, werden Sie schnell Bekanntschaft mit den C-Funktionen machen. Das sind eine Reihe von Funktionen, die in Visual Basic .NET fest definiert sind, weshalb ihre Namen auch Schlüsselwörter sind. Ich nenne sie hier zusammenfassend „C-Funktionen“, da alle mit C beginnen (für engl. convert = umwandeln). Die Typumwandlungsfunktionen verwenden Sie wie normale Funktionen. Alle C-Funktionen verlangen einen Parameter vom Typ Object. Object (eigentlich System.Object) kann alles sein, wirklich alles. Wie das möglich ist, sehen wir später, das hat mit objektorientierter Programmierung (genauer Vererbung und Polymorphie) zu tun. Alle C-Funktionen haben dann einen spezifischen Rückgabetyp, nämlich den Typ, in den umgewandelt wird. Der Name der konkreten C-Funktion orientiert sich an diesem Zieltyp. Zum Beispiel konvertiert CInt in Integer und CDbl in Double. Visual-Basic-6-Programmierern werden viele Namen bekannt vorkommen. (Allerdings haben sich auch einige Namen geändert; CBln heißt jetzt etwa CBool.) Diese Liste zeigt alle C-Funktionen:
|
|
|
Die C-Funktionen haben allerdings auch einige Einschränkungen, was den Ausgangsdatentyp angeht (aus logischen Gründen): Zum Beispiel akzeptiert CChar nur Char- und String-Werte. Achtung auch bei CStr: Die Konvertierung von Date-Werten erfolgt nach dem aktuellen Gebietsschema; es wird also das Datum in der landesüblichen Schreibweise ausgegeben.
Spezifische Funktionen
System.Convert
Völlig äquivalent zu den C-Funktionen verhalten sich die Funktionen in System.Convert. Der Name der Funktion setzt sich zusammen aus „To“ und dem Datentyp, zum Beispiel konvertiert die System.Convert.ToInteger-Funktion in Integer, die System.Convert.ToBoolean-Funktion in Boolean. Es wird allerdings empfohlen, nicht die Convert-Funktionen, sondern die C-Funktionen zu verwenden, da diese besser auf Visual Basic abgestimmt sind als die Convert-Funktionen und die folgenden Funktionen aus dem programmiersprachenunabhängigen .NET-Framework.
Type.Parse
Die Parse-Funktionen sind dem jeweiligen Datentyp zugeordnet; die Parse-Funktion mit dem Zieltyp Integer finden Sie unter dem Bezeichner Integer.Parse. Auch hier ist der erste Parameter der umzuwandelnde Ausdruck und der Rückgabewert der umgewandelte Wert. Als zweiten oder dritten Parameter können Sie weitere Informationen übergeben, in welchem Format der Ursprungsausdruck vorliegt. So können zum Beispiel hexadezimale Zahlen oder 1000er-Trennpunkte berücksichtigt werden. Falls Sie sich mit dieser Funktionalität, deren Komplexität den Rahmen sprengen würde, näher befassen wollen, empfehle ich Ihnen die ausführlichen Artikel in der MSDN Library (die Sie als SharpDevelop-Nutzer im Internet finden, siehe Link im Inhaltsverzeichnis). Suchen Sie einfach nach dem Namen der Funktion, zum Beispiel SByte.Parse. Beachten Sie, dass es keine String.Parse-Funktion gibt. Das Äquivalent ist nämlich String.Format.
String.Format
Format-Funktionen sind wohl eine der flexibelsten Varianten der Datendarstellung. Da sie v.a. in der C-Familie sehr erfolgreich sind, wurden Sie ins .NET-Framework übernommen. Somit lässt sich diese Funktionalität auch in Visual Basic .NET nutzen. Da die Format-Funktion zum Datentyp String gehört, finden Sie Sie unter dem Bezeichner String.Format. Das Erstaunliche an der String.Format-Funktion ist, dass sie beliebig viele Parameter haben kann (mindestens jedoch einen). Die String.Format-Funktion gibt die formatierte Zeichenfolge (Typ String) zurück.
Der erste Parameter ist das sogenannte Format vom Typ String. Dieses gibt die Form der formatierte Zeichenfolge an. Alle weiteren Parameter sind Daten (vom Typ Object, d.h. Sie können wirklich alles als Daten übergeben). Diese werden gemäß den Informationen im Format in die formatierte Zeichenfolge eingebaut. Wichtig sind hierbei die Formatelemente, die im Format in {geschweiften Klammern} stehen. Diese werden bei der Formatierung durch die jeweiligen Daten ersetzt. Die Zahl im Formatelement gibt an, welches Datum verwendet werden soll. (Nicht wundern: „Datum“ ist die Einzahl von „Daten“, das ist nur nicht so bekannt.) Die Zahl ist der Index dieses Datums. So wie bei Feldern ist dieser Index nullbasiert, das heißt, das erste Element trägt den Index 0 (nicht 1).
Dim Zahl1 As Integer = 3, Zahl2 As Integer = 6 Dim Text As String = String.Format("Zweimal {0} ergibt {1}.", Zahl1, Zahl2) MessageBox.Show(Text)
Zweimal 3 ergibt 6.
Hier haben wir das Format "Zweimal {0} ergibt {1}." sowie die Daten 3 und 6. Im Format werden nun die Formatelemente ("{0}" und "{1}") durch die jeweiligen Daten (3 und 6) ersetzt, genauer gesagt durch das Ergebnis der Umwandlung der Daten in den Datentyp String (hier "3" und "6"). So wird aus dem Format "Zweimal {0} ergibt {1}." die formatierte Zeichenfolge "Zweimal 3 ergibt 6."
Man kann das Codebeispiel von 3 Zeilen auf eine verkürzen, wenn man folgende Umstände ausnutzt:
- Auch Literale können als Parameter übergeben werden. Die Deklaration von Zahl1 und Zahl2 als Zwischenspeicher ist nicht nötig.
- Der Rückgabewert einer Funktion kann direkt wieder als Parameter eingesetzt werden, hier der Rückgabewert von String.Format als Parameter von MessageBox.Show.
Allerdings ist zu beachten, dass unter einer Codeverkürzung manchmal die Lesbarkeit leidet. Jetzt fragen Sie sich wahrscheinlich, warum wir einen solchen Aufwand betrieben haben, wenn wir es mit impliziter Konvertierung und dem geschickten Einsatz der Verkettungsoperation ("Zweimal " & Zahl1 & " ergibt" & Zahl2 & "."
) doch viel leichter gehabt hätten. Allerdings ist das nur die einfachste Einsatzmöglichkeit von Formatelementen. Es gibt auch komplexere Möglichkeiten, bei denen die Daten nicht einfach in String umgewandelt werden, sondern anhand bestimmter Regelsätze formatiert werden.
Erweiterte Syntax von Formatelementen
Ein Beispiel hierfür ist die Ausgabe von formatierten Tabellen.
Dim i As Integer Dim myData(,) As Integer = {{203, 15, 100, 2}, {203, 16, 2000, 10}, {103, 1, 30000, 125}, {4, 135, 1, 6055}} 'Ausgabe Tabellentitel Console.WriteLine(String.Format("{0,-10}:{1,-10}:{2,-10}:{3,-10}", "Kategorie", "Typ", "Anzahl", "Preis")) 'Ausgabe Tabelle For i=0 To 3 Console.WriteLine(String.Format("{0,-10:0000}:{1,-10}:{2,10}:{3,10:C2}", myData(i, 0), myData(i, 1), myData(i, 2), myData(i, 3))) Next
Kategorie :Typ :Anzahl :Preis 0203 :15 : 100: 2,00 € 0203 :16 : 2000: 10,00 € 0103 :1 : 30000: 125,00 € 0004 :135 : 1:6.055,00 €
Sofort ins Auge fallen die Erweiterungen der Formatelemente. Der Index des Formatelements wird nun gefolgt von einem Komma und der eigentlichen Deifinition, wie dieses Element formatiert werden soll.
Betrachten wir den Index 0 des Tabellentitels: "{0,-10}". Die Ausage wird so formatiert, dass der übergebene Wert zehn Zeichen breit ist. Ein negatives Vorzeichen gibt eine linksbündige ein positives eine rechtsbündige Zeichenkette aus. Weiter im Code wird das Formatelement (Index 0) des Tabelleninhaltes nochmals um den Formatbezeichner geführt von einem Doppelpunkt erweitert.
Format- bezeichner |
Beschreibung | Beispiel |
---|---|---|
"0" | Ersetzt "0" sofern vorhanden durch die entsprechende Ziffer. Ist keine vorhanden so wird "0" eingetragen. | 123.456 > {0:0000} => 0123 123.456 > {0:0000.00} => 0123,46 |
"#" | Ersetzt "#" sofern vorhanden durch die entsprechende Ziffer. Ist keine vorhanden so wird nichts eingetragen. | 123.456 > {0:####} => 123 0.456 > {0:#.##} => ,46 |
"." | Bestimmt den Ort des Dezimaltrennzeichens. | 123.456 > {0:00.00} => 123.46 0.456 > {0:00.00} => 00,46 |
"," | Dient als Tausendertrennzeichen. | 1987654321 > {0:##,#} => 1.987.654.321 1987654321 > {0:#,#,,} => 1.987 |
"%" | Führt eine Multiplikation mit 100 durch und Stellt "%" voran. | 0.246 > {0:%0.00} => %24,60 0.246 > {0:%##} => %25 |
"C" oder "c" | Währungsformat für numerische Werte. | 123.456 > {0:C} => 123 € 123.456 {0:C2} => 123,46 € |
"D" oder "d" | Ganzzahlige numerische Werte. | 123 > {0:D} => 123 -123 > {0:D6} => -000123 |
"E0" "E+0" "E-0" "e0" "e+0" "e-0" |
Ermöglicht eine exponentielle Schreibweise. | 654321 > {0:#0.0e0} => 65,4e4 1234.56789 > {0:0.0##e+00} => 1,234e+03 |
"F" oder "f" | Dezimalstelle für ganzzahlige und dezimale Werte. | 123.456 > {0:F} => 123,46 123 > {0:F1} => 123,0 |
"G" oder "g" | Die Zahl wird je nach Typ in die kompakteste Festkomma- oder wissenschaftliche Schreibweise konvertiert. | 12345.6789 > {0:G} => 13245,6789 12345.6789 > {0:G7} => 13245,68 .0000012 > {0:G} => 1,2E-06 |
"N" oder "n" | Die Zahl wird in eine Zeichenfolge mit Tausendertrennzeichen konvertiert. | -12345.6789 > {0:N} => -12.345,68 -12345.6789 > {0:N1} => -12.345,7 |
"P" oder "p" | Die Zahl wird in eine Prozentangabe konvertiert. (Ähnlich "%") | .6789 > {0:P} => %67,89 .6789 > {0:P1} => %67,9 |
"R" oder "r" | Nur für Single oder Double. Stellt sicher, dass ein Wert in den gleichen numerischen Wert zurück konvertiert werden kann. | Math.Pi > {0:R} => 3,1415926535897931 1.623e-21 > {0:R} => 1,623E-21 |
"X" oder "x" | Nur für Ganzzahlige Typen. Konvertiert den Wert in hexdezimale Schreibweise. | &h1034e > {0:X} => 1034E 123456789 > {0:X10} => 00075BCD15 |
\ | Gibt das folgende Zeichen als Literal wieder und nicht als Formatbezeichner. | 123.456 > {0:\####.#\#} => #123.5# |
'string' | Übergibt ein Literal ohne Änderung an die Ausgabe. | 123.456 > {0:#.# 's'} => 123,5s 123.456 > {0:#' Zeilen'} => 123 Zeilen |
";" | Trennt unterschiedliche Formatierungen für positive Zahlen, negative Zahlen und Null. | 123 > {0:#0.0#;(#0.0#);-\0-} => 123,0 -123 > {0:#0.0#;(#0.0#);-\0-} => (123,0) 0 > {0:#0.0#;(#0.0#);-\0-} => -0- |
Im Folgenden wird die Funktion "ToString()" näher eläutert. Die oben abgebildete Tabelle ist allerdings schon ein kleiner Vorgriff. Wird der Formatbezeichner als Parameter an die Funktion übergeben, so liefert sie einen formatierten Sting zurück. Hinzu kommt, dass als weiterer Parameter noch ein FormatProvider übergeben werden kann, um auf länderspezifische Schreibweisen einzugehen.
'.ToString(String, FormatProvider) Dim num As Double = 123.456 Console.WriteLine(num.ToString(("C", System.Globalization.CultureInfo.CreateSpecificCulture("en-US")))
123.46 $
Informationen zum Thema „Erweiterte Syntax von Formatelementen“ sind leider noch nicht vorhanden, da sich bis jetzt noch keiner gefunden hat, der sich mit diesem Thema gut genug auskennt, um anderen dieses Thema näher zu bringen. Wenn Du dich mit diesem Thema auskennst, hilf bitte mit, dieses Kapitel zu vervollständigen.
Die To-Funktionen
Für die Umwandlung eines "unpassenden" Datentyps in einen passenden empfiehlt es sich auch, die Klasse der umzuwandelnden Variable im Objektbrowser zu betrachten. Dort finden sich dann im Member-Bereich gelegentlich Methoden mit den sprechenden Namen "ToDatentyp (...) As Datentyp, die nicht mit den ähnlich lautenden Funktionen der "Convert"-Klasse zu verwechseln sind. Manchmal findet sich auch als Namensbestandteil der Funktion nicht der Zieldatentyp, sondern der Zweck der Umwandlung, etwa in der Klasse "DateTime" die Funktionen "ToFileTime() As Long" oder "ToOADate() As Double".
Haupsächlich finden sich diese Funktionen außerhalb des Bereichs, in dem "CInt" & Co Anwendung finden. So definiert die Klasse "Decimal", die kein Ziel einer Konversion mit C-Funktionen darstellt, ohne "ToString"-Funktionen (siehe der nächste Abschnitt) insgesamt 11 "To-Funktionen" (ToByte, ToDouble, ToInt16 usw.).
Funktionen diesen Typs finden sich auch außerhalb der einfachen Datentypen, so definieren sowohl die Klasse "ArrayList" als auch die generische Klasse "List(Of T)" eine Methode "ToArray", die ein untypisiertes bzw typisiertes Array zurückgeben.
Sonderfall: ToString-Funktion
Jeder Datentyp ist von System.Object abgeleitet und verfügt daher über dessen ToString-Methode, Diese gibt einen Ausdruck vom Typ "String" zurück. Bei den einfachen Wert-Typen (Byte, Short, Integer, Long, Date, Boolean, Char usw.) gibt diese Funktion die String-Entsprechung dieser Typen in einer Standardformatierung zurück, bei Variablen, die ein Objekt vom Typ "String" darstellen, ist der Rückgabewert der String selbst. Insoweit kann die Funktion zur Typumwandlung benutzt werden. "Hinter den Kulissen" ist die ToString-Funktion auch bei der Typumwandlung innerhalb der String.Format-Methode (siehe vorstehend) beteiligt. Die ToString-Methode kann dabei für den konkreten Datentyp nicht nur überschrieben, sondern auch überladen sein und dann Formatparameter entgegennehmen.
Bei Objekten, die Instanzen von komplexeren, auch selbstprogrammierten Klassen darstellen, gibt aber "ToString" in der Standardimplementierung den Klassennamen zurück, oder im Falle der Überschreibung einen vom Programmierer der Klasse festgelegten Wert. Bei Objekten z.B., die über eine "Name"-Eigenschaft verfügen, kann dies der Wert dieser Eigenschaft sein. Ein Objekt der Klasse StringBuilder, mit der Teilstrings verkettet werden können, liefert auf den Aufruf von ToString den gesamten verketteten String zurück. Es handelt sich also um eine Routine die nicht speziell der Typumwandlung dient, sondern - diesen Zweck teilt sie mit String.Format - um eine Methode, die eine sinnvolle Stringrepräsentation des zugrundeliegenden Objekts liefert.
Erweiternde und eingrenzende Konvertierungen
Jeder Datentyp hat einen anderen Wertebereich. Integer hat etwa einen größeren Wertebereich als Byte. Wenn Sie einen Byte-Wert nach Integer konvertieren, spricht man deshalb von einer erweiternden Konvertierung, andersrum von einer eingrenzenden Konvertierung. Man sagt auch, Byte kann zu Integer erweitert werden. Bei einer erweiternden Konvertierung kann nichts Schlimmes passieren, denn alle Werte des Ausgangsdatentyps können in Werte des Zieldatentyps umgewandelt werden. Bei eingrenzenden Konvertierungen sieht das schon ganz anders aus. Hier treten des Öfteren Überlaufsfehler auf, wenn Sie etwa versuchen, den Integer-Wert 100000 nach Byte zu konvertieren, also einzugrenzen. 100000 ist aber zu groß für Byte, es kommt zum Überlauf.
Gute Entwicklungsumgebungen warnen Sie, wenn Sie eine eingrenzende Konvertierung vornehmen wollen. Allerdings warnt Sie etwa das Microsoft-Produkt nicht bei der eingrenzenden Konvertierung von String in irgendeinen Zahlentyp, etwa Long. Werte wie "abc" lassen sich nämlich nicht auf Long abbilden, sodass es auch hier zu einem Laufzeitfehler kommt. Wie Sie die eingrenzenden Konvertierungen unter Kontrolle bringen, verrät Ihnen das Kapitel Fehlerbehandlung. Dort geht es darum, wie Sie Fehler, die zum Beispiel bei eingrenzenden Konvertierungen entstehen, abfangen können, bevor diese Ihr Programm zum Stillstand bringen.
Boolean
Auch Boolean-Werte können in andere Werte umgewandelt werden. Bei Umwandlung in eine Zahl wird False zu 0 und True zu -1, bei Umwandlung in einen String wird False zu "False" und True zu "True".
Optionen
Man kann das Verhalten von Visual Basic mit sogenannten Optionen beeinflussen. Insgesamt gibt es drei verschiedene Optionen: Explicit, Strict und Compare.
Typisierung
In den letzten Kapiteln hatten Sie viel mit der sogenannten Typisierung zu tun, wahrscheinlich ohne es gemerkt zu haben. Typisierung bedeutet, dass ein Wert immer an einen bestimmten Datentyp gebunden ist. So kann leicher überprüft werden, ob Sie mit den Werten nur das machen, was Sie damit machen können. Zwei String-Werte können Sie verketten, zwei Long-Werte nicht. Dafür können Sie die String-Werte nicht miteinander multiplizieren, die Long-Werte schon. Die Typisierung in Visual Basic .NET ist:
- stark - Die Datentypen sind klar voneinander getrennt, Sie können einen Long-Wert nicht einfach einer Single-Variablen zuweisen, denn dazu muss der Long-Wert erst in Single konvertiert werden. Da diese Konvertierung aber meist automatisch, da implizit, geschieht, kann man nicht von wirklich starker Typisierung, sondern eigentlich nur von halbstarker Typisierung sprechen.
- statisch - Schon bei der Kompilierung steht fest, welcher Wert und welche Variable wann welchen Datentyp haben wird.
- explizit - Sie müssen für jeden Wert und jede Variable selber festlegen, welcher Datentyp er hat. (Bei Variablen geschieht das bei der Deklaration, bei Literalen entweder implizit über die Standarddatentypen oder explizit über Literalsuffixe.)
Die Typisierung können Sie mit den Optionen Explicit und Strict in einem gewissen Rahmen beeinflussen.
Option Explicit
Die Option Explicit kann entweder On (an) oder Off (aus) sein. Standardmäßig ist sie angeschaltet, d.h. die Typisierung ist explizit (s.o.). Schaltet man die Option Explicit auf Off, so wird das Typisierungsverhalten hybrid implizit und explizit. Implizite Typisierung bedeutet, dass Variablen bei der ersten Verwendung automatisch (implizit) deklariert werden. Mit „hybrid“ meine ich, dass man Variablen sowohl explizit mit der Dim-Anweisung deklarieren kann oder sie implizit bei der ersten Verwendung deklarieren lassen können.
Achtung: Implizit deklarierte Variablen haben immer den Typ Object, der jeden möglichen Wert annehmen kann. Einfach ausgedrückt hat der auf der Variable gespeicherte Wert aber einen eigenen Typ, wie das folgende Beispiel zeigt. Das folgende Codebeispiel ist nur mit ausgeschalteter Option Explicit gültig, da Variablen dann nicht explizit deklariert werden müssen.
Die Variable a wird bei der ersten Verwendung automatisch als Object-Variable deklariert. Der nach der ersten Anweisung gespeicherte Wert (3) ist vom Typ Integer. In der zweiten Zeile wird die Object-Variable b deklariert. Der gespeicherte Wert ist der Rückgabewert der Verkettungsoperation, also ein Wert vom Typ String. (Beachten Sie, dass der auf a gespeicherte Wert für die Verkettungsoperation von Integer nach String konvertiert wird.) Beachten Sie, dass man auf einer Object-Variablen wirklich zu jeder Zeit jeden möglichen Wert speichern kann. Im Beispiel kann man, nachdem man die Variable a mit dem Integer-Wert 3 initialisiert hat, auf derselben Variable etwa einen String- oder Boolean-Wert speichern.
Doch wie deaktiviert man diese ominöse Option Explicit, falls man die implizite Variablendeklaration nutzen möchte? Ganz einfach: Im Code setzen Sie ganz am Anfang, also noch vor die Zeile Private Class ..., diese Zeile.
Das erste Wort Option leitet die Optionsschalteranweisung ein, das zweite Wort Explicit gibt den Optionsschalter ein, und das letzte Wort Off die neue Einstellung. Mit Option Explicit On können Sie die implizite Variablendeklarierung explizit verbieten, das ist aber meist nicht nötig, außer wenn Sie, wie in manchen Entwicklungsumgebungen möglich, den Explicit-Optionsschalter global, also für alle Dateien, auf Off stellen.
Nun, wo Sie wissen, wie man die Option Explicit benutzt: Benutzen Sie sie nicht. Implizite Variablendeklaration ist ein Relikt aus Ur-BASIC-Zeiten und wird heute nur noch in Skriptsprachen angewandt. Für Applikationsprogrammierung wie in VB.NET ist die explizite Variablendeklaration definitiv die bessere Wahl:
- Explizit deklarierte Variablen erlauben eine größere Kontrolle über den Datentyp der gespeicherten Werte. Auf einer Object-Variable kann immer alles gespeichert werden. Vor allem bei tief verschachtelten Funktionsaufrufen geht da schnell die Übersicht verloren, was für ein Wert gerade gespeichert ist. Unvorsichtige Operationen lösen dann schnell einen Fehler aus.
- Impliziert deklarierte Variablen stellen eine häufige Fehlerquelle dar. Falls Sie sich nämlich bei einem Variablennamen vertippen, wird, anstatt die gewünschte Variable zu verwenden, eine neue Variable angelegt.
- Mit impliziert deklarierten Variablen erschweren Sie sich selber die Nutzung der Memberreferenzierung, eines der größten Vorteile der objektorientierten Programmierung.
Verwenden Sie deshalb immer nur explizit deklarierte Variablen. Die Erfahrung hat gezeigt, dass man alles ohne implizit deklarierte Variablen programmieren kann, meistens sogar effektiver.
Option Strict
Auch die Strict-Option kann On oder Off sein. Anders als die Explicit-Option ist sie standardmäßig abgeschaltet, also Off. Ist die Strict-Option angeschaltet, ist die implizite Typumwandlung verboten. Sie müssen dann eine der expliziten Typumwandlungsfunktionen einsetzen, egal ob es eine C-Funktion, eine Parse-Funktion oder eine andere Funktion ist. Das folgende Beispiel ist bei aktivierter Strict-Option falsch.
Der Wert des Literals 4 ist Integer. Die MessageBox.Show-Funktion erfordert allerdings einen Parameter vom Typ String. Da mit eingeschalteter Strict-Option die implizite Typumwandlung deaktiviert ist, tritt deshalb ein Fehler auf. Mit einer expliziten Typumwandlung lässt sich das Problem lösen.
Hier wird zuerst durch die CStr-Funktion der Literal von Integer (4) nach String ("4") konvertiert. Dieser Wert hat den korrekten Datentyp und kann ausgegeben werden. Die Option Strict zu aktivieren, ist eine gute Idee, wenn man einen Fehler in einer Funktion sucht. Die durch die deaktivierte implizite Typumwandlung verursachten Fehler stellen nämlich gute Hinweisquellen dar.
Option Compare
Die Compare-Option unterscheidet sich in einigen Punkten von den bisher vorgestellten Optionen: Erstens geht es hier nicht um das Typisierungsverhalten, sondern um das Operationsverhalten, genauer das der Vergleichsoperationen. Zweitens sind die möglichen Schalterzustände hier nicht On oder Off, sondern Text oder Binary.
Mit der Compare-Option schalten Sie die Sortierung um, nach der die Vergleichsoperatoren Strings miteinander vergleichen.
Option Compare Binary
Mit diesem Schalterzustand werden Zeichen nach ihrem Zeichensatzindex eingeordnet. (Ein Zeichensatz ist eine Tabelle, die bestimmte Zeichengruppen mit Indizes versieht, die anstatt der Zeichen abgespeichert werden. Schließlich kann man Zahlen leichter speichern als Zeichen.) Der Zeichensatz hängt vom System ab. Am wahrscheinlichsten sind ISO-8859-1 und Unicode, auf fremdsprachigen Systemen mit anderen Alphabeten (kyrillisch, arabisch, etc.) hat man es oft mit ISO-8859-Zeichensätzen zu tun.
Die Basis für fast alle heutigen Zeichensätze stellt der ASCII-Zeichensatz dar. Er definiert 128 Zeichen (Indices 0 - 127). Die ersten 32 Zeichen (Indices 0 - 31) sind Steuerzeichen wie „Zeilenvorschub“ oder „Tabulator“. Auf Index 32 liegt das Leerzeichen. Auf Index 127 liegt das DEL-Steuerzeichen, welches nur in der Zeit von Fernschreibern von Bedeutung war. Von Index 33 bis Index 126 finden Sie in dieser Reihenfolge folgende Zeichen:
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
Bei der Binary-Einstellung, die übrigens die Standardeinstellung für die Compare-Option ist, wird alphabetisch verglichen, wobei die obige Darstellung das Alphabet ist. Also ist „!“ kleiner als „#“, „1“ kleiner als „3“ und „g“ größer als „H“ (obwohl das nicht der Reihenfolge im normalen Alphabet entspricht). Sie sehen, alles wird nach der Reihenfolge im Zeichensatz sortiert, die, vor allem bei Vergleichen eines Klein- mit einem Großbuchstaben, nicht immer der Reihenfolge im Alphabet entspricht.
Kurzbeispiel: A < C < T < a < c < t
Option Compare Text
Bei der Text-Einstellung wird vorrangig nach dem Alphabet und erst zweitrangig nach der Reihenfolge im Zeichensatz sortiert. Die intuitive Sortierung nach dem Alphabet funktioniert also wieder. „g“ ist zum Beispiel kleiner als „H“, da „g“ im Alphabet vor „H“ steht.
Kurzbeispiel: (A = a) < (C = c) < (T = t)
Beachten Sie, dass die Sortierart nicht nur für Vergleichsoperatoren gilt, sondern auch für andere Funktionen, die einen Vergleich von Werten anstellen können, etwa die Array.Sort-Funktion.
Blöcke und Variablensichtbarkeit
Quellcode in Visual Basic .NET besteht aus einzelnen Blöcken, bestimmten Befehlsgruppen. Blöcke werden durch Anweisungen definiert. Zum Beispiel definiert eine If-Anweisung zwei Blöcke: einen zwischen If ... Then und Else, dessen Anweisungen ausgeführt werden, wenn die Bedingung True ist, und einen zwischen Else und End If, dessen Anweisungen ausgeführt werden, wenn die Bedingung zu False aufgelöst wird. Blöcke können verschachtelt (kaskadiert) werden, d.h. Blöcke können andere Blöcke enthalten. Zum Beispiel kann in einem Block einer If-Anweisung wieder eine If-Anweisung stehen.
Nur manche Anweisungen bilden Blöcke. Ein zweites Beispiel für eine blockbildende Anweisung sind die Sub- und Function-Anweisungen, welche Prozeduren und Funktionen deklarieren und definieren. (Wie Sie sehen, werden Anweisungen immer nach dem Schlüsselwort benannt, das sie kennzeichnet.) Diese Anweisungen bilden nur einen Block. Die Anweisungen dieses Blockes werden ausgeführt, wenn die Prozedur oder Funktion ausgeführt wird.
Lebenszyklus von Variablen
Vor einiger Zeit hatten wir die Begriffe „lokale Variable“ und „globale Variable“ eingeführt. Lokale Variablen werden innerhalb einer Funktion deklariert und sind deshalb nur dort verfügbar. Globale Variablen werden außerhalb einer bestimmten Funktion deklariert und sind in mehreren Funktionen gültig. Die Begriffe der lokalen und globalen Variablen werden wir nun zum Begriff des Variablenlebenszyklus erweitern. Dieser Begriff lässt sich so zusammenfassen: Eine Variable ist nur in dem Block verfügbar, in dem sie deklariert wurde.
Bis jetzt haben wir uns immer nur mit dem Zeitpunkt befasst, von dem an die Variable existiert, also der Deklaration. Wichtig ist aber für den Lebenszyklus auch, wann die Variable aufhört, zu existieren. Da dies noch ein sehr junger Aspekt ist, gibt es noch keinen anerkannten deutschen Namen dafür. Das .NET-Framework verwendet den engl. Begriff „finalization“ (engl. finalize = beenden, vollenden). Aufgrund des obenstehenden Satzes zur Variablensichtbarkeit wird eine Variable finalisiert, wenn der Block, in dem die Variable deklariert wurde, beendet wird, also wenn die Ausführung dieses Blockes abgeschlossen wurde. (Dieser Block wird auch Kontext der Variable genannt.)
Die Grafik rechts verdeutlicht den Lebenszyklus verschiedener Variablen.
Die Class-Anweisung (die wir später kennenlernen werden) definiert einen Block, in die Funktionen eines Objektes (hier Form1) zusammengefasst werden. Variable a ist in diesem Block notiert und gilt deshalb während der Lebenszeit des Objektes Form1. Das heißt, dass Sie die Variable a verwenden können, wenn das Objekt Form1 erstellt wurde. (Das geschieht hier automatisch mit dem Start des Programmes.) Innerhalb der Funktionen von Form1 können Sie a garantiert immer verwenden, da diese Funktionen nur aufgerufen werden können, wenn Form1 erstellt wurde.
Die Variable b ist im Block der Sub-Anweisung deklariert. Sie gilt nur innerhalb der durch die Sub-Anweisung deklarierten und definierten Prozedur. Außerhalb dieser Prozedur können Sie b nicht verwenden. Beachten Sie, dass a beim Erstellen des Objektes Form1 automatisch erstellt wird, egal wo die Deklaration von a im Class-Block steht. b hingegen ist vor der Deklaration auch innerhalb des Blockes nicht verfügbar, sondern erst, wenn die Dim-Anweisung ausgeführt wurde.
Die Variable c stellt eine Besonderheit innerhalb dieser Thematik dar. Rein theoretisch wurde Sie im Sub-Block definiert, da die For-Anweisung zu selbigem gehört. Allerdings wird die Variable nicht erst am Ende des Sub-Blockes finalisiert, sondern bereits am Ende der For-Anweisung. Das ist die berühmte Ausnahme von der Regel.
Mit der Variable d muss man vorsichtig sein. Da der For-Block mit jedem Schleifendurchlauf neu ausgeführt wird, wird d auch jedesmal neu deklariert. Die Werte eines Schleifendurchlaufes stehen im nächsten Schleifendurchlauf also nicht mehr zur Verfügung. Sie müssen die Ergebnisse eines Schleifendurchlaufes also ggf. in einem höheren Kontext (hier in einer Variable im Sub-Block) zwischenspeichern.
Variablensichtbarkeit
Im Zusammenhang mit dem Lebenszyklus von Variablen tritt häufig der Begriff Variablensichtbarkeit auf. Man sagt, eine Variable ist in einem bestimmten Bereich des Codes sichtbar, wenn man aus diesem Abschnitt heraus auf die Variable zugreifen kann. Oben ist die Variable a in allen Funktionen des Objektes Form1 sichtbar. Die Variablen b, c und d wären in einer zweiten Funktion des Objektes Form1 nicht sichtbar.
Die Variablensichtbarkeit ist eine oft unterschätzte Fehlerquelle, so wie im folgenden Beispiel.
Public Class Form1 Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load Dim Eingabe As String = InputBox("Geben Sie bitte ""Hallo"" ein.") If Eingabe = "Hallo" Then Dim Ausgabe As String = "Gut." Else Dim Ausgabe As String = "Falsch." End If MessageBox.Show(Ausgabe) End Sub End Class
Fehler
Ein Fehler, wie er im Lehrbuch stehen könnte (und es hier ja auch tut). Die Variable(n) Ausgabe wird/werden im Then- und im Else-Block deklariert. Am Ende der Blöcke werden sie finalisiert. Beim Aufruf der MessageBox.Show-Funktion sind sie deshalb nicht mehr sichtbar; der Zugriff auf die dort unbekannte Variable Ausgabe versucht einen Fehler. Sie können den Fehler umgehen, indem Sie die Variable(n) Ausgabe in den nächsthöheren Kontext verschieben.
Public Class Form1 Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load Dim Eingabe As String = InputBox("Geben Sie bitte ""Hallo"" ein.") Dim Ausgabe As String If Eingabe = "Hallo" Then Ausgabe = "Gut." Else Ausgabe = "Falsch." End If MessageBox.Show(Ausgabe) End Sub End Class
Gut. (Falls "Hallo" eingegeben wurde.) Falsch. (Andernfalls.)
Shadowing
Dass Variablen an den deklarierenden Block gebunden sind, bringt noch ein anderes Problem mit sich. Der folgende Code zeigt, was ich meine.
Dim Eingabe As String = InputBox("Sag hallo.") Dim Ausgabe As String = "Gut." If Eingabe = "Hallo" Then Dim Ausgabe As String = "Richtig." Else Dim Ausgabe As String = "Falsch." End If MessageBox.Show(Ausgabe)
Gut.
Erstaunlicherweise kann man dieses Beispiel kompilieren und ausführen. Allerdings ist das Ergebnis nicht das erwartete: Statt „Richtig.“ oder „Falsch.“ wird „Gut.“ angezeigt. Den Effekt, der das bewirkt, nennt man Shadowing (engl. shadow = Schatten). Das bedeutet folgendes: Ein Block deklariert eine Variable mit einem bestimmten Namen, hier Ausgabe. Ein zweiter Block, der sich in diesem Block befindet, deklariert eine Variable desselben Namens. In dem zweiten Block ist nun unter dem Namen die eigene Variable sichtbar, die Variable des höheren Kontexts ist dort nicht mehr sichtbar. Sie steht quasi im Schatten der zweiten Variable, daher der Name.
Für unser Beispiel bedeutet das folgendes: Der oberste Block (also höchstwahrscheinlich der Sub-Block der Prozedur Form1_Load) definiert in der zweiten Zeile eine Variable Ausgabe, die mit dem Wert „Gut.“ initialisiert wird. Der Then-Block und der Else-Block definieren jeweils eigene Variablen mit dem Namen Ausgabe. Diese führen im jeweiligen Block Shadowing auf die Ausgabevariable des Sub-Blockes durch, sodass diese dort nicht mehr sichtbar ist. Mit Ende des Then- bzw. des Else-Blockes werden diese Variablen finalisiert. Durch die Zuweisungen im Then- und im Else-Block hat sich an der Ausgabevariable des Sub-Blockes nichts geändert, da diese dort nicht sichtbar war. Deshalb wird der ursprüngliche Wert, nämlich „Gut.“, ausgegeben.
Strukturierte Fehlerbehandlung
Früher hatten es die Programmierer nicht leicht. Ständig musste man sichergehen, dass man keinen Fehler macht, denn Fehler hätten an der Hardware schwere Schäden anrichten können oder zumindest den Rechner zum Absturz bringen. Zum Beispiel ist eine Division durch Null für die Funktionalität einer mathematischen Berechnungseinheit nicht besonders förderlich. Auch der Versuch, in eine nicht vorhandene Datei zu schreiben, kann für ein Dateisystem gefährlich werden.
Also musste man alles gegenprüfen. Wollte man zum Beispiel in eine Datei schreiben, musste man prüfen, ob das Laufwerk existiert, verfügbar ist und die Datei existiert. Dann musste man die Datei öffnen und prüfen, ob das erfolgreich war und ob man überhaupt in die Datei schreiben kann. Dann konnte man schreiben, schloss die Datei und musste prüfen, ob die Datei auch richtig geschlossen wurde. Nach Wunsch konnte man auch noch nachprüfen, ob der Schreibvorgang erfolgreich war, indem man die Datei noch mal ausliest. Das schließt natürlich alle vorangegangenen Tests wieder ein.
Sie sehen schon: Das ist uns nichts. Zum Glück haben wir heute die Zukunft. Die Zukunft heißt in diesem Fall „strukturierte Fehlerbehandlung“. Das bedeutet, dass man den Computer anweist, einen bestimmten Code „auszuprobieren“. Wenn ein Fehler auftritt, „wirft“ die Funktion, in dem der Fehler aufgetreten ist, eine „Ausnahme“, den die aufrufende Funktion „auffangen“ kann. Die gewählten Begriffe sind kein Scherz, sondern wortwörtliche Übersetzungen der zu verwendenden Schlüsselwörter. Kernstück der ganzen Sache sind die Anweisungen Try zum Versuch und Throw beim Irrtum.
Versuch...
Der einfachste Fehler, der auftreten kann, ist ein arithmetischer Fehler, zum Beispiel bei einer Division durch Null oder durch einen Überlauf (weil eine Operation eine für den jew. Datentyp zu großen Wert ergibt). Eine zweite sehr häufige Fehlerquelle stellen eingrenzende Konvertierungen dar. Der folgende Code ist anfällig für solche Fehler.
Dim a As Byte = InputBox("a = ") Dim b As Byte = InputBox("b = ") MessageBox.Show("Produkt: " & a * b) MessageBox.Show("Quotient: " & a / b)
Bei den Beispieleingaben a = 4 und b = 2: Produkt: 8 Quotient: 2
Bei den Beispieleingaben a = 200 und b = 200: Fehler (Überlauf) -> Programmabbruch
Bei den Beispieleingaben a = 4 und b = 0: Produkt: 0 Quotient: +unendlich
Bei der Beispieleingabe a = hallo: Fehler (ungültige Konvertierung)
Ich habe hier bewusst den Datentyp Byte gewählt, da dieser Datentyp im wahrsten Sinne des Wortes schnell an seine Grenzen stößt. Oben sieht man das Beispiel „a = 200 und b = 200“. Werden diese Werte multipliziert, kommt 40000 heraus. Das ist aber zu groß für eine Byte-Variable. Der Überlauf wird als Fehlermeldung angezeigt, und damit nichts durcheinander kommt, wird das Programm sicherheitshalber beendet. Es könnte ja sein, dass dieser Wert nochmal von Bedeutung ist, und das ist er ja auch, nämlich für den Aufruf der MessageBox.Show-Funktion. Leider hat das zur Folge, dass weder die Division ausprobiert wird, noch der Benutzer Gelegenheit erhält, seine Eingabe zu korrigieren. Interessanterweise bricht das Beispiel „a = 4 und b = 0“ nicht mit einem Fehler ab, sondern gibt „+unendlich“ aus. Der Computer ist halt bemüht, für jede Rechnung ein Ergebnis zu definieren. Das Beispiel „a = hallo“ bricht wegen einer ungültigen Konvertierung an. Der String "hallo", der von der InputBox-Funktion zurückgegeben wird, kann nicht in Integer umgewandelt werden. Logischerweise können nur eingrenzende Konvertierungen solche Fehler verursachen.
Mit einer Anweisung lassen sich diese ganzen Unannehmlichkeiten verhindern: mit der Try-Anweisung (engl. try = versuchen, ausprobieren). Die Try-Anweisung definiert zunächst den Try-Block. Hier stehen die Befehle, die ausprobiert werden sollen, in diesem Fall die MessageBox.Show-Aufrufe mit den kritischen Berechnungen. Wenn ein Befehl schief geht, gibt dieser an die Try-Anweisung eine Ausnahme zurück. Die Ausnahme (engl. exception) ist ein Objekt und enthält Informationen über den aufgetretenen Fehler. Nach dem Try-Block können beliebig viele Catch-Blöcke folgen. Jeder Catch-Block behandelt eine Art von Ausnahme, etwa den Überlauf. Ein Catch-Block kann aber auch eine ganze Familie von Ausnahmen behandeln, z.B. alle Fehler, die beim Rechnen auftreten, oder allgemein alle Fehler. Mindestens ein Catch-Block ist aber Pflicht, es sei denn, man definiert einen Finally-Block (dazu später mehr).
Der Try-Block wird mit dem einzelnen Schlüsselwort Try eingeleitet. Nach dem Try-Block folgt der Catch-Block, der durch das Schlüsselwort Catch, gefolgt von der Deklaration der Ausnahmenvariable, eingeleitet wird. Mit einer ähnlichen Zeile wird der aktuelle Catch-Block abgeschlossen und ein weiterer Catch-Block eingeleitet. Den letzten Catch-Block schließt End Try ab. Das folgende Beispiel verdeutlicht die ganze Sache.
Try Dim a As Byte = InputBox("a = ") Dim b As Byte = InputBox("b = ") MessageBox.Show("Produkt: " & a * b) MessageBox.Show("Quotient: " & a / b) Catch ex As OverflowException MessageBox.Show("Die eingegebenen Werte sind zu groß.") Catch ex As InvalidCastException MessageBox.Show("Bitte nur Zahlen eingeben.") End Try End
Bei den Beispieleingaben a = 4 und b = 2: Produkt: 8 Quotient: 2
Bei den Beispieleingaben a = 200 und b = 200: Die eingegebenen Werte sind zu groß.
Bei den Beispieleingaben a = 4 und b = 0: Produkt: 0 Quotient: +unendlich
Bei der Beispieleingabe a = hallo: Bitte nur Zahlen eingeben.
Nun tritt kein unkontrollierter Fehler mehr auf. Stattdessen wird, falls ein Fehler, sprich eine Ausnahme, auftritt, der entsprechende Catch-Block aufgerufen. Doch der Reihe nach: Die erste Ausnahme tritt in den oben gezeigten Beispielen im Falle „a = 200 und b = 200“ auf. Sie heißt OverflowException (engl. overflow = Überlauf, exception = Ausnahme) und steht für die Tatsache, dass das Ergebnis einer Berechnung zu groß für den Zieldatentyp war. Mit einem Catch-Block (engl. catch = fangen) fangen wir diese Ausnahme auf. ex As OverflowException steht dabei dafür, dass die Ausnahme in einer Variable namens ex verfügbar ist. Dadurch kann man Nachrichten aus der Nachricht extrahieren, in unserem Falle dient diese Deklaration nur dazu, alle Ausnahmen zu filtern. Schließlich sollen mit diesem Catch-Block nur OverflowExceptions abgefangen werden. Eine Alternative wäre die einleitende Zeile Catch ex As ArithmeticException. ArithmeticException ist nämlich die Oberart von OverflowException und anderen Ausnahmen, etwa DivisionByZeroException (in manchen Fällen ist die Division durch Null ein Fehler, in unserem Fall jedoch nicht). Ein Block Catch ex As ArithmeticException fängt also OverflowExceptions und DivisionByZeroExceptions ab. Es wird empfohlen, immer ex als Variablennamen für eine Ausnahme zu verwenden und diesen Namen sonst nicht zu benutzen. Die Variable wird nämlich am Ende des Catch-Blockes entladen, weshalb der Name in allen Catch-Blöcken wiederverwendet werden kann.
In unserem Fall besteht die Fehlerbehandlung aus einer eigenen Fehlermeldung. Man beachte, dass das Programm nicht automatisch abgebrochen wird. Allerdings wird es nach der Fehlerbehandlung durch die End-Anweisung beendet. Nach der Ausführung des jeweiligen Catch-Blockes springt die Ausführung nämlich an das Ende der Catch-Anweisung.
Bei der Eingabe eines ungültigen Wertes, z.B. "hallo", wird der Block Catch ex As OverflowException übersprungen, da diese Ausnahme keine OverflowException, sondern eine InvalidCastException ist (engl. invalid cast = ungültige Konvertierung). Diese Ausnahme wird vom zweiten Catch-Block abgedeckt, der ebenfalls eine entsprechende Fehlermeldung ausgibt. Ein Problem hat unser Programm aber noch: Es sagt dem Benutzer zwar, was er falsch gemacht hat, gibt ihm aber keine Gelegenheit, das zu korrigieren. Ein zweiter Entwurf soll das ändern.
Dim VorgangAbgeschlossen As Boolean = False Do Try Dim a As Byte = InputBox("a = ") Dim b As Byte = InputBox("b = ") MessageBox.Show("Produkt: " & a * b) MessageBox.Show("Quotient: " & a / b) VorgangAbgeschlossen = True Catch ex As OverflowException MessageBox.Show("Die eingegebenen Werte sind zu groß.") Catch ex As InvalidCastException MessageBox.Show("Bitte nur Zahlen eingeben.") End Try Loop Until VorgangAbgeschlossen End
Es wird solange versucht, aus eingegebenen Zahlen Produkt und Quotient zu berechnen, bis VorgangAbgeschlossen wahr wird. Das ist genau dann der Fall, wenn bei Produkt- und Quotientberechnung kein Fehler auftritt, denn dann lösen die Zeilen mit MessageBox.Show keine Ausnahme aus und die Ausführung gelangt zu VorgangAbgeschlossen = True.
...und Irrtum
Nun wissen wir, wie man Fehler abfängt. Wie aber werden solche Fehler erzeugt? Diese Möglichkeit ist keineswegs auf die .NET-Bibliothek beschränkt, sondern kann von jedem Programm, jeder Funktion verwendet werden. Die entsprechende Anweisung heißt Throw (engl. werfen). Damit werfen wir die Ausnahme, die von einem Catch-Block aufgefangen und behandelt wird. Man sollte keine Ausnahme unbehandelt lassen, denn eine unbehandelte Ausnahme ist nicht nur ein Zeichen eines unvorsichtigen, wenn nicht gefährlichen, Programmierstils, sondern auch eine unnötige Last für den Benutzer, der dazu vielleicht noch wichtige Daten verliert.
Also immer die Ausnahmen auffangen. Für die Throw-Anweisung muss ein Ausnahmeobjekt erzeugt werden. Das geht mit der New-Anweisung, die eigentlich im Zusammenhang mit der objektorientierten Programmierung steht und deshalb jetzt nur angeschnitten wird. Ein einfaches Beispiel soll die ganze Sache verdeutlichen.
Private Function Dividieren(ByVal Wert1 As Double, ByVal Wert2 As Double) As Double If Wert2 = 0 Then Throw New DivideByZeroException() Return Wert1 / Wert2 End Function Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load Try MessageBox.Show(Dividieren(4,2)) MessageBox.Show(Dividieren(4,0)) Catch ex As DivideByZeroException MessageBox.Show("Division durch Null!") End Try End End Sub
2 Division durch Null!
Hier haben wir neben der Form1_Load-Funktion eine weitere Funktion namens Dividieren, die zwei Zahlen durcheinander teilt. Dabei wird sichergestellt, dass keine Division durch Null durchgeführt wird. Wenn der zweite Wert dennoch Null ist, wird eine DivideByZeroException ausgelöst. Das geht mit dem Befehl Throw New DivideByZeroException. Der besteht eigentlich aus zwei Anweisungen (Throw und New). Die Anweisung New DivideByZeroException erstellt ein neues DivideByZeroException-Objekt. (Was das soll, soll uns erstmal nicht interessieren, da das mit Objektorientierung zusammenhängt. Nehmen Sie es deshalb erstmal hin, dass mit New und Objektbezeichner ein neues solches Objekt erstellt wird.)
Die New-Anweisung hat einen Rückgabewert, nämlich das neue Objekt. Die Throw-Anweisung erfordert, quasi als Parameter, dieses Objekt, das dann geworfen wird. Anders als bei einem Funktionsaufruf müssen hier keine Klammern um den Parameter gesetzt werden, sodass der Befehl so aussieht: Throw Objekt. Kombiniert mit der New-Anweisung ergibt sich Throw New Objekt, so wie im obigen Beispiel zu sehen ist. Throw hat den gleichen Effekt wie Return, nämlich dass die Ausführung der Funktion beendet wird. Auch wenn ein Catch-Block behandelt wird, wird die Ausführung nicht nach der Throw-Anweisung, sondern nach der Try-Anweisung der aufrufenden Funktion fortgesetzt.
Durch Throw wird kein normaler Rückgabewert zurückgegeben. Man kann sich das so vorstellen, dass die Ausführung des normalen Codes gestoppt wird und nach einem Catch-Block gesucht wird. Wenn in der Funktion, in der die Throw-Anweisung steht, keiner gefunden wird, wird in der aufrufenden Funktion gesucht, dann in der Funktion, die diese aufgerufen hat, und so weiter. Wird keiner gefunden, geht das über die Verwaltungsschicht des .NET-Frameworks bis in die Windows-Ausführungsschicht zurück, die den Prozess beendet und eine Fehlermeldung anzeigt.
Das war jetzt ganz schön theoretisch. Wichtig ist nur soviel: Mit Throw lassen sich Ausnahmen werfen, die von einem Catch-Block in der jeweiligen Funktion oder einer der aufrufenden Funktionen behandelt werden. Bleibt die Ausnahme unbehandelt, wird das Programm abgebrochen.
Finally
Nach den Catch-Blöcken kann man einen weiteren Block einfügen, den Finally-Block (engl. finally = zum Schluss). Hier werden Befehle eingefügt, die nach dem Try-Block auf jeden Fall ausgeführt werden sollen, selbst wenn eine Ausnahme ausgelöst wurde. So können wir zum Beispiel Objekte aufräumen, was in der objektorientierten Programmierung sehr nützlich ist, da zum Beispiel der Zugriff auf eine Datei explizit abgeschlossen werden muss. Die entsprechende Anweisung steht dann im Finally-Block. Ein einfaches Beispiel dazu:
Dim VorgangAbgeschlossen As Boolean = False Do Try Dim a As Byte = InputBox("a = ") Dim b As Byte = InputBox("b = ") MessageBox.Show("Produkt: " & a * b) MessageBox.Show("Quotient: " & a / b) VorgangAbgeschlossen = True Catch ex As OverflowException MessageBox.Show("Die eingegebenen Werte sind zu groß.") Catch ex As InvalidCastException MessageBox.Show("Bitte nur Zahlen eingeben.") Finally MessageBox.Show("Schleifendurchlauf abgeschlossen.") End Try Loop Until VorgangAbgeschlossen End
Ein Test zeigt, dass nach jedem Durchlauf die Meldung „Schleifendurchlauf abgeschlossen.“ angezeigt wird, selbst wenn eine Ausnahme ausgelöst wurde. Ich verzichte hier auf ein konkretes Beispiel, da der wirkliche Nutzen von Finally so gut wie nur in der OOP liegt. Man kann den Block aber auch nutzen, um Code zu notieren, der am Ende des Try-Blockes und aller Catch-Blöcke ausgeführt worden wäre. Eine interessante Eigenheit des Finally-Blockes ist übrigens, dass er sogar dann ausgeführt wird, wenn im Try-Block oder im Catch-Block eine Exit-Anweisung ausgeführt wurde, zum Beispiel Exit Sub.
Konzepte der objektorientierten Programmierung
Zuerst möchte ich Sie beglückwünschen. Mit den ganzen Operatoren, Datentypen und Kontrollstrukturen verfügen Sie über genug Basiswissen, um sich in Visual Basic komplexeren Themen zuzuwenden, vor allem der objektorientierten Programmierung. Zum Glück setzt alles Folgende größtenteils auf den bereits bekannten Elementen wie Variablen und Funktionen auf. Gerade deshalb lohnt ein Blick zurück. Scheuen Sie sich also nicht, noch einmal in den vorherigen Kapiteln nachzublättern, wenn Sie etwas nicht gleich verstehen.
Eine Programmiersprache folgt immer bestimmten Grundprinzipien, sogenannten Programmierparadigmen. Visual Basic .NET orientiert sich im Wesentlichen an 6 Paradigmen, von denen wir 3 bereits unbewusst kennengelernt haben:
- Die imperative Programmierung sieht ein Programm als eine Abfolge von Befehlen an, bei der immer klar feststeht, welcher Befehl als nächstes ausgeführt wird. Imperative Programmierung stellt das Wie in den Vordergrund, das heißt, der Programmierer teilt dem Computer durch das Programm mit, wie ein bestimmtes Problem gelöst werden soll. Diese Idee ist der historisch älteste Ansatz, und wird heute von fast allen Programmiersprachen benutzt. Ihm gegenüber steht das relativ junge Paradigma der deklarativen Programmierung. Er stellt das Was in den Vordergrund, das heißt, der Programmierer teilt dem Computer nur mit, was für ein Problem gelöst werden soll. Der Computer sucht dann selbstständig (aber nach vorgegebenen Mustern) nach einer Antwort.
- Die strukturierte Programmierung führt Kontrollstrukturen wie Schleifen und Bedingungen ein, um den Programmablauf zu regeln. Früher wurden stattdessen sogenannte GOTO-Anweisungen verwendet, mit denen man an einen durch eine Marke markierten Punkt des Programmes springen konnte. Als andere Sprachen längst bewiesen hatten, dass GOTO unnötig ist, wenn man Kontrollstrukturen hat, hielt BASIC am GOTO fest, da dieses Konzept einfacher zu erlernen war. Auch heute ist in Visual Basic die GOTO-Anweisung noch vorhanden. Ihre Verwendung ist aber nicht nur unnötig, sondern auch allgemein verpönt, da der Code durch viele GOTOs (die in professionellen BASIC-Programmen immer in großer Zahl vorkamen) undurchsichtig und verworren wird.
- Die prozedurale Programmierung spaltet Programme in kleinere Teilaufgaben auf, namentlich Prozeduren und Funktionen. Die entstehenden Teilaufgaben sind wiederverwendbar, zum Beispiel können Sie die Funktionen zur Berechnung der Fakultät oder der Fibonaccifolge ohne Probleme in ein anderes Visual-Basic-.NET-Programm übernehmen.
Als Weiterentwicklung der prozeduralen Programmierung gilt die modulare Programmierung. Hier werden thematisch zueinander gehörende Prozeduren zu einem Modul zusammengefasst, dass wie die einzelne Prozedur ebenfalls wiederverwendbar ist. Mit der Modularität von Programmen will man vor allem der wachsenden Größe von Softwareprojekten Herr werden, die oft mehrere Millionen Codezeilen umfassen. Die Modularität findet man dabei sowohl auf Codeebene (Codemodule) als auch auf Dateiebene (Programmbibliotheken, unter Windows vor allem als DLL).
Zwei weitere Paradigmen werden wir noch kennenlernen, die objektorientierte und die generische Programmierung. In die Grundkonzepte der objektorientierten Programmierung werde ich Sie schon jetzt einführen, da dieses Thema eine wichtige Grundlage ist für das nächste große Thema auf dem Stundenplan: die Programmierung (oder vielmehr Erstellung, denn viel ist daran nicht mehr zu programmieren) von Windows-Oberflächen.
Objekte
Bis jetzt habe ich immer nur von Objekten gesprochen, wenn es um objektorientierte Programmierung (OOP) geht. Das ist nicht ganz richtig. Man muss ein Objekt von einer Klasse unterscheiden (denken Sie an das Class-Schlüsselwort in der Codezeile Private Class Form1
). Objekte der OOP sind in einer Hinsicht wie normale Variablen, denn Sie können bestimmte Werte speichern. Anders als normale Variablen ist die Anzahl an möglichen gleichzeitig gespeicherten Werten theoretisch unbegrenzt. (Technisch werden einzelne Werte wieder durch je eine Variable realisiert.) In einer zweiten Hinsicht sind Objekte besser als Variablen: Sie können neben Werten auch Methoden beinhalten. Das sind Funktionen, die dem Objekt zugeordnet sind und bestimmte Aufgaben an oder mit diesem Objekt erfüllen. Methoden und Werte eines Objektes werden als Member (engl. member = Mitglied) bezeichnet. Alle Member sind im Objekt gekapselt und bilden so eine logische Einheit.
Ein Problem bleibt: Wie weiß man, was für Member ein Objekt hat? Dafür gibt es Klassen. Klassen sind gewissermaßen die Baupläne für Objekte. Ein hier gern gewähltes Beispiel ist das der Autofabrik. Es gibt einen Konstruktionsplan für das gewünschte Automodell, nach dem Tausende von gleichen Autos produziert werden können. Ein Programm kann auf Basis einer Klasse zur Laufzeit Tausende von gleichen Objekten erzeugen. Die Klassendefinition enthält die Deklarationen (und ggf. Startwerte) der Membervariablen und die Deklaration und Definition der Memberfunktionen (Methoden). Die Klasse ist also das, was der Programmierer schreibt.
Um Ihnen ein praktisches Beispiel zu geben: Ein Formular (also ein Fenster auf dem Windows-Desktop) ist in Visual Basic .NET als Klasse implementiert. Die Klasse gibt an, dass ein Formular z.B. eine Höhe, Breite (zwei Eigenschaften vom Typ Integer) und einen Titel hat (eine Eigenschaft vom Typ String) und über gewisse Funktionen (Methoden) verfügt, etwa zum Öffnen und Schließen.
Visual Basic .NET verfügt über viele nützliche Aufbauten auf dieses Konzept, die das Programmieren intuitiver, einfacher und logischer machen. Dazu zählen Eigenschaften (nach dem engl. Begriff auch Properties). Eigenschaften verhalten sich nach außen wie Variablen, d.h. man kann sie mit Werten belegen und die Werte abrufen. Wenn man das aber tut, wird nicht nur ein Wert gespeichert oder geladen, sondern eine spezielle Funktion ausgeführt. So verfügt ein Formular nicht über Funktionen wie Minimieren, Maximieren und Wiederherstellen, sondern über die Property WindowState (engl. Fensterstatus). Wenn der Wert von WindowState geändert wird (z.B. von minimiert auf maximiert), wird das Fenster automatisch entsprechend angeordnet.