2.5 Variablen und Datentypen
Dateninformationen bilden die Grundlage der Datenverarbeitung und hauchen einem Programm Leben ein: Daten können anwendungsspezifisch sein, den Zustand von Objekten beschreiben, Informationen aus Datenbanken repräsentieren oder auch nur eine Netzwerkadresse. Daten bilden also gemeinhin die Basis der Gesamtfunktionalität einer Anwendung.
Praktisch jedes Programm benötigt Daten, um bestimmte Aufgaben zu erfüllen. Daten werden in Variablen vorgehalten. Dabei steht eine Variable für eine Adresse im Hauptspeicher des Rechners. Ausgehend von dieser Adresse wird eine bestimmte Anzahl von Bytes reserviert – entsprechend dem Typ des Werts. Das, was eine Variable repräsentiert, kann vielfältiger Art sein: eine einfache Zahl, eine große Fließkommazahl, ein einzelnes Zeichen, eine Zeichenkette, eine Datums- oder Zeitangabe, aber auch die Referenz auf die Startadresse eines Objekts.
Der Variablenname, auch Bezeichner genannt, dient dazu, die Speicheradresse im Programmcode mit einem Namen anzusprechen, der sich einfach merken lässt. Er ist also vom Wesen her nichts anderes als ein Synonym oder Platzhalter eines bestimmten Speicherortes. Der Speicherplatz wird von .NET automatisch verwaltet.
2.5.1 Explizite und implizite Variablen (Option Explicit)
Im Gegensatz zu den meisten anderen Programmiersprachen hat man in Visual Basic die Wahl zwischen zwei verschiedenen Arten, Speicher für eine Variable zu reservieren:
- implizit durch Zugriff auf eine Variable
- explizit durch Deklaration einer Variablen
Schauen wir uns zuerst die implizite Art an. Da sie standardmäßig nicht erlaubt ist, muss diese Möglichkeit erst freigeschaltet werden, entweder in den Projekteigenschaften für alle Dateien des Projekts oder auf Dateiebene im Programmcode. Abbildung 2.31 zeigt die erste Variante.
Abbildung 2.31 Implizite Variablen erlauben
Die andere Alternative wird in der nächsten Anwendung gezeigt. Hier wird mit Option Explicit Off in der ersten Zeile der Zwang zur Variablendeklaration unterdrückt. In der Anwendung wird einer Variablen ein Wert zugewiesen, und es wird versucht, diesen Wert auszugeben. Da das Erfordernis abgeschaltet ist, Variablen zu deklarieren, kann der Variablen ohne vorherige Deklaration etwas zugewiesen werden. Jedoch hat sich ein Tippfehler eingeschlichen: Die beiden letzten Buchstaben sind bei der Ausgabe vertauscht. Wenn Sie, wie hier dargestellt, mit Namensräumen arbeiten, kann es erforderlich sein, den Einstiegspunkt festzulegen (siehe Abbildung 2.18).
' ...\Sprachsyntax\ImpliziteVariablen\Implizit.vb |
Option Explicit Off Namespace Sprachsyntax Module Module1 Sub Main() Variable = 1 Console.WriteLine("Variable = {0}",Variabel) Console.ReadLine() End Sub End Module End Namespace
Dass sich beide Bezeichner unterscheiden, interessiert den Compiler nicht im Geringsten. Er nimmt keine Notiz davon, denn die explizite Variablendeklaration ist zugunsten der impliziten deaktiviert. Was macht der Compiler nun? Ganz einfach, er interpretiert den falsch geschriebenen Variablennamen als zweite Variable, deren Inhalt dann an der Konsole entsprechend ausgegeben wird (siehe Abbildung 2.32) – er ist leer!
Abbildung 2.32 Implizite Variable
Mit Option Explicit On meldet der Compiler einen Fehler (siehe Abbildung 2.33).
Abbildung 2.33 Implizite Variablen mit »Option Explicit On«
Die Korrektur besteht darin, die Variable mit dem Schlüsselwort Dim zu deklarieren und den Tippfehler zu korrigieren.
Option Explicit On Namespace Sprachsyntax Module Module1 Sub Main() Dim Variable = 1 Console.WriteLine("Variable = {0}",Variable) Console.ReadLine() End Sub End Module End Namespace
2.5.2 Typisierte und untypisierte Variablen (Option Strict)
Die nächste Stufe zu einer sauberen Variablendeklaration wäre es, die Compileroption Strict auf On zu setzen, um eine explizite Typangabe für jede Variable zu erzwingen. Dies ist nicht der Standard in Visual Basic 2008, aber extrem zu empfehlen. Das nächste Listing für das Projekt UntypisierteVariablen verwendet dieselbe Variable für eine ganze Zahl und für eine Zeichenkette.
' ...\Sprachsyntax\UntypisierteVariablen\Untypisiert.vb |
Option Strict Off Namespace Sprachsyntax Module Module1 Sub Main() Dim Variable = 1 Variable = "1.78" Console.WriteLine("Variable = {0}",Variable) Console.ReadLine() End Sub End Module End Namespace
Die Ausgabe in Abbildung 2.34 zeigt, dass durch die erste Zuweisung der Typ der Variablen implizit festgelegt wurde und spätere Zuweisungen einen Wert implizit in diesen Typ konvertieren, also in eine ganze Zahl. Wir können also mit untypisierten Variablen nur vermeiden, selbst explizit zu konvertieren. Dass eine deklarierte Variable einen fixierten Typ hat, ist nicht zu vermeiden.
Abbildung 2.34 Untypisierte Variable
Mit Option Strict On meldet der Compiler einen Fehler. Der Korrekturvorschlag (siehe Abbildung 2.35) zeigt die bisher vorgenommene automatische Konvertierung mit CInt. Er erscheint, wenn man auf den Pfeil klickt, der angezeigt wird, wenn man mit dem Mauszeiger eine Weile über dem fehlerhaften Wert verharrt.
Abbildung 2.35 Fehler bei einer untypisierten Variablen
Durch eine explizite Typangabe lassen wir uns vom Compiler bei der Arbeit helfen. Er findet automatisch Inkonsistenzen. Da wir weiterhin explizite Konvertierungen vornehmen können, ist die erzwungene Typisierung keine Einschränkung der Funktionalität. Das folgende Listing zeigt eine solche Konvertierung. Die Ausgabe ist identisch zum Programm mit untypisierten Variablen.
Option Strict On Namespace Sprachsyntax Module Module1 Sub Main() ' Deklaration der Variablen als ganze Zahl Dim Variable As Integer = 1 Variable = CInt("1.78") Console.WriteLine("Variable = {0}",Variable) Console.ReadLine() End Sub End Module End Namespace
Hinweis |
Im Gegensatz zu früheren Visual-Basic-Versionen kann der Compiler den Datentyp einer ohne Typangabe deklarierten Variablen aus der ersten Wertzuweisung ermitteln – es sei denn, der Mechanismus wurde mit Option Infer Off abgeschaltet. Eine explizite As-Klausel bei jeder Deklaration macht das Programm aber leichter lesbar und ist daher dringend zu empfehlen. |
2.5.3 Variablendeklaration
In den vorigen Abschnitten haben wir gesehen, wie problematisch Variablen sein können, deren Typ nicht explizit angegeben wird. Wir werden daher alle folgenden Beispiele im ganzen Buch immer mit der Compileroption
Option Strict On
übersetzen. Die damit erforderliche Variablendeklaration muss vor der ersten Wertzuweisung an die Variable erfolgen, gegebenenfalls in derselben Zeile, und setzt sich immer aus drei Informationen zusammen, eine vierte ist optional:
- einem Modifikator, der die Lebensdauer sowie die Sichtbarkeit und somit den Zugriff auf die Variable beeinflusst
- dem Variablennamen, auch Bezeichner genannt
- dem Datentyp, den die Variable repräsentiert
- einer Zuweisung des Startwerts
Die allgemeine Syntax zur Deklaration einer Variablen lautet damit wie folgt (optionale Teile stehen in eckigen Klammern):
<Modifikator> <Bezeichner> As <Datentyp> [= <Wert>] |
Variablen zu deklarieren heißt, einem Bezeichner mit der As-Klausel einen bestimmten Datentyp zuzuordnen und die Art des Zugriffs festzulegen. Bisher haben wir nur Dim als Modifikator kennen gelernt. Eine Deklaration könnte beispielsweise folgendermaßen aussehen:
Dim intVar As Integer
Damit wird dem Compiler mitgeteilt, dass der Bezeichner intVar für ein Datum steht, das vom Typ einer Ganzzahl ist, genauer gesagt vom Typ Integer, die einen bestimmten Wertebereich abdecken kann. Mit
intVar = 1000
wird dieser Variablen die Zahl 1000 zugewiesen.
In einer Codezeile können durchaus mehrere Variablen deklariert werden. Außerdem kann eine Variable schon bei der Deklaration initialisiert werden, das heißt, ihr kann also ein spezifischer Startwert zugewiesen werden.
' x, y und z sind vom Typ Integer Public x, y, z As Integer ' x ist vom Typ Short, y vom Typ String Dim x As Short, y As String ' strText wird als String deklariert und sofort mit der ' Zeichenfolge "Hallo Welt" initialisiert Dim strText As String = "Hallo Welt" ' die Variable iVar wird deklariert, und ihr wird das Datum 12 ' zugewiesen, dblGehalt ist vom Typ Double Dim iVar As Integer = 12, dblGehalt As Double
Hinweis |
Für eine Variable, die einer Methode lokal zugeordnet ist, kann der Typ aus der Initialisierung vom Compiler automatisch ermittelt werden (es sei denn, Option Infer Off ist gesetzt). Zwecks besserer Lesbarkeit empfiehlt sich aber eine As-Klausel. |
Variablenname
Der Variablenname (Bezeichner) kann nahezu beliebig festgelegt werden, unterliegt aber besonderen Reglementierungen:
- Ein Bezeichner darf sich nur aus alphanumerischen Zeichen und dem Unterstrich zusammensetzen. Leerzeichen und andere Sonderzeichen wie beispielsweise #, §, $ usw. sind nicht zugelassen.
- Ein Bezeichner muss mit einem Buchstaben oder dem Unterstrich anfangen.
- Ein alleinstehender Unterstrich als Variablenname ist nicht zulässig.
- Der Bezeichner muss eindeutig sein. Er darf nicht denselben Namen wie eine Prozedur, eine Klasse oder ein Objekt im gleichen »Codingabschnitt« haben (siehe Abschnitt 2.5.5, »Sichtbarkeit und Lebensdauer«).
- Soll ein Bezeichner wie ein Schlüsselwort lauten, muss er in eckige Klammern gesetzt werden.
Für die letzte Regel soll direkt ein Beispiel angegeben werden. Wenn Sie beispielsweise – aus welchen Gründen auch immer – einer Variablen den Namen des von Visual Basic reservierten Schlüsselworts Public geben wollen, können Sie diesen Bezeichner in eckige Klammern setzen:
Dim [Public] As Integer [Public] = 10
Mit der Klammerung wird die Bedeutung des Schlüsselworts außer Kraft gesetzt. Ich vermeide, wenn möglich, die Verwendung von Schlüsselwörtern für eigene Variablen. Einige Beispiele sollen diese Regeln verdeutlichen:
' korrekte Variablendeklarationen Dim lngMyVar As Long Dim bResult_12 As Byte Dim intCarColor As Integer ' fehlerhafte Variablendeklarationen Dim 34M As Integer Dim strMessage Text As String Dim lngSalary%Tom as Integer
Visual Basic unterscheidet nicht zwischen der Groß- und Kleinschreibung wie viele andere Programmiersprachen. Dennoch wird der Groß-/Kleinschreibung im Texteditor Rechnung getragen. Ausschlaggebend ist die Angabe in der Deklaration. Sie können sich dies sogar zunutze machen, wenn Sie bei der Deklaration einer Variablen dem Bezeichner einen oder mehrere Großbuchstaben mit auf den Lebensweg geben, zum Beispiel:
Dim meineVariable As Integer
Wenn Sie im Laufe des weiteren Programmierens der Variablen einen Wert zuweisen und den Namen dabei nur in Kleinbuchstaben schreiben, beispielsweise
meinevariable = 10
dann wird die Entwicklungsumgebung beim Zeilenumbruch automatisch die deklarierte Schreibweise erkennen und entsprechend in
meineVariable = 10
korrigieren. Mit dieser Technik können Sie sofort erkennen, ob Sie den Namen der Variablen richtig geschrieben haben, und mögliche lästige Fehlermeldungen beim späteren Testen vermeiden.
Noch ein Hinweis zur Namensvergabe: Wählen Sie grundsätzlich beschreibende Namen, damit Ihr Code später besser lesbar wird. Einfache Bezeichner wie x oder y usw. sind wenig aussagefähig. Besser ist eine Wahl wie intFarbe, dblGehalt, strVorname usw. Nur den Zählervariablen von Schleifen werden meistens Kurznamen gegeben.
Die Bezeichner von Variablen sollten mit einem Kleinbuchstaben anfangen – im Gegensatz zu den Namen von (öffentlichen) Prozeduren und Objekten. Sie können ein Präfix benutzen, aus dem auch innerhalb einer Anweisung hervorgeht, welcher Datentyp von der Variablen beschrieben wird:
Dim intVar As Integer Dim strText As String
Die Verwendung eines Präfixes zusammen mit einem sinnvollen, beschreibenden Bezeichner macht den Programmcode lesbarer. Wenn Ihr Programm durch Einzüge sauber strukturiert und mit ausreichenden Kommentaren versehen ist, wird auch eine dritte Person kaum Schwierigkeiten haben, in angemessener Zeit die Programmlogik zu interpretieren.
Tabelle 2.2 enthält als Vorschlag Präfixe für Datentypen.
Datentyp | Präfix | Datentyp | Präfix | Datentyp | Präfix |
Byte |
bt |
Single |
sng |
Char |
chr |
Short |
sh |
Double |
dbl |
String |
str |
Integer |
int |
Decimal |
dec |
Date |
dt |
Long |
lng |
Boolean |
bln |
Object |
obj |
Datentyp
Mit dem Datentyp wird festgelegt, wie groß der Wertebereich der zu speichernden Dateninformation sein soll, wie viel Speicherplatz dafür reserviert werden muss und damit auch, wie dieser Speicherplatz zur Laufzeit interpretiert wird. Weiter unten in Abschnitt 2.5.4, »Einfache Datentypen«, werden alle zur Verfügung stehenden Datentypen erläutert.
Die Datentypen lassen sich in zwei Gruppen einteilen:
- native Typen, wie beispielsweise Integer oder Decimal
- Objekttypen
In diesem Kapitel werden Sie nur die nativen Datentypen kennenlernen, mit den anderen werden wir uns ab dem nächsten Kapitel befassen.
Eine weitere Gruppierung der Datentypen erfolgt danach, wie sie im Speicher behandelt werden. Man unterscheidet:
- Wertetypen
- Referenztypen
Für Wertetypen wird an der Stelle Speicher reserviert, an der sie deklariert werden. Eine Variable in einer Prozedur erhält also einen Speicherbereich innerhalb der Prozedur. Demgegenüber wird für Referenztypen einmal global Speicher reserviert, und eine Variable dieses Typs enthält nur eine Adresse auf den Speicher.
Besonders wichtig sind die Referenztypen Object und String. Während Object die Basis aller Referenztypen ist, repräsentiert String Zeichenketten. Obwohl die Begriffe Objekt, Referenztyp und Wertetyp in den folgenden Abschnitten noch öfter fallen werden, wird die genaue Erklärung erst im nächsten Kapitel erfolgen, wenn wir uns intensiv mit Klassen und Objekten beschäftigen.
Modifikatoren
Die Modifikatoren lassen sich in drei Gruppen einteilen:
- Lebensdauer: Dim bzw. Const und Static
- Gültigkeitsbereich: Dim und Shared bzw. Const
- Sichtbarkeit: Public, Private, Protected und Friend
Abschnitt 2.5.5, »Sichtbarkeit und Lebensdauer«, erläutert das erste Konzept (Lebensdauer) und geht kurz auf die Sichtbarkeit ein. Diese und das Konzept der Gültigkeitsbereiche werden ausführlicher im Rahmen der Objektorientierung ab Kapitel 3, »Klassendesign«, beschrieben. Die Deklaration erlaubt in der Regel nur einen Modifikator; Ausnahmen sind zum Beispiel: Protected Friend und Dim Shared.
2.5.4 Einfache Datentypen
Die .NET-Laufzeitumgebung verfolgt das Konzept der Objektorientierung nach strengen Maßstäben. Selbst einfache Datentypen werden als Objekte angesehen, die Methoden bereitstellen, um mit einer Variablen bestimmte Aktionen auszuführen. In Tabelle 2.3 sind alle nativen Datentypen sowie Object und String zusammenfassend aufgeführt.
.NET-Typ | VB-Alias | Typ-Postfix | Literal-Postfix | CLS- konform | Wertebereich |
Byte |
Byte |
ja |
0 bis 255 |
||
SByte |
SByte |
nein |
-128 ... 127 |
||
Int16 |
Short |
S |
ja |
-215 … 215 – 1 |
|
UInt16 |
UShort |
US |
nein |
0 … 65.535 |
|
Int32 |
Integer |
% |
I |
ja |
-231 … 231 – 1 |
UInt32 |
UInteger |
UI |
nein |
0 ... 232 – 1 |
|
Int64 |
Long |
& |
L |
ja |
-263 … 263 – 1 |
UInt64 |
ULong |
UL |
nein |
0 … 264 – 1 |
|
Single |
Single |
! |
F |
ja |
1,4*10-45 bis 3,4*1038 (23 Bit Ziffern) |
Double |
Double |
# |
R |
ja |
5,0*10-324 bis 1,7*10308 (52 Bit Ziffern) |
Decimal |
Decimal |
@ |
D |
ja |
+/-79E27 (-296 – 1 bis 296 – 1 mit Skalierungsfaktor 10-28 bis 100, d. h. 28 bis 0 Nachkommastellen) |
Char |
Char |
C |
ja |
Unicode-Zeichen zwischen 0 und 65.535 |
|
Boolean |
Boolean |
ja |
True oder False |
||
DateTime |
Date |
# (auch Präfix) |
1/1/1 12:0:0 – 31/12/9999 23:59:59 |
||
Object |
Object |
ja |
Eine Variable vom Typ Object kann jeden anderen Datentyp enthalten, ist also universell. |
||
String |
String |
$ |
ja |
ca. 231 Unicode-Zeichen |
In der ersten Spalte ist der Typbezeichner in der .NET-Klassenbibliothek angeführt, in der zweiten Spalte das Visual-Basic-Pseudonym, das bei der Deklaration einer Variablen dieses Typs angegeben werden kann. Welche der beiden Typen man verwendet, spielt keine Rolle. Damit sind die beiden folgenden Deklarationen der Variablen intVar absolut gleichwertig:
Dim intVar As Integer Dim intVar As Int32
Statt den Typ in einer As-Klausel anzugeben, kann für einige Typen auch ein Zeichen der dritten Spalte angehängt werden. Die beiden nächsten Zeilen sind daher identisch:
Dim sngVar As Single
Dim sngVar!
.NET verfolgt nicht nur ein plattformunabhängiges Konzept, sondern auch ein sprachunabhängiges. Soll der Visual-Basic-Code mit anderen .NET-Sprachen zusammenarbeiten, muss darauf geachtet werden, dass alle Datentypen der öffentlichen Schnittstelle in diesen Sprachen bekannt sind. Der private Teil einer Anwendung verursacht keine Probleme. Alle .NET-Sprachen müssen mindestens die Typen der Common Language Specification (CLS) implementieren. Wenn ein in der fünften Spalte als nichtkonform gekennzeichneter Datentyp in der öffentlichen Schnittstelle verwendet wird, ist die Zusammenarbeit mit anderen Sprachen wahrscheinlich unmöglich. Solche Typen haben in der Schnittstelle nichts zu suchen.
Das bedeutet: Wenn eine .NET-Anwendung CLS-konform codiert wird, ist eine Garantie damit verbunden, dass jeder andere .NET-Code Zugriff auf die (öffentlichen) Komponenten der CLS-konformen Anwendung hat – unabhängig davon, in welcher Sprache sie codiert ist.
Ganzzahlige Datentypen
Visual Basic stellt acht ganzzahlige Datentypen zur Verfügung, von denen vier vorzeichenbehaftet sind. Die uns interessierenden CLS-konformen Datentypen sind:
- Byte
- Int16
- Int32
- Int64
Byte ist besonders wichtig, wenn auf binäre Daten zugegriffen wird, und speichert nur positive Zahlen und die Null. Die anderen drei Typen decken den positiven und negativen Wertebereich nahezu identisch ab.
Literale hexadezimale Zahlen (Basis = 16) erhalten das Präfix &H, oktale Zahlen (Basis = 8) das Präfix &O, und dezimale Zahlen schreibt man ohne Präfix. Die folgenden Zahlen beschreiben beide dieselbe Dezimalzahl, nämlich 225, zuerst in hexadezimaler, danach in oktaler Schreibweise:
Dim intHex As Integer = &HE1 Dim intOct As Integer = &O341
Um bei der Division zweier Zahlen eine ganze Zahl zu erhalten, muss der richtige Operator verwendet werden (siehe auch Abschnitt 2.7.1, »Arithmetische Operatoren«).
Dim fliess As Integer = 2/3 'Compilerfehler!!
Dim ganz As Integer = 2\3
Fließkommazahlen
Single, Double und Decimal sind die drei Typen, mit denen unter Visual Basic Fließkommazahlen dargestellt werden können. Sie beschreiben nicht nur unterschiedliche Wertebereiche, sondern auch – was noch viel wichtiger ist – unterschiedliche Genauigkeiten. Ein Single reserviert 23 Bit für Ziffern, ein Double 52 und ein Decimal 96. Also sind 223 -1=8.388.607, 252 -1=4.503.599.627.370.495 und 296 -1=79.228.162.514.264.337.593.543.950.336 die größten speicherbaren Ziffernfolgen mit 7, 16 und 29 Stellen und liegen zwischen 0 und 1. Die Vorfaktoren 2-2^7+2 1.2*10-38 bis 22^7 3.4*1038 , 2-2^11+2 2.2*10-308 bis 22^11 1.8*10308 und 10-28 bis 1 legen die Größe der Zahlen fest.
Die kleinsten positiven Zahlen werden dadurch erreicht, dass die Bits der Ziffernfolgen die Größe der Zahl mitbestimmen. Für ein Single zum Beispiel existiert zwischen 2-126-23 und 2-125-23 keine Zahl, und erst ab 2-126 aufwärts ist die Darstellung wieder kontinuierlich.
Das folgende Codefragment demonstriert die Genauigkeit, die mit einem Single erreicht werden kann:
' ...\Sprachsyntax\Zahlen\Fliesskomma.vb |
Namespace Sprachsyntax
Module Fliesskomma
...
Sub Genauigkeit()
Dim x As Single = 0.8388608
Dim y As Single = 0.83886081
Console.WriteLine("x=y {0}", x = y) ' True
Console.ReadLine()
End Sub
End Module
End Namespace
Es werden zunächst zwei Variablen vom Typ Single deklariert, und danach wird beiden ein Wert zugewiesen, der sich nur an der letzten Nachkommastelle unterscheidet. In der Ausgabe werden beide Zahlen verglichen.
Ein Lauf des Programms behauptet, beide Werte wären gleich. Diese offensichtliche Falschaussage ist darauf zurückzuführen, dass ein Single-Typ nicht in der Lage ist, alle hier angegebenen Nachkommastellen exakt zu interpretieren – er ist schlichtweg überfordert. Für Berechnungen, die eine höhere Genauigkeit erfordern, ist dieser Datentyp daher weniger gut geeignet.
Wir können dieser Stolperfalle leicht entgehen, indem wir die Zahlenliterale kennzeichnen. Ohne Kennzeichnung wird
- ein ganzzahliges Literal als Integer interpretiert und
- ein Literal vom Typ einer Dezimalzahl als Double.
Setzen wir ein »F« hinter die Zahl, kennzeichnen wir sie als Single. Sobald wir die Zeile verlassen, wird
0.83886081F
automatisch in
0.8388608F
eingekürzt, da die letzte 1 vom Datentyp Single sowieso nicht erfasst wird. Wir sehen also schon bei der Eingabe, ob alles wie gewünscht läuft. In Abschnitt 2.5.7, »Datentypkonvertierung«, gehen wir näher auf die Thematik ein.
Bei einem Double tritt das gleiche Problem erst bei einer größeren Stellenzahl auf. Dieser Datentyp bietet sich also für genauere Rechnungen an. Der Vorteil eines größeren Wertebereichs ist meistens nur zweitrangig, da ein Single mit 1038 bereits extrem große Werte speichern kann.
Die Genauigkeit, die hier betrachtet wird, bezieht sich auf die Anzahl der Stellen einer Zahl, unabhängig davon, wo das Komma steht. Wenn wir den vorigen Test mit einem um 2 Positionen nach rechts verschobenen Komma wiederholen, ergibt sich dieselbe Ausgabe.
' ...\Sprachsyntax\Zahlen\Fliesskomma.vb |
Namespace Sprachsyntax
Module Fliesskomma
...
Sub Komma()
Dim x As Single = 83.88608
Dim y As Single = 83.886081
Console.WriteLine("x=y {0}", x = y) ' True
Console.ReadLine()
End Sub
End Module
End Namespace
Reicht die Stellenzahl von Double nicht, kann man noch auf Decimal zurückgreifen. Der Typ hat 28 signifikante Stellen, die sich alle direkt vor oder hinter dem Komma befinden (ein Skalierungsfaktor 10x ist unnötig). Da wir hier immer die Compileroption Strict auf On setzen, müssen wir ein Decimal-Zahlenliteral mit »D« kennzeichnen, da eine ungekennzeichnete Fließkommazahl als Double angesehen wird und nicht alle Double in Decimal hineinpassen, zum Beispiel:
decA = 0.123D
Decimal wird meist im Finanzsektor verwendet, da man mit diesem Datentyp riesige Beträge handhaben kann, ohne auch nur einen Cent zu verlieren. Da Rechnungen mit Decimal nicht direkt von der Hardware durchgeführt werden, sondern in eine spezielle Ganzzahlarithmetik übersetzt werden, sind sie deutlich langsamer als solche mit Double.
Hinweis |
Beachten Sie, dass der ganzzahlige Anteil einer Fließkommazahl grundsätzlich immer durch einen Punkt vom Nachkommaanteil getrennt wird. Diese Regel gilt auch dann, wenn Sie in der spezifischen Ländereinstellung auf Systemebene ein Komma als Trennzeichen eingestellt haben. Lediglich auf Zeichenketten mit Anführungszeichen, wie zum Beispiel »2,58«, haben diese Einstellungen Einfluss, und zwar sowohl beim Einlesen als auch beim Rausschreiben. |
Zeichenbasierte Datentypen
Intern wird jedes Zeichen als eine Zahl dargestellt. Die Übersetzungstabelle zwischen Zeichen und Zahlen wird Zeichensatz genannt. Zu DOS-Zeiten war der ASCII-Zeichensatz üblich, in Windows wird der ANSI-Zeichensatz eingesetzt. Beide können maximal 256 verschiedene Zeichen gleichzeitig verwalten und beanspruchen 1 Byte Speicherplatz. Im Rahmen der Internationalisierung wurde es nötig, mehr Zeichen zu erlauben. .NET benutzt daher für Zeichen den Typ Char, der 2 Byte belegt und in Unicode kodiert ist. Damit sind 65.536 verschiedene Zeichen direkt nutzbar. Wenn nötig, erlauben sogenannte Surrogate, drei oder vier Char als ein Zeichen zu interpretieren. Sie sollten sich daher bei der Verarbeitung nichtwestlicher Sprachen keinesfalls darauf verlassen, dass ein Zeichen immer durch einen einzigen Char repräsentiert wird. Zwecks Kompatibilität sind die ersten 128 Zeichen aller Zeichensätze identisch. Sie enthalten alle Zeichen, die für Visual Basic benötigt werden.
Da hier immer Option Strict eingeschaltet ist (On), muss die Zuweisung eines Zeichens an die Char-Variable umgewandelt werden. Entweder wird das Typkennzeichen c angehängt oder die Konvertierungsfunktion CChar benutzt (nur das erste Zeichen wird konvertiert). Eine Umwandlung aus dem Zeichencode ist auch möglich:
' ...\Sprachsyntax\Zeichen\Zeichen.vb |
Option Strict On Namespace Sprachsyntax Module Buchstaben ... Sub Initialisierung() Dim a As Char = "A"c Dim b As Char = CChar("AB") Dim c As Char = ChrW(65) Console.WriteLine("abc {0}{1}{2}",a,b,c) ' AAA Console.ReadLine() End Sub End Module End Namespace
Um eine Zeichenkette, die sich aus keinem oder bis zu maximal ungefähr 231 Einzelzeichen zusammensetzt, zu speichern oder zu bearbeiten, deklarieren Sie eine Variable vom Datentyp String. Die Einzelzeichen werden dabei wie ein Char-Typ als Unicode-Zeichen der Größe 16 Bit behandelt. Zeichenketten werden grundsätzlich in Anführungsstriche gesetzt (normale doppelte oder die Unicode-Zeichen 0x201C und 0x201D). Ein Zugriff auf einzelne Buchstaben ist über einen Index in runden Klammern möglich. Der erste Buchstabe hat die Nummer null.
' ...\Sprachsyntax\Zeichen\Zeichen.vb |
Option Strict On Namespace Sprachsyntax Module Buchstaben ... Sub Zeichenketten() Dim a As String = "Text" Dim b As Char = a(1) Console.WriteLine("a-b {0}-{1}", a, b) ' Text-e Console.ReadLine() End Sub End Module End Namespace
Hinweis |
Anders als in vielen anderen Programmiersprachen wird ein Anführungszeichen innerhalb einer Zeichenkette nicht durch \" dargestellt, sondern durch ein verdoppeltes Zeichen: "". |
Logischer Datentyp
Variablen vom Typ Boolean können nur die Zustände True oder False beschreiben.
In vielen Programmiersprachen wird False numerisch mit 0 beschrieben, True durch alle Werte, die von 0 abweichen. .NET ist hier sehr viel strenger. Hier ist True nicht 1 und auch nicht 67, sondern ganz schlicht True. Aus diesem Grund ist auch die folgende Anweisung falsch, die so in anderen Programmiersprachen durchaus möglich ist:
Dim myBool As Boolean = 2 'Compilerfehler!!
Das beeinflusst Bedingungsprüfungen, wie Sie später noch sehen werden. Eine explizite Konvertierung zu ganzzahligen Typen ist möglich (False entspricht 0, True setzt alle Bits, eine Zahl ungleich 0 ergibt True).
Datumsangaben
Der Datentyp Date speichert Datumsangaben. Datumsliterale werden in Lattenzäune eingeschlossen. Jahresangaben müssen 4 Stellen haben, gegebenenfalls mit führenden Nullen. Der Monat wird vor dem Tag angegeben. Ohne AM und PM hat ein Tag 24 Stunden. Ein fehlender Tag wird als 1/1/1 angenommen, eine fehlende Zeit mit Mitternacht (12AM). Fehlende Minuten oder Sekunden werden als 0 angenommen.
Dim d As Date d = # 12/13/2007 4:22:58PM # ' vollständiges Datum d = # 12/13/2007 # ' wird ergänzt durch 12:00:00AM d = # 16:22:58 # ' wird ergänzt durch 1/1/1
Native Datentypen als Objekte
Egal, was wir in .NET betrachten, immer ist es ein Objekt. Damit dies möglich ist, lassen sich alle Datentypen auf einen Typ namens Object zurückführen. Da er für alles stehen kann, darf man auch darin speichern, was man will. Im folgenden Codefragment ist es einmal eine Zahl, dann ein Text:
Dim x As Object
x = 1.23
x = x + 1
x = "irgendwas"
Wie ist das mit einem festen Typsystem in .NET zu vereinbaren, das für gleiche Datentypen auch immer gleichen Speicherplatz reserviert? In x werden nicht direkt Daten gespeichert, sondern ein Zeiger auf diese Daten. Dieser 4 Byte große Zeiger ist natürlich unabhängig davon, worauf er gerade zeigt. Interessant wird dieses Konzept besonders dadurch, dass zur Laufzeit des Programms .NET dafür sorgt, dass sich das Objekt »richtig« verhält. Im obigen Beispiel wird zu x eine Zahl addiert, ohne dass wir uns speziell Gedanken machen müssen, welchen Typ x gerade hat. Dieses Konzept wird uns in ausgebauter Form noch bei der Definition von Klassen begegnen, und zwar unter dem Begriff Polymorphie.
Das funktioniert selbst für die bisher betrachteten einfachen Datentypen. Selbst so etwas wie eine Zahl wird, bei Bedarf, automatisch in ein Objekt umgewandelt. Diese Umwandlung erfolgt komplett automatisch und wird Boxing genannt.
Eine einfache ganze Zahl vom Typ Integer soll so etwas Komplexes wie ein Objekt sein? Wenn Sie, wie in Abbildung 2.36, eine entsprechende Variable vereinbaren und in der nächsten Zeile nach dem Namen einen Punkt tippen, klappt eine Liste mit Methoden und Eigenschaften des Objekts Integer auf, auf die man direkt Zugriff hat. Sollte die Liste nicht auto-matisch erscheinen, drücken Sie + . Diese kontextsensitive Hilfe wird IntelliSense genannt.
Abbildung 2.36 IntelliSense-Liste einer Integervariablen
Hinweis |
Wenn Sie eine Auswahl in der IntelliSense-Liste mit + oder bestätigen, wird kein Zeilenvorschub an die Auswahl angehängt. |
Sie können sich dieser Funktionalitäten bedienen. Wenn Sie beispielsweise wissen wollen, wo die Ober- bzw. Untergrenze des Integers liegt, könnten Sie dies mit dem folgenden Codefragment abfragen:
Sub Main() Dim intV As Integer Console.WriteLine("Integer(min) = {0}", intV.MinValue) Console.WriteLine("Integer(max) = {0}", intV.MaxValue) Console.ReadLine() End Sub
Beachten Sie bitte hierbei, dass Sie in syntaktisch gleicher Weise, wie Sie die Methode WriteLine der Klasse Console mittels Punktnotation aufrufen, auch auf die Ober- und Untergrenze des Typs Integer zugreifen: Zuerst wird der Objektname angegeben, der in unserem Beispiel intV lautet, danach folgt, durch einen Punkt abgetrennt, die Methode bzw. Eigenschaft (in unserem Fall handelt es sich um eine Eigenschaft). An der Konsole wird danach Folgendes angezeigt:
Integer(min) = –2147483648 Integer(max) = 2147483647
2.5.5 Sichtbarkeit und Lebensdauer
Grundsätzlich können Variablen
- innerhalb einer Prozedur eines Moduls oder
- auf der Ebene des Moduls
deklariert werden. Im folgenden Codefragment entspricht procVar dem ersten und modVar dem zweiten Fall.
Module Variablenart Dim modVar As Integer Sub proc() Dim procVar As Integer End Sub End Module
Da Prozedurvariablen nur innerhalb derselben Prozedur verwendet werden können, werden sie auch lokale Variablen genannt. Im Gegensatz dazu werden Variablen außerhalb von Prozeduren, aber innerhalb eines Moduls globale Variablen genannt. (Außerhalb jeglichen Moduls können keine Variablen deklariert oder verwendet werden.)
Sichtbarkeit
Bezüglich des Zugriffs auf eine Variable können wir zwei Fälle unterscheiden:
- Sie ist erreichbar.
- Sie ist sichtbar.
Der erste Fall liegt vor, wenn es syntaktisch möglich ist, eine Variable anzusprechen. Zum Beispiel ist es nicht möglich, von außerhalb auf eine Prozedurvariable zugreifen. Demgegenüber ist der zweite Fall bei Zugriffen auf Modulvariablen über Modulgrenzen hinweg interessant. Hierbei gibt es vier Ebenen der Beschränkung, von denen die vorletzte erst verständlich werden wird, nachdem wir das Konzept der Klassen besprochen haben.
- Public: Es liegen keine Beschränkungen vor.
- Friend: Die Variable ist nur innerhalb der Anwendung sichtbar.
- Protected: Die Variable ist nur für »Kinder« eines Moduls sichtbar.
- Private: Die Variable ist nur von innerhalb desselben Moduls sichtbar (Dim hat denselben Effekt).
Hinweis |
Module (und andere Typen), die sich direkt unterhalb eines Namensraums befinden, dürfen nur Public oder Friend sein, wobei keine Angabe gleichbedeutend mit Friend ist. Die Sichtbarkeit des Programmeinstiegspunkts (meist Main) ist bedeutungslos. |
Wenn zum Beispiel eine Modulvariable als Private gekennzeichnet ist, kann nicht von einem anderen Modul auf sie zugegriffen werden, da sie von dort aus nicht sichtbar ist. Innerhalb desselben Moduls ist sie sichtbar und der Zugriff ist möglich.
Es gilt als guter Programmierstil, einer Variablen nur die Sichtbarkeit zuzugestehen, die sie im Code auch tatsächlich benötigt. Dahinter verbirgt sich die Absicht, eine nicht zulässige, unkontrollierte Manipulation auszuschließen. Außerdem müssen Sie auch bedenken, dass globale Variablen dauerhaft Speicherressourcen verbrauchen, was trotz der guten Ausstattung moderner Rechner in großen Anwendungen durchaus ein gewichtiges Argument sein kann.
Das folgende Listing zeigt die verschiedenen Fälle, wobei Unmögliches und Unerlaubtes auskommentiert ist.
' ...\Sprachsyntax\Sichtbarkeit\Sichtbarkeit.vb |
Option Strict On Namespace Sprachsyntax Module Sichtbarkeit Public pub As Integer = 1 Friend frn As Integer = 2 Private priv As Integer = 3 Sub fun() Dim proc As Integer = 5 End Sub Sub Main() Dim proc As Integer = 4 Console.WriteLine("öffentlich {0}", pub) Console.WriteLine("Freund {0}", frn) Console.WriteLine("privat {0}", priv) Console.WriteLine("Prozedur {0}", proc) ' Console.WriteLine("andere Prozedur {0}", fun.proc) Console.WriteLine("öffentlich Fremd {0}", Fremd.pub) Console.WriteLine("Freund Fremd {0}", Fremd.frn) ' Console.WriteLine("privat Fremd {0}", Fremd.priv) Console.ReadLine() End Sub End Module Module Fremd Public pub As Integer = 5 Friend frn As Integer = 6 Private priv As Integer = 7 End Module End Namespace
Bitte beachten Sie, dass die Kennzeichnung als Public, Friend oder Private nur auf Modulebene möglich ist. Prozedurvariablen sind effektiv noch privater als Private.
Ein weiterer Aspekt der Sichtbarkeit betrifft nur Module und nicht andere Datentypen, wie zum Beispiel Klassen. Bei Modulen wird eine Definition außer im Modul selbst noch im umgebenden Namensraum zugänglich gemacht, sodass man sie auch ohne Qualifizierung in anderen Modulen (oder Klassen) verwenden kann. Der Wert dieses Konzepts scheint mir fragwürdig, da man so nicht direkt sehen kann, wo etwas herkommt, das man benutzt. Das nächste Codefragment zeigt, dass die Methode Gefunden() mit oder ohne Qualifizierung verwendet werden kann.
' ...\Sprachsyntax\Qualifizierung\Qualifizierung.vb |
Option Strict On Namespace Sprachsyntax Module Anderswo Public Wert As Integer End Module Module Qualifizierung Sub Main() Console.WriteLine(Anderswo.Wert) Console.WriteLine(Wert) Console.ReadLine() End Sub End Module End Namespace
Der Effekt ist der gleiche, der sich durch das folgende Codefragment ergeben würde (das man so selbst nicht schreiben darf):
Namespace Sprachsyntax Public Wert As Integer ... End Namespace
Hinweis |
Elemente, die Module von Object übernimmt, müssen qualifiziert werden (Equals und ReferenceEquals). |
Lebensdauer
Variablen können maximal so lange erhalten bleiben, wie
- ein Prozeduraufruf dauert oder
- das sie umgebende Modul existiert.
Nach ihrer Lebensdauer werden sie zerstört, und der Speicherplatz wird automatisch freigegeben.
Alle mit Dim gekennzeichneten Prozedurvariablen gehören zur ersten Gruppe; die Variablen auf Modulebene und alle mit Static gekennzeichneten Prozedurvariablen gehören zur zweiten. Die letzte Art Variable erinnert sich an den Wert, den sie beim letzten Prozeduraufruf hatte. Schauen wir uns dazu die folgende Anwendung an. Sie enthält eine Modulvariable glob, eine normale Prozedurvariable dyn und eine speziell mit Static gekennzeichnete Variable stat. In der Einstiegsprozedur Main() wird die Prozedur Zugriff() dreimal aufgerufen. In ihr werden alle drei Variablen um eins erhöht und ausgegeben.
' ...\Sprachsyntax\Lebensdauer\Lebensdauer.vb |
Option Strict On Namespace Sprachsyntax Module Lebensdauer Dim glob As Integer Sub Zugriff() Dim dyn As Integer Static stat As Integer dyn = dyn + 1 stat = stat + 1 : glob = glob + 1 Console.Write("dyn {0} ", dyn) Console.WriteLine("stat/glob {0}/{1}", stat, glob) End Sub Sub Main() Zugriff() : Zugriff() : Zugriff() Console.ReadLine() End Sub End Module End Namespace
Die Ausgabe des Programms sieht so aus:
dyn 1 stat/glob 1/1 dyn 1 stat/glob 2/2 dyn 1 stat/glob 3/3
Sie zeigt, dass nur die normale Prozedurvariable dyn bei jedem Aufruf der Prozedur neu initialisiert wird. Die beiden anderen behalten ihren Wert.
Es gibt Situationen, da ist es nicht wünschenswert, dass der Inhalt einer Variablen verloren geht. Angenommen, Sie möchten feststellen, wie oft eine Prozedur während der Laufzeit des Programms ausgeführt wird. Ob Sie dann eine mit Static gekennzeichnete Prozedurvariable verwenden oder eine private Modulvariable, ist oft Geschmackssache.
Hinweis |
Static-Variablen haben die gleiche Bindung wie die Methode (Objekt oder Klasse). |
Die Kennzeichnung mit Const verhindert jede Änderung einer Variablen nach deren Initialisierung. Vom Effekt her ist dies eine schreibgeschützte Static-Variable.
Const stat As Integer = 2
Hinweis |
Der Wert der Konstanten muss zur Compilezeit bekannt sein. Für Laufzeitkonstanten auf Modulebene wird ReadOnly verwendet. |
Überdeckte Variablen
Variablen müssen eindeutig benannt sein. Entweder müssen unterschiedliche Namen gewählt werden, oder einer der folgenden Bereiche ist verschieden:
- Namensraum
- Modul
- Prozedur
Auf verschiedenen Hierarchieebenen sind gleichnamige Variablen erlaubt. Im folgenden Codefragment ist die »gleiche« Variable auf Prozedur- und auf Modulebene definiert:
Module Module1 Public intVar As Integer Sub Main() Dim intVar As Integer ... End Sub End Module
Für Namen gilt ein Lokalitätsprinzip: Eine Prozedurvariable verdeckt eine Variable im Modul. In Main() wird also die Prozedurvariable verwendet. Die Modulvariable ist von der Prozedur aus über einen qualifizierenden Namen zu erreichen.
Module1.intVar
Der vollqualifizierte Name enthält, durch einen Punkt getrennt, den Namensraum (oder die Namenshierarchie), den Modulnamen und den Namen der Variable.
<Namensraum>.<Modulname>.<Variablenbezeichner> |
Ein Sonderfall ergibt sich für den Fall, das das Modul den Namen eines Wurzelnamensraums trägt. Das folgende Codefragment zeigt, dass dann dem Ganzen noch Global vorangestellt werden muss.
Module System
Sub Main()
Global.System.Console.ReadLine()
End Sub
End Module
2.5.6 Initialisierung von Variablen
Bei jeder Deklaration einer Variablen, ob in einer Prozedur oder einem Modul, wird diese mit einer datentypabhängigen »Null« initialisiert (siehe Tabelle 2.4).
Datentypen | Initialisierung |
Short, Integer, Long, Single, Double, Decimal, Byte |
0 |
String |
Leerstring "" |
Boolean |
False |
Object |
Nothing |
Diese Vorbelegung kann durch eine explizite Angabe überschrieben werden:
Public intNewVar As Integer = 9 Dim strMeldung As String = "Visual Studio"
Hinweis |
Jede Initialisierung erfordert eine eigene As-Klausel. Fehler bei der Initialisierung einer Static-Variablen setzt diese auf den Standardwert. |
2.5.7 Datentypkonvertierung
Jede Kombination von Daten verschiedenen Typs, zum Beispiel die Addition einer ganzen Zahl und einer Fließkommazahl, wirft die Frage auf, wie die verschiedenen Typen kombiniert werden sollen. Häufig werden dabei Konvertierungen notwendig, die in zwei Gruppen eingeteilt werden können:
- implizite Konvertierung: Sie erfolgt automatisch durch den Compiler.
- explizite Konvertierung: Sie erfolgt durch den Programmierer.
Implizite Konvertierung
Die erste Art ist nur dann sinnvoll, wenn dabei keine Daten verloren gehen. Damit dies funktioniert, muss der Typ, in den konvertiert werden soll, in der Lage sein, alle möglichen Werte des Ausgangstyps aufzunehmen. Zahlen lassen sich wie folgt ohne wesentlichen Datenverlust ineinander umwandeln.
Decimal |
||||||||||
Byte |
→ |
Short |
→ |
Integer |
→ |
Long |
→ | |||
Single |
→ |
Double |
Die Umwandlungen erfolgen transparent, wie das folgende Codefragment zeigt:
Dim sh As Short = 2367 Dim ln As Long = sh Dim ch As Char = "A"c Dim st As String = ch
Hinweis |
Die Größe einer Zahl bleibt bei einer impliziten Konvertierung in jedem Fall erhalten. Bei der Konvertierung von Long in eine Fließkommazahl kann es zu einem Genauigkeitsverlust kommen. |
Das folgende Codefragment zeigt eine Umwandlung mit Genauigkeitsverlust:
' ...\Sprachsyntax\Konvertierungen\Konvertierungen.vb |
Option Strict On Namespace Sprachsyntax Module Konvertierungen ... Sub Genauigkeit() Dim longVar As Long = 1234567890123456789 Dim singleVar As Single = longVar Dim doubleVar As Double = longVar Console.WriteLine("long {0}", longVar) Console.WriteLine("double-single {0}", doubleVar – singleVar) Console.ReadLine() End Sub End Module End Namespace
Die Ausgabe des Programms zeigt, dass der Single- und Double-Wert in den ersten 9 Stellen übereinstimmt (longVar hat 19 Stellen, die Differenz 11). Dies ist durch die kleinere Genauigkeit eines Single bedingt.
long 1234567890123456789 double-single –49427152640
Hinweis |
Sind an einer mathematischen Operation oder einer Vergleichsoperation Operanden mehrerer Datentypen beteiligt, so werden erst alle implizit auf den »größten« Typ konvertiert, und erst dann wird die Operation ausgeführt. Dieser »größte« Typ ist auch der Ergebnistyp der Operation. |
Dies ist der Grund, warum im letzten Beispiel ein Vergleich einer Long- und einer Double-Variablen immer True ergeben würde.
Hinweis |
Benutzerdefinierte verlustlose (erweiternde) Konvertierungsoperatoren werden implizit vom Compiler genutzt wie eingebaute (siehe Abschnitt 3.11, »Benutzerdefinierte Operatoren«). Mit der nicht empfehlenswerten Einstellung Option Strict Off und bei der For Each-Schleife (siehe Abschnitt 2.10.2, »For«) werden auch einengende Konvertierungen implizit verwendet (wenn keine erweiternde passt). |
Explizite Konvertierung
Jeder Programmierer kennt sein eigenes Werk besser als jeder Compiler. Insbesondere hat ein Compiler keine Möglichkeit, konkrete Werte des Programms zu kennen. Der Entwickler weiß jedoch um seine Programmlogik und kann explizite Konvertierungen erzwingen, die im Allgemeinen nicht möglich sind – frei nach dem Motto: »Compiler vertraue mir, ich bin der Ingenieur« (hoffentlich …).
Es gibt eine Reihe von speziellen Konvertierungsfunktionen in Visual Basic: CShort, CInt, CLng, CDec, CSng, CDbl, CBool, CByte, CChar und CStr. Einfacher zu merken ist jedoch die einzelne Funktion CType, die neben dem zu konvertierenden Ausdruck noch den Zieldatentyp entgegennimmt und alle speziellen Konvertierer umfasst. Im folgenden Codefragment passt der konkrete Wert der Long-Variablen in ein Integer, und die Zuweisung ist daher sinnvoll.
Dim lng As Long, int As Integer
lng = 122
int = CType(lng, Integer)
CType berücksichtigt alle definierten Konvertierungen, um zum Zieltyp zu gelangen (inklusive benutzerdefinierte). Demgegenüber fordert DirectCast, dass die Variable ohne Umwege vom angegebenen Typ ist. Es ist daher schneller, erfordert aber eine größere Disziplin des Programmierers, um einen Laufzeitfehler zu vermeiden. Unumgänglich ist diese Funktion in eigenen Konvertierungsfunktionen, um zu vermeiden, dass die Konvertierungsfunktion sich selbst aufruft. Dies setzt sich dann beliebig fort. Man nennt dies eine infinite Rekursion.
Hinweis |
DirectCast unterstützt nur native Datentypen. |
Ein Irrtum über die eigene Programmlogik kann leicht zu einem Laufzeitfehler führen. Das folgende Codefragment führt zu einer OverflowException-Ausnahme. Da wir in der Prozedur den Fehler nicht abfangen, hält der Debugger an der Zeile an, die die ungültige Konvertierung enthält. In einem solchen Fall kann das Programm nicht sinnvoll fortgesetzt werden, und es empfiehlt sich ein Abbruch über den Menüpunkt Debuggen • Debuggen beenden.
' ...\Sprachsyntax\Konvertierungen\Konvertierungen.vb |
Option Strict On Namespace Sprachsyntax Module Konvertierungen ... 'unzulaessige Umwandlung Sub Overflow() Dim int As Integer = 256 Dim bt As Byte = CType(int, Byte) End Sub End Module End Namespace
Nicht immer ist es sofort offensichtlich, dass eine explizite Konvertierung gebraucht wird. Abbildung 2.37 zeigt einen solchen Fall.
Abbildung 2.37 Automatische Operandenumwandlung
Dies liegt an dem folgenden Prinzip:
Der Standardtyp für ganze Zahlen ist Integer, für Fließkommazahlen Double. Bei der Kombination von Daten, die einen »kleineren« Typ haben, konvertiert der Compiler implizit zu diesen Standardtypen. |
Dadurch wird shortVar in shortVar + 3 in einen Integer umgewandelt, da die Zahl 3 ein Integer ist. Das Ergebnis der Addition ist daher vom Typ Integer und passt nicht in jedem Fall in den Datentyp von shortVar. Die Lösung besteht wieder in einer expliziten Konvertierung.
' ...\Sprachsyntax\Konvertierungen\Konvertierungen.vb |
Option Strict On
Namespace Sprachsyntax
Module Konvertierungen
...
Sub Explizit()
Dim shortVar As Short = 782
'Standardtypen sind Integer und Double
shortVar = CType(shortVar + 3, Short)
Console.WriteLine("shortVar {0}", shortVar)
...
End Sub
End Module
End Namespace
Im Gegensatz zu CType vermeidet TryCast eine Ausnahme, wenn die Konvertierung nicht möglich ist. Das Scheitern wird durch den Wert Nothing angezeigt. Damit ist TryCast nur auf Referenztypen anwendbar, nicht auf Zahlen. Im folgenden Codefragment erzeugt die erste Konvertierung den Wert Nothing, die zweite den gewünschten Wert.
' ...\Sprachsyntax\Konvertierungen\Konvertierungen.vb |
Option Strict On Namespace Sprachsyntax Module Konvertierungen ... Sub Versuch() Dim var As Object = Console.Out Dim st As String = TryCast(var, String) Console.WriteLine("Object als String {0}", st) var = "123" st = TryCast(var, String) Console.WriteLine("String als String {0}", st) Console.ReadLine() End Sub End Module End Namespace
In der Ausgabe wird Nothing als Leerstring gezeigt und ist nicht sichtbar.
Object als String String als String 123
Die bisher vorgestellten expliziten Konvertierungen haben den Nachteil, dass sie nur in Visual Basic zur Verfügung stehen. Will man sprachunabhängig bleiben, um mit der für alle .NET-Sprachen verwendeten Spezifikation (Common Language Specification – CLS) konform zu sein, verwendet man die Funktionalität des Convert-Moduls. Das folgende Codefragment zeigt die Konvertierung zu einem Integer:
' ...\Sprachsyntax\Konvertierungen\Konvertierungen.vb |
Option Strict On
Namespace Sprachsyntax
Module Konvertierungen
...
Sub Explizit()
...
Dim von As Single, nach As Integer
von = 7.5
nach = CType(von, Integer)
Console.WriteLine("von-nach {0}-{1}", von,nach) '7.5-8
nach = Convert.ToInt32(von)
Console.WriteLine("von-nach {0}-{1}", von,nach) '7.5-8
Console.ReadLine()
End Sub
End Module
End Namespace
Die Ausgabe zeigt einerseits, dass sich CType und Convert gleich verhalten, und andererseits, dass bei der Konvertierung zu ganzen Zahlen gegebenenfalls gerundet wird.
von-nach 7.5-8 von-nach 7.5-8
Das Convert-Modul enthält eine Menge Konvertierungsfunktionen, die alle ToX heißen und nach Eingabe des Punkts nach Convert von IntelliSense gelistet werden (siehe Abbildung 2.36). Das X hinter dem To steht für einen Zahlendatentyp des .NET Frameworks. Da Short, Integer und Long nur in Visual Basic zu finden sind, werden die korrespondierenden Typen Int16, Int32 und Int64 verwendet (die Zahl steht für die Bitbreite des Typs).
Hinweis |
Die Notwendigkeit der hier gezeigten expliziten Konvertierungen ist durch die Compileroption Strict On bedingt. Lassen Sie sich vom Compiler bei der Fehlersuche helfen, und lassen Sie den Schalter auf On. Er ist keine Einschränkung, sondern erfordert an nur wenigen Stellen etwas Tipparbeit. |
Ermittlung des Datentyps
Bei diesen ganzen Möglichkeiten zur Konvertierung mögen Sie sich fragen, wie ein Programm den Datentyp eines Ausdrucks selbst ermitteln kann, um zum Beispiel Problemen bei der Konvertierung vorzubeugen. Visual Basic stellt dazu zwei Mechanismen bereit:
- statisch: GetType(Typname)
- dynamisch: <Ausdruck>.GetType()
Die erste Variante wird meist zum Vergleich mit der zweiten gebraucht. Das Codefragment
Dim i As Integer Console.WriteLine("Variablentyp: {0}", i.GetType()) Console.WriteLine("Typ: {0}", GetType(Int32))
liefert folgende Ausgabe:
Variablentyp: System.Int32 Typ: System.Int32
Zeichenketten
Oft liegen Eingaben für ein Programm in Textform vor, zum Beispiel bei einer konkreten Benutzereingabe oder einer Textdatei. Wenn diese Texte Zahlen repräsentieren sollen, müssen diese vor ihrer Verwendung noch umgewandelt werden. Hierzu haben die Zahlentypen wie Single eine Methode namens Parse.
Dim sin As Single = Single.Parse("8.619")
Alle Typen können mit der Methode ToString in eine Zeichenkette umgewandelt werden. Exakt diesen Mechanismus nutzt WriteLine, um Daten beliebigen Typs auszugeben. Das folgende Codefragment zeigt, dass dies selbst für einfache Zahlen zutrifft:
Console.WriteLine("2.89 als Text {0}", (2.89).ToString())
Hinweis |
Ob von Parse und ToString ein Komma oder ein Punkt verwendet wird, hängt von den Ländereinstellungen ab. Der aktuelle Wert steht in System.Globalization.NumberFormatInfo.CurrentInfo.NumberDecimalSeparator. |
Die Tatsache, dass wirklich alle Typen in eine Zeichenkette umgewandelt werden können, erlaubt es, beliebige Daten in einem String zu kombinieren. Dazu wird der &-Operator verwendet. Damit haben wir in WriteLine eine Alternative zur {0}-Notation. Die beiden nächsten Zeilen zeigen dies. Die Ausgabe beider Zeilen ist identisch.
Console.WriteLine("Pi {0}", 3.1515)
Console.WriteLine("Pi " & 3.1515)
Hinweis |
Der &-Operator verbindet Zeichenketten und Daten direkt miteinander, wenn der Datentyp den &-Operator definiert, wie zum Beispiel bei Zahlen, Date, Char und String. Für andere Datentypen muss deren ToString()-Methode aufgerufen werden. |
2.5.8 Funktionen
Visual Basic bietet auch die Möglichkeit, Routinen zu schreiben, die etwas zurückgeben. Auf Modulebene werden sie Funktionen genannt, als Wert einer Variablen λ-Ausdruck (Funktionsobjekt). Letztere werden in Abschnitt 3.9.5 »Funktionsobjekte: λ-Ausdrücke (Function)« besprochen.
Funktionen
Bisher haben wir nur Methoden betrachtet, die mit Sub deklariert werden und keinen Wert zurückliefern. Im Gegensatz dazu liefern mit Function deklarierte Methoden ein Ergebnis zurück. Dazu muss auch der Ergebnistyp in einer As-Klausel angegeben werden. Das folgende Programm zeigt eine eigene Funktion ohne Argumente namens Konstante(), die eine ganze Zahl zurückgibt. Diese Zahl ist vom Typ Byte, der Rückgabetyp der Funktion ist aber Integer. Da eine Konvertierung immer ohne Datenverlust möglich ist, nimmt der Compiler die Umwandlung automatisch vor. In dem WriteLine-Befehl verwenden wir unsere Funktion genau so wie eine beliebige in .NET eingebaute Funktion.
' ...\Sprachsyntax\Funktionen\Funktionen.vb |
Option Strict On
Namespace Sprachsyntax
Module Funktionen
Sub Main()
Console.WriteLine("konst/10: {0}", Konstante() / 10)
Console.ReadLine()
End Sub
Function Konstante() As Integer
Dim konst As Byte = 127
Return konst
End Function
End Module
End Namespace
Die Deklaration des Rückgabetyps ist völlig unabhängig davon, ob eine Funktion Argumente hat oder nicht. Bei der Besprechung von Wert- und Referenztypen im Rahmen der Objektorientierung werden wir Funktionen mit Parametern selbst erstellen.
Eine Alternative zur Verwendung von Return am Ende der Funktion ist die Zuweisung eines Wertes an den Funktionsnamen. Das folgende Codefragment zeigt obige Konstantenfunktion in dieser Variante:
' ...\Sprachsyntax\Funktionen\Funktionen.vb |
Option Strict On
Namespace Sprachsyntax
Module Funktionen
...
Function Konstante2() As Integer
Konstante2 = 127
End Function
End Module
End Namespace
Hinweis |
Wird eine Funktion ohne explizites Return und ohne explizite Zuweisung an einen Funktionsnamen beendet, gibt sie eine typangepasste Null zurück (siehe Abschnitt 2.5.6, »Initialisierung von Variablen«). |
Main
Der Haupteinstiegspunkt einer Anwendung (das, womit begonnen wird) darf die folgenden vier Formen annehmen:
Sub Main() Sub Main(args As String) Function Main() As Integer Function Main(args As String) As Integer
Die optionalen Argumente enthalten die Kommandozeilenparameter, mit denen die Anwendung gestartet wurde. Wird ein Wert zurückgegeben, wird dieser an den Prozess weitergereicht, der das Programm gestartet hat.
2.5.9 Schlüsselwörter
Visual Basic 2008 reserviert einige Schlüsselwörter (siehe Tabelle 2.5). Soll eine Variable wie eines dieser Wörter heißen, muss sie in eckige Klammern gesetzt werden.
Kontext | Schlüsselwörter |
Kommentar |
REM |
Compiler |
On, Option |
Namensraum |
Global, Imports, Namespace |
Vordefiniert |
Exit, False, GetType, Me, MyBase, MyClass, Nothing, Object, Stop, True |
Primitive |
Boolean, Byte, Char, Date, Decimal, Double, Integer, Long, SByte, Short, Single, String, UInteger, ULong, UShort |
Datentyp |
AddressOf, As, Delegate, Class, Enum, Interface, Module, Of, Structure, With |
Konvertierung |
CBool, CByte, CChar, CDate, CDbl, CDec, CInt, CLng, CObj, CSByte, CShort, CSng, CStr, CType, CUInt, CULng, CUShort, DirectCast, TryCast |
Operator |
And, AndAlso, Is, IsNot, Like, Mod, New, Not, Or, OrElse, TypeOf, Xor |
Ressourcen |
Erase, ReDim, Using |
Flusskontrolle |
Case, Continue, Do, Each, Else, ElseIf, End, For, GoTo, If, In, Loop, Next, Select, Step, Then, To, While |
Fehler |
Catch, Finally, Throw, Try, When |
Funktion |
Call, Function, Get, Operator, Property, Return, Set, Sub |
Externe Funktion |
Alias, Declare, Lib |
Ereignis |
AddHandler, Event, RaiseEvent, RemoveHandler |
Modifikator |
ByRef, ByVal, Const, Default, Dim, Friend, Handles, Implements, Inherits, MustInherit, MustOverride, Narrowing, NotInheritable, NotOverridable, Optional, Overloads, Overridable, Overrides, ParamArray, Partial, Private, Protected, Public, ReadOnly, Shadows, Shared, Static, Widening, WithEvents, WriteOnly |
Objektzugriff |
SyncLock, Using, With |
Veraltet |
EndIf, Error, GoSub, Let, Resume, Variant, Wend |
Hinweise |
Stop löst eine Debuggerausnahme aus, während Exit das Programm sofort beendet (Finally-Zweige werden nicht ausgeführt). Ein Funktionsaufruf, dem Call vorangestellt wird, verwirft den Rückgabewert. |
2.5.10 Zusammenfassung
- Standardmäßig müssen Variablen deklariert werden. Dies kann durch den Schalter Option Explicit Off umgangen werden (implizite Deklaration vom Typ Object). Dies ist nicht zu empfehlen.
- Der Schalter Option Strict On sollte immer explizit gesetzt werden, damit der Compiler die Typkonsistenz prüfen kann und wir das nicht machen müssen.
- Eine Anweisung wird durch einen Zeilenvorschub oder einen Doppelpunkt beendet.
- Eine lange Anweisung kann durch einen Unterstrich auf zwei Zeilen aufgeteilt werden. Vor dem Unterstrich muss ein Leerzeichen stehen, und der Umbruch kann nicht mitten in einer Zeichenfolge erfolgen.
- Kommentare werden mit einem Hochkomma eingeleitet und gehen bis zum Zeilenende.
- Jede Quellcodedatei kann ein oder mehrere Module enthalten. Die Modulnamen müssen innerhalb des Projekts eindeutig sein.
- Für den Programmcode jeder Quellcodedatei wird in der Entwicklungsumgebung ein eigener Texteditor bereitgestellt.
- Die Common Language Runtime (CLR) unterscheidet native Datentypen und klassenbasierte Objekte. Ein weiteres Unterscheidungskriterium sind Wertetypen und Referenztypen.
- Ganzzahlige Literale werden als Integer interpretiert, Literale mit Dezimaltrennzeichen als Double.
- Die Zeitspanne, über die hinweg eine Variable ihre Gültigkeit behält, wird als die Lebensdauer bezeichnet. Die Sichtbarkeit einer Variablen bestimmt, von welchen Stellen des Programmcodes auf deren Inhalt zugegriffen werden kann.
- Lokale Variablen sind innerhalb einer Prozedur deklariert und nur in dieser sichtbar. Sie können nur als Dim oder Static deklariert werden.
- Globale Variablen sind außerhalb jeglicher Prozedurdefinition deklariert. Ihre Sichtbarkeit hängt vom Sichtbarkeitsmodifikator ab (z. B. Private oder Public).
- Wird bei einer Operation das Datum eines bestimmten Typs in ein Datum eines größeren Typs umgewandelt, wird von impliziter Konvertierung gesprochen. Implizite Konvertierungen werden vom Compiler automatisch vorgenommen.
- Bei der expliziten Konvertierung muss der Wertebereich des Zieldatentyps groß genug sein, um das übergebene Datum aufzunehmen. Ansonsten wird eine Ausnahme ausgelöst.
- Eine explizite Konvertierung kann sowohl mit sprachspezifischen Konvertierungsfunktionen als auch mit den Methoden der Klasse Convert ausgeführt werden.
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.