Visual C# 2005
Unser Online-Tipp für noch mehr Wissen …
... aktuelles Fachwissen rund um die Uhr – zum Probelesen, Downloaden oder auch auf Papier.
www.InformIT.de
Visual C# 2005 Mit einfachen Beispielen programmieren WALTER SAUMWEBER
Ü
leicht
Ü
klar
Ü
sofort
Bibliografische Information Der Deutschen Bibliothek Die Deutsche Bibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.ddb.de abrufbar.
Die Informationen in diesem Produkt werden ohne Rücksicht auf einen eventuellen Patentschutz veröffentlicht. Warennamen werden ohne Gewährleistung der freien Verwendbarkeit benutzt. Bei der Zusammenstellung von Texten und Abbildungen wurde mit größter Sorgfalt vorgegangen. Trotzdem können Fehler nicht vollständig ausgeschlossen werden. Verlag, Herausgeber und Autoren können für fehlerhafte Angaben und deren Folgen weder eine juristische Verantwortung noch irgendeine Haftung übernehmen. Für Verbesserungsvorschläge und Hinweise auf Fehler sind Verlag und Herausgeber dankbar. Alle Rechte vorbehalten, auch die der fotomechanischen Wiedergabe und der Speicherung in elektronischen Medien. Die gewerbliche Nutzung der in diesem Produkt gezeigten Modelle und Arbeiten ist nicht zulässig. Fast alle Hardware- und Softwarebezeichnungen und weitere Stichworte und sonstige Angaben, die in diesem Buch verwendet werden, sind als eingetragene Marken geschützt. Da es nicht möglich ist, in allen Fällen zeitnah zu ermitteln, ob ein Markenschutz besteht, wird das ® Symbol in diesem Buch nicht verwendet. Umwelthinweis: Dieses Buch wurde auf chlorfrei gebleichtem Papier gedruckt.
10 9 8 7 6 5 4 3 2 1
09 08 07 06
ISBN-13: 978-3-8272-4160-3 ISBN-10: 3-8272-4160-X © 2006 by Markt+Technik Verlag, ein Imprint der Pearson Education Deutschland GmbH, Martin-Kollar-Straße 10–12, D-81829 München/Germany Alle Rechte vorbehalten Coverkonzept: independent Medien-Design, Widenmayerstraße 16, 80538 München Coverlayout: Thomas Arlt,
[email protected] Titelfoto: jupiterimages, Starnberg Herstellung: Monika Weiher,
[email protected] Lektorat: Brigitte Alexandra Bauer-Schiewek,
[email protected] Korrektorat: Petra Kienle Satz: Ulrich Borstelmann, Dortmund (www.borstelmann.de) Druck und Verarbeitung: Bosch Druck, Ergolding Printed in Germany
Inhaltsverzeichnis Liebe Leserin, lieber Leser ......................................................... 9
1
Allgemeine Aspekte der Programmentwicklung
11
Der Quellcode...........................................................................12 Kompilierung ...........................................................................13 Integrierte Entwicklungsumgebungen ..................................... 18 Eine kleine Erfolgskontrolle ..................................................... 19
2
Die Programmierumgebung von C#
21
Besonderheiten bei der Kompilierung in C# ........................... 22 Das .NET Framework ............................................................... 24 Installation der Visual C# 2005 Express Edition...................... 26 Eine kleine Erfolgskontrolle .................................................... 33
3
Ihr erstes C#-Programm
35
Programmentwicklung mit der Visual C# 2005 Express Edition ....................................................................... 36 Projektmappen und Projekte .................................................. 43 Hätten Sie gedacht ... ............................................................ 45
4
Aufbau von C#-Programmen
47
Aufbau von C#-Programmen ................................................... 48 Wo befindet sich nun eigentlich die ausführbare (.exe-)Datei? ........................................................................... 57 Eine kleine Erfolgskontrolle .................................................... 57
6
Inhaltsverzeichnis
5
Syntaxregeln
59
Textbausteine ......................................................................... 60 Anweisungsende .....................................................................61 Blöcke .................................................................................... 62 Leerräume .............................................................................. 64 Programmierstil ...................................................................... 66 Kommentare ........................................................................... 67 Programm(ier)fehler ............................................................... 69 Hätten Sie gedacht ... ............................................................. 72
6
Ausgabe
75
Die Methoden Write() und WriteLine() .................................... 76 Zeichenketten ......................................................................... 78 Steuerzeichen ..........................................................................81 Eine kleine Erfolgskontrolle .................................................... 85
7
Variablen und Datentypen
87
Variablendefinition ................................................................. 88 Die Zuweisung ........................................................................ 93 Unterschiedliche Bedeutung von Variablenbezeichnern im Quellcode .......................................................................... 99 Notationen für Bezeichner ..................................................... 101 Die Methode ReadLine() ........................................................103 Eine kleine Erfolgskontrolle ................................................... 107
8
Konstanten
109
Literale .................................................................................. 110 Benannte Konstanten ............................................................122 Implizite Typumwandlungen ..................................................124 Explizite Typumwandlungen ..................................................129 Eine kleine Erfolgskontrolle ...................................................137
Inhaltsverzeichnis
9
Rechenoperationen
139
Ausdrücke und arithmetische Operatoren .............................140 Erstellen Sie ein Euro-DM-Umrechnungsprogramm ...............143 Formatieren von Ausgaben ....................................................147 Eine kleine Erfolgskontrolle ..................................................155
10
Verzweigungen
157
Logische Ausdrücke ...............................................................158 Prioritätsstufen von Operatoren ............................................162 Die if-Anweisung ....................................................................164 Die switch-Anweisung ...........................................................177 Eine kleine Erfolgskontrolle ................................................... 191
11
Wiederholungsanweisungen
193
Die while-Schleife ..................................................................194 Die do-while-Schleife .............................................................198 Die for-Schleife ......................................................................199 Zufallszahlen generieren ...................................................... 204 Realisieren Sie ein Ratespiel ................................................. 209 Hätten Sie gedacht ... ............................................................219
12
Arrays
221
Arrays definieren .................................................................. 222 Arrays mit der for-Schleife verwalten .................................... 224 Arrays sortieren .................................................................... 226 Die foreach-Schleife ............................................................. 227 Initialisieren von Arrays ........................................................ 230 Eine kleine Erfolgskontrolle ...................................................231
7
8
Inhaltsverzeichnis
13
Methoden
233
Methoden definieren ............................................................ 234 Methoden verwenden ........................................................... 236 Parameterübergabe .............................................................. 238 Rückgabewerte von Methoden ............................................. 245 Eine kleine Erfolgskontrolle .................................................. 249
14
Klassen und Objekte
251
Vorüberlegungen .................................................................. 252 Klassendefinition .................................................................. 254 Statische Klassenelemente ................................................... 272 Namensräume ...................................................................... 277 Eine kleine Erfolgskontrolle .................................................. 279
15
Windows-Anwendungen entwickeln
281
Der Windows Forms-Designer ............................................... 282 Steuerelemente mit Code verbinden – Ereignisbehandlungsroutinen ............................................... 293 Hätten Sie gedacht ... ........................................................... 302
Anhang – Antworten
303
Glossar
315
Stichwortverzeichnis
323
9
Liebe Leserin, lieber Leser, vielen Dank, dass Sie sich für dieses Buch entschieden haben. Es richtet sich in erster Linie an Programmiereinsteiger. Aber auch, wenn Sie bereits erste Erfahrungen in einer anderen Programmiersprache gesammelt haben und einen schnellen Einstieg in die Programmiersprache C# (sprich »C Sharp«) im Allgemeinen sowie in die objektorientierte Programmierung und in die Programmierung von grafischen Benutzeroberflächen im Besonderen erhalten wollen, sollten Sie aus diesem Buch den gewünschten Nutzen ziehen. Mein oberstes Ziel war es, die genannten Themen übersichtlich und gut verständlich darzustellen. Es beginnt mit den Grundlagen der Programmiersprache C# selbst und danach schließen sich die Konzepte der objektorientierten Programmierung sowie eine Einführung in die Windows-Programmierung an. Da die Grundlagen wichtig für das Verständnis sind und alle Themen aufeinander aufbauen, empfiehlt es sich für einen schnellen Lernerfolg, das Buch Seite für Seite von vorne nach hinten durchzuarbeiten. Als Software erhalten Sie mit diesem Buch eine komfortable und leistungsfähige Entwicklungsumgebung für C#-Programme, die Visual C# 2005 Express Edition von Microsoft, die auf der Buch-CD enthalten ist. Dabei handelt es sich um eine vollwertige Teilmenge von Visual Studio 2005, die weder funktional eingeschränkt noch zeitlich befristet ist. Visual C# 2005 Express eignet sich sowohl für den Hobbyprogrammierer als auch für den professionellen Softwareentwickler. Die Installation, die sich in der Regel als problemlos erweist, ist in Kapitel 2 ausführlich erklärt. Dort können Sie sich auch über die Systemvoraussetzungen informieren. Zum Umgang mit Ihrer Entwicklungsumgebung werden Sie im Buch ebenfalls genügend Hinweise erhalten. Alle abgedruckten Listings plus weitere Beispiele finden Sie als Projekte auf der Buch-CD, sodass Sie die benötigten Codezeilen bei Bedarf per Copy and Paste in den Code-Editor Ihrer Visual C# 2005 Express Edition übertragen oder dort gleich das ganze Projekt laden können. Eventuell zusätzliche Infos zum Buch sowie gegebenenfalls eine Fehlerliste finden Sie auf meiner Webseite http://www.mipueblo.de. Ich empfehle Ihnen, sich für die Lektüre des Buchs und für das Nachvollziehen der Beispiele Geduld und Zeit zu nehmen, um daraus den größtmöglichen Nutzen zu ziehen. Nun bleibt mir nur noch, Ihnen viel Spaß beim Lesen und viel Erfolg mit der Visual C#-Programmierung zu wünschen. Walter Saumweber
Das lernen Sie neu: Wie ein ausführbares Programm entsteht
11
Die Bedeutung der Begriffe »Quellcode«, »Compiler«, »IDE«
12
Kapitel 1
Allgemeine Aspekte der Programmentwicklung Dieses Kapitel bietet Ihnen einen ersten Einstieg in die Programmierung. Dabei werden die einzelnen Entwicklungsschritte bis zur fertigen Anwendung dargestellt. Dadurch erhalten Sie ein Grundverständnis für die Programmierung. Insbesondere werden Sie mit Fachbegriffen bekannt gemacht, die Ihnen im weiteren Verlauf des Buchs immer wieder begegnen werden.
12
Kapitel 1
Der Quellcode Im Folgenden werden die einzelnen Schritte bei der Programmentwicklung unter herkömmlichen Programmiersprachen aufgezeigt. Deren Verständnis ist für das der zugrunde liegenden Philosophie von C# und dem .NET unerlässlich. Ursprung jeder fertigen Anwendung ist der so genannte Quellcode. Dieser besteht aus reinem Text, setzt sich also aus Buchstaben, Ziffern und weiteren Zeichen zusammen, die Ihnen von der Tastatur Ihres PCs her bekannt sind. Der Quellcode wird daher auch als »Quelltext« bezeichnet (vor allem, wenn nur ein Teil des Quellcodes gemeint ist, spricht man auch kurz vom »Code«). Die Hauptaufgabe für Sie als Programmierer besteht in der Erstellung dieses Quellcodes. Dazu geben Sie diesen in einen Editor ein und speichern ihn schließlich als Quellcode-Datei auf einem Datenträger. Die Dateierweiterung wird dabei von der verwendeten Programmiersprache vorgegeben. Für C#Quellcode-Dateien lautet sie .cs. Hinweis Bei der Eingabe des Quelltexts sind Sie natürlich den Vorgaben der verwendeten Programmiersprache unterworfen. Eben das Erlernen dieser Vorgaben – oder positiv ausgedrückt: Möglichkeiten – der Programmiersprache C# bildet das eigentliche Thema dieses Buchs.
Für die Eingabe des Quellcodes kann prinzipiell jeder beliebige Texteditor Verwendung finden, etwa der mit dem Betriebssystem mitgelieferte Editor, z.B. Notepad bei Windows. (Bei Programmen, die neben dem eigentlichen Text auch Formatierungen ablegen, z.B. Microsoft Word, ist allerdings darauf zu achten, dass der Quellcode im richtigen Format – als reiner Text – gespeichert werden muss). Der Quelltext selbst setzt sich aus mehreren Befehlen zusammen, von denen später jeder eine ganz bestimmte Wirkung während des Programmlaufs haben wird. Diese Programmierbefehle werden »Anweisungen« oder auch einfach »Befehle« genannt. Jede Programmiersprache stellt einen ihr eigenen Befehlssatz zur Verfügung. Sie können sich das in etwa so vorstellen wie eine Landessprache, die sich in Wortschatz und Grammatik von anderen unterscheidet. So wäre eine Programmiersprache denkbar, welche eine Anweisung wie Schreibe »Guten Tag« auf den Bildschirm
Kompilierung
ermöglicht – mit der Wirkung, dass bei Ausführung dieses Befehls während des Programmlaufs die Worte Guten Tag auf dem Bildschirm erscheinen. Hinweis In C# lautet die entsprechende Anweisung: Console.Write("Guten Tag");
Es sei angemerkt, dass sich nahezu alle Programmiersprachen angloamerikanischer Wörter bedienen.
Ist der Quellcode einmal erstellt, so ist die eigentliche Programmierarbeit bereits getan. Wenn Sie den Quellcode syntaktisch und logisch korrekt eingegeben haben – was das genau bedeutet, werden Sie im weiteren Verlauf noch erfahren –, dann werden die letzten Schritte zum fertigen Programm nicht mehr problematisch sein.
Kompilierung Letztlich besteht das Endprodukt jeder erfolgreichen Programmierarbeit in einer ausführbaren Datei (manchmal sind es auch mehrere, die sich dann unter Umständen gegenseitig aufrufen). Hinweis Die Begriffe »ausführbare Datei«, »Programm«, »ausführbares Programm« oder auch »Programmdatei« werden im Allgemeinen gleichbedeutend verwendet. Es handelt sich um solche Dateien, die vom Computer zur Ausführung vorgesehen sind. Dies trifft z.B. für Text- oder Grafikdateien nicht zu.
Eine Programmdatei wird in den meisten Betriebssystemen an der Dateierweiterung erkannt. Die am häufigsten anzutreffende Erweiterung von ausführbaren Dateien unter Windows ist .exe. Auch die Resultate Ihrer zukünftigen Bemühungen werden sich in .exe-Dateien zeigen. Wenn Ihr Computer also auf eine Datei mit der Erweiterung .exe trifft, wird er versuchen, den Inhalt dieser Datei als Befehlsfolge zu verstehen und auszuführen. Wenn dieser Versuch misslingt, wird er dies mit einer Fehlermeldung kundtun (was hier am Beispiel
13
14
Kapitel 1
von .exe-Dateien geschildert wird, gilt natürlich in Abhängigkeit vom Betriebssystem ebenso für bestimmte andere Dateierweiterungen). Hinweis Genau genommen ist es der Prozessor Ihres Computers, der die Datei zu interpretieren versucht, und der Mittler zwischen Programmdatei und Prozessor ist das Betriebssystem. Mit anderen Worten, das Betriebssystem reicht .exe-Dateien an den Prozessor des Computers zur Ausführung weiter.
Nun versteht jeder Computer aber nur seinen eigenen Befehlssatz, der »Maschinensprache« genannt wird. Da Ihr Computer letztlich auf der Basis von zwei unterschiedlichen Zuständen arbeitet – Ein und Aus –, bestehen Maschinenbefehle ausschließlich aus Nullen und Einsen. Nur so kodierte Befehle können vom Prozessor Ihres Computers verstanden werden. Ein lauffähiges Programm … … muss in Maschinencode vorliegen, damit es vom Prozessor des Computers verstanden werden kann.
Folglich wird Ihr Computer den Quelltext in seiner ursprünglichen Form nicht ausführen können, da dieser sich aus Sprachmitteln der verwendeten Programmiersprache zusammensetzt (siehe oben). Der Quellcode muss daher erst in Maschinensprache übersetzt werden. Dies besorgt eine Software, die »Compiler« genannt wird. Bei herkömmlichen Programmiersprachen – auf die Besonderheiten von C# werden wir im nächsten Kapitel zu sprechen kommen – übersetzt der Compiler den Quelltext als Ganzes in Maschinensprache und speichert das Endprodukt in einer eigenen Datei (unter Windows in der Regel mit der Dateierweiterung .exe). Dies geschieht in verhältnismäßig kurzer Zeit und ohne Ihr weiteres Zutun. Da es sich beim Compiler ebenfalls um ein Computerprogramm handelt, müssen Sie dieses lediglich starten.
Kompilierung
Hinweis Natürlich verrichtet der Compiler seine Arbeit nur, wenn Ihr Quelltext korrekt ist, ansonsten quittiert er den Kompilierversuch mit entsprechenden Fehlermeldungen. In diesem Fall müssen Sie den Quellcode nochmals überarbeiten und den Kompiliervorgang erneut in Gang setzen.
Nach erfolgreichem Übersetzungsvorgang liegt das Programm nunmehr in seiner ausführbaren Form vor. Hinweis Die Bezeichnung »Programm« wird – je nach Sichtweise – nicht nur für die fertige Anwendung, sondern mitunter auch für den – noch nicht lauffähigen – Quellcode verwendet. Während der Anwender unter »Programm« in der Regel die ausführbare Applikation versteht, meint der Programmierer mit dieser Bezeichnung oft in erster Linie den von ihm verfassten Quellcode.
Beachten Sie, dass die Quellcode-Datei beim Übersetzungsvorgang unverändert erhalten bleibt. Als Ergebnis der Programmierarbeit liegen nunmehr die Quellcode-Datei und die ausführbare Programmdatei vor. Es sei noch erwähnt, dass bei verschiedenen Programmiersprachen bzw. in modernen Entwicklungssystemen mit dem Kompilieren in der Regel eine Vielzahl von weiteren Zwischen- bzw. Hilfsdateien entstehen, was uns hier jedoch aus Gründen der Anschaulichkeit nicht weiter interessieren soll. In diesem Sinne stellt die folgende Skizze eine grobe Betrachtung des Entstehungsablaufs einer Anwendung bei herkömmlichen Programmiersprachen dar.
15
16
Kapitel 1
Programmierer erstellt den Quellcode im Editor
Quellcode
Compiler übersetzt den Quellcode
ausführbares Programm Abbildung 1.1: Der Weg zum lauffähigen Programm unter herkömmlichen Programmiersprachen
Achtung Mit dem Programmiercode bestimmen Sie die Funktionsweise der ausführbaren Datei. Oder anders ausgedrückt: Die ausführbare Datei ist die binärkodierte Entsprechung der Quellcode-Datei.
Je nachdem, wie weit der Quellcode vom ausführbaren Programm »entfernt« ist, also wie viele interne Übersetzungsschritte bis zur fertigen Anwendung stattfinden, spricht man entweder von »maschinennahen Sprachen« oder von »Hochsprachen«. Ein Beispiel für eine maschinennahe Programmiersprache ist Assembler. Beispiele für Hochsprachen sind Cobol, Pascal, C und moderne Sprachen wie C++, Java und natürlich C#.
Kompilierung
Warum nicht gleich ... Da der Computer nur in Maschinensprache vorliegende Befehle unmittelbar verstehen und damit ausführen kann, mag der Gedanke nahe liegen, das Programm in einer maschinennahen Sprache oder gleich in Maschinensprache zu kodieren. In diesem Fall liegt der Quellcode in Form von Nullen und Einsen vor: 01101010100101010101110001011100 10101010110101100101100111101011 00101000110101110101100100101010
Der offensichtliche Nachteil von Maschinencode liegt jedoch in seiner schlechten Lesbarkeit. Er lässt sich daher kaum sinnvoll strukturieren und ist somit nur schwer nachvollziehbar. Hinzu kommt, dass sich Maschinenbefehle nur schwer aus dem Gedächtnis reproduzieren lassen. Alles zusammengenommen gestaltet sich die Entwicklung eines Programms auf diese Weise sehr schwer, ganz zu schweigen von einer eventuellen späteren Überarbeitung. Auf der anderen Seite lässt sich bei Anweisungen wie Console.WriteLine("Hallo");
oder name = Console.ReadLine();
schon ohne Vorwissen erahnen, was sie bewirken. Beide stammen aus dem Sprachschatz von C#. So bewirkt der Ausdruck Console.WriteLine("Hallo");, dass das Wort Hallo auf dem Bildschirm erscheint. Das Line in WriteLine deutet darauf hin, dass zusätzlich die Einfügemarke an den Beginn der nächsten Zeile (engl. line = Zeile) gesetzt wird. Ebenso wird mit der Anweisung name = Console.ReadLine(); eine Eingabe vom Anwender gelesen (engl. to read = lesen) und das Line in ReadLine führt auch hier im Anschluss zu einer Zeilenschaltung. Beide Anweisungen werden Sie schon bald genauer kennen lernen. Es liegt auf der Hand, dass für den Vorteil der besseren Erinnerbarkeit und Lesbarkeit mehr interne Übersetzungsschritte bis zum Maschinencode in Kauf genommen werden müssen. Das braucht Sie aber nicht zu kümmern, da Ihnen Ihr Compiler diese Arbeit abnimmt.
Allerdings sind auch Compiler keine Multitalente. Compiler unterscheiden sich in Abhängigkeit sowohl von der Programmiersprache als auch vom System, für das die zukünftige Anwendung bestimmt ist. So ist etwa ein C-Compiler nicht in der Lage, einen in einer anderen Programmiersprache geschriebenen Quell-
17
18
Kapitel 1
code zu übersetzen. Aber auch ein unter Windows kompiliertes C-Programm ist z.B. unter Unix-Systemen nicht lauffähig. Wenn Sie also ein entsprechendes Linux-Programm erzeugen möchten, dann müssen Sie denselben Quelltext unter einem für diese Plattform vorgesehenen C-Compiler erneut kompilieren.
Integrierte Entwicklungsumgebungen Wie Sie gerade erfahren haben, ist es für die Programmierung notwendig, Text in einen Editor einzugeben und diesen dann kompilieren zu lassen. Dazu kann jeder beliebige Texteditor benutzt und für den Übersetzungsvorgang ein externes Programm namens Compiler herangezogen werden. In modernen Entwicklungsumgebungen ist die Software zum Übersetzen des Quellcodes genauso wie ein mit nützlichen Funktionen ausgestatteter Editor als fester Bestandteil in ein umfassendes Softwareprogramm eingebunden. Dort wird der Compiler meist über einen entsprechenden Befehl in der Menüleiste oder noch einfacher mit einem Klick auf ein Symbol gestartet. Zudem werden in der Regel eine Reihe weiterer Hilfsmittel für die Programmentwicklung bereitgestellt. Weil hier alle Programmierwerkzeuge unter einer Bedienoberfläche zusammengefasst sind, bezeichnet man ein solches System als »integrierte Entwicklungsumgebung« oder kurz »IDE« (für Integrated Development Environment). Die bekannteste IDE für C#-Programme ist das Visual Studio .NET von Microsoft. Es fungiert gleichzeitig als Entwicklungsumgebung für C++- und Visual Basic-Programme. Auch Sie werden mit einer integrierten Entwicklungsumgebung arbeiten, nämlich der Visual C# 2005 Express Edition von Microsoft. Dabei handelt es sich um eine komfortable und verhältnismäßig bequem zu bedienende Benutzeroberfläche, die ausschließlich für die Entwicklung von .NET Framework-Anwendungen konzipiert ist. Die IDE erweist sich für den Anfänger geradezu als ideal und ist auch für die Entwicklung von professionellen .NET-Applikationen bestens geeignet. Hinweis Die Visual C# 2005 Express Edition ist eine »relativ bequem« zu bedienende Benutzeroberfläche. Die Einschränkung bezieht sich darauf, dass der Umgang mit modernen Entwicklungssystemen nie ganz ohne das erforderliche Know-how auskommt. Im Übrigen ist die Arbeit mit der Visual C# 2005 Express Edition bestens geeignet, um Sie – neben dem Erlernen der Programmiersprache C# – gleichzeitig mit der Bedienung aufwändiger Programmieroberflächen vertraut zu machen.
Eine kleine Erfolgskontrolle
Eine kleine Erfolgskontrolle • • •
Was ist ein Compiler? Worin unterscheiden sich Hochsprachen von maschinennahen Sprachen? Was ist eine IDE?
19
Das können Sie schon: Entwicklungsschritte zum ausführbaren Computerprogramm
11
Das lernen Sie neu: Was der Begriff »Laufzeitumgebung« bedeutet
22
Was es mit dem .NET Framework auf sich hat
24
Wie Sie die Visual C# 2005 Express Edition auf Ihrem Computer installieren
26
Kapitel 2
Die Programmierumgebung von C# In diesem Kapitel gehen wir auf die Besonderheiten bei der Kompilierung im Zusammenhang mit C#-Programmen ein. Sie erfahren unter anderem, welche Rolle das .NET Framework bei der Ausführung von C#-Programmen spielt. Im Anschluss daran installieren Sie die Visual C# 2005 Express Edition, wobei Sie auf das Setup-Programm der Buch-CD zurückgreifen. Das .NET Framework in der aktuellen Version wird dabei – sofern es noch nicht auf Ihrem Computer vorhanden ist – automatisch mitinstalliert.
22
Kapitel 2
Besonderheiten bei der Kompilierung in C# Wie in Kapitel 1 beschrieben, wird bei den herkömmlichen Programmiersprachen der Quellcode vom Compiler mehr oder weniger direkt in Maschinencode übersetzt, sodass das kompilierte Programm in ausführbarer Form vorliegt. Jedes Betriebssystem besitzt jedoch seine Eigenheiten. Daher muss damit gerechnet werden, dass die fertig übersetzten Programme nur auf Betriebssystemen laufen, auf denen sie kompiliert wurden. Hinweis In den Urzeiten der Programmierung war es nicht einmal die Regel, dass ein in einer bestimmten Umgebung verfasster Quelltext auf mehreren Betriebssystemen kompilierbar war. Wenn ein Quelltext auf verschiedenen Systemen übersetzt werden kann, sagt man, er (bzw. die entsprechende Programmiersprache) sei portierbar. Ein großer Vorteil der Programmiersprache C war von Anfang an deren Portierbarkeit. Das heißt, ein in C geschriebener Quellcode konnte auf nahezu allen Systemen kompiliert werden. Die Portierbarkeit von Programmiersprachen hat sich erst in den 80er-Jahren zum Standard entwickelt.
Intermediate Language Code Diesem Nachteil der herkömmlichen Programmiersprachen wird in C# begegnet, indem das .NET Framework zwischen Programm und Betriebssystem geschaltet wird (allerdings setzt die Ausführung eines C#-Programms voraus, dass eben dieses .NET Framework auf dem Computer installiert ist). Der Quellcode wird vom C#-eigenen Compiler zunächst in eine Zwischenform umgewandelt. Dieser Zwischencode heißt Intermediate Language Code oder kurz IL-Code. Dieser wird dann bei der Ausführung vom Compiler des .NET Framework in die Form gebracht, welche vom jeweiligen Betriebssystem verstanden wird. Es sind also insgesamt zwei Compiler am Werk, einmal der von C# und bei der Programmausführung der des .NET Framework.
Besonderheiten bei der Kompilierung in C#
Hinweis Ein Vorläufer dieses Konzepts ist die Programmiersprache Java. Auch der Compiler von Java erzeugt zunächst so genannte class-Dateien, welche bei der Ausführung dann die Java Virtual Machine (JVM) benötigen. Inzwischen kann das Vorhandensein der Java Virtual Machine auf den Computern vorausgesetzt werden. Die Funktionsweise der JVM für Java-Programme entspricht hier der des .NET Framework für C#-Anwendungen.
Allgemein lässt sich feststellen: • Das .NET Framework muss auf jedem Computer installiert sein, welcher C#-Anwendungen ausführen soll. • Umgekehrt kann auf jedem Computer (mit beliebigem Betriebssystem) jede C#-Anwendung ausgeführt werden, sobald dort das .NET Framework vorhanden ist. Daher muss, um den Weg von der Programmierung bis zum erfolgreichen Programmlauf zu verdeutlichen, die Darstellung in Kapitel 1 (Abschnitt »Kompilierung«) für C#-Programme etwas modifiziert werden (Abbildung 2.1). Programmierer erstellt den Quelltext im Editor
Quellcode
der Compiler von C# übersetzt den Quellcode in IL-Code
Intermediate Language Code
der Compiler des .NET Framework übersetzt den IL-Code in systemspezifischen, ausführbaren Code
Programmlauf Abbildung 2.1: Vom C#-Quellcode bis zur Programmausführung
23
24
Kapitel 2
Beachten Sie, dass sich beide Darstellungen nicht widersprechen. Nach der Übersetzung der C#-Quellcode-Datei durch den C#-Compiler liegt bereits ein fertiges Endprodukt vor. Nur benötigt dieses sozusagen ausführbare Programm eine besondere Laufzeitumgebung, eben das .NET Framework. Laufzeitumgebung – was ist das? Benötigt ein Programm eine bestimmte Software, damit es ausgeführt werden kann, spricht man von einer Laufzeitumgebung (engl. Runtime Environment).
Das .NET Framework Dieser Abschnitt soll Ihnen einen kurzen Überblick über das .NET Framework verschaffen. Sie werden einiges Wissenswertes über das .NET Framework erfahren. Bestimmte Features des .NET Framework werden Sie aber möglicherweise erst dann näher einordnen können, wenn Sie schon mehr über die Programmierung in C# wissen. Das .NET Framework stellt die Laufzeitumgebung für C#-Programme dar, ähnlich der Java Virtual Machine für Java-Anwendungen. Die Funktionalität des .NET Framework ist aber wesentlich umfassender als die der JVM. So dient das .NET Framework als Laufzeitumgebung für mehrere Programmiersprachen, die in diesem Kontext auch miteinander interagieren können. Weitere Programmiersprachen können für eine Unterstützung an das .NET Framework angepasst werden. Somit ist das .NET Framework nicht nur unabhängig vom Betriebssystem, sondern in gewisser Weise auch von der verwendeten Programmiersprache. Daneben stellt das .NET Framework eine Reihe von Hilfsmitteln zur Verfügung, welche die Programmentwicklung erleichtern und eine Verbesserung des Laufzeitverhaltens der Programme mit sich bringen. Ganz allgemein lässt sich feststellen, dass das .NET Framework nicht nur als Laufzeitumgebung dient, sondern zusätzlich den Programmcode verwaltet. Hinweis Die Entwicklung von C# wurde speziell am Konzept des .NET Framework ausgerichtet. C# als Systemsprache des .NET Framework zieht somit aus den genannten Vorteilen besonders großen Nutzen.
Das .NET Framework
Übrigens wird vom .NET Framework auch ein C#-Compiler zur Verfügung gestellt. Sein Dateiname ist csc.exe und er lässt sich mit dem Befehl csc auch von der Konsole aus starten. Hinweis Das .NET Framework wird auch kurz .NET genannt (gesprochen »dot net«). Die ursprünglich zugedachte Bezeichnung für das .NET Framework war NGWS, was für »Next Generation Windows Services« steht und so viel heißt wie »Windows-Dienste der nächsten Generation«. Dieser Name (Next Generation Windows Services) macht zwei wesentliche Aspekte besonders deutlich: zum einen die Bereitstellung von zusätzlichen Diensten und zum anderen die enge Verbindung des .NET Framework mit dem Betriebssystem.
Eine Besonderheit beim Kompilieren von C#-Programmen soll nun noch herausgestellt werden. Diese Besonderheit betrifft den Compiler des .NET Framework. Betrachten Sie dazu nochmals die Abbildung am Ende des letzten Abschnitts. Wir gehen von dem Fall aus, dass die Entwicklung eines Computerprogramms bereits abgeschlossen ist (also der IL-Code vorliegt) und nun ausgeführt werden soll. Nehmen wir an, es handle sich um eine Anwendung, die dem Benutzer mehrere mathematische Berechnungsfunktionen zur Verfügung stellt, von denen er eine auswählen kann. Dann könnte sich etwa die in Abbildung 2.2 dargestellte Konstellation ergeben.
Benutzerauswahl Subtraktion
Division
Addition
Multiplikation
Potenzfunktion
Abbildung 2.2: Verschiedene Programmzweige
Man spricht in diesem Fall von einer Programmverzweigung, da die Befehle an das Computerprogramm je nach Benutzerauswahl verschieden sind. Angenommen, der Benutzer hätte die Potenzfunktion des Programms gewählt. Für diesen Fall kommen andere Anweisungen zur Ausführung, als wenn er sich etwa für die Addition entschieden hätte. Es wäre nun schön, wenn nur die Be-
25
26
Kapitel 2
fehle in Maschinencode übersetzt werden würden, die tatsächlich gebraucht werden (hier die Befehle für das Potenzieren) und nicht alle Programmanweisungen. Und genauso verhält es sich auch. Der Compiler des .NET Framework übersetzt nicht auf ein Mal den ganzen Zwischencode und legt ihn in Maschinensprache ab, sondern kompiliert die Befehle des IL-Codes erst dann, wenn sie gebraucht werden. Aus diesem Grund bezeichnet man den Compiler des .NET Framework auch als »Just-in-Time-Compiler« (oder kurz JITter). Sollte nun während desselben Programmlaufs die Potenzfunktion durch den Anwender erneut aufgerufen werden, liegen die entsprechenden Befehle bereits in Maschinencode vor und der JITter des .NET Framework wird den entsprechenden IL-Code nicht erneut übersetzen. Er wird stattdessen gleich auf den für diesen Programmzweig bereits vorhandenen Maschinencode zurückgreifen. Somit gestaltet sich der Übersetzungsvorgang im Kontext des .NET Framework sehr effizient. Hinweis Wir sprechen oben vom JITter bzw. vom Compiler des .NET Framework. Dies ist eine Vereinfachung, welche der Anschaulichkeit dienen soll, und entspricht nicht ganz den Tatsachen. Denn eigentlich handelt es sich nicht nur um einen, sondern um mehrere Compiler, die zum .NET Framework gehören. Zum Beispiel wird das .NET Framework für WindowsBetriebssysteme mit drei verschiedenen Just-in-Time-Compilern ausgeliefert. Es existiert auch ein Programm namens JIT Compiler Manager, mit dem sich bestimmte Einstellungen bezüglich dieser Compiler vornehmen lassen. Damit könnten Sie auf Laufzeit und Speicherplatzbedarf Ihrer C#Programme Einfluss nehmen. Dies sei nur ergänzend erwähnt; im Allgemeinen besteht keine Notwendigkeit, an den Standardeinstellungen Änderungen vorzunehmen.
Installation der Visual C# 2005 Express Edition Bei der Visual C# 2005 Express Edition handelt es sich erfreulicherweise um eine vollwertige Teilmenge des Visual Studio 2005 und nicht etwa um eine funktional eingeschränkte oder gar zeitlich befristete Demoversion. Sie eignet sich vom Hobbyprogrammierer bis zum professionellen Softwareentwickler. Die Software ist konzipiert für Windows 2000 (ab Service Pack 4), Windows XP (ab Service Pack 2), Windows Server 2003 (ab Service Pack 1) und Windows Vista. Außerdem sollten Sie über mindestens 256 MB RAM und einen Prozes-
Installation der Visual C# 2005 Express Edition
sor mit wenigstens 1 Gigahertz verfügen (angegebene Mindestwerte: 192 MB und 600 Megahertz). Achtung Über die genauen Systemvoraussetzungen für die Installation der Visual C# 2005 Express Edition können Sie sich in der Datei readme.htm bzw. in der Datei contents.htm auf der Buch-CD informieren.
Die Installation der Visual C# 2005 Express Edition erweist sich in der Regel als unkompliziert. Bevor Sie mit der Installation beginnen, sollten Sie alle laufenden Programme schließen. Achtung Vor der Installation müssen Sie alle Vorabversionen von Microsoft SQL Server 2005, Visual Studio 2005 und .NET Framework 2.0 deinstallieren. Sollten Sie tatsächlich Beta-Versionen dieser Produkte auf Ihrem Computer installiert haben, lesen Sie bitte in der Datei readme.htm bzw. in der Datei contents.htm (Buch-CD) unter Punkt 1.2 Deinstallieren von Express-Editionen nach.
1 Legen Sie die Buch-CD in Ihr CD- bzw. DVD-Laufwerk ein. Falls die Installation nicht automatisch startet, navigieren Sie im Windows-Explorer zum entsprechenden Verzeichnis und starten das Installationsprogramm durch Doppelklick auf die Datei setup.exe.
Hinweis Um die Installation des .NET Framework brauchen Sie sich nicht weiter zu kümmern. Es wird bei der Installation der Visual C# 2005 Express Edition – sofern noch nicht vorhanden – automatisch mitinstalliert.
Zunächst kopiert das Setup-Programm alle benötigten Dateien in ein temporäres Verzeichnis und lädt die Installationskomponenten in den Arbeitsspeicher.
27
28
Kapitel 2
Dieser Vorgang sollte allenfalls einige Minuten in Anspruch nehmen. Danach werden Sie vom Setup-Assistenten begrüßt.
Abbildung 2.3: Der Setup-Assistent stellt sich vor
Falls Sie Ihre Erfahrungen mit der Installation an Microsoft weitergeben möchten, aktivieren Sie das entsprechende Kontrollkästchen.
2 Klicken Sie auf die Schaltfläche Weiter
>.
Falls Sie die Lizenzbedingungen ausdrucken möchten, klicken Sie im folgenden Dialog zunächst die entsprechende Schaltfläche an.
Installation der Visual C# 2005 Express Edition
3 Sie müssen die Lizenzbedingungen akzeptieren, um die Installation fortsetzen zu
können. Setzen Sie daher im Kontrollkästchen unten ein Häkchen. Danach klicken Sie wiederum auf Weiter >.
Entscheiden Sie sich nun bei Bedarf für eine zusätzliche Installation des angebotenen Programms Microsoft SQL Server. Dieser kann Ihnen für die Zukunft, wenn Sie Anwendungen mit Datenbankanbindung erstellen, sehr von Nutzen sein. Es sei jedoch darauf hingewiesen, dass Sie ihn zum Durcharbeiten dieses Buchs nicht benötigen. Das Angebot zur Installation der MSDN Express Library sollten Sie auf jeden Fall wahrnehmen. Ansonsten müssten Sie bei der Arbeit mit Visual C# Express jedes Mal auf das Internet zugreifen, um Hilfe von MSDN Online zu erhalten.
29
30
Kapitel 2
4 Klicken Sie erneut auf Weiter
>.
Legen Sie im folgenden Dialog den Ort fest, an dem die Software installiert werden soll. Falls Sie die Voreinstellung C:\Programme\Microsoft Visual Studio 8 nicht übernehmen möchten, klicken Sie auf Durchsuchen... und wählen Sie ein anderes Zielverzeichnis aus. Wenn das Verzeichnis, das Sie hier angeben, nicht existiert, wird es automatisch angelegt.
Installation der Visual C# 2005 Express Edition
5 Starten Sie nun den eigentlichen Installationsvorgang mit Klick auf die Schaltfläche Installieren >.
Danach brauchen Sie sich vorerst um nichts mehr zu kümmern. Die Installation selbst nimmt gewöhnlich – in Abhängigkeit von der Computergeschwindigkeit und den Installationsoptionen – eine Weile in Anspruch. Warten Sie einfach ab, bis Ihnen das Setup-Programm das erfolgreiche Ende der Installation mitteilt. Ein Computer-Neustart ist danach in der Regel nicht erforderlich.
31
32
Kapitel 2
Abbildung 2.4: Die eigentliche Installation geht ohne weiteres Zutun vonstatten
Abbildung 2.5: Die Visual C# 2005 Express Edition inklusive .NET Framework ist installiert
Eine kleine Erfolgskontrolle
6 Klicken Sie auf Beenden. Haben Sie sich auf der Willkommensseite dafür entschieden, Ihre Erfahrungen mit der Installation weiterzugeben, werden die entsprechenden Informationen – wobei es sich nicht um persönliche Daten handelt – nun zu den Servern von Microsoft gesendet.
Damit haben Sie die Visual C# 2005 Express Edition erfolgreich auf Ihrem Computer installiert.
Eine kleine Erfolgskontrolle • • • • •
Was bedeutet der Begriff »Laufzeitumgebung«? Was hat es mit dem IL-Code auf sich? Welcher Compiler erzeugt aus dem Quellcode den IL-Code? Welche Rolle spielt der Compiler des .NET Framework bei der Ausführung von C#-Programmen? Wie lautet eine zusammenfassende Beschreibung der Bedeutung des .NET Framework für die Ausführung von C#-Programmen?
33
Das können Sie schon: Die Entwicklungsschritte zum ausführbaren Programm
11
Die Bedeutung des .NET Framework für C#-Programme
24
Das lernen Sie neu: Kompilieren und Ausführen eines C#-Programms
35
Wie Visual C# Express Ihre Projekte verwaltet
43
Kapitel 3
Ihr erstes C#-Programm In diesem Kapitel werden Sie Ihr erstes Erfolgserlebnis als C#-Programmierer haben. Sie erstellen Ihr erstes kleines C#-Programm und bringen dieses zur Ausführung. Außerdem erfahren Sie, wie Visual C# Express Ihre Projekte verwaltet.
36
Kapitel 3
Programmentwicklung mit der Visual C# 2005 Express Edition Wir gehen davon aus, dass Sie nicht länger auf Ihr erstes Programm warten wollen und die einzelnen Schritte von der Eingabe des Quellcodes über dessen Kompilierung bis zur Programmausführung schnell kennen lernen möchten. Daher werden Sie im Folgenden ein Konsolenprogramm erstellen, welches sich allerdings fürs Erste darin erschöpfen wird, den Text Mein erstes C#-Programm auf den Bildschirm auszugeben. Konsolenprogramm – was ist das? Zu alten Zeiten von DOS und anderer älterer Betriebssysteme lief die Kommunikation zwischen Anwender und Computer in aller Regel über die so genannte Konsole ab. Zur Eingabe diente die Tastatur und zur Ausgabe der Bildschirm. Dabei bildete die Einheit aus Tastatur und Bildschirm die Konsole. Die Ausgabe erfolgte zeilenweise von oben nach unten, wobei hierfür der ganze Bildschirm zur Verfügung stand. Der Computer nahm dabei Eingaben des Benutzers (Befehle an das Betriebssystem, aber auch Startbefehle für Computerprogramme) in der aktuellen Zeile entgegen. Dabei forderte eine meist blinkende Einfügemarke zur Eingabe auf. Diese Stelle wird eben »Eingabeaufforderung«, »Befehlszeile« oder kurz »Prompt« genannt. Falls am Prompt der Startbefehl für ein Programm eingegeben wurde (durch Eingabe der dafür notwendigen Zeichenfolge über die Tastatur und anschließendes Betätigen der (¢)-Taste), übernahm dieses Programm gewissermaßen die Konsole. Das heißt, das aufgerufene Programm nutzte die Konsole – damals also den ganzen Bildschirm – für Mitteilungen an den Benutzer und um Daten vom Benutzer entgegenzunehmen (»Geben Sie bitte … ein: «, »Das Ergebnis ... ist …«). Heutzutage verläuft die Kommunikation zwischen Anwender und Programm in der Regel über grafische Benutzeroberflächen. Dies gilt z.B. auch für die Interaktion mit dem Betriebssystem. So bietet Windows verschiedene grafische Schnittstellen zum Starten von Computerprogrammen an, etwa über Einträge im Start-Menü, Symbole auf dem Desktop und eine Auflistung von ausführbaren Dateien im Windows-Explorer. Das ändert aber nichts an der Tatsache, dass die Konsole unter Windows nach wie vor zur Verfügung steht. Nur nimmt sie nicht mehr wie früher den ganzen Bildschirm ein, sondern läuft in einem Fenster. Die Fixierung auf die zeilenweise Ausgabe sowie das Prinzip der Eingabe von Befehlen gelten weiterhin. Außerdem erscheint die Konsole nicht sofort beim Start
Programmentwicklung mit der Visual C# 2005 Express Edition
des Computers, sondern ist ebenfalls über grafische Schnittstellen erreichbar. Unter Windows erreichen Sie die Konsole z.B. über Start/Alle Programme/Zubehör/Eingabeaufforderung bzw. Start/Programme/Zubehör/Eingabeaufforderung oder über Start/Ausführen.... Im letzteren Fall müssen Sie in der dann erscheinenden Dialogbox in das Textfeld cmd eintippen und mit Klick auf die Schaltfläche OK bestätigen; danach befinden Sie sich auf der Konsole. Ein Konsolenprogramm zeichnet sich dadurch aus, dass es selbst keine grafische Benutzeroberfläche (auch GUI genannt; für »Graphical User Interface«) zur Verfügung stellt, sondern für die Interaktion mit dem Anwender eben ausschließlich die Konsole benutzt.
Aus einer ganzen Reihe von Gründen werden wir uns zum Erlernen der Programmiersprache C# zunächst auf Konsolenprogramme beschränken. Dies wird Sie möglicherweise zunächst etwas enttäuschen, da grafische Programme optisch ansprechender sind und weit mehr Möglichkeiten bieten als die rein textorientierten Konsolenprogramme. Dieses Vorgehensweise ist aber von großem Vorteil, für die ersten Schritte beim Erlernen einer Programmiersprache sogar ideal, da Sie sich auf diese Weise ohne weiteren Ballast auf das Wesentliche konzentrieren können. Umgekehrt wird es Ihnen dann später leichter fallen, sich auf die Windows-Programmierung einzustellen, wenn Sie das ABC der Programmiersprache C# bereits beherrschen. Nichtsdestoweniger werden Sie alle Ihre Programme mit der Visual C# Express-Oberfläche erstellen. Diese erlaubt es Ihnen, den Quellcode für ein Programm bequem zu kompilieren und das Programm gleichzeitig aus Visual C# Express heraus zu starten. Dies gilt selbstverständlich auch für Konsolenprogramme. Das heißt, Sie brauchen sich um den Aufruf der Eingabeaufforderung (Konsole) nicht selbst zu kümmern, wie Sie im Folgenden gleich sehen werden.
1
Starten Sie Visual C# Express, z.B. über Start/Alle Programme/Microsoft Visual C# 2005 Express Edition.
2 Klicken Sie im Menü Datei auf Neues Projekt....
37
38
Kapitel 3
Abbildung 3.1: So beginnen Sie ein neues Programmierprojekt
Daraufhin wird das Dialogfeld Neues Projekt angezeigt, das verschiedene Projektvorlagen zur Auswahl anbietet. Dabei handelt es sich um Standardanwendungstypen, welche die Visual C# 2005 Express Edition für Sie erstellen kann.
3
Wählen Sie die Projektvorlage Konsolenanwendung aus und ändern Sie im unteren Textfeld den Namen der Anwendung in Mein erstes Programm.
Natürlich könnten Sie Ihrem ersten Projekt auch einen anderen Namen geben oder es bei der Vorgabe ConsoleApplication1 belassen.
Programmentwicklung mit der Visual C# 2005 Express Edition
Abbildung 3.2: Mit der Auswahl der Projektvorlage bestimmen Sie den Projekttyp
4 Klicken Sie auf OK. Die Visual C# 2005 Express Edition erstellt nun eine neue Projektmappe für Ihr Projekt und es erscheint ein Fenster für den Codebereich, in dem Sie den C#Quellcode eingeben bzw. vervollständigen können. Zur besseren Übersicht hier das vorgegebene Grundgerüst der Klasse Program: class Program { static void Main(string[] args) { } } Listing 3.1: Von Visual C# Express erzeugtes Grundgerüst der Hauptklasse
5 Fügen Sie der Main()-Methode die beiden nachfolgenden Befehle hinzu: Console.WriteLine("Mein erstes C#-Programm"); Console.ReadLine();
Setzen Sie dazu die Einfügemarke an das Ende der Zeile, welche auf die Zeile static void Main(string[] args) folgt – also hinter die öffnende geschweifte Klammer ({) – und drücken Sie die (¢)-Taste, um eine neue Zeile zu beginnen. Beachten Sie, wie der Editor die Einfügemarke automatisch nach rechts verschiebt, um einen Zeileneinzug zu erzeugen. Tippen Sie an der vor-
39
40
Kapitel 3
gegebenen Stelle die Anweisung Console.WriteLine("Mein erstes C#Programm"); ein (vergessen Sie das abschließende Semikolon nicht) und
drücken Sie wiederum (¢). Zum Schluss geben Sie die Anweisung Console.ReadLine(); ein: class Program { static void Main(string[] args) { Console.WriteLine("Mein erstes C#-Programm"); Console.ReadLine(); } } Listing 3.2: Der vollständige Code der Klasse Program
Achtung Halten Sie sich an die vorgegebene Groß- und Kleinschreibung. Schreiben Sie also nicht etwa console oder Writeline (richtig: Console bzw. WriteLine).
Die erste Anweisung sorgt dafür, dass beim Programmlauf der Text Mein erstes C#-Programm im Konsolenfenster erscheint. Falls Sie eine anderslautende Ausgabe wünschen, geben Sie zwischen den Anführungszeichen ("") den entsprechenden Text ein. Die zweite Anweisung hält das Programm an, bis die Eingabetaste gedrückt wird. Falls Sie die letzte Anweisung weglassen, wird das Konsolenfenster nach der Programmausführung sofort wieder geschlossen, sodass Sie von der Ausgabe des Programms kaum etwas zu sehen bekommen werden. Console ist der Name einer Klasse, WriteLine() und ReadLine() sind Methoden dieser Klasse. Was es mit Klassen, Methoden und anderen Programmbestandteilen auf sich hat, wird später noch ausführlich beschrieben.
Programmentwicklung mit der Visual C# 2005 Express Edition
Tipp Lassen Sie sich bei der Eingabe von Schlüsselwörtern, Klassen- oder Methodennamen von IntelliSense helfen. Dieses Tool ist im Code-Editor von Visual C# Express fest eingebaut. Es bietet Ihnen während der Eingabe eine Popup-Liste mit Namen an, die mit den bereits eingegebenen Zeichen beginnen. Mit dem weiteren Eintippen wird die Auswahl immer weiter verfeinert. Damit genügt es oft, nur die ersten Zeichen selbst einzutippen und dann einen der Vorschläge zu übernehmen. Sobald das gewünschte Wort ausgewählt ist, können Sie dieses dem Code hinzufügen, indem Sie die Eingabetaste drücken, das Wort doppelt anklicken oder ein Zeichen eingeben, das keinen Buchstaben bzw. keine Ziffer darstellt, z.B. eine öffnende Klammer (Letzteres ist besonders praktisch, wenn nach dem Namen ohnehin eine Klammer folgen soll, es lässt sich dann weiter Tipparbeit sparen).
Abbildung 3.3: Der Quellcode Ihres ersten C#-Programms
6 Drücken Sie die (F5)-Taste. Damit bringen Sie Ihr Programm zur Ausführung. Falls Ihr Code fehlerhaft ist, blendet Visual C# Express im unteren Bildschirmbereich ein Fenster mit der Bezeichnung Fehlerliste ein, welches Ihnen für jeden Fehler eine Beschreibung mit Zeilen- und Spaltennummer anzeigt. Außerdem wird standardmäßig zusätzlich ein Dialogfeld mit der Fehlermeldung »Fehler beim Erstellen. Möchten Sie den Vorgang fortsetzen und den letzten erfolgreichen Build ausführen?« eingeblendet, das Sie mit einem Klick auf Nein schließen. Soll das Dialogfeld mit dieser Meldung in Zukunft nicht mehr erscheinen, aktivieren Sie das Kon-
41
42
Kapitel 3
trollkästchen Dieses Dialogfeld nicht mehr anzeigen, bevor Sie auf Nein klicken. Überarbeiten Sie nun die fehlerhaften Codeteile und drücken Sie erneut (F5).
Abbildung 3.4: Das Programm wird ausgeführt
Ihr Programm sollte nun genau das machen, wozu Sie es bestimmt haben: die Zeichenfolge Mein erstes C#-Programm auf den Bildschirm ausgeben.
7 Beenden Sie das Programm durch Drücken der (¢)-Taste. Hinweis Beim Anlegen eines neuen Projekts speichert Visual C# Express die benötigten Dateien vorerst in einem temporären Verzeichnis und verwirft sie später wieder, falls auf eine Speicherung des aktuellen Projekts verzichtet wird.
Zum Speichern des aktuellen Projekts verwenden Sie den Menübefehl Datei/ Alle Speichern (Shortcut (Strg)+(ª)+(S)) oder klicken Sie das entsprechende Icon an. Ansonsten werden Sie beim Beenden der Visual C# 2005 Express Edition gefragt (»Sollen die Änderungen in der aktuellen Projektmappe gespeichert oder verworfen werden?«). Legen Sie im folgenden Dialog den Speicherort für Ihr Projekt fest. Tipp Richten Sie der Ordnung halber für Ihre C#-Programme einen separaten Ordner ein – womöglich sogar zwei Unterordner, einen für Ihre Konsolenprogramme, den anderen für Ihre Windows-Programme.
Es steht Ihnen frei, von Visual C# Express zusätzlich ein Projektmappenverzeichnis erstellen zu lassen. Dies erscheint allerdings erst dann sinnvoll, wenn Sie in einer professionellen Anwendung mehrere Projekte zusammenfassen und die entsprechende Struktur auch auf der Festplatte wiedergeben möchten (zu Projektmappen siehe den nächsten Abschnitt).
Projektmappen und Projekte
Abbildung 3.5: Mit dem Speichern legt Visual C# Express einen Ordner für Ihr Projekt mit den zugehörigen Dateien und Verweisen an
Auf der CD-ROM Das Beispiel finden Sie im Ordner Beispiele/Konsolenprogramme der Buch-CD unter dem Projektnamen (= Projektordner) Mein erstes Programm. Um ein bestehendes Beispiel in Visual C# Express zu öffnen, öffnen Sie die Projektmappendatei – das ist die Datei mit der Erweiterung .sln. Sobald Sie eine Projektmappendatei laden, werden alle der Projektmappe zugeordneten Elemente ebenso geöffnet. Um das Beispiel zu öffnen, rufen Sie in der Menüleiste den Eintrag Datei/Projekt öffnen... auf, navigieren im Dialogfenster Projekt öffnen zum Projektordner Mein erstes Programm und wählen schließlich die Datei Mein erstes Programm.sln aus.
Projektmappen und Projekte Die Visual C# 2005 Express Edition verwaltet den Code in Projekten und Projektmappen, wobei eine Projektmappe nicht nur ein Projekt, sondern auch mehrere Projekte enthalten kann und damit als eine Art Container für Projekte fungiert. Wenn Sie z.B. ein selbst programmiertes grafisches Steuerelement, etwa eine Schaltfläche, in Form eines eigenes Projekts auslagern, können Sie dieses gleichzeitig in mehreren Applikationen – also in mehreren Projektmappen – verwenden, indem Sie es einfach den entsprechenden Projektmappen hinzufügen. Solange Sie jedoch noch keine umfangreichen professionellen Anwendungen entwickeln, werden Ihre Projektmappen in der Regel mit nur einem Projekt auskommen. Zum Verständnis: Die Projektmappe selbst ist grundsätzlich rein virtuell, also auch nach dem Speichern eines Projekts nicht als Ordner auf der Festplatte vorhanden. Allerdings können Sie Visual C# 2005 Express im Speicherdialog anweisen, einen Projektmappenordner anzulegen (siehe oben).
43
44
Kapitel 3
Projektmappen-Explorer Der Projektmappen-Explorer ist Teil Ihrer Entwicklungsumgebung (IDE) und stellt ein Werkzeug zum Anzeigen und Verwalten von Projektmappen mit den dazugehörigen Projekten (gegebenenfalls mit nur einem Projekt) und Elementen dar. Bei Letzteren kann es sich um Verweise, Datenverbindungen, Ordner und Dateien handeln. Hinweis Zum Anzeigen des Projektmappen-Explorers wählen Sie in der Menüleiste Ansicht/Projektmappen-Explorer oder klicken Sie auf der Symbolleiste (rechts oben) auf das Symbol Projektmappen-Explorer. Eine weitere Möglichkeit besteht im Shortcut (Strg) + (W), (S) (bei gehaltener (Strg)-Taste nacheinander die Tasten (W) und (S) drücken).
Die Datei, auf die es in Ihren weiteren Programmen vor allem ankommt, ist Program.cs. Sie enthält unter anderem den Code der Main()-Methode, in welcher fürs Erste der Großteil Ihrer Programmiertätigkeit stattfindet (was es mit dieser Methode auf sich hat, erfahren Sie gleich im nächsten Kapitel). Um das Editorfenster mit dem Code von Program.cs zur Anzeige zu bringen, führen Sie im Projektmappen-Explorer einen Doppelklick auf den Dateieintrag aus.
Abbildung 3.6: Mit dem Projektmappen-Explorer können Sie in Ihren Projekten navigieren
Hätten Sie gedacht ...
Hinweis Um den Projektmappen-Explorer zu schließen, klicken Sie rechts in der Titelleiste des Fensters auf den Schließen-Button. Mit dem Stecknadelsymbol blenden Sie das Fenster des Projektmappen-Explorers aus. Dies gilt auch für andere Fenster, etwa das Eigenschaftenfenster oder die Toolbox (Letztere werden für Sie erst später von Bedeutung sein).
Es sei an dieser Stelle darauf hingewiesen, dass Ihre IDE Visual C# 2005 Express sehr komfortabel, aber natürlich auch sehr komplex ist. Das bedeutet unter anderem, dass für eine Funktion in der Regel mehrere Wege zur Verfügung stehen. So können Sie das Fenster des Projektmappen-Explorers beispielsweise auch mit dessen Kontextmenü ausblenden (Klick mit der rechten Maustaste auf die Titelleiste und Auswahl von Automatisch im Hintergrund). Es gibt zwei gute Gründe, hier nicht alle Möglichkeiten der Visual C# 2005 Express Edition darzustellen. Zum einen würde es den Umfang dieses Buchs bei weitem überschreiten. Zum anderen würde es Sie zum jetzigen Zeitpunkt vermutlich nur verwirren, mit Details Ihrer Visual C#-IDE überfrachtet zu werden. Im Übrigen sei empfohlen, für Informationen zur Oberfläche der Visual C# 2005 Express Edition nach und nach die sehr gute und ausführliche Hilfe zu bemühen. Sie rufen die Hilfe mit (F1) oder in der Menüleiste unter Hilfe auf.
Hätten Sie gedacht ... ... dass Microsoft auf der Internetseite http://www.learnvisualstudio. net/videos/Visual_CSharp_2005_Express_Edition_for_Beginners.htm eine Reihe von Video-Animationen zur Visual C# 2005 Express Edition anbietet? Tipp Das Video Getting started with Visual C# 2005 Express Edition können Sie mit der URL http://msdn.microsoft.com/vstudio/ express/media/en/AbsoluteBeginner/vc/01vcs.wvx kostenlos in Ihrem Windows Media Player anschauen. Für weitere Videos ändern Sie einfach die Ziffer im Dateinamen – für das nächste Video geben Sie also die Adresse http://msdn.microsoft.com/ vstudio/express/media/en/AbsoluteBeginner/vc/02vcs.wvx
an usw.
45
Das können Sie schon: Entwicklungsschritte zum ausführbaren C#-Programm
11
Kompilieren und Ausführen von C#-Programmen
35
Programmentwicklung mit Visual C# Express
36
Projekte und Projektmappen
43
Das lernen Sie neu: Erstellen des Quellcodes
47
Aufbau von C#-Programmen
48
Kapitel 4
Aufbau von C#Programmen In diesem Kapitel erfahren Sie etwas über den grundsätzlichen Aufbau eines C#-Programms. Dabei wird das Beispiel aus dem vorigen Kapitel noch einmal Schritt für Schritt umgesetzt und näher erklärt, wodurch Sie bereits mit den wesentlichen Grundlagen der Programmiersprache C# vertraut gemacht werden.
48
Kapitel 4
Aufbau von C#-Programmen Wir müssen uns also überlegen, wie unser Quellcode auszusehen hat. C# unterliegt wie jede andere Programmiersprache bestimmten Syntaxregeln und Vorgaben, die Sie beachten müssen. Halten Sie sich dagegen nicht an diese, bricht der Compiler beim Übersetzen den Kompiliervorgang ab und antwortet mit einer oder mehreren Fehlermeldungen. Jedes einzelne Zeichen hat in C# eine ganz bestimmte Bedeutung. Gerade Programmiersprachen wie C#, deren Entwicklung sich auf die Sprache C zurückverfolgen lässt, sind darin sehr empfindlich.
Von innen nach außen Wie ist nun ein C#-Programm aufgebaut? Die kleinste Einheit eines C#-Programms wurde schon angesprochen. Es handelt sich um die einzelnen Programmbefehle. Diese werden »Anweisungen«, »Statements« oder kurz »Befehle« genannt. Jede einzelne, syntaktisch korrekte Anweisung hat eine ganz bestimmte Wirkung. Zum Beispiel bewirkt die Anweisung name = Console.ReadLine(); unter anderem, dass eine Eingabe vom Benutzer empfangen wird, und die Anweisung Console.WriteLine("Hallo"); erzeugt die Bildschirmausgabe Hallo – wobei das »Line« in WriteLine eine zusätzliche Zeilenschaltung bewirkt. Die letzte Anweisung kommt also dem, was wir mit unserem ersten Programm erreichen wollen, sehr nahe. Wir möchten aber nicht Hallo, sondern Mein erstes C#-Programm auf dem Bildschirm stehen haben. Daher notieren Sie wie folgt. Console.WriteLine("Mein erstes C#-Programm");
Denn auf dem Bildschirm wird genau das ausgegeben, was in den Klammern zwischen den doppelten Anführungszeichen steht. Falls Sie einen anderen Text ausgeben wollen, müssen Sie dort diesen anstelle von Mein erstes C#Programm eintippen.
Aufbau von C#-Programmen
Achtung Vermeiden Sie in jedem Fall einen Zeilenumbruch innerhalb des Ausgabetextes. Wenn dieser zu lang wird, dann benutzen Sie nacheinander die Befehle Console.Write(); und Console.WriteLine();: Console.Write("Dies ist mein erstes, aber nicht"); Console.WriteLine(" mein letztes C#-Programm");
Die Wirkungsweise von Console.Write() entspricht der von Console.WriteLine() mit dem Unterschied, dass nach der Ausgabe keine Zeilenschaltung erfolgt. Denken Sie daran, dass Leerzeichen, welche innerhalb einer Zeichenkette (das heißt zwischen "...") stehen, ebenfalls auf den Bildschirm ausgegeben werden. Ebenso wirkt es sich auf die Bildschirmausgabe aus, wenn diese fehlen. So ist im obigen Code ein Leerzeichen vor dem Wort mein in der zweiten Ausgabeanweisung – bzw. nach nicht in der ersten – notwendig, da sonst auf dem Bildschirm die Wörter nicht und mein nicht, wie gewünscht, durch ein Leerzeichen getrennt, sondern als ein Wort dargestellt werden.
Haben Sie bei den Anweisungen, die Sie bisher gesehen haben, eine Gemeinsamkeit entdeckt? Es geht um das Semikolon (;) am Ende. Merken Sie sich schon einmal: Jede Anweisung in C# muss mit einem Semikolon enden. Das Fehlen eines Semikolons am Ende einer Anweisung wird als Syntaxfehler interpretiert. Somit besteht unser Code bis jetzt lediglich aus der Anweisung Console.WriteLine("Mein erstes C#-Programm");
Selbstverständlich ist die Ausgabeanweisung syntaktisch korrekt. Dennoch würde sich Ihr Compiler auf einen Kompilierversuch hin mit einer oder gar mehreren Fehlermeldungen beschweren. Da Anweisungen ja die kleinste Einheit eines C#-Programms darstellen, müssen sie in eine größere eingebunden – sozusagen »verpackt« – werden. Diese dürfen nicht, so wie es jetzt der Fall ist, für sich alleine im freien Raum stehen.
Der Methoden-»Karton« Die nächstgrößere Einheit heißt »Methode«. Ein C#-Programm kann genauso wie mehrere Anweisungen auch mehrere Methoden besitzen und jede dieser Methoden kann eine beliebige Anzahl von Anweisungen enthalten. Jede Anweisung in C# muss jedoch innerhalb einer Methode stehen. Sie können sich das so vorstellen wie eine Aneinanderreihung einzelner Kartons mit Inhalt, den
49
50
Kapitel 4
Anweisungen (ein solcher »Karton« ist wiederum selbst Inhalt eines größeren »Kartons«, wie wir gleich sehen werden). Der Aufbau einer Methode gliedert sich grob in einen Methodenkopf und einen Methodenrumpf. Bis wir zu einer ausführlicheren Betrachtung der objektorientierten Programmierung kommen, werden wir uns nur mit einer Methode befassen, der Methode Main(), die auch gleichzeitig die wichtigste darstellt. Diese Methode wird wie folgt definiert: static void Main() { }
Achtung Beachten Sie, dass Main() mit einem Großbuchstaben beginnt. Schreiben Sie nicht main(), wie Sie es vielleicht von C++ oder Java her gewohnt sind.
Die Zeile static void Main() bildet den Methodenkopf von Main(). Wenn Sie in Ihrem Quelltext einen Methodennamen schreiben, dürfen Sie die nachfolgenden Klammern nicht vergessen. Diese gehören zur Methode. Aber auch wenn man in erklärenden Texten auf eine Methode verweist, ist es üblich, hinter den Methodennamen ein leeres Klammernpaar zu setzen. Man schreibt also nicht Main, sondern Main(). Dann weiß der Leser sofort, dass es sich beim bezeichneten Objekt um eine Methode handelt und nicht etwa um eine Variable, Klasse oder um einen Namensraum (letztgenannte Elemente werden Sie allesamt noch kennen lernen). Hinweis Vielleicht haben Sie bemerkt, dass Visual C# Express zwischen den runden Klammern automatisch den Ausdruck string[] args einfügt. Dabei handelt es sich um einen so genannten Parameter, den der Programmierer verwenden kann, um eventuelle Informationen, die ein Konsolenprogramm beim Aufruf auf der Kommandozeile erhält, im Code zu verarbeiten. Wir werden diesen Parameter in unseren Beispielen jedoch nicht verwenden. Natürlich dürfen Sie die Main()-Methode auch – wie oben gezeigt – parameterlos definieren (was es mit Parametern auf sich hat, erfahren Sie in Kapitel 6 im Abschnitt »Die Methoden Write() und WriteLine()«, Genaueres dann im Zusammenhang mit Methoden in Kapitel 13).
Aufbau von C#-Programmen
Der Beginn des Methodenrumpfs wird durch eine öffnende ({) und dessen Ende durch eine schließende geschweifte Klammer (}) festgelegt. Alle Anweisungen einer Methode stehen zwischen diesen beiden Klammern, also static void Main() { // Anweisung(en) }
Bei dem doppelten Slash (//) handelt es sich um ein Kommentarzeichen. Es bewirkt, dass alle nachfolgenden Zeichen bis zum Zeilenende als Kommentar angesehen werden. Kommentare werden beim Übersetzungsvorgang ignoriert, haben also keine Auswirkung auf den späteren Programmlauf. Wir werden auf Kommentare im nächsten Kapitel noch zu sprechen kommen. Hier soll mithilfe des Kommentars lediglich darauf hingewiesen werden, an welche Stelle Anweisungen positioniert werden. Wir müssen also die Kommentarzeile gleich durch die tatsächliche Anweisung ersetzen. Tipp Geschweifte Klammern sollten in eine extra Zeile geschrieben und Anweisungen innerhalb der beiden Klammern um eine festgelegte Anzahl von Stellen eingerückt werden. Dies trägt wesentlich zur Übersichtlichkeit Ihres Quellcodes bei. Erfreulicherweise brauchen Sie sich in Ihrer IDE nicht weiter darum zu kümmern. Visual C# Express führt diese Einrückungen automatisch durch.
Die Methode Main() ist deshalb so wichtig, weil sie den Einstiegspunkt ins Programm darstellt. Wenn Sie den Kompiliervorgang starten, sucht der Compiler nach der Methode Main(). Findet er sie nicht, erhalten Sie eine entsprechende Fehlermeldung. Auch beim fertigen – kompilierten – Programm beginnt der Programmlauf immer in Main(); oder anders ausgedrückt: Beim Start des Computerprogramms wird die Methode Main() direkt vom Betriebssystem bzw. vom Prozessor Ihres Computers aufgerufen. Die erste Aktion wird also von der ersten Anweisung im Rumpf der Methode Main() bestimmt. Merken Sie sich bitte: • Jede Anweisung muss innerhalb eines Methodenrumpfs stehen. • In jedem Programm muss genau eine Methode Main() definiert sein. Damit wird die Stelle festgelegt, an der die Programmausführung beginnt.
51
52
Kapitel 4
Nun sieht unser bisheriger Quelltext so aus: static void Main() { Console.WriteLine("Mein erstes C#-Programm"); }
Über die Zeile static void Main() brauchen Sie sich noch keine Gedanken zu machen. Ihnen soll nur klar sein, dass es sich dabei um den Methodenkopf von Main() handelt. Die Bedeutung der Wörter static und void einschließlich der runden Klammern nach dem Methodennamen werden Sie später noch kennen lernen. Solange Ihre Programme einzig die Methode Main() beinhalten, lautet der Methodenkopf stets gleich. Damit haben Sie nun die Anweisung Console.WriteLine("Mein erstes C#-Programm"); in einen passenden Karton, nämlich in die Methode Main(), gepackt.
Methode Anweisung(en)
Abbildung 4.1: Anweisungen dürfen nur innerhalb von Methoden stehen
Der Klassen-»Karton« Aber immer noch wäre Ihr Compiler unzufrieden und würde das bei einem Kompilierversuch mit einer Fehlermeldung zum Ausdruck bringen. Er würde Ihnen sogar ziemlich deutlich sagen, dass er eine Klasse erwartet. Wir können daraus schließen, dass auch Methoden nicht für sich alleine stehen dürfen, sondern ebenfalls in größere Einheiten eingebunden werden müssen. Diese Einheiten nennt man »Klassen«. Klassen gehören zu den Features der objektorientierten Programmierung. Ein Computerprogramm darf aus beliebig vielen Klassen bestehen, von denen wiederum jede beliebig viele Methoden enthalten darf. Umgekehrt gilt: Genauso wie eine Anweisung nur innerhalb einer Methode stehen darf, darf auch eine Methode nur innerhalb einer Klasse definiert sein.
Aufbau von C#-Programmen
Hinweis Nicht objektorientierte Programmiersprachen wie etwa ältere Versionen von Pascal, Basic und Cobol, aber auch C kennen weder Klassen noch Methoden. Stattdessen besitzen sie als Bausteine Funktionen. Was Aufbau und Wirkungsweise angeht, entsprechen die Methoden den Funktionen. Methoden werden nur als solche bezeichnet, weil sie in Klassen eingebunden sind. C# kennt keine Funktionen, da ein C#-Programm ausschließlich aus Klassen besteht. Anders dagegen C++, der objektorientierte Nachfolger von C. Hier gibt es sowohl Funktionen als auch Methoden. Steht eine Funktion für sich alleine, ohne in eine Klasse eingebunden zu sein (was in C++ erlaubt ist), nennt man sie eben Funktion. Wird eine »Funktion« innerhalb einer Klasse definiert (auch das ist in C++ möglich, da C++ Klassen und somit Methoden kennt), dann spricht man von einer Methode. Da die Programmierung in C# ausschließlich in Klassen erfolgt, ist C# – so wie Java – eine rein objektorientierte Programmiersprache, während C++ sowohl objektorientierte Features (Klassen, Methoden) als auch nicht objektorientierte Features besitzt (z.B. Funktionen).
Klassen definieren Sie folgendermaßen: class { // // // }
Klassenname Innerhalb diese Blocks können mehrere Methoden definiert sein.
Die Definition einer Klasse beginnt mit dem Schlüsselwort class. Schlüsselwörter So nennt man Wörter, die der Compiler kennt und die eine ganz bestimmte Bedeutung haben. Beispielsweise weiß der Compiler, dass es sich um den Beginn einer Klassendefinition handelt, wenn er auf das Schlüsselwort class trifft. Auch static und void im Methodenkopf von Main() sind Schlüsselwörter, die eine genau definierte Bedeutung haben (die Sie noch kennen lernen werden). Keine Programmiersprache kommt ohne Schlüsselwörter aus.
53
54
Kapitel 4
Nach dem Schlüsselwort class steht der Name der Klasse. Bei diesem handelt es sich um einen grundsätzlich frei wählbaren Bezeichner. Visual C# Express vergibt jedoch für die Hauptklasse stets den Namen Program. Hinweis Beachten Sie, dass C# streng zwischen Groß- und Kleinschreibung unterscheidet. Ein Bezeichner Program ist deshalb ein anderer als program oder PROGRAM.
Auch die Definition einer Klasse gliedert sich in einen Kopf und einen Rumpf. Der Kopf unserer Klasse ist nun festgelegt: class Program
Daran schließt sich der Rumpf an: class Program { // Innerhalb diese Blocks // können mehrere Methoden // definiert sein }
Der Rumpf einer Klassendefinition ist wie bei der Methodendefinition gekennzeichnet durch ein Anfangs- (öffnende geschweifte Klammer) und ein Endezeichen (schließende geschweifte Klammer). Dazwischen können eine oder mehrere Methoden definiert sein. Sie müssen also Ihre Methode Main() in den Rumpf Ihrer Klasse Program schreiben. Dann sieht Ihr Quelltext so aus: class Program { static void Main() { Console.WriteLine("Mein erstes C#-Programm"); } }
Um bei dem Bild mit den Kartons zu bleiben: Sie haben nun Ihre Main()Methode zusammen mit der Ausgabeanweisung in die Klasse Program gepackt.
Aufbau von C#-Programmen
Klasse Methode Anweisung(en)
Abbildung 4.2: Auch Methoden dürfen in C# nur innerhalb von Klassen stehen
Jetzt müssten Sie das Listing nur noch durch die Zeile using System;
ergänzen. Diese Zeile steht am Anfang eines jeden Programms. Bei using handelt es sich ebenfalls um ein dem Compiler bekanntes Schlüsselwort, System bezeichnet einen Namensraum. Mit using wird dieser Namensraum dem Programm bekannt gemacht. Was das genau zu bedeuten hat, erfahren Sie, wenn wir uns im Zuge der objektorientierten Programmierung mit Klassen und Namensräumen beschäftigen. Vorläufig reicht es aus, wenn Sie wissen, dass diese Zeile am Anfang eines jeden Programms stehen muss. Der Quelltext für unser Programm sieht dann wie folgt aus: using System; class Program { static void Main() { Console.WriteLine("Mein erstes C#-Programm"); } }
Was noch fehlt, ist ein Befehl, der das Programm anhält. Ansonsten bekommen Sie nach dem Programmstart nicht viel zu sehen, da Windows das Konsolenfenster sofort wieder schließt, sobald die letzte Anweisung im Rumpf von Main() ausgeführt ist. using System; class Program { static void Main() { Console.WriteLine("Mein erstes C#-Programm"); Console.ReadLine(); } }
55
56
Kapitel 4
Wenn Sie allein diesen Code nun in das Codefenster der Visual C# 2005 Express Edition eingeben, werden Sie sehen, dass er sich kompilieren und mit (F5) ausführen lässt. Allerdings erzeugt Visual C# Express ein etwas umfangreicheres Programmgerüst. Unter anderem enthält es zwei weitere usingDirektiven und die Main()-Methode wird mit einem Parameter ausgestattet. Außerdem schließt es die Klasse Program in einen Namensraum ein, dessen Bezeichner dem Namen des Projekts entspricht. Hinweis Der Bezeichner für den Namensraum weicht allerdings im Beispiel etwas ab und lautet Mein_erstes_Programm. Visual C# Express fügt die Unterstriche hinzu, da Mein erstes Programm wegen der Leerzeichen kein gültiger Bezeichner ist (zur Bezeichnerwahl siehe Kapitel 7, Abschnitt »Variablendefinition«). Bei Namensräumen handelt es sich gewissermaßen um einen weiteren »Karton«, der jedoch – anders als das Klassengerüst – optional ist.
Sie werden das von Ihrer IDE vorgeschlagene Grundgerüst natürlich schon aus Gründen der Bequemlichkeit übernehmen, sodass sich das vollständige Programmlisting wie folgt darstellt: using System; using System.Collections.Generic; using System.Text; namespace Mein_erstes_Programm { class Program { static void Main(string[] args) { Console.WriteLine("Mein erstes C#-Programm"); Console.ReadLine(); } } } Listing 4.1: Code der Datei »Program.cs« (Projekt »Mein erstes Programm«)
Wo befindet sich nun eigentlich die ausführbare (.exe-)Datei?
Auf der CD-ROM Das Beispiel Mein erstes Programm finden Sie im Ordner Beispiele/Konsolenprogramme der Buch-CD.
Wo befindet sich nun eigentlich die ausführbare (.exe-)Datei? Diese Frage werden Sie sich vermutlich schon gestellt haben. Sie finden sie im Projektordner, dort unter bin/Debug, auf der Buch-CD also im Ordner Beispiele\ Konsolenprogramme\Mein erstes Programm\bin\Debug. Der Dateiname ist Mein erstes Programm.exe. Zur Wiederholung: Die Datei Mein erstes Programm.exe ist das Endprodukt Ihrer Programmierarbeit, die ausführbare Programmdatei. Diese ist jedoch nicht unmittelbar vom Betriebssystem abrufbar, da sie aus einem Zwischencode, dem so genannten IL-Code besteht. Um sie ausführen zu können, benötigt Ihr Computer das .NET Framework, das Sie ja schon zusammen mit Ihrer Visual C# 2005 Express Edition installiert haben. Das .NET Framework fungiert sozusagen als Mittler zwischen C#-Programm (gemeint ist die .exe-Datei) und Betriebssystem (siehe Kapitel 2, Abschnitt »Besonderheiten bei der Kompilierung in C#«). Hinweis In der Sprache des .NET werden ausführbare Dateien auch Assemblies (Einzahl Assembly) genannt.
Eine kleine Erfolgskontrolle • • • • •
Nennen Sie eine Anweisung, welche die Bildschirmausgabe ***** einschließlich einer Zeilenschaltung bewirkt. Woran erkennt der Compiler den Anfang und das Ende eines Methodenrumpfs? Welche Anweisung wird beim Programmlauf als Erstes abgearbeitet? Wie wird die Definition einer Klasse eingeleitet? Nennen Sie in hierarchisch aufsteigender Reihenfolge drei Bausteine eines C#-Programms.
57
Das können Sie schon: Die einzelnen Entwicklungsschritte zum lauffähigen C#-Programm
11
Umgang mit der Benutzeroberfläche der Visual C# 2005 Express Edition
36
Wie C#-Programme aufgebaut sind
48
Das lernen Sie neu: Grundlegende Syntaxregeln von C#
59
Verwenden von Kommentarzeichen
67
Unterscheiden verschiedener Fehlerarten
69
Kapitel 5
Syntaxregeln Wie in jeder anderen Programmiersprache müssen Sie auch in C# beim Editieren des Quellcodes bestimmte sprachspezifische Syntaxregeln beachten. Einige davon wurden bereits angesprochen. In diesem Kapitel fassen wir die wesentlichen Regeln zur Kodierung von C#-Quelltexten zusammen.
60
Kapitel 5
Textbausteine Wie Sie bereits wissen, ist die kleinste zusammengehörige Einheit in C# die Anweisung. Eine Anweisung setzt sich in der Regel aus einer Reihe von Textbausteinen zusammen – insofern es sich nicht um eine leere Anweisung handelt (was eine leere Anweisung ist, erfahren Sie gleich im nächsten Abschnitt). Textbausteine – auch Token genannt – können Schlüsselwörter, Bezeichner, konstante Werte, Operatoren oder Zeichen mit besonderer Bedeutung sein. Was Schlüsselwörter sind, wissen Sie bereits. Was es mit Operatoren, Bezeichnern und Konstanten auf sich hat, werden Sie noch erfahren. Beispiele für Zeichen mit besonderer Bedeutung sind etwa in Verbindung mit Zeichenketten die doppelten Anführungszeichen ("…"), welche den Beginn und das Ende eines Strings festlegen. Auch das Semikolon (;) als Endezeichen einer Anweisung fällt darunter. Hinweis Eine Zeichenfolge, welche zwischen "…" steht, wird im Programmierjargon Zeichenkette oder String genannt.
Die einzelnen Textbausteine müssen innerhalb einer Anweisung eindeutig unterscheidbar sein, um vom Compiler in ihrer Bedeutung erkannt zu werden. Wo das nicht automatisch der Fall ist, müssen sie durch mindestens ein Leerzeichen voneinander getrennt werden. Nehmen wir als Beispiel die Anweisung int name = 15;
Die genaue Bedeutung dieser Anweisung und der einzelnen Textbausteine werden Sie im nächsten Kapitel erfahren. Hier nur soviel: int ist ein Schlüsselwort zur Angabe eines Datentyps, name ist ein Bezeichner für ein Datenobjekt – nämlich eine Variable –, das =-Zeichen steht für einen Zuweisungsoperator und bei der Zahl 15 handelt es sich um einen konstanten Wert. Wenn Sie nun schreiben intname = 15; // FEHLER
kann das Schlüsselwort int nicht mehr als solches erkannt werden. Auch ist jetzt in der Anweisung kein Bezeichner name enthalten – allenfalls ein Bezeichner intname. Die Fehlermeldung, die diese Programmierzeile hervorruft, ergibt sich aber aus dem Fehlen eines einleitenden Schlüsselworts zur Angabe
Anweisungsende
eines Datentyps (was das genau heißt, erfahren Sie ebenfalls im nächsten Kapitel). Im gerade genannten Beispiel ist es also zwingend erforderlich, zwischen dem Schlüsselwort int und dem Bezeichner name zumindest eine Leerstelle zu setzen. Andererseits ist die Anweisung int name=15;
bei der die beiden Leerzeichen zwischen Bezeichner name, Zuweisungsoperator (=) und der Zahl 15 weggelassen wurden, syntaktisch in Ordnung. Dies liegt daran, dass der Compiler hier die einzelnen Textbausteine auch ohne Trennzeichen unterscheiden kann. Ein Bezeichner name= ist nicht zulässig, folglich wird dieser Ausdruck vom Compiler auch nicht als Bezeichner interpretiert. Tipp Um sich selbst vor Nachlässigkeiten zu schützen und auch um die Lesbarkeit des Quellcodes zu verbessern, ist es in jedem Fall sinnvoll, einzelne Textbausteine durch ein Leerzeichen voneinander zu trennen. Beim Code-Editor von Visual C# müssen Sie sich allerdings nicht darum kümmern, da dieser die Leerzeichen gegebenenfalls selbstständig einfügt, zumindest, wenn er durch die verwendeten Sonderzeichen einen Anhaltspunkt hat wie eben im Beispiel int name=15;. Eine Eingabe wie intname = 15; kann auch der Code-Editor nicht korrigieren.
Anweisungsende C# ist nicht zeilenorientiert. Das heißt, das Ende einer Anweisung wird nicht am Zeilenende erkannt. Ausschlaggebend hierfür ist allein das abschließende Semikolon (;). Jedes Semikolon wird in C# als Ende einer Anweisung interpretiert. Dabei spielt es keine Rolle, welche Textbausteine vor dem Semikolon stehen. Das geht so weit, dass ein Semikolon ohne vorangehende Token ebenfalls als C#Anweisung angesehen wird. Diese hat sogar einen speziellen Namen. Man spricht in diesem Fall von einer leeren Anweisung. In dem Codefragment Console.Write("Syntaxregeln"); // erste Anweisung ;; // zwei Anweisungen in einer Zeile
61
62
Kapitel 5
sind demzufolge drei Anweisungen enthalten. Natürlich haben die zweite und dritte keinerlei Auswirkungen auf das Geschehen während des Programmlaufs. Beachten Sie: • Immer, wenn im C#-Quellcode ein Semikolon auftritt, bildet dieses zusammen mit dem vorangehenden Text – beginnend beim letzten Semikolon oder { – eine Anweisung. • Codeteile, welche zwischen { und } stehen und nicht mit einem Semikolon abgeschlossen werden, sind in der Regel unvollständig und damit fehlerhaft.
Blöcke Blöcke beschreiben zusammengehörige Teile des Quellcodes. Grundsätzlich wird jeder Bereich zwischen einer öffnenden und einer schließenden geschweiften Klammer in C# als Block bezeichnet. Sie haben bereits mehrere Blöcke kennen gelernt. So handelt es sich beim Methodenrumpf von Main() um einen Block. Statt vom Methodenrumpf spricht man deshalb auch vom Methodenblock. static void Main() { // Anweisung(en) }
Auch eine Klasse besitzt einen Block, in dem die eigentliche Implementierung der Klasse stattfindet: class Program { // Hier können z.B. eine oder // mehrere Methoden stehen }
Ein C#-Programm kann beliebig viele Blöcke enthalten, welche auch ineinander liegen dürfen. Ein Beispiel für eine verschachtelte Blockstruktur ist das Grundgerüst Ihres ersten Programms: class Program { static void Main() { // Anweisung(en) } }
Blöcke
Der Methodenblock von Main() befindet sich dabei innerhalb des Klassenblocks. Alle Anweisungen, welche im Block einer Methode stehen, sind Teil dieser Methode, so wie alle Methoden, welche sich im Block einer Klasse befinden, als zu dieser Klasse gehörig angesehen werden. Hinweis In unserem Einführungsprogramm ist die Ausgabeanweisung Console.WriteLine("Mein erstes C#-Programm"); Teil der Main()Methode und damit auch Teil der Klasse Program, da sie innerhalb beider Blöcke steht.
Mithilfe von Blöcken lassen sich also mehrere Anweisungen zusammenfassen. Sie werden davon im Zusammenhang mit Kontrollstrukturen (Kapitel 10 und 11) noch intensiv Gebrauch machen. In den obigen Beispielen ist das Setzen von Blöcken zwingend vorgeschrieben, da Klassen sowie Methoden von einem Block begrenzt werden müssen. Grundsätzlich ist es aber auch erlaubt, innere Blöcke willkürlich zu setzen, ohne dass dafür eine syntaktische Notwendigkeit besteht: static void Main() { … { // innerer Block … } // Ende innerer Block … } // Ende von Main()
Die Auslassungspunkte sollen deutlich machen, dass an den jeweiligen Stellen eine oder mehrere Anweisungen stehen dürfen – und in der Regel auch stehen. Die Verwendung zusätzlicher Blöcke kann unter Umständen ein Mittel zur besseren Strukturierung des Quelltexts sein. In der Regel werden Sie Blöcke aber nur dann setzen, wenn es die Syntax von C# vorschreibt.
63
64
Kapitel 5
Leerräume Wie Sie weiter oben erfahren haben (Abschnitt Textbausteine), ist es häufig notwendig, einzelne Textbausteine durch Leerzeichen voneinander zu trennen. Umgekehrt würden jedoch Namen (Bezeichner, Schlüsselwörter) oder Operatoren – sofern sie aus mehreren Einzelzeichen zusammengesetzt sind – durch das Hinzufügen von Zwischenraumzeichen ihre Bedeutung verlieren. Wenn Sie also schreiben static v o i d Main()
dann ist das Schlüsselwort void nicht mehr als solches erkennbar und weil der Ausdruck v o i d auch nicht anderweitig interpretiert werden kann, ist obige Programmierzeile fehlerhaft. Damit wird deutlich, dass Leerräume im Quelltext in zwei Varianten syntaktische Bedeutung erlangen: • Trennung zweier Textbausteine durch Einfügen von Zwischenraumzeichen, sofern dies zu deren Unterscheidung notwendig ist (wie Sie im Abschnitt über Textbausteine erfahren haben, ist das nicht immer der Fall), • unerlaubtes Einfügen von Zwischenraumzeichen innerhalb eines Tokens. Dies führt zu Syntaxfehlern. In allen anderen Zusammenhängen haben Leerräume im Quellcode keinerlei Bedeutung. Diese werden beim Kompilieren einfach ignoriert. Das bedeutet, dass Sie zwischen den einzelnen Textbausteinen so viele Zwischenraumzeichen (Leerzeichen, Tabulatorschritte, Zeilenumbrüche) einfügen dürfen, wie Sie wollen. Ebenso können Sie auch ganz darauf verzichten. Auf die Bedeutung des Quellcodes hat dies keinen Einfluss. Es spielt also im Hinblick auf die Kompilierbarkeit und das Endergebnis – das fertige Programm – keine Rolle, ob Sie Ihr Einführungsprogramm in lesbarer Form, was aus den schon bekannten Gründen zu empfehlen ist, oder in einer Kurzform aufbauen, wie hier zu sehen: using System;using System.Collections.Generic; using System.Text;namespace Mein_erstes_Programm {class Program{static void Main(string[] args) {Console.WriteLine("Mein erstes C#-Programm" );Console.ReadLine();}}}
Leerräume
Sie müssen lediglich darauf achten, den String "Mein erstes C#-Programm" nicht durch eine Zeilenschaltung zu unterbrechen, da Ihr Compiler dieses Steuerzeichen innerhalb eines Strings nicht zu interpretieren vermag. Mit dem gleichen Ergebnis kompilierbar wäre eine überlange Version des Quellcodes, die wir hier aus Platzgründen nur andeutungsweise wiedergeben: ... namespace Mein_erstes_Programm { class
Program
{
static void Main ...
(
Dies könnten Sie beliebig weiter treiben. Solange die Bedeutung der einzelnen Textbausteine dabei erhalten bleibt – was in beiden Listings der Fall ist –, ist der Programmcode syntaktisch in Ordnung. Um es ganz deutlich zu machen, sei noch einmal auf die Bedeutung des Semikolons und der {} hingewiesen. Da C# eben nicht zeilenorientiert ist, wird das Anweisungsende allein am Semikolon und das Blockende allein an der schließenden Klammer erkannt. So sind die Anweisungen Console.WriteLine("Willkommen bei C#");
oder Console.WriteLine("Willkommen bei C#") ;
für den Compiler völlig gleichwertig, ebenso die Blockstrukturen static void Main() { Console.WriteLine("Hallo"); }
und static void Main() { Console.WriteLine("Hallo");
}
65
66
Kapitel 5
Der Block ist erst mit dem } zu Ende und selbst wenn die schließende Klammer im Abstand von mehreren Bildschirmseiten folgen würde, erkennt Ihr Compiler sie als Blockende. Tipp Um sich vor Fehlern zu schützen, sollten Sie sich angewöhnen, eine öffnende Klammer sogleich wieder zu schließen, bevor Sie am Quelltext weiterarbeiten.
Programmierstil Soviel nur zum Verständnis. Lassen Sie sich gerade von der oben gezeigten fünfzeiligen Programmvariante nicht verführen und gewöhnen Sie sich einen schlechten Programmierstil erst gar nicht an. Ihre Programme werden im Verlauf Ihrer weiteren Programmiertätigkeit immer umfangreicher werden und Sie werden nur dann in der Lage sein, die Übersicht über Ihren Quellcode zu behalten, wenn Sie diesen entsprechend strukturieren. Zudem können Anwendungsprogramme in den seltensten Fällen ohne Wartung über einen längeren Zeitraum hinweg eingesetzt werden. Zum einen ist es auf Grund äußerer Gegebenheiten oft notwendig, die Funktionalität einer Applikation zu erweitern, zum anderen kann das Auftreten logischer Fehler zu einer Überarbeitung des Quellcodes zwingen. Daher ist es unumgänglich, den Quelltext von Anfang an wartungsfreundlich zu implementieren. Das ausschlaggebende Kriterium hierfür ist eine schnelle Nachvollziehbarkeit des Quellcodes. Im Idealfall muss dieser daher so eingerichtet sein, dass er auch von anderen Programmierern ohne große Mühe gelesen und verstanden werden kann. Unter diesem Gesichtspunkt seien hier noch einmal zusammenfassend einige Empfehlungen gegeben: • Schreiben Sie die Zeichen für den Blockanfang ({) und das Blockende (}) jeweils in eine separate Zeile. • Rücken Sie alle Anweisungen – und auch innere Blockstrukturen – innerhalb eines Blocks um eine konstante Anzahl von Leerzeichen ein.
Kommentare
{ // Beginn äußerer Block // Anweisung im äußeren Block // ... { // Beginn innerer Block // Anweisung im inneren Block // ... } // Ende innerer Block // Anweisung im äußeren Block // ... } // Ende äußerer Block
•
Beginnen Sie jede Anweisung möglichst in einer eigenen Zeile. Dagegen ergibt es Sinn, eine sehr lange Anweisung auf zwei oder mehr Zeilen zu verteilen.
Diese Ratschläge sollten Sie sich grundsätzlich immer zu Herzen nehmen, vor allem dann, wenn Sie mit einem einfachen Text-Editor arbeiten. Allerdings werden Sie bei der Formatierung des Quellcodes von Visual C# Express im genannten Sinne weitgehend unterstützt (siehe dazu auch unter Hätten Sie gedacht ... am Ende des Kapitels).
Kommentare Schließlich gehört es auch zu einem guten Programmierstil, den Quellcode an geeigneten Stellen zu kommentieren. Dafür stehen Ihnen zwei verschiedene Kommentarzeichen zur Verfügung: • // ... • /* ... */ Mit dem ersten Zeichen leiten Sie einen einzeiligen Kommentar ein. Das heißt, jedes Zeichen, welches in derselben Zeile nach // steht, sieht Ihr Compiler als Kommentar an. Mit den Zeichen /* und */ erzeugen Sie dagegen einen Kommentar über beliebig viele Zeilen. Alles, was zwischen dem einleitenden /* und dem abschließenden */ steht, wird dann als Kommentar interpretiert:
67
68
Kapitel 5
/* Das Programm gibt die Zeichenfolge »Mein erstes C#-Programm« auf der Konsole aus. */ using System; class Program { // Hier beginnt die Main()-Methode static void Main() { Console.WriteLine("Mein erstes C#-Programm"); Console.ReadLine(); // Haltebefehl } }
Auf der CD-ROM Die so kommentierte Version Ihres ersten C#-Programms finden Sie auf der Buch-CD im Verzeichnis Beispiele/Konsolenprogramme unter dem Projektnamen K05.
Selbstverständlich soll obiges Listung nur der Demonstration dienen. In Ihrer Praxis werden Sie es im Allgemeinen unterlassen, auf triviale Dinge mittels Kommentar hinzuweisen. Schließlich würden Sie damit Ihren Quellcode nicht verständlicher machen, sondern vermutlich sogar den gegenteiligen Effekt erzielen. Bei größeren Programmen ist der sinnvolle Einsatz von Kommentaren jedoch anzuraten. Übrigens ist ein einleitender Kommentar, der die Funktionsweise eines Programms kurz beschreibt und über ein paar wesentliche Details wie z.B. den Namen des Programmautors oder die Programmversion informiert, durchaus üblich. Hinweis Darüber, dass Kommentare möglicherweise den Speicherplatzbedarf Ihrer ausführbaren Programme erhöhen oder gar die Ausführungsgeschwindigkeit reduzieren, brauchen Sie sich keine Sorgen zu machen: Der Compiler entfernt alle Kommentare, bevor er daran geht, den eigentlichen Quellcode zu kompilieren. Die Anzahl der Kommentare im Code hat also keinerlei Einfluss auf die Größe oder das Laufzeitverhalten der ausführbaren Programmdatei.
Programm(ier)fehler
Programm(ier)fehler Es wird Ihnen nicht immer gelingen, Ihren Quellcode von Anfang an ohne Syntaxfehler zu erstellen. Dies gilt umso mehr, je umfangreicher Ihre Programme werden. Wenn beim Kompilieren Fehler auftreten, müssen Sie Ihren Quellcode nochmals überarbeiten und danach den Kompiliervorgang erneut in Gang setzen. Dies kann sich unter Umständen mehrmals wiederholen. Um rechtzeitig auf Syntaxfehler hingewiesen zu werden, ist es deshalb gerade für den Programmierneuling ratsam, den Quellcode zwischenzeitlich immer mal wieder zu kompilieren (z.B. über Taste (F5) in der Visual C# Express-Umgebung). Hinweis Nach jedem erfolglosen Kompilierversuch erscheint im unteren Bereich Ihrer Entwicklungsumgebung ein Fenster mit dem Namen Fehlerliste. In diesem zeigt Visual C# Express alle Syntaxfehler mit Beschreibung und Zeilennummer an. Sie können die Fehlerliste mit dem Menübefehl Ansicht/Fehlerliste auch standardmäßig einblenden.
Bei der Fehlersuche sollten Sie berücksichtigen, dass Fehlermeldungen letztendlich aus der Betrachtungsweise Ihres Compilers resultieren. Das hat zur Folge, dass Zeilenangaben – und noch weniger etwaige Spaltenangaben, insofern die verwendete IDE solche mitteilt –, die Anzahl der Fehler und auch die angegebene Fehlerart nicht immer zutreffen müssen. Um dies zu verdeutlichen, sei angenommen, der Programmautor habe zwischen zwei Ausgabeanweisungen ein Semikolon vergessen: Console.WriteLine("erste Zeichenfolge") Console.WriteLine("zweite Zeichenfolge");
Dann begreift ein Compiler die beiden Codezeilen als eine Anweisung, die mit dem Semikolon in der zweiten Zeile zu Ende ist. Diese Anweisung ist jedoch fehlerhaft, weil sie Token enthält, welche in diesem Kontext nicht stehen dürfen.
69
70
Kapitel 5
Dabei ist die erste Codezeile noch völlig korrekt. Der fehlerhafte Code beginnt erst mit dem Console.WriteLine der unteren Zeile, da nach Console.WriteLine("erste Zeichenfolge") als nächstes Token allein ein Semikolon syntaktisch korrekt ist. Jeder andere Textbaustein wäre in diesem Kontext nicht erlaubt. Wie Ihnen nun hinlänglich bekannt ist, muss das Semikolon im obigen Codefragment aber nicht notwendig in der oberen Zeile stehen. Daher wird die Fehlermeldung einiger Compiler möglicherweise auf die untere Zeile verweisen, obwohl der Programmierer das Semikolon eigentlich in der ersten Zeile vergessen hat. Andererseits würden wir die Fehlerzahl sicherlich mit 1 angeben, es fehlt ja nur ein einziges Semikolon. Ein Compiler dagegen könnte beginnend mit der zweiten Zeile konsequenterweise jeden einzelnen Textbaustein als Fehler melden, wobei der Ausdruck Console.WriteLine allein aus insgesamt drei Token besteht. Es sei hier angemerkt, dass das Compilerverhalten in dieser Hinsicht von Fall zu Fall verschieden und nicht hundertprozentig vorhersehbar ist. Wie gesagt, hat jeder Compiler bei der Fehlerbewertung seine eigenen Maßstäbe und letzten Endes handelt es sich bei einem Compiler auch um ein Softwareprogramm, das sich eben so verhält, wie es programmiert ist. Wir möchten Sie mit diesem Beispiel auf die vereinzelt auftretende Notwendigkeit vorbereiten, eventuelle Compiler-Fehlerangaben relativiert zu betrachten. Als Richtlinie für die Fehlersuche sollten Sie Compiler-Fehlermeldungen auf jeden Fall ansehen. Außerdem gilt ohne Einschränkung: Wenn Ihr Compiler sich weigert, den Quellcode zu übersetzen, sind in diesem ohne Zweifel Syntaxfehler enthalten. Hinweis Erfreulicherweise erweist sich Visual C# Express hinsichtlich der Fehlererkennung als sehr intelligent (beispielsweise meldet der Compiler der Visual C# Express Edition bezüglich des oben gezeigten Codefragments das Fehlen genau eines Semikolons und lokalisiert es auch in der oberen Zeile).
Programm(ier)fehler
Fehlerarten Im Allgemeinen nimmt die Fehlersuche einen Großteil der Programmierarbeit in Anspruch. Und leider sind Syntaxfehler nicht die einzigen, mit denen Sie es als Programmierer zu tun haben werden. Schließlich arbeitet kaum ein größeres Programm (lauffähige Programme liegen in kompilierter Form vor, enthalten also keine Syntaxfehler) gänzlich fehlerfrei, wie Ihnen aus eigener Anwendererfahrung bekannt sein wird. Auch wenn Sie bei der Entwicklung sehr sorgfältig vorgehen, lassen sich Fehler zwar minimieren, jedoch nicht immer ganz vermeiden. Hinsichtlich ihrer Auswirkungen unterscheidet man drei Gruppen von Fehlern: • Syntaxfehler sind solche, welche vom Compiler erkannt und gemeldet werden. Quellcode, der Syntaxfehler enthält, kann nicht erfolgreich kompiliert werden. Da somit keine ausführbare Applikation aus syntaxfehlerbehaftetem Code entstehen kann, ist für diese Fehlergruppe allein die Bezeichnung Programmierfehler angebracht. Hinsichtlich der Häufigkeit ihres Auftretens stehen Syntaxfehler unbestreitbar an erster Stelle. Dennoch sind sie am einfachsten zu behandeln, weil man in der Regel frühzeitig von ihnen Kenntnis erlangt. Um dies zu forcieren, empfiehlt es sich, wie gesagt, den Quelltext zwischenzeitlich immer wieder zu kompilieren. • Laufzeitfehler sind Fehler, welche vom Compiler nicht erkannt werden und erst zur Laufzeit, also bei Ausführung des Programms, auftreten. Ein Beispiel für einen Laufzeitfehler ist der Lesezugriff auf eine nicht vorhandene Datei. • Logische Fehler sind die gefürchtetsten, weil sie in der Regel am schwersten zu finden sind. Das Programm scheint zwar fehlerfrei zu arbeiten, es macht aber eben nicht das, was es eigentlich soll. Ein Beispiel hierfür sind etwa fehlerhafte Berechnungsergebnisse.
71
72
Kapitel 5
Hätten Sie gedacht ... ... dass Sie mit der Visual C# 2005 Express Edition bezüglich der Formatierung des Quelltextes nahezu jede Einstellung treffen können. Wählen Sie dazu in der Menüleiste Extras/Optionen... und aktivieren Sie, falls notwendig, zunächst das Kontrollkästchen Alle Einstellungen anzeigen, sonst stehen die im Folgenden erwähnten Optionen nicht zur Verfügung. Öffnen Sie dann im Listenfeld links den Eintrag Text-Editor. Hinweis Wundern Sie sich nicht weiter darüber, dass der Optionen-Dialog zwischen C# und anderen Programmiersprachen (Alle Sprachen) unterscheidet. Dies rührt aus der Verwandtschaft der Visual C# 2005 Express Edition mit dem Visual Studio her. Die Aufteilung wurde vom Visual Studio, das ja für mehrere Programmiersprachen konzipiert ist, einfach übernommen.
Beispielsweise legen Sie unter Tabstopps (Text-Editor/C#/Tabstopps) die Anzahl der Leerzeichen fest, die der Code-Editor innerhalb eines Blocks automatisch einrücken soll (siehe dazu die Empfehlungen im Abschnitt Programmierstil oben). Geben Sie dazu in das untere Textfeld, neben Einzugsgröße:, den gewünschten Wert ein.
Abbildung 5.1: Unter Tabstopps bestimmen Sie die Anzahl der Leerzeichen, die der automatische Einzug umfassen soll
Hätten Sie gedacht ...
Im Übrigen ist der Editor der Visual C# 2005 Express Edition standardmäßig darauf ausgerichtet, die Formatierung des Codes in einem strukturierten und gut lesbaren Layout zu halten. Gegebenenfalls – wenn der Code unübersichtlich aussieht, z.B. nachdem Sie externen Code in Ihr Projekt geladen bzw. kopiert haben – können Sie mit Bearbeiten/Erweitert/Dokument formatieren oder der Tastenkombination (Strg) + (E), (D) das gesamte Dokument neu formatieren lassen.
73
Das können Sie schon: Entwicklungsschritte zum lauffähigen C#-Programm
11
Umgang mit der Visual C# Express-IDE
36
Aufbau von C#-Programmen
48
Syntaxregeln
59
Kommentare
67
Das lernen Sie neu: Die Bedeutung von Strings
75
Zeichenkettenverknüpfung
79
Verwenden von Steuerzeichen
81
Kapitel 6
Ausgabe Die Ausgabe gehört zu den fundamentalsten Operationen in der Datenverarbeitung. In diesem Kapitel lernen Sie die Ausgabeanweisungen Console.Write() und Console.WriteLine() näher kennen. In diesem Zusammenhang erfahren Sie auch, was es mit den so genannten Steuerzeichen auf sich hat.
76
Kapitel 6
Die Methoden Write() und WriteLine() Beide Anweisungen wurden bereits angesprochen bzw. verwendet. Write() schreibt den Wert eines in Klammern anzugebenden Parameters auf den Bildschirm. Wir werden gleich sehen, dass es sich dabei nicht in jedem Fall um eine Zeichenfolge handeln muss. Parameter – was ist das? Parameter sind Werte, welche beim Aufruf einer Methode an diese übergeben werden. Sie müssen zwischen zwei runden Klammern nach dem Methodennamen stehen. Mehrere Parameter werden durch Kommata getrennt, also Methodenname(Parameter_1, Parameter_2, Parameter_3)
Die Methoden Write() sowie WriteLine() erwarten beim Aufruf im Normalfall nur einen einzigen Parameter.
Beachten Sie, dass Write() an der Stelle weiterschreibt, an der die Einfügemarke steht. So können Sie die Bildschirmausgabe Willkommen bei C#
statt mit der Anweisung Console.Write("Willkommen bei C#");
beispielsweise auch mit den folgenden Anweisungen erzielen: Console.Write("Willkommen"); Console.Write(" bei C#");
Im Gegensatz zu Write() bewirkt WriteLine() nach Ausgabe des Texts zusätzlich einen Zeilenvorschub. Ansonsten entspricht die Funktionsweise der von Write(). Console.WriteLine("Willkommen"); Console.WriteLine(" bei C#");
Obige Anweisungen erzeugen somit die Ausgabe Willkommen bei C#
Das letzte WriteLine() setzt anschließend die Einfügemarke in die darauf folgende Zeile.
Die Methoden Write() und WriteLine()
Achtung Beachten Sie, das C# zwischen Groß- und Kleinschreibung unterscheidet. Sie dürfen z.B. nicht schreiben Console.Writeline("Willkommen"); // FEHLER
da der Compiler eine Methode Writeline() nicht kennt. Bestenfalls könnte es sich um eine andere Methode handeln, was hier aber nicht zutrifft. Genauso falsch wäre console.WriteLine("Willkommen"); // FEHLER
oder CONSOLE.WriteLine("Willkommen"); // FEHLER
da Sie den Compiler damit anweisen würden, nach einer Klasse console bzw. CONSOLE zu suchen. In beiden Fällen würde er eine entsprechende Klasse nicht finden und dies mit einer Fehlermeldung zum Ausdruck bringen. Sie wollen aber die Methode WriteLine() der Klasse Console verwenden, folglich müssen Sie schreiben Console.WriteLine("Willkommen");
Programmiersprachen, welche sich so verhalten, werden als case-sensitive bezeichnet, was so viel heißt wie »empfindlich für Groß- und Kleinschreibung«.
Vordefinierte Klassen C# stellt eine Reihe von vordefinierten Klassen mit deren Methoden zur Verfügung. Um eine Methode anzuwenden, müssen Sie dem Compiler mitteilen, in welcher Klasse diese definiert ist. Dies tun Sie, indem Sie den Namen der Klasse vor den Methodennamen schreiben. Klassenname und Methodenname werden dabei durch einen Punkt getrennt, also Console.WriteLine("Hallo");
da die Methode WriteLine() – ebenso wie die Methode Write() – in der Klasse Console definiert ist. Mit anderen Worten: Die Methoden Write() und WriteLine() gehören zur Klasse Console.
77
78
Kapitel 6
Die Klasse Console repräsentiert sozusagen die Konsole des Betriebssystems. Das heißt, sie stellt Methoden zur Kommunikation mit der Konsole zur Verfügung.
Zeichenketten Zeichenketten sind Zeichenfolgen, welche von zwei doppelten Anführungszeichen begrenzt werden. Sie haben bereits eine Zeichenkette ("Mein erstes C#-Programm") in Ihrem ersten Programm verwendet. Zeichenketten werden vom Compiler als konstante Werte behandelt. Man spricht deshalb auch von Zeichenkettenkonstanten bzw. Stringkonstanten. Merken Sie sich schon einmal: • C# unterscheidet zwischen verschiedenen Datentypen. • Auch jeder konstante Wert besitzt einen Datentyp. Zeichenketten unterliegen dem Datentyp string (die einzelnen Datentypen lernen Sie im nächsten Kapitel kennen). Daher werden Zeichenketten von Programmierern häufig als »Strings« bezeichnet. Hinweis string heißt der Name eines Datentyps in C#. Gleichzeitig ist »String« eine alternative Bezeichnung für »Zeichenkette«, also für eine Konstante vom Datentyp string.
Zum Vergleich sei hier noch der Datentyp int genannt. Dieser Datentyp umfasst alle ganzen Zahlen im Wertebereich von -2147483648 bis 2147483647 (also etwa ±2 Milliarden). Beispielsweise besitzt die Zahlenkonstante 2002 den Datentyp int. Das Gleiche gilt z.B. für 3, 1, -22, die 0 usw. Beachten Sie, dass die Regel, nach der alles, was zwischen den "" steht, vom Compiler als Zeichenkette interpretiert wird, also vom Datentyp string ist, uneingeschränkt Gültigkeit beansprucht. Demnach handelt es sich bei "123" um eine string-, nicht etwa um eine Integer-Konstante. Hinweis »Integer« ist eine in der Programmierung übliche Bezeichnung für ganzzahlige Datentypen.
Zeichenketten
Wie oben bereits angedeutet, muss der an die Methoden Write() bzw. WriteLine() zu übergebende Parameter nicht unbedingt vom Datentyp string sein. So bewirkt z.B. die Anweisung Console.WriteLine(999);
die Ausgabe von 999 auf den Bildschirm. Natürlich ist die Bildschirmausgabe in diesem Fall genau die gleiche, wenn Sie den Wert an die Methode WriteLine() als String übergeben: Console.WriteLine("999");
Hinweis Die Methoden Write() und WriteLine() sind »überladen«. Das bedeutet, dass in der Klasse Console tatsächlich mehrere Methoden gleichen Namens existieren, von denen die eine mit einem string-Parameter, eine andere mit einem Integer-Parameter definiert ist usw. Mit dem Überladen von Methoden werden Sie sich in Kapitel 14 beschäftigen. Im Moment reicht es aus, wenn Sie wissen, dass Sie an Write() bzw. WriteLine() Werte unterschiedlicher Datentypen übergeben können.
Allerdings bestimmt der Datentyp, welche Operationen mit den entsprechenden Werten möglich sind. Um das zu verdeutlichen, greifen wir im Folgenden ein weiteres Mal auf die bekannten Ausgabeanweisungen zurück.
Verknüpfung von Strings Strings lassen sich mithilfe des so genannten Verknüpfungsoperators miteinander verketten. Dieser Operator wird durch das Plus-Zeichen (+) dargestellt. Miteinander verknüpfte Strings werden vom Compiler so behandelt, als stünden alle Zeichen zusammen in einer Zeichenkette. Demzufolge sind die Ausgabeanweisungen Console.Write("Guten Tag");
und Console.Write("Guten" + " Tag");
hinsichtlich des Ergebnisses völlig äquivalent. Beachten Sie aber, dass der Compiler in der zweiten Anweisung intern zwei Arbeitsschritte durchzuführen hat. Zunächst wird die Verknüpfungsoperation "Guten" + " Tag" ausge-
79
80
Kapitel 6
führt. Danach erfolgt die Bildschirmausgabe der nunmehr verknüpften Zeichenketten. Natürlich ergibt die Stringverknüpfung in diesem Zusammenhang wenig Sinn. Dieser wird erst in Verbindung mit dem Gebrauch von Variablen deutlich. Der Grund, weswegen wir Ihnen die Verknüpfung von Zeichenketten jetzt schon vorstellen, ist eine Gemeinsamkeit mit der mathematischen Addition – beide Operationen werden mit demselben Zeichen repräsentiert. So dient das Plus-Zeichen (+) nicht nur als Verknüpfungsoperator für Strings, sondern ebenfalls als Additionsoperator. Mit Letzterem verfährt Ihr Compiler so, wie Sie es von der Mathematik her kennen. Das heißt, ein Ausdruck wie z.B. 123 + 33
ergibt bei der Berechnung durch den Compiler den Wert 156. Demnach erzeugt die Anweisung Console.Write(17 + 3);
die Bildschirmausgabe 20, da Ihr Compiler zunächst den Ausdruck in der Klammer berechnet, bevor er das Ergebnis ausgibt. Wir haben oben bereits angedeutet, dass ein Compiler anhand des Datentyps entscheidet, welche Operation durchzuführen ist. Handelt es sich bei beiden Operanden – oder auch nur bei einem – um string-Konstanten, so führt er dagegen eine Stringverknüpfung durch. Dies trifft auch dann zu, wenn die Strings ausschließlich Ziffern enthalten. Console.Write("17" + "3"); Console.Write(17 + "3");
Beide Anweisungen ergeben die Bildschirmausgabe 173. Beachten Sie folglich, dass Ihr Compiler eine Zeichenkette – also eine Konstante vom Typ string – allein an den doppelten Anführungszeichen erkennt. Dabei bestimmt das erste " den Anfang und das folgende " das Ende des Strings.
Steuerzeichen
Hinweis Dem Gesagten zufolge handelt es sich auch bei "" um einen String. Dieser stellt gewissermaßen einen Sonderfall dar, da er ja keine Zeichen enthält. Man spricht dann von einer leeren Zeichenkette bzw. einem Leerstring. Er ist sozusagen das Pendant zur 0 bei Zahlentypen und ist nicht zu verwechseln mit der Zeichenkette " ", welche ein Leerzeichen enthält. So ist zwar Console.Write("");
eine syntaktisch korrekte Anweisung, sie erzielt jedoch keinerlei Wirkung (ebenso wie das bei der Addition von Null zu einem Zahlenwert der Fall ist). Dagegen setzt die Anweisung Console.Write(" "); // Ausgabe eines Leerzeichens
die Einfügemarke um eine Stelle nach rechts.
Steuerzeichen Möglicherweise werden Sie sich schon gefragt haben, wie man mehrere Zeilenvorschübe programmiert, ohne jedes Mal auf die Methode WriteLine() zurückgreifen zu müssen. Die Antwort ist, Sie verwenden dazu das NewlineZeichen \n. Es gehört zur Gruppe der Steuerzeichen. Derartige Zeichen werden – obwohl sie mit zwei Tastaturzeichen dargestellt werden – vom Compiler wie ein einziges Zeichen – allerdings mit besonderer Bedeutung – behandelt. Steuerzeichen dürfen wie jedes »normale« Zeichen innerhalb einer Zeichenkette stehen. Dementsprechend erzeugt Ihr Compiler an jeder Stelle, an der er ein Newline-Zeichen (\n) antrifft, einen Zeilenvorschub.
81
82
Kapitel 6
»Der Compiler macht dies, der Compiler macht das ...« Diese Formulierung ist eine in der Programmierliteratur allgemein übliche – nicht ganz korrekte – Vereinfachung. Tatsächlich erzeugt ein Compiler lediglich die Maschinenbefehle bzw. in C# den IL-Code, welcher für die genannten Aktionen dann beim Programmlauf verantwortlich ist. Wenn man es also ganz genau nimmt, müsste es oben heißen: »Ihr Compiler erzeugt an jeder Stelle, an der er ein Newline-Zeichen antrifft, einen IL-Code-Befehl, welcher beim Programmlauf einen Zeilenvorschub bewirkt«.
1 Starten Sie Visual C# Express. 2 Legen Sie ein neues Projekt mit der Projektvorlage Konsolenanwendung an. Geben Sie dem Projekt einen Namen Ihrer Wahl (falls Ihnen die notwendigen Schritte zur Programmentwicklung mit der Visual C# Express Edition noch nicht ganz geläufig sind, lesen Sie bitte in Kapitel 3 nach).
3 Fügen Sie der Main()-Methode die folgenden Zeilen hinzu: Console.Write("**************\n"); Console.Write("Willkommen bei\n Console.Write("\n**************"); Console.ReadLine();
Abbildung 6.1: Beispiel zum Thema Steuerzeichen
C#");
Steuerzeichen
Auf der CD-ROM Das Beispiel K06 zu diesem Kapitel finden Sie unter Beispiele/Konsolenprogramme auf der Buch-CD. Sie könnten diesem auch die benötigten Codezeilen entnehmen und sie per Copy and Paste in die Main()Methode Ihres Projekts einfügen.
4 Bringen Sie Ihr Programm mit (F5) zur Ausführung. Falls sich Ihr Code nicht kompilieren lässt, weil er Syntaxfehler enthält, korrigieren Sie diese und drücken Sie erneut (F5). Sie sollten nun folgende Bildschirmausgabe zu sehen bekommen.: ************** Willkommen bei C# **************
5 Beenden Sie das Programm durch Drücken der (¢)-Taste und speichern Sie Ihr Projekt.
Die wichtigsten Steuerzeichen sind in der folgenden Tabelle aufgelistet: Steuerzeichen
Bedeutung
\n
Zeilenvorschub (Newline)
\t
Tabulatorschritt
\b
Backspace (Rückschritt)
\a
Piepton
\r
Wagenrücklauf (Carriage Return)
\f
Seitenvorschub (Formfeed)
\v
Vertikaler Tabulator
Hinweis Steuerzeichen werden wegen des führenden Backslash auch als »Escape-Sequenzen« bezeichnet.
83
84
Kapitel 6
Wenn Sie ein doppeltes Anführungszeichen – dessen Bedeutung ja als StringBegrenzungszeichen vorbelegt ist – mit ausgeben wollen, stellen Sie einen Backslash vor jedes auszugebende ": Console.Write("Ich finde, \"Programmieren macht Spaß\"");
Bildschirmausgabe: Ich finde, "Programmieren macht Spaß"
Der Backslash nimmt in diesem Fall dem nachfolgenden Zeichen die Sonderbedeutung. Aus diesem Grund wird er auch verwendet, um den Backslash selbst als Zeichen auszugeben. Man schreibt den Backslash in diesem Fall doppelt in die Zeichenfolge: Console.Write("Das Newline-Zeichen wird mit \\n dargestellt");
erzeugt daher die Ausgabe Das Newline-Zeichen wird mit \n dargestellt
Abbildung 6.2: Das Beispiel wurde noch um ein paar zusätzliche Steuerzeichen erweitert
Eine kleine Erfolgskontrolle
Eine kleine Erfolgskontrolle •
Welche Bildschirmausgabe erzeugt folgende Anweisung? Console.Write(11 + 22 + "33");
•
Schreiben Sie ein Programm, welches folgende Ausgabe erzeugt: Das Newline-Zeichen wird mit "\n" dargestellt
85
Das können Sie schon: Entwicklungsschritte zum lauffähigen C#-Programm
11
Umgang mit der Visual C# Express-IDE
36
Aufbau von C#-Programmen
48
Syntaxregeln
59
Kommentare
67
Arbeiten mit Strings
75
Steuerzeichen
81
Das lernen Sie neu: Variablendefinition
88
Wichtige Datentypen in C#
89
Die von Microsoft empfohlene Notation für Bezeichner
101
Verarbeiten von Benutzereingaben
103
Kapitel 7
Variablen und Datentypen Variablen gehören zu den wichtigsten Bestandteilen eines Programms. In diesem Kapitel erfahren Sie, wofür Variablen benötigt und wie diese eingesetzt werden. Ebenso steht die Bedeutung unterschiedlicher Datentypen auf dem Programm. Außerdem wird beschrieben, wie Sie Tastatureingaben vom Benutzer abfragen – schließlich kommt kaum ein Programm ohne Benutzereingaben aus. Die bereits in den vorigen Kapiteln erlernten Techniken können Sie gut einsetzen, um die eingegebenen Daten zu verarbeiten und das Ergebnis wieder auf dem Bildschirm auszugeben.
88
Kapitel 7
Variablendefinition Vielfach ist es beim Programmlauf erforderlich, Werte zwischenzuspeichern, um sie später weiterverarbeiten zu können. Dies können sowohl Benutzereingaben als auch Informationen sein, welche vom Programm intern erzeugt werden. Zu Letzteren zählen etwa Zwischenergebnisse im Rahmen umfangreicher Berechnungen. C# stellt, wie alle modernen Programmiersprachen, hierfür das Konzept der Variablen zur Verfügung. Eine Variable ist ein benannter Ort im Arbeitsspeicher, an dem Werte abgelegt, gelesen und auch verändert werden können. Bevor eine Variable verwendet werden kann, das heißt eine Speicherstelle sich mit Namen ansprechen lässt, müssen Sie die Variable zunächst definieren. Erst mit der Variablendefinition reservieren Sie Speicherplatz für Informationen. Bei der Definition von Variablen gehen Sie wie folgt vor: • Geben Sie den Datentyp an, den die Variable besitzen soll. • Wählen Sie einen Bezeichner für die Variable. Regeln zur Bezeichnerwahl Für verschiedene Elemente wie z.B. Variablen, Klassen oder Methoden vergeben Sie im Code Namen. Der Fachausdruck für solche Namen ist »Bezeichner«. Grundsätzlich dürfen Sie Bezeichner unter Beachtung folgender Regeln frei wählen: • Bezeichner setzen sich aus Buchstaben und Ziffern zusammen. Sonderzeichen sind mit Ausnahme des Unterstrichs (_) nicht zulässig. Umlaute, das »ß« und andere landesspezifische Zeichen sind zwar erlaubt, wir empfehlen Ihnen jedoch, diese möglichst zu vermeiden. • Das erste Zeichen darf keine Ziffer sein. • Groß- und Kleinschreibung wird – wie generell in C# – unterschieden. • Bezeichner müssen eindeutig sein. Letzteres hat zur Folge, dass Sie innerhalb eines Gültigkeitsbereichs (dazu später mehr) grundsätzlich nicht den gleichen Bezeichner für zwei verschiedene Elemente vergeben dürfen. Ebenso wenig dürfen Bezeichner gleich lauten wie Schlüsselwörter.
Variablendefinition
Folgende Variablennamen sind z.B. korrekt: kmProSek km_pro_sek myVar1 myVar_2
Beispiele für ungültige Bezeichner: my Var // Leerzeichen nicht erlaubt 1abt // erstes Zeichen darf keine Ziffer sein class // class ist ein Schlüsselwort und darf deshalb // nicht zur Bezeichnerwahl herangezogen werden
Wie bereits erwähnt, unterscheidet C# zwischen verschiedenen Datentypen. Mit der Angabe des Datentyps legen Sie ein für alle Mal fest: • die Größe des reservierten Speicherplatzes, • die Art von Daten, welche an diesem Speicherort abgelegt werden können, • die Art von Operationen, welche mit diesen Daten ausgeführt werden können (vergleiche Kapitel 6, »Verknüpfung von Strings «). So werden z. B. für eine Variable vom Datentyp int, welche zur Aufnahme von ganzen Zahlen vorgesehen ist, vier Byte reserviert. Dagegen beansprucht eine Variable vom Datentyp double acht Byte im Arbeitsspeicher. Letztere wird zur Aufnahme von Zahlen mit Nachkommastellen verwendet. Des Weiteren kann eine Variable nur Werte des Datentyps aufnehmen, für den sie vorgesehen ist. So kann eine Variable vom Datentyp string ausschließlich Zeichenketten (Strings) speichern, nicht etwa Zahlenwerte. Ebenso ist es z. B. offensichtlich, dass Sie mit Zeichenketten keine mathematischen Operationen durchführen können, wohl aber mit Werten vom Datentyp int. Von daher bestimmt der Datentyp einer Variablen, welche Operationen mit den enthaltenen Werten möglich sind. Wir werden uns auf die Verwendung der wichtigsten der von .NET zur Verfügung gestellten Datentypen beschränken. Dazu gehört ein Datentyp für Strings, einer für Ganzzahlen, einer zur Verarbeitung von Zahlen mit Nachkommastellen, ein Datentyp zur Aufnahme von einzelnen Zeichen sowie der boolesche Datentyp zum Speichern von Wahrheitswerten. Mit diesen Datentypen können Sie alle Aufgaben lösen. Daneben existieren eine Reihe weiterer Datentypen, die sich von den genannten jedoch allein in puncto Wertebereich und Speicherbedarf unterscheiden. Genannt sei etwa der Datentyp byte zum Speichern von positiven Ganzzahlen im Bereich von 0 bis 255. Wenn Sie also
89
90
Kapitel 7
wissen, dass eine Variable positive Ganzzahlen aufnehmen wird, die nicht größer als 255 sind, können Sie an Stelle des Datentyps int auch den Datentyp byte verwenden. Während eine int-Variable vier Byte beansprucht, benötigt eine Variable vom Typ byte nur ein Byte im RAM (bei den heutigen Speicherressourcen dürfte das allerdings das geringste Problem sein). Die genannten Datentypen sind in der folgenden Tabelle aufgelistet: Alias
Größe
Datentyp
int
32 Bit
Int32
double
64 Bit
Double
char
16 Bit
Char
string
16 Bit pro Zeichen
String
bool
1 Bit
Boolean
Tabelle 7.1: Wichtige Datentypen in C#
In C# steht hinter jedem Datentyp eine Klasse des .NET Framework und Schlüsselwörter wie int, string, double usw. sind eigentlich nichts anderes als von der Sprache C# bereitgestellte Synonyme zur Bezeichnung der entsprechenden Klassen. Diese Schlüsselwörter (erste Tabellenspalte) verwenden Sie auch zur Angabe des Datentyps bei der Variablendefinition. In der letzten Spalte steht der Name des eigentlichen Datentyps, so wie er im .NET Framework definiert ist. Die zweite Spalte gibt die Größe an, die eine Variable dieses Datentyps beansprucht. Dabei stellt der Datentyp string einen Sonderfall dar. Er beansprucht für jedes Zeichen zwei Byte. Sie können in eine Variable vom Datentyp string jede beliebige Zeichenkette ablegen. Die einzige Größenbeschränkung ergibt sich aus den Speicherressourcen des Computers, auf dem Ihr Programm ausgeführt wird. Dies setzt eine dynamische Speicherverwaltung voraus, worauf wir aber zum jetzigen Zeitpunkt nicht näher eingehen wollen. Hier die Wertebereiche der anderen Datentypen: • Der Datentyp int umfasst alle ganzen Zahlen im Bereich von -2147483648 bis 2147483647. • Der Wertebereich des Datentyps double erstreckt sich von ±5.0 * 10-324 bis ±1.7 * 10308.
Variablendefinition
•
Der Datentyp char dient zur Aufnahme von einzelnen Zeichen. C# verwendet dabei wie beim Datentyp string den Unicode-Zeichensatz. Dies ermöglicht die Speicherung praktisch jedes Schriftzeichens jeder beliebigen Landessprache. Was ist das? Der Unicode-Zeichensatz erlaubt die Darstellung von bis zu 65536 verschiedenen Zeichen. Jedem Zeichen ist eine Ordnungszahl eindeutig zugeordnet. Die Ordnungsnummern sind fortlaufend. Die ersten 256 Zeichen entsprechen dabei der bis dato häufig verwendeten ASCII-Tabelle.
•
Der Wertebereich des Datentyps bool besteht aus den beiden Wahrheitswerten true und false.
Besondere Bedeutung kommt dem booleschen Datentyp im Zusammenhang mit Kontrollstrukturen zu. Diese werden Sie in den Kapiteln 10 und 11 kennen lernen. Nun gehen Sie daran, Ihre erste Variable zu definieren. Wie schon angedeutet, gestaltet sich dieser Vorgang denkbar einfach. Sie müssen nur festlegen, welchen Datentyp die Variable besitzen soll, also eines der in der obigen Tabelle (erste Spalte) angegebenen Schlüsselwörter zur Angabe des Datentyps hinschreiben.
1 Legen Sie in Ihrer Visual C# Express Edition ein neues Projekt vom Typ Konsolenanwendung an.
Lassen Sie uns als Erstes eine Variable zur Aufnahme von Strings definieren. Dazu verwenden Sie das Schlüsselwort string.
2 Tippen Sie in der Main()-Methode das Schlüsselwort string ein: static void Main(string[] args) { string }
3 Wählen Sie einen Bezeichner für die Variable. Nennen Sie diese doch sinnigerweise ersteVariable:
static void Main(string[] args) { string ersteVariable; }
91
92
Kapitel 7
Da eine Variablendefinition schließlich auch eine Anweisung wie jede andere ist, dürfen Sie das abschließende Semikolon nicht vergessen. Damit ist die Definition Ihrer string-Variablen bereits abgeschlossen. Hinweis Der eigentliche Datentyp – der Name der entsprechenden .NET-Klasse – heißt zwar String (siehe Tabelle oben, letzte Spalte). Das von C# zur Verfügung gestellte Schlüsselwort zur Festlegung des Datentyps bei der Definition wird aber kleingeschrieben – string. Ebenso verhält es sich mit den Datentypen Double (Schlüsselwort double), Char (char), Boolean (bool) und Int32 (int). Genau genommen könnten Sie bei der Definition auch den tatsächlichen Klassennamen angeben – was jedoch nicht üblich ist.
Die Variable ersteVariable ist nun existent. Der Name ersteVariable bezeichnet somit einen Ort im Arbeitsspeicher, an dem Ihr Programm später Werte ablegen kann. Zunächst sind dort jedoch noch keine Werte vorhanden. Eine Variable kann man sich unmittelbar nach der Definition wie einen leeren Behälter vorstellen, wobei der Name des Behälters der Bezeichner der Variablen ist.
ersteVariable
Abbildung 7.1: Die Variable – hier »ersteVariable« – fungiert als eine Art Behälter, der vorerst noch leer ist
Um in diesem Behälter Werte abzulegen oder aber Werte auszulesen, welche in dem Behälter gespeichert wurden (möglicherweise, um damit Berechnungen durchzuführen), müssen Sie den Bezeichner der Variablen in den Quellcode schreiben. Dazu gleich mehr.
Die Zuweisung
Hinweis Tatsächlich besitzt eine Variable nach ihrer Definition einen undefinierten (nicht kontrollierbaren) Wert. Denn der Speicherort, welcher im Zuge der Variablendefinition für zukünftige Werte reserviert wird, erfährt vorerst keine Veränderung. Sind dort bereits Daten vorhanden – die möglicherweise von früheren Programmläufen stammen, gegebenenfalls auch Relikte früher ausgeführter Programme darstellen –, werden diese zunächst nicht gelöscht. Dies geschieht erst dann, wenn während des Programmlaufs neue Werte in die Variable – das heißt an diese Stelle – geschrieben werden. Um Programmierer vor Fehlern zu schützen, verhindert C# jedoch den Zugriff auf Variablen, welche keine kontrollierten Werte enthalten (mit »kontrollierten Werten« sind Werte gemeint, die Variablen per Programmcode nach der Definition zugewiesen werden). Von daher kommt das Bild vom leeren Behälter der Wirklichkeit ausreichend nahe.
Die Zuweisung Bei der Zuweisung handelt es sich um eine spezielle Anweisung. Wie der Name bereits andeutet, wird mit der Ausführung einer solchen Anweisung während des Programmlaufs in einer Variablen ein neuer Wert abgelegt. Man sagt auch, »die Variable bekommt einen neuen Wert zugewiesen«. Tatsächlich ändert sich mit einer Zuweisung der Inhalt der in der Zuweisung bezeichneten Variablen, das heißt der Speicherstelle, für die der Variablenname steht. Hinweis Beachten Sie, dass eine Variable zur gleichen Zeit nur einen einzigen Wert enthalten kann. Mit jeder Neuzuweisung geht somit der alte Wert unwiederbringlich verloren.
Eine Zuweisung stellt ebenso wie die schon gezeigte mathematische Addition oder die Stringverknüpfung eine Operation dar. Sie wird durch den so genannten Zuweisungsoperator repräsentiert. Dieser wird mit dem Gleichheitszeichen (=) dargestellt. Es sei bereits an dieser Stelle darauf hingewiesen, dass
93
94
Kapitel 7
die Bedeutung des Zuweisungsoperators nicht dem Ist-Gleich-Zeichen in der Mathematik entspricht. Eine Zuweisung setzt sich insgesamt aus drei Teilen zusammen: • dem Zuweisungsoperator, • einem linken Operanden. Dieser besteht immer aus genau einem Variablennamen. Damit wird die Variable bezeichnet, die mit der Zuweisung einen neuen Wert erhält. • Als rechter Operand steht der zuzuweisende Wert. Allerdings darf hier auch ein komplexer Ausdruck stehen, welcher nach – interner – Berechnung den zuzuweisenden Wert ergibt. Weisen Sie nun Ihrer Variablen ersteVariable einen Wert zu. Gehen Sie dabei wie oben beschrieben vor.
4 Schreiben Sie als Erstes den Zuweisungsoperator (also das Zeichen =) in den Quelltext:
static void Main(string[] args) { string ersteVariable; = }
Hinweis Beachten Sie, dass alle Anweisungen beim Programmlauf von oben nach unten abgearbeitet werden (in den Kapiteln 10 und 11 werden Sie erfahren, wie der Programmierer in diesen Ablauf eingreifen kann). Daraus folgt, dass die Zuweisung nach der Definition stehen muss, da eine Variable erst nach ihrer Definition existent ist und verwendet werden kann.
5 Links vor den Zuweisungsoperator setzen Sie den Bezeichner für Ihre Variable, also ersteVariable:
static void Main(string[] args) { string ersteVariable; ersteVariable = }
6 Rechts neben dem Zuweisungsoperator notieren Sie den Wert, den die Variable erhalten soll (plus abschließendes Semikolon).
Die Zuweisung
static void Main(string[] args) { string ersteVariable; ersteVariable = "C#"; }
Nach Ausführung der Zuweisung ersteVariable = "C#" beim Programmlauf enthält Ihre Variable den String "C#". »C#« ersteVariable
Abbildung 7.2: Der Variablen »ersteVariable« wurde ein Wert zugewiesen
7 Lassen Sie sich das Ergebnis der Zuweisung zeigen, indem Sie nach der Zuweisung den Inhalt der Variablen auf den Bildschirm ausgeben: static void Main(string[] args) { string ersteVariable; ersteVariable = "C#"; Console.WriteLine(ersteVariable); }
Um den Wert der Variablen ersteVariable auf dem Bildschirm anzuzeigen, verwenden Sie die bereits bekannte Methode WriteLine(). Als Parameter schreiben Sie den Namen Ihrer Variablen zwischen die runden Klammern. Es versteht sich von selbst, dass die Ausgabeanweisung nach der Zuweisung stehen muss. Der Inhalt der Variablen kann schließlich erst dann ausgegeben werden, wenn er vorhanden ist.
8 Fügen Sie den obligatorischen Haltebefehl hinzu: static void Main(string[] args) { string ersteVariable; ersteVariable = "C#"; Console.WriteLine(ersteVariable); Console.ReadLine(); }
9 Bringen Sie Ihr Programm zur Ausführung, z.B. mit (F5). Danach sollten Sie die Ausgabe C# erhalten.
95
96
Kapitel 7
Allerdings wirkt die bloße Anzeige des Inhalts von ersteVariable etwas trocken. Ferner erschließt sich der Sinn der Ausgabe nicht für jemanden, der keine Kenntnis darüber hat, welche Art von Daten überhaupt angezeigt wird. Es macht folglich einen informativeren Eindruck, wenn Sie die Ausgabe des Variablenwerts mit einem erklärenden Text versehen, z.B. in der Form: Der Inhalt der Variablen "ersteVariable" ist:
Dies erreichen Sie, indem Sie vor der Anweisung Console.WriteLine(ersteVariable); den String "Der Inhalt der Variablen \"ersteVariable\" ist: " ausgeben. Hinweis Erinnern Sie sich? Um das Zeichen " zur Ausgabe zu bringen, müssen Sie innerhalb der Zeichenkette einen Backslash vor jedes auszugebende doppelte Anführungszeichen setzen, also die Form \" wählen.
Was tun, wenn eine Anweisung sehr lang wird? Anweisungen beanspruchen in C# mitunter sehr viel Platz im Quellcode. Dies ist vor allem dann der Fall, wenn der in einer Anweisung verwendete String sehr lang ist: Console.Write("Der Inhalt der Variablen \"ersteVariable\" ist: ");
Achtung Sie dürfen innerhalb eines Strings auf keinen Fall die Taste (¢) drücken. Das mit diesem Tastendruck eingefügte Zeichen ist vom Compiler innerhalb eines Strings nicht interpretierbar. Die folgende Form ist also nicht zulässig: Console.Write("Der Inhalt der(¢) Variablen \"ersteVariable\" ist: "); // FEHLER
Sie würden beim Kompilierungsversuch eine Fehlermeldung erhalten. Viele Editoren brechen allerdings lange Zeilen automatisch um, was aber dann in aller Regel nur eine optische Angelegenheit ist, es wird also an der Umbruchstelle kein Carriage Return eingefügt. Der Code-Editor der Visual C# Express Edition führt standardmäßig keinen Zeilenumbruch durch, kann aber über das Kontrollkästchen Zeilenumbruch in Extras/ Optionen/Text-Editor/C#/Allgemein so konfiguriert werden, dass lange Zeilen optisch umbrochen werden.
Die Zuweisung
Natürlich können Sie im Editor einfach in einer Zeile weiterschreiben (allerdings wird der Quellcode dann meist schnell unübersichtlich). Die gleiche Bildschirmausgabe lässt sich aber auch mit mehreren Anweisungen realisieren: Console.Write("Der Inhalt der Variablen"); Console.Write(" \"ersteVariable\" ist: ");
Eine andere Möglichkeit ergibt sich aus der Tatsache, dass in C# Zwischenraumzeichen beim Kompilieren grundsätzlich keine Rolle spielen (siehe Kapitel 5, Abschnitt »Leerräume«). Achtung Diese Aussage gilt natürlich nicht für Zwischenraumzeichen, welche sich innerhalb von Strings befinden. Denken Sie daran, dass ein String einen konstanten Wert darstellt. Der Compiler versucht grundsätzlich, jedes Zeichen in einer Zeichenkette zu interpretieren. Ist dies nicht möglich – wie im Falle eines mit (¢) erzeugten Zeichens – wird er mit einer Fehlermeldung darauf aufmerksam machen.
Also können Sie unter Anwendung des Verknüpfungsoperators den String zerlegen, um so die Ausgabeanweisung auf mehrere Zeilen zu verteilen: Console.Write("Der Inhalt der Variablen " +"\"ersteVariable\" ist: ");
Hinweis Ob Sie die zweite Zeile am Zeilenanfang beginnen lassen oder wie hier bündig zur Klammer, ist dabei eine reine Geschmacksfrage.
Auf diese Weise ließe sich in unserem Beispiel sogar die Ausgabe des Variablenwerts mit einbeziehen: Console.Write("Der Inhalt der Variablen " + "\"ersteVariable\" ist: " + ersteVariable);
10 Fügen Sie der Ausgabe des Variablenwerts einen erklärenden Text hinzu. Verwenden Sie dazu eine der vorgeschlagenen Techniken. static void Main(string[] args) { string ersteVariable;
97
98
Kapitel 7
ersteVariable = "C#"; Console.Write("Der Inhalt der Variablen " +"\"ersteVariable\" ist: "); Console.WriteLine(ersteVariable); Console.ReadLine(); }
Auf der CD-ROM Das Beispiel finden Sie unter Beispiele/Konsolenprogramme auf der Buch-CD (Projektname K07a).
11 Überzeugen Sie sich vom Ergebnis.
Abbildung 7.3: Das Beispielprogramm gibt den Inhalt der Variablen »ersteVariable« aus
Initialisieren von Variablen Es ist erlaubt, Variablen bereits im Zuge ihrer Definition einen Anfangswert zuzuweisen. Man spricht in einem solchen Fall vom Initialisieren. So können Sie z.B. die Variable ersteVariable auch in einer Anweisung definieren und mit dem String "C#" initialisieren: static void Main(string[] args) { string ersteVariable = "C#"; Console.Write("Der Inhalt der Variablen " +"\"ersteVariable\" ist: "); Console.WriteLine(ersteVariable); Console.ReadLine(); }
Unterschiedliche Bedeutung von Variablenbezeichnern im Quellcode
Unterschiedliche Bedeutung von Variablenbezeichnern im Quellcode Bedenken Sie, dass Sie Variablenbezeichner in drei Varianten im Quellcode verwenden können: • In einer Definitionsanweisung. Damit legen Sie die Variable an, das heißt, Sie reservieren Speicherplatz, den bzw. dessen Inhalt Sie ab diesem Zeitpunkt mit dem Variablenbezeichner ansprechen können. • Auf der linken Seite einer Zuweisung. In diesem Fall bezeichnen Sie den Speicherort an sich. Mit der Zuweisung schreiben Sie an die mit dem Variablennamen bezeichnete Speicherstelle einen neuen Wert. • An jeder anderen Stelle, an der Sie im Quellcode einen Variablenbezeichner notieren, sprechen Sie damit den Inhalt der Variablen (also der bezeichneten Speicherstelle) – und damit den enthaltenen Wert – an. Beim Programmlauf geschieht Folgendes: Die Variable wird gelesen, das heißt, es wird intern zur Weiterverarbeitung eine Kopie des Variableninhalts angefertigt. Die Variable selbst bleibt dabei unangetastet. Ein Beispiel für die letzte Variante ist etwa die Anweisung Console.Write("Der Inhalt der Variablen " + "\"ersteVariable\" ist: " + ersteVariable);
Zum Zeitpunkt, zu dem diese Anweisung im Rahmen Ihres kleinen Programms zur Ausführung gelangt, enthält die Variable ersteVariable die Zeichenkette "C#". Bei Abarbeitung der Anweisung wird zunächst die Variable ersteVariable gelesen. Das heißt, es wird eine Kopie des in der Variablen enthaltenen Werts ("C#") erzeugt. Mit dem kopierten Wert wird im nächsten Schritt die Zeichenkette zusammengesetzt. Im Anschluss daran erfolgt die Ausgabe Der Inhalt der Variablen "ersteVariable" ist: C# auf den Bildschirm. Die Variable ersteVariable (deren Wert) ist dabei völlig unverändert geblieben. Es ist nicht selten, dass ein Variablenbezeichner innerhalb einer Zuweisung in zweifacher Bedeutung auftritt. Dies wird deutlich, wenn Sie mit einer erneuten Zuweisung den Inhalt Ihrer Variablen um den String-Wert "ist modern" ergänzen: ersteVariable = ersteVariable + " ist modern";
Hier tritt der Variablenbezeichner sowohl auf der rechten als auch auf der linken Seite der Zuweisung auf. Machen Sie sich die Reihenfolge der internen Schritte deutlich:
99
100
Kapitel 7
• • •
Die Variable ersteVariable wird gelesen (kopierter Wert: "C#"). Die beiden Strings "C#" und " ist modern" werden miteinander verkettet. Nun erfolgt die Zuweisung der verknüpften Zeichenkette "C# ist modern" an die Variable ersteVariable. Die bis dahin in der Variablen gespeicherte Zeichenkette ("C#") wird dabei überschrieben. Hinweis Mit jeder Neuzuweisung geht der alte Wert einer Variablen verloren. Wenn Sie also den aktuellen Wert einer Variablen berücksichtigen wollen, müssen Sie ihn vor der Zuweisung lesen. Mit anderen Worten, Sie müssen den Variablenbezeichner in irgendeiner Form auch rechts des Zuweisungsoperators verwenden.
Falls Sie testen möchten, ob die Neuzuweisung das beschriebene Ergebnis zeigt, hier das zugehörige Listing: static void Main(string[] args) { string ersteVariable = "C#"; ersteVariable = ersteVariable + " ist modern"; Console.Write("Der Inhalt der Variablen " + "\"ersteVariable\" ist: "); Console.WriteLine(ersteVariable); Console.ReadLine(); }
Ausgabe: Der Inhalt der Variablen "ersteVariable" ist: C# ist modern
Auf der CD-ROM Wir beschränken uns bei der Darstellung wie gewohnt auf den Code der Main()-Methode. Das vollständige Beispiel finden Sie unter dem Projektnamen K07b im Ordner Beispiele/Konsolenprogramme auf der BuchCD.
Beachten Sie in diesem Zusammenhang auch, dass die Abarbeitung einer Zuweisung stets von rechts nach links erfolgt. Dies ist offensichtlich, wenn als rechter Operand ein komplexer Ausdruck steht. Dieser muss erst ausgewertet
Notationen für Bezeichner
werden, bevor die Zuweisung des Berechnungsergebnisses erfolgen kann. Aber auch, falls es sich um einen einfachen Wert handelt, muss dieser vor der eigentlichen Zuweisung zumindest einmal »angefasst« werden.
Notationen für Bezeichner In umfangreichen Quelltexten treten in der Regel eine Vielzahl von Variablenbezeichnern auf. Dazu kommen Bezeichner für Klassen, Methoden, Felder, Eigenschaften, eventuell Namensräume usw. Um die Lesbarkeit des Quellcodes zu verbessern, ist es sinnvoll, sich bei der Bezeichnerwahl gewissen – selbst auferlegten – Regeln zu unterwerfen: • Verwenden Sie möglichst »sprechende« Namen. Das heißt, greifen Sie auf Bezeichner zurück, die etwas über den Anwendungszweck des Elements aussagen, z.B. der Variablen. • Gewöhnen Sie sich an eine einheitliche Groß- und Kleinschreibung. Das Überarbeiten eines Quelltextes gestaltet sich viel einfacher, wenn Sie es mit Variablennamen zu tun haben, die sich selbst erklären, z.B. betrag, summe und anzahl. Nichts sagende Variablenbezeichner wie a, b, c, x und y dagegen führen dazu, dass die Nachvollziehbarkeit von umfangreichen Quelltexten sehr schwer wird. Die zweite Empfehlung ergibt sich aus der Tatsache, dass C# zwischen Großund Kleinschreibung unterscheidet. Wenn Sie eine Variable summe wie folgt deklarieren double summe;
dürfen Sie später im Quellcode nicht schreiben Console.Write(Summe); // FEHLER
Ein in der letzten Anweisung bezeichnetes Element Summe ist dem Compiler nicht bekannt. Er kennt nur – weil zuvor definiert – eine Variable summe (nicht Summe). Natürlich ist die Gefahr, dass Ihnen solche Fehler unterlaufen, bei sehr kleinen Programmen eher gering. Wie verhält es sich aber, wenn Ihr Quellcode beispielsweise 50 Variablen enthält und sich über mehrere Seiten erstreckt. Angenommen, Sie verwenden auf Seite X eine Variable, die Sie fünf Seiten zuvor definiert haben. Dann ist es müßig, jedes Mal nachschauen zu müssen, weil Sie sich nicht sicher sind, wie Sie die Variable bei deren Definition geschrieben haben, vor allem in puncto Groß- und Kleinschreibung. Wenn Sie sich jedoch z.B. angewöhnt haben, für Bezeichner von Variablen grundsätzlich immer
101
102
Kapitel 7
Kleinbuchstaben zu verwenden, dann stellt sich die Frage nach der Schreibweise erst gar nicht. Die von Microsoft empfohlenen Schreibweisen für Bezeichner heißen PascalCasing bzw. camelCasing.
PascalCasing Beim PascalCasing wird der erste Buchstabe eines Bezeichners stets groß-, alle folgenden kleingeschrieben: Summe, Rechteck, X, Y
Besteht ein Bezeichner aus mehreren Wörtern, so wird zusätzlich jeweils der erste Buchstabe eines Worts großgeschrieben: FirstClass, MeineKlasse
Microsoft empfiehlt PascalCasing für die Bezeichner von Konstanten, Eigenschaften, Methoden, Klassen und Namensräumen. Diese Elemente lernen Sie im weiteren Verlauf des Buchs noch kennen.
camelCasing Beim camelCasing wird der erste Buchstabe eines Bezeichners immer kleingeschrieben: summe, rechteck, x, y
Ansonsten verhält es sich wie beim PascalCasing. Besteht ein Bezeichner aus mehreren Wörtern, so wird entsprechend der erste Buchstabe jedes folgenden Worts großgeschrieben: ersteVariable, myNumber, bigPoint
Microsoft empfiehlt camelCasing für die Bezeichner von lokalen Variablen und Methodenparametern. Was ist das? Alle Variablen, die im Rumpf einer Methode deklariert sind, nennt man lokale Variablen. Man sagt, sie sind lokal zu der Methode, in der sie definiert sind. Dies hat Einfluss auf ihren Gültigkeitsbereich. Dazu später mehr. Vorerst haben Sie es ausschließlich mit lokalen Variablen zu tun. Auf Methodenparameter werden wir in Kapitel 13 zu sprechen kommen.
Die Methode ReadLine()
Es sei hier noch erwähnt, dass es sich mit diesen Schreibweisen lediglich um Empfehlungen handelt. Es besteht für Sie kein Zwang, sich daran zu halten. Ratsam ist es auf jeden Fall, sich hinsichtlich der Bezeichnerwahl an eine einheitliche Notation zu gewöhnen.
Die Methode ReadLine() Um Benutzereingaben auf der Konsole entgegenzunehmen, stellt C# die Methode ReadLine() zur Verfügung. Diese Methode ist wie die Methoden Write() und WriteLine() in der Klasse Console deklariert. Wenn Sie die Methode ReadLine() verwenden, müssen Sie also schreiben Console.ReadLine();
Wenn Sie die Anweisung so in den Quellcode schreiben, ist das zwar syntaktisch korrekt, das heißt der Quellcode lässt sich ohne Fehlermeldung kompilieren. Um den Eingabebefehl jedoch wirklich sinnvoll einzusetzen, müssen Sie noch eine Kleinigkeit ergänzen. Schauen wir uns zunächst an, wie ReadLine() in der obigen Form arbeitet.
1 Legen Sie in Ihrer IDE ein neues Projekt vom Typ Konsolenanwendung an. 2 Fügen Sie Ihrer Main()-Methode den Aufruf von ReadLine() hinzu: static void Main(string[] args) { Console.ReadLine(); }
3 Starten Sie mit (F5). Sie bekommen im Konsolenfenster eine blinkende Einfügemarke zu sehen. Das Programm hält an und wartet auf eine Eingabe.
4 Geben Sie irgendetwas ein und drücken Sie (¢). Die Einfügemarke springt in die nächste Zeile – was Sie nicht mehr mitbekommen werden, da das Konsolenfenster sofort geschlossen und das Programm beendet wird. Was die Anweisung Console.ReadLine(); nach außen bewirkt, haben Sie nun gesehen: • Unterbrechung des Programmlaufs • Blinken der Einfügemarke
103
104
Kapitel 7
• •
Entgegennahme einer Benutzereingabe Setzen der Einfügemarke in die nächste Zeile Hinweis Wie bei der Methode WriteLine() können Sie die letztgenannte Wirkung (Zeilenvorschub) von ReadLine() an dem Line im Methodenbezeichner ablesen.
Wie ist es nun im weiteren Verlauf möglich, auf einen eingegebenen Wert zuzugreifen? Wenn Sie die Eingabeanweisung so belassen, muss die Antwort lauten: überhaupt nicht. Die Eingabedaten sind mit der nächsten Programmzeile für immer verloren. Um eine Benutzereingabe weiterverarbeiten zu können, ist es notwendig, diese fest zu speichern. Und dafür steht Ihnen schließlich der Gebrauch von Variablen zur Verfügung. Wir setzen voraus, eine string-Variable mit dem Bezeichner ein sei bereits definiert. Um den Eingabewert in dieser Variablen abzulegen, schreiben Sie wie folgt: ein = Console.ReadLine();
Mit anderen Worten: Sie weisen den Rückgabewert der Methode ReadLine() der Variablen ein zu. Rückgabewert: Was ist das? Viele Methoden, z.B. ReadLine(), liefern nach Ausführung einen Wert zurück, man spricht dabei vom Rückgabewert. Es gibt aber auch Methoden, die keinen Wert zurückgeben, etwa Write() oder WriteLine(). Der Rückgabewert von ReadLine() entspricht der Benutzereingabe auf der Konsole. Beachten Sie: • Die Verwendung des Namens einer Methode im Programmiercode repräsentiert gleichzeitig den Rückgabewert der Methode (dies gilt natürlich nur für Methoden, welche einen Rückgabewert besitzen). Der Ausdruck Console.ReadLine() steht also gleichzeitig für den Rückgabewert dieser Methode.
Die Methode ReadLine()
•
Auch Rückgabewerte besitzen einen Datentyp. Der Rückgabewert der Methode ReadLine() ist vom Datentyp string. Deshalb können Sie ihn einer string-Variablen zuweisen. Hätten Sie die Variable ein etwa als int-Variable definiert, wäre die Anweisung ein = Console.ReadLine(); fehlerhaft (unerlaubter Versuch, einer int-Variablen einen Wert vom Datentyp string zuzuweisen).
In Kapitel 13 werden Sie sich mit Methoden und deren Rückgabewerten genauer befassen.
Solange Sie den Wert einer Variablen nicht durch Neuzuweisung überschreiben, können Sie im weiteren Verlauf Ihres Programms darauf zugreifen. Dazu verwenden Sie den Variablenbezeichner einfach an passender Stelle, z.B. : Console.Write("Sie haben \"" + ein + "\" eingegeben");
Ausgabe: Sie haben "Benutzereingabe" eingegeben Benutzereingabe steht hier für eine zuvor vom Benutzer während eines Programmlaufs getätigte Eingabe.
Lassen Sie uns zur Übung ein Programm schreiben, welches den Benutzer zur Eingabe seines Vornamens auffordert und diesen dann in einer Begrüßungsformel verwendet.
5 Definieren Sie eine Variable vorname vom Datentyp string und verwenden Sie diese dazu, die Benutzereingabe festzuhalten. static void Main(string[] args) { string vorname; vorname = Console.ReadLine(); }
6 Richten Sie eine Ausgabeanweisung ein, welche den Benutzer zur Eingabe seines Vornamens auffordert.
Natürlich muss die Aufforderung vor der Eingabe stehen: static void Main(string[] args) { string vorname; Console.Write("Sagen Sie mir Ihren Vornamen: "); vorname = Console.ReadLine(); }
105
106
Kapitel 7
Tipp An dieser Stelle sei nochmals daran erinnert: Lassen Sie sich das Ergebnis Ihrer Programmierarbeit auch während der Entstehungsphase Ihrer Programme in regelmäßigen Abständen anzeigen (Taste (F5)), um so von eventuellen Fehlern frühzeitig Kenntnis zu erlangen. Diese Empfehlung gilt umso mehr, je weniger Programmiererfahrung Sie besitzen.
Die Vorteile: • Die Fehlersuche gestaltet sich wesentlich einfacher. Dies gilt nicht nur für Syntaxfehler. Gerade logische Fehler bzw. deren Ursachen sind in einem frühen Entwicklungsstadium viel leichter auszumachen. • Es gibt Ihnen während der Programmentwicklung immer wieder die Gewissheit, »bis hier ist alles o.k.«. Und noch ein Hinweis: Solange Sie keine Fehlermeldungen, sondern nur Warnmeldungen erhalten, wird Ihr Code erfolgreich kompiliert und ausgeführt. Um potenzielle Fehlerquellen auszuschließen, sollten Sie Warnmeldungen dennoch immer auf den Grund gehen.
Wenn Sie den Code in Ihrer IDE ausführen lassen, sollten Sie die Bildschirmausgabe Sagen Sie mir Ihren Vornamen: erhalten. Nach der Eingabe, die Sie mit (¢) bestätigen, bricht das Programm ab. Was noch fehlt, ist die Begrüßungsformel.
7 Begrüßen Sie den Benutzer mit seinem zuvor eingegebenen Vornamen. An-
schließend verwenden Sie ReadLine() wiederum, um das Programm anzuhalten: static void Main(string[] args) { string vorname; Console.Write("Sagen Sie mir Ihren Vornamen: "); vorname = Console.ReadLine(); Console.WriteLine("Hallo " + vorname); Console.ReadLine(); }
Auf der CD-ROM Das Beispiel finden Sie unter Beispiele/Konsolenprogramme auf der Buch-CD (Projektname K07c).
Eine kleine Erfolgskontrolle
Die letzte Ausgabe setzt sich aus dem String "Hallo " und dem Inhalt der Variablen vorname zusammen. Letztere enthält zum Zeitpunkt des Zugriffs den zuvor eingegebenen Vornamen des Benutzers.
8 Bringen Sie Ihr Programm zur Ausführung. Nach Programmende sollte – in Abhängigkeit Ihrer Eingabe – etwa das in der Abbildung dargestellte Ergebnis erscheinen.
Abbildung 7.4: Der eingegebene Name gelangt in Form einer Begrüßungsformel zur Ausgabe
Eine kleine Erfolgskontrolle •
zahl sei eine Variable vom Datentyp int. Was bewirkt die folgende Zuwei-
sung? zahl = zahl + 3;
• • •
Was bedeutet Initialisieren von Variablen? Definieren Sie eine int-Variable mit dem Bezeichner z und initialisieren Sie diese mit dem Wert 0. Worin liegt der Fehler in folgendem Codestück? string name; Console.Write(name);
•
Erklären Sie die Funktionsweise folgender Anweisung: Console.ReadLine();
•
Ist 1A ein gültiger Bezeichner?
107
Das können Sie schon: Entwicklungsschritte zum lauffähigen C#-Programm
11
Umgang mit der Visual C# Express-IDE
36
Aufbau und grundlegende Syntax von C#-Programmen
48
Kommentare
67
Arbeiten mit Strings
75
Steuerzeichen
81
Variablen und Datentypen
87
Verarbeiten von Benutzereingaben
103
Das lernen Sie neu: Literale und benannte Konstanten
110
Konvertieren eines Werts von einem Datentyp in einen anderen 124
Kapitel 8
Konstanten In diesem Kapitel lernen Sie mehr über den Umgang mit konstanten Werten. Insbesondere erfahren Sie, wie Werte eines bestimmten Datentyps in einen anderen Datentyp konvertiert werden können und wann dies notwendig ist. Das in diesem Kapitel erworbene Wissen bildet die Grundlage für weitere Übungen in diesem Buch.
110
Kapitel 8
Literale »Literal« ist in der Programmierung die fachsprachliche Bezeichnung für einen konstanten Wert bzw. für dessen Repräsentation im Quellcode. Man spricht auch von unbenannten Konstanten – im Gegensatz zu den benannten Konstanten, welche wir Ihnen im nächsten Abschnitt vorstellen. Literale lassen sich demnach wie folgt charakterisieren: • Sie stehen im Quelltext für unveränderliche Werte, das heißt, sie repräsentieren diese Werte. • Sie beanspruchen keinen benannten Speicherplatz, daher die Bezeichnung unbenannte Konstanten. • Sie besitzen ebenfalls einen bestimmten Datentyp. Bis jetzt hatten Sie es ausschließlich mit string- und Integer-Konstanten zu tun. string-Konstanten sind nichts anderes als Zeichenketten (Strings). Mit diesen sind Sie ja schon hinreichend vertraut. Hinweis Statt »Strings« bzw. »string-Konstanten« könnten wir natürlich ebenso string-Literale sagen (analog Integer-Literale für Integer-Konstanten).
Integer-Konstanten haben wir bereits in Kapitel 6 (Abschnitt Zeichenketten) angesprochen, als wir zeigten, dass die Methode WriteLine() nicht nur Strings, sondern z.B. auch Integer-Werte verarbeiten kann: Console.WriteLine(999);
Hinweis Wir haben dort festgestellt, dass Zahlenkonstanten wie beispielsweise 3, 47, -1 und natürlich auch 999 vom Datentyp int sind. An anderer Stelle wurde außerdem darauf hingewiesen, dass in den Übungen dieses Buchs nicht alle der von C# zur Verfügung gestellten Datentypen Verwendung finden werden. Tatsächlich stellt C# noch eine Reihe weiterer Datentypen zur Verarbeitung von Ganzzahlen bereit. Diese unterscheiden sich vom Datentyp int allein hinsichtlich ihres Wertebereichs. Ansonsten entspricht deren Handhabung der des Datentyps int.
Literale
Oberbegrifflich wird jeder Datentyp, der zur Verarbeitung von ganzen Zahlen bereitsteht, als Integer-Datentyp bezeichnet. Analog spricht man auch von Integer-Werten (-Konstanten, -Literalen). 999 ist also eine Konstante vom Datentyp int – oder kurz: eine Integer-Konstante.
Der Datentyp einer unbenannten Konstanten ist durch ihre Schreibweise im Quellcode eindeutig festgelegt. Hierbei gelten die nachfolgend genannten Regeln.
Literale zur Darstellung von Textwerten Folgende Regel kennen Sie bereits: Jede beliebige Folge von Zeichen, welche zwischen doppelten Anführungszeichen steht, wird vom Compiler als Zeichenkette interpretiert – ist also vom Datentyp string. Beispiele: "Hallo" "String\nmit\t\"Steuerzeichen\"\n" "9022" "a" "33 + 107"
Wie Sie an dem String "9022" sehen können, gilt dies uneingeschränkt auch dann, wenn es sich ausschließlich um Ziffern handelt. Obwohl diese Zeichenkette auch sinnvoll als numerischer Wert interpretiert werden könnte, entscheiden die "" über den Datentyp. Aus dem gleichen Grund handelt es sich mit "a" um eine Zeichenkette, welche eben nur ein einziges Zeichen enthält, und nicht um ein char-Literal. Desgleichen drückt "33 + 107" nicht die Darstellung einer mathematischen Addition aus, sondern eben einen bloßen Textwert. Der Datentyp char dient zur Repräsentation einzelner Zeichen. Wir haben diesen Datentyp in Kapitel 7 bereits vorgestellt, jedoch hierfür noch kein Beispiel gegeben. Das heißt, Sie haben bis jetzt weder eine Variable noch ein Literal dieses Typs verwendet.
1 Legen Sie in Ihrer IDE ein neues Projekt an.
111
112
Kapitel 8
Auf der CD-ROM Das entsprechende Projekt finden Sie unter Beispiele/Konsolenprogramme auf der Buch-CD (Projektname K08a).
2 Definieren Sie eine Variable vom Datentyp char: static void Main(string[] args) { char ch; } char-Literale werden im Quelltext daran erkannt, dass sie zwischen einfachen Apostrophen ('...') stehen.
3 Weisen Sie der Variablen ch einen gültigen Wert zu, also z.B. ch = 'X';
Hinweis Beachten Sie, dass auch Steuerzeichen als einzelne Zeichen interpretiert werden. Es sind daher auch Zuweisungen folgender Art möglich. ch = '\n';
4
Geben Sie im Anschluss daran den Variablenwert mit WriteLine() auf den Bildschirm aus und fügen Sie am Ende den obligatorischen Haltebefehl hinzu: static void Main(string[] args) { char ch; ch = 'X'; Console.WriteLine(ch); Console.ReadLine(); }
5 Testen Sie Ihr Programm. Sie könnten dies abwechselnd mit verschiedenen Zeichen tun, indem Sie in der Zuweisung ch = 'X' nacheinander verschiedene Literale verwenden.
Literale
Achtung Beachten Sie, dass 'X' ein char-Literal, "X" jedoch ein Literal vom Datentyp string ist. Eine Zuweisung wie char ch = "X";
ist daher nicht erlaubt.
Literale zur Darstellung von ganzen Zahlen Ein ganzzahliges Literal besteht aus einer Folge von Ziffern, gegebenenfalls mit einem negativen Vorzeichen (-). Es gilt die Regel: Jede Ganzzahl, welche im Wertebereich des int-Datentyps liegt, wird als von diesem Datentyp interpretiert. Das ist nicht so selbstverständlich, wie es sich zunächst anhört. Um dies deutlich zu machen, wollen wir Ihnen den Datentyp long vorstellen. Der Wertebereich dieses Datentyps umfasst alle ganzen Zahlen von -9223372036854775808 bis 9223372036854775807 (also etwa im Bereich von ±9 Trillionen), ist also um einiges größer als der von int. Eine Variable dieses Typs nimmt im Arbeitsspeicher 64 Bit (8 Byte) ein. Zur Erinnerung: Der Wertebereich des Datentyps int erstreckt sich von -2147483648 bis 2147483647 (also etwa im Bereich von ±2 Milliarden). Genau in diesem Bereich tritt also eine Überschneidung beider Wertebereiche auf (int mit long). Besitzen nun Literale wie 33, -4500, 1000000 und -11 den Datentyp int oder den Datentyp long? Denken Sie daran, dass jedem Literal genauso wie jeder Variablen ein bestimmter Datentyp eindeutig zugeordnet ist. Oben genannte Regel legt nun fest, dass ein Literal dieser Größe eben den Datentyp int aufweist. Lassen Sie sich dies zeigen. Dazu verwenden Sie eine Methode namens GetType(). Diese liefert als Rückgabewert den Datentypnamen des Objekts,
für das sie aufgerufen wird. Was dieser Satz bedeutet, werden wir nun versuchen zu klären.
113
114
Kapitel 8
Hinweis Wenn eine Methode im Quellcode verwendet wird, spricht man auch von einem Methodenaufruf. string myString; myString = Console.ReadLine();
Bezüglich des Ausdrucks Console.ReadLine() können Sie also sagen, »die Methode ReadLine() wird verwendet«, aber auch »die Methode ReadLine() wird aufgerufen«.
Bis jetzt haben Sie ausschließlich den Fall angetroffen, in dem Methoden unter Angabe eines Klassenbezeichners aufgerufen wurden; Console.WriteLine("...");
oder eben myString = Console.ReadLine();
Beide Methoden (WriteLine() sowie ReadLine()) sind in der Klasse Console definiert, gehören also zu dieser Klasse. Bezüglich der Verwendung dieser Methoden könnte man sich auch dahingehend ausdrücken, dass diese Methoden für die Klasse Console aufgerufen werden. Nun ist aber auch der Fall denkbar, dass eine Methode nicht für die Klasse, in der sie definiert ist, aufgerufen wird, sondern für ein Objekt dieser Klasse. Dann wird zum Zugriff auf die Methode nicht der Klassenname, sondern ein Objektname verwendet. Nehmen wir als Beispiel eine fiktive Klasse Auto. Objekte dieser Klasse seien die Autos MyOpel und YourVW. Die Klasse hätte unter anderem eine Methode Beschleunigen() und eine Methode AutosZaehlen(). Letztere Methode liefere als Rückgabewert die Anzahl aller – im Programm – existenten Autos. Dann wäre diese Methode sicher als zur Klasse selbst gehörig definiert, da sie auf alle Autos Bezug nehmen muss. Andererseits kann die Methode Beschleunigen() nur sinnvoll eingesetzt werden, wenn sie auf einzelne Objekte der Klasse angewendet wird. Jeder Autotyp hat ja andere Beschleunigungswerte. Die Methode Beschleunigen() wird deshalb innerhalb der Klasse Auto entsprechend definiert sein. Die korrekten Methodenaufrufe lauten dann: Auto.AutosZaehlen()
aber
Literale
MyOpel.Beschleunigen()
bzw. YourVW.Beschleunigen()
Aufrufe wie MyOpel.AutosZaehlen() oder Auto.Beschleunigen() würden beim Kompilierversuch Fehlermeldungen verursachen. Es gilt, eine Methode entweder immer mit dem Klassenbezeichner oder ausschließlich mit einem Objektnamen aufzurufen. Dies hängt von der einzelnen Methode (genauer von deren Definition innerhalb der Klasse) ab. Wenden wir uns wieder der Methode GetType() zu. Dass diese Methode objektbezogen verwendet wird, wissen Sie bereits. Sie müssen sich also fragen, in welcher Klasse diese Methode definiert ist und wie die einzelnen Objekte dieser Klasse heißen. Nun müssen Sie wissen, dass hinter jedem elementaren Datentyp in C# eigentlich eine Klasse steht. Hinweis Die elementaren Datentypen sind diejenigen, welche bis jetzt angesprochen wurden. Sie gehören sozusagen zur Grundausstattung der Programmiersprache C#. Deswegen werden sie auch Standarddatentypen genannt.
Des Weiteren sind alle Literale und Variablen eines bestimmten Datentyps Objekte der entsprechenden Klasse. Von daher stellt beispielsweise das Literal 2003 ein Objekt der Klasse Int32 dar. Hinsichtlich der Methode GetType() gilt: Jeder Datentyp (bzw. die Klasse, welche dahinter steht) stellt eine Methode dieses Namens – und mit ähnlicher Funktion – zur Verfügung.
115
116
Kapitel 8
Hinweis Das war nun schon eine kleine Einführung in die objektorientierte Programmierung, somit ein Vorgriff auf Themen, welche erst in Kapitel 14 auf dem Plan stehen. Seien Sie deshalb nicht betrübt, wenn Sie nicht gleich alles verstanden haben. Die Ausführungen sind vornehmlich als Verständnishilfe für die Verwendung der Methode GetType() – und weiterer Methoden, welche Sie demnächst kennen lernen werden – gedacht.
Fassen wir zusammen: • Jeder Standarddatentyp ist eigentlich eine Klasse. Die Klassennamen sind Ihnen bekannt: Int32, Char, String, Double usw. Hinter dem Typ long steht übrigens die Klasse Int64. • Jede dieser Klassen besitzt eine eigene Methode GetType(). • Zum Zugriff auf diese Methode wird nicht der Klassenname, sondern der Objektname verwendet. Falsch wäre also Int32.GetType() // FEHLER
•
Jedes Literal und jede Variable ist ein Objekt derjenigen Klasse, welche hinter dem Datentyp des Literals bzw. der Variablen steht. Das Literal 999 ist demnach ein Objekt der Klasse Int32.
Wenn Sie also feststellen wollen, von welchem Datentyp das Literal 999 ist, schreiben Sie 999.GetType();
6 Fügen Sie diese Anweisung Ihrer Main()-Methode hinzu. static void Main(string[] args) { char ch; ch = 'X'; Console.WriteLine(ch); 999.GetType(); Console.ReadLine(); }
Die Methode GetType() gibt den Datentyp des Objekts zurück, für das sie aufgerufen wird. Um nun zu zeigen, welchen Datentyp das Literal 999 besitzt, müssen Sie diesen Rückgabewert auf den Bildschirm bringen. Dazu übergeben Sie ihn der Methode WriteLine().
Literale
Console.WriteLine(999.GetType());
Ein verschachtelter Methodenaufruf wie dieser mag für Sie zunächst etwas ungewöhnlich erscheinen. Erinnern Sie sich daran, dass ein Methodenaufruf im Quelltext gleichzeitig für den Rückgabewert der Methode steht (vergleiche Kapitel 7, Abschnitt »Die Methode ReadLine()«). Sie dürfen also den Ausdruck 999.GetType() grundsätzlich an jeder Stelle im Quellcode verwenden, an der auch ein einfacher Wert dieses Datentyps stehen kann. Hinweis Der Rückgabewert der Methode GetType() ist vom Datentyp Type. Es handelt sich also nicht um einen elementaren Datentyp. Das braucht Sie aber hier nicht zu kümmern, da die Methode WriteLine() nahezu jeden Datentyp verarbeiten kann (dies gilt ebenso für die Methode Write()).
Beachten Sie, dass die Abarbeitung eines verschachtelten Methodenaufrufs stets von innen nach außen erfolgt. Das bedeutet für die Anweisung Console. WriteLine(999.GetType());: • Als Erstes kommt die Methode GetType() zur Ausführung. • Im nächsten Schritt wird der Ausdruck 999.GetType() durch den Rückgabewert ersetzt. Dieser wird der Methode WriteLine() übergeben. • Zuletzt startet WriteLine() und sorgt für die Bildschirmausgabe.
7 Ergänzen Sie nun die letzte Anweisung wie beschrieben: static void Main(string[] args) { char ch; ch = 'X'; Console.WriteLine(ch); Console.WriteLine(999.GetType()); Console.ReadLine(); }
117
118
Kapitel 8
8 Setzen Sie folgende Ausgabeanweisung davor: Console.Write("Das Literal 999 ist vom Datentyp ");
Damit ergibt sich für die Main()-Methode folgendes Listing: static void Main(string[] args) { char ch; ch = 'X'; Console.WriteLine(ch); Console.Write("Das Literal 999 ist vom Datentyp "); Console.WriteLine(999.GetType()); Console.ReadLine(); }
9 Bringen Sie Ihr Programm zur Ausführung. Nun können Sie den Datentyp des Literals 999 ablesen. Da die Klasse Int32 im Namensraum System definiert ist, lautet die Bildschirmausgabe Das Literal 999 ist vom Datentyp System.Int32.
Abbildung 8.1: Ausgabe des Datentyps per Methode »GetType()«
Wie Sie nun wissen, sind alle ganzzahligen Literale im Bereich von -2147483648 bis 2147483647 grundsätzlich vom Datentyp int. Sie können jedoch durch Anhängen des Suffixes L oder l den Datentyp long (Klasse Int64) erzwingen: long myVar; myVar = 999L;
Literale
Hinweis Weshalb diese Zuweisung auch ohne Anhängen eines Datentyp-Suffixes funktioniert, werden wir weiter unten darlegen.
Die Literale 999l und 999L sind identisch. Hier brauchen Sie ausnahmsweise auf Groß- und Kleinschreibung keine Rücksicht zu nehmen.
Literale zur Darstellung von Zahlen mit Nachkommastellen Diese werden dargestellt wie ganzzahlige Literale zusätzlich eines Punkts zur Trennung von Vor- und Nachkommastellen. So gebildete Literale im Wertebereich von ±5.0 * 10-324 bis ±1.7 * 10308 sind grundsätzlich vom Datentyp double. Beispiele: 99.9 117897.5647 0.33 -20.0 1.0 -9033.11
Beachten Sie, dass der Punkt für den Datentyp ausschlaggebend ist. So repräsentiert das Literal 3.0 zwar einen ganzzahligen Wert, sein Datentyp ist jedoch double. Folgende Initialisierung würde daher eine Fehlermeldung erzeugen. int test = 3.0; // FEHLER
Der Grund: Es ist ein unerlaubter Versuch, einer Variablen vom Typ int einen Wert vom Typ double zuzuweisen. Das Literal 3 hingegen ist vom Datentyp int. Richtig ist also: int test = 3; // OK
119
120
Kapitel 8
Hinweis Werte vom Datentyp double werden auch als Gleitkommawerte bzw. Gleitkommazahlen bezeichnet. Die Bezeichnung bezieht sich auf den Einfluss des Trennzeichens bei einer eventuellen Rundung. So besitzt der Datentyp double eine Genauigkeit von 15-16 Stellen. Das heißt, Rundungsfehler treten erst dann auf, wenn das Literal mehr als 15 Ziffern hat. Dies ist unabhängig davon, an welcher Position sich das Trennzeichen befindet. Daher
spielt
es
beispielsweise
bei
einem
Literal
wie
1234567891234567.99 keine Rolle, wie viele Nachkommastellen sich
hinter dem Trennzeichen befinden und wie groß deren Wert ist. Vor dem Punkt befinden sich ja bereits mehr als 15 Stellen. Anders verhält es sich dagegen bei dem Literal 1.23456789123456799. Hier spielen Größe und Anzahl der Nachkommastellen sehr wohl eine Rolle. Je weiter der Punkt also nach links »gleitet«, desto größer wird seine Bedeutung für die Genauigkeit.
Denken Sie daran, dass Sie im Quellcode als Trennzeichen immer den Punkt verwenden müssen. Die Anzeige hingegen richtet sich nach landesspezifischen Einstellungen.
10 Definieren Sie eine double-Variable und weisen Sie dieser einen Wert zu. static void Main(string[] args) { char ch = 'X'; double myDouble = 2004.99; Console.WriteLine(ch); Console.Write("Das Literal 999 ist vom Datentyp "); Console.WriteLine(999.GetType()); Console.ReadLine(); }
Literale
Tipp Möglicherweise wundern Sie sich darüber, weshalb wir die Definition von myDouble an den Anfang der Methode Main() gesetzt haben. Dies gebietet keine Vorschrift. Solange Sie Ihre Variablen definieren, bevor Sie diese verwenden, ist das syntaktisch in Ordnung. Im Sinne eines guten Programmierstils ist es jedoch empfehlenswert, alle Variablen zu Beginn eines Blocks zu definieren. Sie schaffen sich damit gewissermaßen einen separaten Definitionsteil. Auf diese Weise können Sie leichter erfassen, welche Variablen – gegebenenfalls mit welchen Initialisierungswerten – im Code vorkommen, welche Bezeichner bereits verwendet wurden usw. Sie verlieren dann auch bei einer Vielzahl von Variablen nicht so schnell den Überblick.
11 Geben Sie den Wert von myDouble mithilfe der Methode WriteLine() aus: static void Main(string[] args) { char ch = 'X'; double myDouble = 2004.99; Console.WriteLine(ch); Console.WriteLine(myDouble); Console.Write("Das Literal 999 ist vom Datentyp "); Console.WriteLine(999.GetType()); Console.ReadLine(); }
12 Bringen Sie Ihr Programm zur Ausführung. Die Ausgabe erfolgt mit dem Komma als Trennzeichen: 2004,99
Abbildung 8.2: Zur Ausgabe ist ein Wert vom Typ »double« hinzugekommen
121
122
Kapitel 8
Benannte Konstanten Paradoxerweise handelt es sich bei den benannten Konstanten, mit denen wir uns hier befassen, vom Funktionsprinzip eigentlich um Variablen. Ebenso wie diese beanspruchen sie Platz im Arbeitsspeicher, müssen also vor ihrer Verwendung definiert werden. Allerdings dürfen sie ihren Wert nach der Definition nicht mehr ändern, was zwingend eine Initialisierung voraussetzt. Hinweis Benannte Konstanten werden aus den genannten Gründen mitunter auch als »konstante Variablen« bezeichnet, auch wenn dies durch die widersprüchliche Aussage sprachlich recht ungeschickt formuliert ist. »Variablen«, weil es sich um einen mit Namen ansprechbaren Speicherort handelt und »konstant«, weil der Inhalt dieses Speicherorts unveränderlich ist.
Bei der Definition einer Konstanten gehen Sie wie folgt vor: • Leiten Sie die Definition der Konstanten mit dem Schlüsselwort const ein. • Initialisieren Sie die Konstante (siehe Kapitel 7, Abschnitt »Initialisieren von Variablen«). const int MWST = 16;
Ansonsten müssen Sie wie bei herkömmlichen Variablen den Datentyp angeben und einen Bezeichner wählen. Tipp Microsoft empfiehlt für die Notation von Konstanten PascalCasing (siehe Kapitel 7, Abschnitt »PascalCasing«). Sie könnten aber auch einer guten alten Gewohnheit seit den Zeiten der C-Programmierung folgen und konstante Variablen ausschließlich mit Großbuchstaben notieren. Da dann Großbuchstaben nicht mehr als Worttrenner fungieren können, verwendet man zur Trennung dann üblicherweise den Unterstrich (_), z.B. MWST_REDUZIERT statt MwStReduziert.
Beachten Sie, dass Konstanten allein an dem Schlüsselwort const erkannt werden: double myDouble = 3.14; const double PI = 3.14; // Konstante
Benannte Konstanten
Die Variable myDouble besitzt zwar nach ihrer Vereinbarung ebenfalls den Initialisierungswert 3.14. Dieser kann jedoch im weiteren Verlauf durch Neuzuweisungen immer wieder geändert werden. Hinweis Wenn eine Variable neu angelegt wird, spricht man außer von »Definition« auch von ihrer »Vereinbarung«. Insofern werden beide Begriffe synonym verwendet.
Wenn der Programmierer dagegen im weiteren Verlauf versucht, PI einen neuen Wert zuzuweisen, erntet er beim Kompilieren eine Fehlermeldung. static void Main() { double myDouble = 3.14; const double PI = 3.14; myDouble = 0.123; // OK PI = 7.77; // FEHLER }
Konstanten verwenden Sie in der Regel unter folgenden Gesichtspunkten: • Um sicherzugehen, dass Werte auch wirklich unverändert bleiben, wenn dies erforderlich ist. • Bezeichner können so gewählt werden, dass sie selbstkommentierend sind. Dies erhöht die Lesbarkeit des Quellcodes. So besitzen benannte Konstanten wie MWST oder PI weit mehr Aussagekraft als die Literale 16 bzw. 19 (in Anbetracht der Mehrwertsteuererhöhung vom 1. Januar 2007) oder 3.14. • Eventuelle Änderungen müssen nur an einer einzigen Stelle im Quellcode erfolgen. Angenommen, Sie stehen vor der Aufgabe, eine Applikation für Steuererklärungen zu überarbeiten. Anlass sei die Erhöhung der Mehrwertsteuer von 16 auf 19 Prozent. Bei Verwendung einer Konstanten müssten Sie lediglich den neuen Wert in der Definitionsanweisung austauschen (const int MWST = 19;). Im weiteren Code wird schließlich nur mehr der Bezeichner MWST verwendet. Andernfalls müssten Sie das Literal 16 an jeder Stelle im Code ändern, was bei großen Programmen durchaus Tausende Codestellen betreffen kann, was eine enorme Fehlerquelle darstellt.
123
124
Kapitel 8
Implizite Typumwandlungen Wie Ihnen bekannt ist, gilt folgende Regel: Eine Variable kann entsprechend ihrer Vereinbarung nur Werte eines bestimmten Datentyps aufnehmen. Daher dürfen z.B. in einer Variablen vom Datentyp int allein Werte dieses Typs abgelegt werden. Genauso haben in einer string-Variablen ausschließlich Strings (Werte vom Datentyp string) Platz. Bei einer Zuweisung müssen Sie also grundsätzlich darauf achten, dass rechts des Zuweisungsoperators ein Literal bzw. ein Ausdruck vom Datentyp der Variablen steht: double myDouble; myDouble = 3.77; myDouble = 1.3 + 0.7 + 11.3;
Beide Zuweisungen sind in Ordnung. In der ersten steht rechts vom Zuweisungsoperator ein Literal vom Datentyp double. In der letzten Zuweisung befindet sich als rechter Operand ein Ausdruck, welcher nach seiner Auswertung einen Wert dieses Datentyps ergibt (13.3).
1 Legen Sie in Ihrer IDE ein neues Projekt an und ergänzen Sie die Main()-Methode wie folgt.
static void Main(string[] args) { double myDouble; myDouble = 99; Console.WriteLine(myDouble); Console.ReadLine(); }
Auf der CD- ROM Das entsprechende Projekt finden Sie unter Beispiele/Konsolenprogramme auf der Buch-CD (Projektname K08b).
Sehen Sie sich das obige Listing genau an. Ist Ihnen aufgefallen, dass in der Zuweisung myDouble = 99; auf der rechten Seite ein Literal vom Datentyp int steht, wohingegen die Variable myDouble doch vom Typ double ist. Und dennoch erhalten Sie beim Kompilieren keine Fehlermeldung.
Implizite Typumwandlungen
2 Starten Sie mit (F5). Sie erhalten die Ausgabe 99. Wie Sie sehen, arbeitet das Programm auch hinsichtlich der Anzeige des Variablenwerts völlig korrekt. Nun könnte jemand auf die Idee kommen, dass sich der Typ der Variablen geändert hat. Sie natürlich nicht, weil Sie ja wissen, dass eine Variable ihren Datentyp nach der Definition nicht mehr ändern kann.
3
Weisen Sie nach, dass die Variable myDouble nach wie vor den Datentyp double besitzt. Verwenden Sie dazu die Methode GetType(): static void Main(string[] args) { double myDouble; myDouble = 99; Console.WriteLine(myDouble); Console.WriteLine(myDouble.GetType()); Console.ReadLine(); }
Wie erwartet, zeigt die Bildschirmausgabe für die Variable den Datentyp double (Klasse Double im Namensraum System).
Wenn sich nun der Variablentyp nicht geändert hat, dann muss sich logischerweise der Datentyp des zugewiesenen Werts gewandelt haben, bevor dieser in der Variablen gespeichert wurde. Man spricht in solchen Fällen von einer impliziten Typumwandlung. Das heißt, ein Wert ändert seinen Datentyp, er wird in einen anderen Datentyp konvertiert (umgewandelt). Eine Typkonvertierung wird »implizit« genannt, wenn sie automatisch erfolgt. Das Gegenstück dazu ist die explizite Typumwandlung, die der Programmierer mit Ausdrucksmitteln der Programmiersprache forcieren muss.
125
126
Kapitel 8
Hinweis C# gilt als typsichere Programmiersprache. Das bedeutet, C# ist in der Durchführung automatischer Typumwandlungen sehr restriktiv. Der Vorteil typsicherer Sprachen liegt darin, dass man sich genau Gedanken hinsichtlich der Typen machen und in dieser Hinsicht sehr gewissenhaft arbeiten muss, wodurch Fehler häufig bereits im Vorfeld vermieden werden können. Allerdings kann der Programmieraufwand steigen, da die Sprache weniger Konvertierungen selbsttätig durchführt. Im Zuge eines gut durchdachten Programmcodes spricht aber nichts gegen typsichere Sprachen.
Hier einige Regeln zur impliziten Typkonvertierung in C#: Nicht durchgeführt werden automatische Typumwandlungen von Strings, auch wenn diese als numerische Werte interpretiert werden könnten: int myInteger = "399"; // FEHLER
Dies gilt ebenso kategorisch für die Gegenrichtung. Eine implizite Konvertierung nach string wird grundsätzlich nicht unterstützt: string myString = 399; // FEHLER
Insbesondere verweigert C# die implizite Konvertierung von numerischen Werten nach bool und umgekehrt. bool myBoolean = 1; // FEHLER int myInteger = true; // FEHLER
Hinweis In der Programmiersprache C++ wären beide Initialisierungen in Ordnung. Die implizite Konvertierung erfolgt dort nach folgendem Muster: • jeder numerische Wert ungleich 0 in den booleschen Wert true, 0 in den Wert false; • die boolesche Konstante true in den numerischen Wert 1, false in den Wert 0.
Die implizite Typkonvertierung funktioniert grundsätzlich nicht, wenn der Wertebereich des Quelldatentyps größer ist als der des Zieldatentyps. Dies trifft auch dann zu, wenn der Wert von der Größe her in den Zieldatentyp passt:
Implizite Typumwandlungen
int myInt; myInt = 5.0; // FEHLER myInt = 99L; // FEHLER
Sowohl der Datentyp long als auch der Datentyp double haben einen größeren Wertebereich als int. Deshalb verweigert C# die automatische Umwandlung, obwohl beide Werte (5 und 99) problemlos in der Zielvariablen Platz fänden. Umgekehrt dagegen – falls der Wertebereich des Zieldatentyps größer ist – findet die Konvertierung statt: long myLong = 19; double myDouble = 599;
Unter der genannten Voraussetzung steht a priori fest, dass der zugewiesene Wert immer in die Zielvariable passt. Die Typsicherheit von C# bleibt somit gewährleistet, sodass hier keine Veranlassung besteht, die Kompilierung zu verweigern und den Programmierer auf diese Weise auf etwaige Fehler hinzuweisen. Beachten Sie, dass die Literale 19 und 599 den Datentyp int besitzen (siehe Abschnitt »Literale zur Darstellung von ganzen Zahlen«). Diese müssen also vor der eigentlichen Zuweisung erst nach int bzw. double konvertiert werden. Natürlich handelt es sich dabei um einen nicht wahrnehmbaren, internen Zwischenschritt. In numerischen Ausdrücken konvertiert C# nach double, sobald nur ein Operand von diesem Datentyp ist: 12 + 8 + 7.0 + 3
Obiger Ausdruck ist daher wegen des Literals 7.0 vom Typ double. Sie können dies mit der folgenden Anweisung leicht nachprüfen: Console.WriteLine((12 + 8 + 7.0 + 3).GetType());
Der impliziten Umwandlung von char nach int liegt der Unicode zu Grunde. Das Zeichen wird in seine entsprechende Ordnungsnummer konvertiert. Folgendes Codefragment bewirkt daher die Ausgabe 65, da dem Zeichen 'A' im Unicode-Zeichensatz die Ordnungsnummer 65 zugeordnet ist: int myInt = 'A'; // OK Console.Write(myInt); // Ausgabe: 65
Allerdings funktioniert die Konvertierung nur in eine Richtung. Von int nach char dagegen schlägt sie fehl: char myChar = 65; // FEHLER
127
128
Kapitel 8
Hinweis Grundsätzlich sollten Sie sich nicht auf implizite Typumwandlungen verlassen, sondern möglichst versuchen, diese zu vermeiden.
Implizite Konvertierung im Zusammenhang mit String-Verknüpfungen Wenn in einem Ausdruck X + Y allein ein Operand (X oder Y) vom Datentyp string ist, wird der andere automatisch nach string konvertiert. Dies ist insofern eine Besonderheit, da C# sonst keine impliziten Typumwandlungen nach string unterstützt (siehe oben). int zahl = 555; string myString = zahl; // FEHLER
aber string myString = zahl + " ist eine Zahl"; // OK
Bei Ausführung der letzten Anweisung wird zunächst der Wert 555 (Kopie des in der Variablen zahl enthaltenen Wertes) in den String "555" konvertiert. Danach erfolgt die Verknüpfung, bevor der aus dieser Operation resultierende String der Variablen myString zugewiesen wird. Bezüglich des Plus-Zeichens (+) bedeutet dies: • Sind beide Operanden numerischen Datentyps, wird eine arithmetische Addition durchgeführt. • Handelt es sich bei einem oder bei beiden Operanden um Strings, erfolgt eine String-Verknüpfung. Beachten Sie, dass sich diese Regel jeweils auf zwei benachbarte Operanden bezieht: string myString = "012" + 3 + 4; Console.Write(myString); // Ausgabe 01234
Der Ausdruck auf der rechten Seite des Zuweisungsoperators ("012" + 3 + 4) bringt zwei Verknüpfungsoperationen mit sich. Bei Ausführung erfolgt zuerst die Verknüpfung "012" + 3 (nach Umwandlung von 3 in "3") und im Anschluss daran die Verknüpfung "0123" + 4 (ebenfalls nach entsprechender Konvertierung). Daher erzeugt die letzte Anweisung die Bildschirmausgabe 01234, wie Sie das vielleicht erwartet haben.
Explizite Typumwandlungen
Ändern wir die erste Anweisung etwas ab: string myString = 3 + 4 + "012"; Console.Write(myString); // Ausgabe 7012
Damit ergibt sich nicht etwa die Ausgabe 34012, sondern 7012. Der Grund hierfür ist, dass nach dem genannten Muster zunächst die Operation 3 + 4 zur Ausführung gelangt und hier handelt es sich um eine arithmetische Addition. Beide Operanden sind schließlich vom Datentyp int. Das Resultat dieser Operation ist der Wert 7. Dieser bildet nun den linken Teil der folgenden Operation (7 + "012"). Alles Weitere ist Ihnen bekannt. Hinweis Bei einem Ausdruck der Art A + B + C + D erfolgt die Abarbeitung von »links nach rechts« (A, B, C und D sind Platzhalter für einfache oder auch komplexe Ausdrücke). Durch Setzen von Klammern können Sie jedoch in diese Prioritätsreihenfolge eingreifen: string myString = 3 + (4 + "012"); Console.Write(myString); // Ausgabe 34012
Explizite Typumwandlungen In den meisten Fällen, in denen eine implizite Typumwandlung nicht möglich ist, lässt sich die gewünschte Änderung des Datentyps erzwingen. Dazu stehen dem Programmierer folgende Werkzeuge zur Verfügung: • verschiedene Methoden zur Typumwandlung, • die Cast-Operation.
Umwandlungsmethoden Zunächst wollen wir Ihnen eine typische Situation zeigen, in der eine Typumwandlung unumgänglich ist.
1 Legen Sie ein neues Projekt an, definieren Sie in der Main()-Methode eine double-Variable und weisen Sie dieser per Benutzereingabe einen Wert zu:
static void Main(string[] args) { double ein;
129
130
Kapitel 8
ein = Console.ReadLine(); } ReadLine() sorgt für die Entgegennahme einer Benutzereingabe. Der Rück-
gabewert entspricht der Eingabe des Benutzers (siehe Kapitel 7, Abschnitt »Die Methode ReadLine()«).
2 Starten Sie mit (F5). Sie erhalten die Fehlermeldung »eine implizite Konvertierung vom Typ string in double ist nicht möglich«.
Abbildung 8.3: Der Visual C#-Compiler verweigert die Konvertierung von string nach double
Das Problem liegt darin, dass ReadLine() jede Eingabe als string-Wert zurückliefert, die Variable ein aber vom Typ double ist. Hinweis Wie alle Werte in C# besitzen natürlich auch Rückgabewerte von Methoden einen bestimmten Datentyp.
Explizite Typumwandlungen
Und da eine implizite Konvertierung von string nach double nicht möglich ist, bleibt als einziger Ausweg die explizite Typumwandlung. Hierfür stellt die Klasse Convert eine Reihe von Methoden zur Verfügung. Die wichtigsten davon sind in der folgenden Tabelle wiedergegeben: Methode
konvertiert Übergabewert in den Datentyp …
ToString()
string
ToChar()
char
ToInt32()
int
ToInt64()
long
ToDouble()
double
ToBoolean()
bool
Tabelle 8.1: Umwandlungsmethoden der Klasse Convert
Der Aufruf der Methoden erfolgt unter Angabe des Klassenbezeichners (Convert), gefolgt von einem Punkt. Als Argument übergeben Sie den zu konvertierenden Wert. Was ist das? Argumente nennt man die Werte, welche einer Methode beim Aufruf übergeben werden. Bis jetzt hatten wir ausschließlich die Bezeichnung Parameter verwendet.
Der Rückgabewert der genannten Umwandlungsmethoden entspricht dann dem konvertierten Wert des Arguments. Da Sie den Rückgabewert der Methode ReadLine() in den Typ double konvertieren müssen, kommt für Sie allein die Methode ToDouble() infrage. Wie formulieren Sie nun den korrekten Methodenaufruf? Zunächst erfolgt der Zugriff auf die Methode unter Angabe des Klassennamens: Convert.ToDouble()
Nun müssen Sie noch das Argument zwischen die runden Klammern schreiben. Dabei handelt es sich natürlich um den Rückgabewert der Methode ReadLine().
131
132
Kapitel 8
Denken Sie daran, dass ein Methodenaufruf im Programmcode den Rückgabewert der aufgerufenen Methode repräsentiert. Was die Methode ReadLine() angeht, steht demnach der Ausdruck Console.ReadLine() für ihren Rückgabewert und um diesen geht es schließlich. Also müssen Sie diesen Methodenaufruf als Argument für die Methode ToDouble() verwenden.
3 Setzen Sie die Konvertierungsmethode ToDouble() wie beschrieben ein: static void Main(string[] args) { double ein; ein = Convert.ToDouble(Console.ReadLine()); }
Wie Sie sehen, haben Sie es wieder mit einem verschachtelten Methodenaufruf zu tun. Halten Sie sich die Einzelschritte bei Ausführung der Anweisung ein = Convert.ToDouble(Console.ReadLine());
vor Augen: • Zunächst kommt die innere Methode ReadLine() zum Zug. Diese bewirkt die Annahme einer Benutzereingabe. • Danach wird der Ausdruck Console.ReadLine() durch den Rückgabewert der Methode (Benutzereingabe als String) ersetzt und als Argument an die Methode ToDouble() weitergereicht. • Diese sorgt nun für die Typumwandlung; ihr Rückgabewert ist das nach double konvertierte Argument (wobei es sich nach wie vor um die Benutzereingabe handelt). • Im letzten Schritt wird die – konvertierte – Benutzereingabe der Variablen ein zugewiesen. Da nunmehr beide Datentypen übereinstimmen, hat diese Zuweisung Erfolg. Sie setzen dabei allerdings voraus, dass ein Benutzer beim Programmlauf auch tatsächlich einen Zahlenwert in einem zulässigen Format eingibt. Andernfalls stellt sich ein Laufzeitfehler ein, da die Konvertierung logischerweise nur dann funktioniert, wenn sich die umzuwandelnde Zeichenkette überhaupt als Zahl interpretieren lässt. Hinweis Von Programmabstürzen bleiben Sie innerhalb Ihrer IDE jedoch verschont, da die Visual C# 2005 Express Edition Laufzeitfehler abfängt.
Explizite Typumwandlungen
Nun sollte sich Ihr Code ohne Fehlermeldung übersetzen lassen.
4 Fügen Sie zu guter Letzt eine passende Ausgabe wie folgt hinzu: static void Main(string[] args) { double ein; ein = Convert.ToDouble(Console.ReadLine()); Console.WriteLine("Sie haben die Zahl " + ein + " eingegeben."); Console.ReadLine(); }
Auf der CD-ROM Das Beispiel finden Sie unter Beispiele/Konsolenprogramme auf der Buch-CD (Projektname K08c).
5 Testen Sie Ihr Programm. Achtung Denken Sie bei der Eingabe daran, die Nachkommastellen mit dem Komma zu trennen. (Nur im Quellcode muss der Punkt verwendet werden.)
Nach Programmende sollte in Abhängigkeit Ihrer Eingabe etwa die in der Abbildung dargestellte Ausgabe zu sehen sein.
Abbildung 8.4: Der Zahlenwert der Benutzereingabe wurde in den Datentyp »double« umgewandelt
Des Weiteren besitzt in C# jeder Datentyp eine eigene Methode ToString(). Diese funktioniert ähnlich wie die Umwandlungsmethoden der Klasse Convert, wird allerdings ohne Argumente und objektbezogen aufgerufen (siehe oben, Abschnitt »Literale zur Darstellung von ganzen Zahlen«). Um etwa den Wert Ihrer double-Variablen ein in einen String zu konvertieren, schreiben Sie
133
134
Kapitel 8
ein.ToString();
Alternativ dazu können Sie natürlich nach wie vor zur Konvertierung die Methode ToString() der Klasse Convert verwenden. Dann müssen Sie den Variablenbezeichner als Argument zwischen die runden Klammern schreiben: Convert.ToString(ein);
Es sei nochmals darauf hingewiesen, dass in beiden Fällen nicht die Variable selbst, sondern eine Kopie des in ihr enthaltenen Wertes umgewandelt wird. An welcher Stelle im Quellcode Sie diese Methoden einsetzen, ist immer vom jeweiligen Kontext abhängig. Analog schreiben Sie z.B. (34.88).ToString() oder Convert.ToString (34.88), wenn Sie ein Literal umwandeln müssen. Hinweis In dem Ausdruck (34.88).ToString() ist das Setzen von Klammern in (34.88) zwar nicht zwingend notwendig. Es erhöht jedoch die Lesbarkeit, da der erste Punkt als Trennzeichen eine völlig andere Bedeutung hat als der zweite, welcher zum Zugriff auf die Methode ToString() dient. In ähnlichen Fällen sind Klammern jedoch mitunter erforderlich, da andernfalls ein Ausdruck vom Compiler nicht wie gewünscht interpretiert werden kann. Es ist deshalb ratsam, sich im Zweifel für das Setzen von Klammern zu entscheiden.
Das Casting Als Alternative zu den besprochenen Umwandlungsmethoden stellt C# die so genannte Cast-Operation zur Verfügung. Dabei schreiben Sie das Schlüsselwort für den gewünschten Zieldatentyp in Klammern vor den zu konvertierenden Wert: (char) 48
Es lassen sich alle numerischen Datentypen casten – einschließlich des Datentyps char. Hierzu einige Beispiele: char myChar = (char) 66; Console.Write(myChar); // Ausgabe: B
oder
Explizite Typumwandlungen
int myInt = (int) 5.0; // OK
Wie Sie sehen, funktioniert das Casting auch in Fällen, in denen keine implizite Konvertierung möglich ist. Sie dürfen es aber auch dort anwenden, wo die Typumwandlung ohnehin automatisch stattfinden würde: int myInt = (int) 'A';
Ausschlaggebendes Kriterium hierfür kann die bessere Nachvollziehbarkeit des Quellcodes sein. Wenn der Programmierer die Konvertierung explizit festlegt, kann man beim Lesen des Quelltextes sofort erkennen, dass diese beabsichtigt ist. Andererseits kann man nie so ganz sicher sein, ob eine implizite Konvertierung vom Programmautor tatsächlich so gewollt war oder auf einem Versehen beruht. Dies ist auch der Grund für die Tendenz von C#, implizite Konvertierungen zu verweigern. Nicht durchführbar ist das Casting mit den Datentypen bool und string. Dabei spielt es keine Rolle, ob diese als Quell- oder als Zieldatentypen auftreten: int myInteger = (int) "399"; // FEHLER string myString = (string) 2003; // FEHLER bool myBoolean = (bool) 99; // FEHLER int myInt = (int) true; // FEHLER
Hier bleibt Ihnen immer noch die Möglichkeit, sich der oben gezeigten Umwandlungsmethoden zu bedienen, z.B. int myInt = Convert.ToInt32(true); // OK Console.Write(myInt); // Ausgabe: 1
oder bool myBoolean = Convert.ToBoolean(99); // OK Console.Write(myBoolean); // Ausgabe: True
Hinweis In der Bildschirmausgabe erscheint der erste Buchstabe der booleschen Konstanten in Großbuchstaben (True bzw. False). Das ändert nichts an der Tatsache, dass Sie true und false im Quellcode stets kleinschreiben müssen.
Im Einzelfall haben Sie bei verschiedenen Typumwandlungen jedoch zu berücksichtigen, dass diese nicht immer ohne Informationsverlust vonstatten gehen. So gehen bei der Konvertierung von double nach Integer mit Methoden der Klasse Convert etwaige Nachkommastellen verloren. Der Wert wird nach dem
135
136
Kapitel 8
kaufmännischen Prinzip auf die nächstgrößere bzw. -kleinere Ganzzahl gerundet: int myInt = Convert.ToInt32(3.8799); Console.Write(myInt); // Ausgabe: 4
Beim Casting dagegen wird der Nachkommateil – ohne Rundung – einfach abgeschnitten: int myInt = (int) 3.8799; Console.Write(myInt); // Ausgabe: 3
Beachten Sie auch, dass eine Konvertierung in keinem Fall durchgeführt werden kann, wenn der zu konvertierende Wert in seiner Größe außerhalb des Wertebereichs des Zieldatentyps liegt: int myInt = (int) 3147483647; // FEHLER
bzw. int myInt = Convert.ToInt32(3147483647); // FEHLER
Es macht jedoch insofern einen Unterschied, mit welchen Mitteln Sie diese Umwandlung versuchen, als sich bei Anwendung der Methode ToInt32() der Fehler erst zur Laufzeit einstellt. Beim Casting scheitert bereits der Kompilierversuch (was einen gewissen Vorteil mit sich bringt, da ein fehlerhaftes Endprodukt so gar nicht erst entstehen kann).
Umwandlungen, welche von »Write()« bzw. »WriteLine()« veranlasst werden Die Methoden Write() bzw. WriteLine() sind so eingerichtet, dass sie gegebenenfalls selbstständig Konvertierungen nach string durchführen, nämlich immer dann, wenn Sie ihnen Werte eines anderen Datentyps übergeben: double zahl = 0.99; Console.Write(zahl);
In der letzten Anweisung wird der aus der double-Variablen zahl kopierte Wert vor der Ausgabe zunächst in die Zeichenkette "0,99" konvertiert. Diese Umwandlung ist notwendig, da Ausgaben sowie Benutzereingaben auf der Befehlszeile stets als Zeichenketten behandelt werden. Ein Datenaustausch mit Werten eines anderen Datentyps ist über die Konsole a priori nicht möglich. Beachten Sie, dass es sich hierbei nicht um eine implizite Konvertierung handelt. Wie Sie wissen, wird eine solche (double nach string) von C# prinzipiell nicht durchgeführt. Tatsächlich handelt es sich hier um eine explizite Konver-
Eine kleine Erfolgskontrolle
tierung, mit dem Unterschied, dass diese nicht vom Programmierer, sondern von der Methode selbst angestoßen wird. (Genaueres zur Implementation von Methoden erfahren Sie in Kapitel 13.) Hinweis Natürlich ist es kein Fehler, wenn Sie der Methode den Wert bereits konvertiert übergeben: double zahl = 0.99; Console.Write(Convert.ToString(zahl));
bzw. Console.Write(zahl.ToString());
Eine kleine Erfolgskontrolle • • • • •
Welchen Datentyp besitzt das Literal 3? Nennen Sie den Datentyp des Literals 3.0. Der Datentyp des Literals "3" ist? Der Datentyp des Literals '3' ist? Warum verursacht folgendes Codestück beim Kompilieren eine Fehlermeldung? const string name = "Mustermann"; Console.Write("Wie heißen Sie? "); name = Console.ReadLine();
•
zahl sei eine Variable vom Typ double. Das bedeutet, sie kann ausschließlich Werte von diesem Datentyp aufnehmen. Warum erzeugt folgende Zuweisung dennoch keine Fehlermeldung? zahl = 100;
•
Korrigieren Sie die/den Fehler in folgendem Codestück: int z; Console.Write ("Geben Sie eine Zahl ein: "); z = Console.ReadLine();
•
Ist folgende Variableninitialisierung korrekt? int wert = 25.99;
137
Das können Sie schon: Entwicklungsschritte zum lauffähigen C#-Programm
11
Umgang mit der Visual C# Express-IDE
36
Aufbau und grundlegende Syntax von C#-Programmen
48
Kommentare
67
Arbeiten mit Strings
75
Steuerzeichen
81
Variablen und Datentypen
87
Verarbeiten von Benutzereingaben
103
Konstanten
109
Typkonvertierung
124
Das lernen Sie neu: Komplexe Ausdrücke formulieren
139
Mathematische Operationen durchführen
140
Formatieren von Bildschirmausgaben
147
Kapitel 9
Rechenoperationen Bisher sind wir mit der Weiterverarbeitung von Benutzereingaben sehr zurückhaltend umgegangen. Wir haben diese zwar schon in Variablen festgehalten, um sie anschließend für Bildschirmausgaben zu verwenden, ansonsten haben wir mit diesen Daten aber kaum etwas gemacht. Das lag daran, dass uns hierzu bis jetzt nur wenig Mittel zur Verfügung standen. In diesem Kapitel lernen Sie nun das Formulieren von komplexen Ausdrücken. Sie werden so in die Lage versetzt, mit den Eingabedaten sinnvolle Operationen durchzuführen.
140
Kapitel 9
Ausdrücke und arithmetische Operatoren Jeder Teil einer Anweisung, der einen Wert repräsentiert, wird im programmiertechnischen Sinn als »Ausdruck« bezeichnet. So gesehen fallen hierunter auch einzelne Literale und Variablenbezeichner. Hinweis Für die Bezeichner von Variablen gilt das allerdings nur, wenn diese im Lesekontext auftreten. Auf der linken Seite einer Zuweisung oder in einer Definitionsanweisung repräsentieren sie keinen Wert, sondern den Speicherort selbst (siehe Kapitel 7, Abschnitt »Unterschiedliche Bedeutung von Variablenbezeichnern im Quellcode«).
Komplexe Ausdrücke müssen während der Programmausführung zunächst ausgewertet werden, bevor mit dem Ergebniswert weitergearbeitet werden kann. Hinweis Diese Aussage gilt im übertragenen Sinne auch für einfache Werte (dargestellt durch Literale oder Variablenbezeichner), da diese zumindest einmal »angefasst« – gelesen – werden müssen.
Wie bei Methoden spricht man auch bei Ausdrücken von Rückgabewerten. Analog die Ausdrucksweise: Ein Ausdruck gibt einen Wert zurück bzw. liefert einen Wert. int x; x = 17 + 3;
So ist es korrekt, wenn Sie bezüglich der letzten Anweisung sagen: »Der Ausdruck rechts des Zuweisungsoperators gibt den Wert 20 zurück«. Ein NichtProgrammierer würde das natürlich ganz anders ausdrücken (»Der Ausdruck ergibt den Wert 20«, »Das Ergebnis …«).
Ausdrücke und arithmetische Operatoren
Hinweis Auch ein Methodenaufruf ist nach den oben dargelegten Gesichtspunkten ein Ausdruck – insofern es sich um eine Methode mit Rückgabewert handelt.
Komplexe Ausdrücke werden von Operatoren und ihren Operanden gebildet. Eine beträchtliche Bedeutung kommt dabei den arithmetischen Operatoren zu. Den Operator zur Darstellung der mathematischen Addition kennen Sie bereits: das Plus-Zeichen (+). Beachten Sie, dass das Plus-Zeichen zur Darstellung zweier unterschiedlicher Operationen herangezogen wird – der mathematischen Addition sowie der Stringverknüpfung. Seine Bedeutung ist also von vornherein nicht eindeutig und muss bei der Übersetzung des Quellcodes vom Compiler aus dem Kontext ermittelt werden. Entscheidend hierbei ist der Datentyp der Operanden. Handelt es sich um numerische Datentypen, repräsentiert das Plus-Zeichen die mathematische Addition. Ist nur ein Operand vom Datentyp string, wird die Stringverknüpfung durchgeführt (siehe Kapitel 8, Abschnitt »Implizite Konvertierung im Zusammenhang mit String-Verknüpfungen«). Hinweis Das Verketten von Strings wird neben »Stringverknüpfung« auch »Konkatenation« genannt. Analog spricht man vom »Konkatenationsoperator«.
Der Operator für die Subtraktion (-) ist Ihnen ebenfalls aus der Mathematik geläufig. Die beiden verbleibenden Symbole zur Darstellung der Grundrechenarten sind folgende: • für die Multiplikation der Stern: * • für die Division der Schrägstrich (Slash): /
141
142
Kapitel 9
Der Modulo-Operator Die Modulo-Operation gibt den Rest einer Ganzzahldivision als Wert zurück. Das bedeutet nicht, dass bei dieser Operation tatsächlich eine Division durchgeführt wird. Es wird lediglich ermittelt, wie oft der rechte Operand in dem linken vollständig enthalten ist, und der verbleibende Restwert zurückgegeben. Der Modulo-Operator wird mit dem Prozentzeichen dargestellt (%). Wie die Operatoren für die Grundrechenarten besitzt auch der Modulo-Operator zwei Operanden. Hinweis Operatoren, welche sowohl auf der linken als auch auf der rechten Seite des Symbols Operanden erfordern, werden als »binär« bezeichnet. Zu den binären Operatoren gehört somit auch der Zuweisungsoperator. Im Gegensatz dazu ist ein Operator »unär«, wenn er sich nur auf einen Operanden bezieht. Beispiele für unäre Operatoren werden Sie noch kennen lernen.
Beispielsweise liefert der Ausdruck 8 % 5
den Rückgabewert 3, da eine Division beider Zahlen (8 geteilt durch 5) den Wert 1 mit dem Rest 3 ergibt. Anders ausgedrückt: 5 ist in 8 exakt 1-mal vollständig enthalten, wobei ein Rest von 3 verbleibt. Weitere Beispiele: Console.Write(6 % 2); // Ausgabe: 0 Console.Write(11 % 5); // Ausgabe: 1 Console.Write(3 % 7); // Ausgabe: 3
Die letzte Anweisung erzeugt die Bildschirmausgabe 3, da 7 in 3 nullmal enthalten ist, wobei nach wie vor ein Rest von 3 übrig bleibt. Die Modulo-Operation wird oft angewandt, um festzustellen, ob eine Zahl durch eine andere ohne Rest teilbar ist. Mit ihrer Hilfe lässt sich auch ermitteln, ob eine – möglicherweise vom Anwender eingegebene – Zahl gerade oder ungerade ist (eine Zahl ist dann gerade, wenn sie durch 2 ohne Rest teilbar ist). Für solche Prüfungen benötigen Sie allerdings zusätzlich die Kenntnis von Kontrollstrukturen. Mit diesen werden Sie sich in den beiden folgenden Kapiteln beschäftigen. Sie werden dort sowie im weiteren Verlauf noch Gelegenheit haben, die Modulo-Operation sinnvoll einzusetzen.
Erstellen Sie ein Euro-DM-Umrechnungsprogramm
Nachfolgend sehen Sie eine Übersicht der arithmetischen Operatoren: Operator
steht für ...
+
Addition
-
Subtraktion
*
Multiplikation
/
Division
%
Modulo (Rest einer Division)
Tabelle 9.1: Arithmetische Operatoren in C#
Erstellen Sie ein Euro-DMUmrechnungsprogramm Möglicherweise denken Sie ab und zu an die alte Währung zurück und beginnen in Gedanken zu rechnen, welchem DM-Betrag wohl ein bestimmter EuroBetrag entsprochen hätte. Sei es nur, um sich vor Augen zu halten, wie teuer das Produkt, das Sie gerade im Begriff sind zu kaufen, geworden ist. Im Folgenden erstellen Sie ein Programm, welches einen einzugebenden EuroBetrag in DM umrechnet.
1 Legen Sie in Ihrer IDE ein neues Projekt an. 2 Definieren Sie eine double-Variable euro. static void Main(string[] args) { double euro; }
In dieser Variablen werden Sie die Benutzereingabe speichern. Um dem Benutzer die Möglichkeit zu geben, den Euro-Betrag mit Nachkommastellen einzugeben, benötigen Sie eine Variable des Datentyps double.
3 Fordern Sie den Benutzer zur Eingabe eines Euro-Betrags auf: static void Main(string[] args) { double euro; Console.Write("Umzurechnender Euro-Betrag: "); }
143
144
Kapitel 9
Verwenden
Sie die Methode Console.Write() – anstelle von Console.WriteLine() –, um die Benutzereingabe in derselben Zeile folgen zu lassen.
4 Implementieren Sie eine Benutzereingabe. Dazu rufen Sie die Methode Console.ReadLine() auf: static void Main(string[] args) { double euro; Console.Write("Umzurechnender Euro-Betrag: "); Console.ReadLine(); }
Der Rückgabewert der Methode ReadLine() soll schließlich in der Variablen euro gespeichert werden, um im Weiteren damit rechnen zu können. Da dieser aber vom Typ string ist, müssen Sie ihn zunächst in den Datentyp der Variablen umwandeln.
5
Konvertieren Sie den Rückgabewert der Methode ReadLine() in einen doubleWert. Verwenden Sie dazu die Methode ToDouble() der Klasse Convert.
Übergeben Sie dazu den Rückgabewert – für den der Ausdruck Console. ReadLine() steht – einfach der Methode ToDouble() als Argument: static void Main(string[] args) { double euro; Console.Write("Umzurechnender Euro-Betrag: "); Convert.ToDouble(Console.ReadLine()); }
6 Weisen Sie den so konvertierten Wert der Variablen euro zu: static void Main(string[] args) { double euro; Console.Write("Umzurechnender Euro-Betrag: "); euro = Convert.ToDouble(Console.ReadLine()); }
7 Fügen Sie am Ende der Main()-Methode einen Haltebefehl hinzu. Dies empfiehlt sich schon jetzt, damit Sie Ihr Programm auch zwischenzeitlich in Ihrer Visual C# Express-IDE testen können.
Erstellen Sie ein Euro-DM-Umrechnungsprogramm
Hinweis In den weiteren Beispielen werden wir den obligatorischen Haltebefehl als selbstverständlich voraussetzen. static void Main(string[] args) { double euro; Console.Write("Umzurechnender Euro-Betrag: "); euro = Convert.ToDouble(Console.ReadLine()); Console.ReadLine(); }
Die Ausgabe Ihres Programms soll am Ende wie folgt aussehen: X Euro entsprechen Y DM
Wobei X und Y natürlich als Platzhalter zu verstehen sind.
8 Setzen Sie den bekannten Teil der Ausgabe schon mal um: static void Main(string[] args) { double euro; Console.Write("Umzurechnender Euro-Betrag: "); euro = Convert.ToDouble(Console.ReadLine()); Console.Write(euro + " Euro entsprechen "); Console.ReadLine(); }
Nun müssen Sie sich um die Ausgabe des korrespondierenden DM-Betrags kümmern. Der Umrechnungsfaktor beträgt 1,95583 (1 Euro entsprechen 1,95583 DM). Der DM-Betrag ergibt sich folglich aus dem Ausdruck euro * 1.95583. Sie könnten nun das Ergebnis dieses Ausdrucks in einer Variablen speichern und anschließend diese Variable zur Ausgabe heranziehen: … double dm = euro * 1.95583; … Console.Write (euro + " Euro entsprechen "); Console.WriteLine (dm + " DM."); …
Ebenso gut können Sie auf die Definition einer zusätzlichen Variablen verzichten und den Ausdruck, so wie er ist, für die Ausgabe verwenden.
145
146
Kapitel 9
9 Vervollständigen Sie die Ausgabe: static void Main(string[] args) { double euro; Console.Write("Umzurechnender Euro-Betrag: "); euro = Convert.ToDouble(Console.ReadLine()); Console.Write(euro + " Euro entsprechen "); Console.WriteLine(euro * 1.95583 + " DM."); Console.ReadLine(); }
Damit ist Ihr Umrechnungsprogramm fertig. Auf der CD-ROM Das Beispiel finden Sie unter Beispiele/Konsolenprogramme auf der Buch-CD (Projektname K09a).
10 Testen Sie Ihr Programm. Bei einer Eingabe von 27,99 erhalten Sie die in der Abbildung dargestellte Ausgabe (wie Sie die Anzeige des Umrechnungswerts auf zwei Nachkommastellen begrenzen können, erfahren Sie im nächsten Abschnitt).
Abbildung 9.1: Das Umrechnungsprogramm in Aktion
Eine Eingabe von 1 bewirkt die Ausgabe 1 Euro entsprechen 1,95583 DM.
Dies ist offensichtlich richtig. Insoweit arbeitet das Programm korrekt. Bevor wir uns dem nächsten Thema zuwenden, möchten wir Sie auf eine alternative Implementation des Umrechnungsprogramms aufmerksam machen: • Sie weisen die Benutzereingabe ohne Konvertierung einer Variablen vom Datentyp string zu. • Sie konvertieren den in der string-Variablen gespeicherten Wert, bevor Sie ihn für die Ergebnisberechnung verwenden. Hier das entsprechende Listing:
Formatieren von Ausgaben
static void Main(string[] args) { string euro; Console.Write("Umzurechnender Euro-Betrag: "); euro = Console.ReadLine(); Console.Write(euro + " Euro entsprechen "); Console.WriteLine(Convert.ToDouble(euro) * 1.95583 + " DM."); Console.ReadLine(); }
Auf der CD-ROM Das Beispiel finden Sie unter Beispiele/Konsolenprogramme auf der Buch-CD (Projektname K09b).
Hinweis In der Programmierung gibt es in den seltensten Fällen nur eine einzige Lösung für ein Problem. In der Regel sind für die Umsetzung einer Aufgabenstellung eine Vielzahl von möglichen Implementationen denkbar.
Formatieren von Ausgaben Im letzten Beispiel haben Sie bei der Implementation der Bildschirmausgabe von der Möglichkeit Gebrauch gemacht, Strings zusammenzusetzen. Das heißt, Sie haben den Methoden Write() bzw. WriteLine() jeweils einen zusammengesetzten Ausgabe-String übergeben (wobei Sie sich zu Recht auf die implizite Konvertierung nach string verlassen haben, siehe Kapitel 8, Abschnitt »Implizite Konvertierung im Zusammenhang mit String-Verknüpfungen«). Console.Write(euro + " Euro entsprechen "); Console.WriteLine(euro * 1.95583 + " DM.");
Dieses Verfahren stellt eine bequeme Möglichkeit dar, Variablenwerte und/ oder Berechnungsergebnisse zusammen mit Text in einem Durchgang auszugeben. Alternativ gestatten es die Methoden Write() und WriteLine() innerhalb von Ausgabe-Strings, für Werte Platzhalter zu verwenden. Die eigentlichen Werte werden durch Kommata getrennt nach dem Ausgabe-String angegeben.
147
148
Kapitel 9
Ein Wert wird dabei wie üblich durch einen beliebigen Ausdruck repräsentiert – worunter natürlich auch reine Variablenbezeichner fallen (siehe Abschnitt »Ausdrücke und arithmetische Operatoren«). Als Platzhalter fungieren Zahlen, welche von zwei geschweiften Klammern umschlossen werden, z.B. {0}. Die Zahlen dienen dabei zur Indizierung der hinter dem Ausgabe-String angegebenen Werte, wobei dem ersten Wert der Index 0, dem zweiten Wert der Index 1 usw. zugeordnet ist. Wenn nur ein Wert in den Ausgabe-String eingebaut werden soll, müssen Sie für diesen also den Platzhalter {0} verwenden: double preis = 399.99; Console.Write("Die Digitalkamera kostet {0} Euro", preis);
Den Platzhalter für einen Wert setzen Sie genau an diejenige Stelle in den String, an welcher der Wert in der Ausgabe erscheinen soll. Obige Write()Anweisung erzeugt demnach die Bildschirmausgabe Die Digitalkamera kostet 399,99 Euro
Hier ein Beispiel für die Verwendung mehrerer Platzhalter innerhalb eines Ausgabe-Strings: int l = 7, b = 10, h = 9; Console.Write("Länge: {0} m, Breite: {1} m, Höhe: {2} m" , l, b, h);
Bildschirmausgabe: Länge: 7 m, Breite: 10 m, Höhe: 9 m
Hinweis Es ist möglich, mehrere Variablen gleichen Datentyps in einer einzigen Anweisung zu definieren. Die Variablen werden dabei durch Kommata voneinander getrennt. Demnach werden mit der Anweisung int l = 7, b = 10, h = 9; drei int-Variablen mit den Bezeichnern l, b und h sowie den Initialisierungswerten 7, 10 und 9 vereinbart.
Zurück zum Euro-DM-Umrechnungsprogramm des letzten Abschnitts.
Formatieren von Ausgaben
11 Ersetzen Sie in den Ausgabestrings die Werte für Euro- und DM-Betrag mit Platzhaltern:
static void Main(string[] args) { double euro; Console.Write("Umzurechnender Euro-Betrag: "); euro = Convert.ToDouble(Console.ReadLine()); Console.Write("{0} Euro entsprechen ", euro); Console.WriteLine("{0} DM.", euro * 1.95583); Console.ReadLine(); }
Auf der CD-ROM Die endgültige Version des Euro-DM-Umrechnungsprogramms finden Sie unter dem Projektnamen K09c im Ordner Beispiele/Konsolenprogramme auf der Buch-CD.
Platzhalter bieten den Vorteil erweiterter Formatierungsmöglichkeiten. Dies werden Sie sich nun zunutze machen, um die Ausgabe des Umrechnungsergebnisses auf zwei Nachkommastellen zu begrenzen. Die Formatierung eines in den Ausgabe-String integrierten Werts erfolgt mittels Angabe von Formatierungszeichen. Diese verwenden Sie innerhalb der Platzhalter – durch einen Doppelpunkt getrennt – nach den Indizes: Console.WriteLine("{0:F} DM.", euro * 1.95583);
Das Zeichen F bewirkt bei der Anzeige von Gleitkommawerten eine Begrenzung des Nachkommaanteils auf zwei Stellen. Sind für die Anzeige weniger als zwei Stellen notwendig, wird der Rest mit Nullen aufgefüllt. Hinweis Sie können auch f (kleingeschrieben) verwenden. Wie beim DatentypSuffix L besteht hier ausnahmsweise kein Unterschied zwischen Großund Kleinschreibung.
149
150
Kapitel 9
Es ist auch möglich, die Anzahl der auszugebenden Nachkommastellen explizit festzulegen. Dies geschieht durch Angabe einer entsprechenden Zahl unmittelbar nach dem F: Console.WriteLine("{0:F3} DM. ", euro * 1.95583);
Die Formatierung in der obigen Anweisung bewirkt somit die Anzeige von drei Nachkommastellen für das Umrechnungsergebnis. Die Formatierung {0:F0} lässt dagegen den Nachkommaanteil (einschließlich des Trennzeichens) in der Anzeige komplett verschwinden. Hinweis Da das F alleine (ohne nachfolgende Zahlenangabe) die Anzahl der Nachkommastellen auf zwei begrenzt, sind die Formatierungen {0:F} und {0:F2} völlig äquivalent.
12 Richten Sie es ein, dass das Umrechnungsergebnis in der Anzeige immer mit zwei Nachkommastellen erscheint:
static void Main(string[] args) { double euro; Console.Write("Umzurechnender Euro-Betrag: "); euro = Convert.ToDouble(Console.ReadLine()); Console.Write("{0} Euro entsprechen ", euro); Console.WriteLine("{0:F} DM.", euro * 1.95583); Console.ReadLine(); }
13 Überzeugen Sie sich vom Ergebnis. Die Anzeige des DM-Betrags ist nun auf zwei Nachkommastellen begrenzt. Die letzte Stelle wird dabei nach dem kaufmännischen Prinzip gerundet.
Abbildung 9.2: Das Ergebnis wird wie bei Währungsbeträgen üblich mit zwei Nachkommastellen angezeigt
Im Folgenden lernen Sie einige weitere Formatierungsmöglichkeiten kennen.
Formatieren von Ausgaben
N bzw. n bewirkt eine zusätzliche Ausgabe von Tausender-Trennzeichen. Ansonsten entspricht die Formatierung der von F: Console.Write("{0:N}", 11998.5663);
Ausgabe: 11.998,57 Console.Write("{0:N3}", 11998.5663);
Ausgabe: 11.998,566 Hinweis Es ist ebenfalls erlaubt, die Formatierungszeichen F, f sowie N, n auf Integer-Werte anzuwenden. In diesem Fall erscheint in der Anzeige ein Nachkommaanteil bestehend aus lauter Nullen – so viele, wie in der Formatierung festgelegt wurden: int z = 2007; Console.Write("{0:F4}", z);
Ausgabe: 2007,0000 Console.Write("{0:N}", z);
Ausgabe: 2.007,00 D bzw. d plus Zahlenangabe legt für Integer-Werte eine Mindestanzahl von auszugebenden Stellen fest, wobei freie Stellen mit Nullen aufgefüllt werden. Die Verwendung dieses Formatzeichens ohne Zahlenangabe bleibt ohne jegliche Wirkung. Console.Write("{0:D4}", 99);
Ausgabe: 0099 X bzw. x formatiert den angegebenen Integer-Wert als Hexadezimalzahl: Console.Write("{0:X}", 11323);
Ausgabe: 2C3B Console.Write("{0:X}", 30);
Ausgabe: 1E
151
152
Kapitel 9
Mit einer Zahlenangabe nach dem Formatzeichen können Sie wiederum die Mindestanzahl der auszugebenden Stellen bestimmen. Freie Stellen werden mit führenden Nullen aufgefüllt: Console.Write("{0:X8}", 11323);
Ausgabe: 00002C3B Console.Write("{0:X4}", 30);
Ausgabe: 001E Achtung Die Anwendung der Formatzeichen D, d sowie X, x ist nicht für Gleitkommawerte vorgesehen (Laufzeitfehler!).
Schreiben Sie zur Übung ein kleines Programm, welches eine einzugebende Dezimalzahl in Hexadezimalschreibweise darstellt.
1 Legen Sie in Ihrer IDE ein neues Projekt an und richten Sie in der Main()-Methode eine entsprechende Eingabeaufforderung ein:
static void Main(string[] args) { Console.Write("Geben Sie eine ganze Zahl ein: "); }
2 Definieren Sie eine int-Variable. static void Main(string[] args) { int ein; Console.Write("Geben Sie eine ganze Zahl ein: "); }
3
Rufen Sie die Methode ReadLine() auf und weisen Sie den konvertierten Rückgabewert der Variablen zu: static void Main(string[] args) { int ein; Console.Write("Geben Sie eine ganze Zahl ein: "); ein = Convert.ToInt32(Console.ReadLine()); }
Formatieren von Ausgaben
Den Eingabewert konvertieren Sie mit der Methode ToInt32() in den Datentyp der Variablen ein.
4 Implementieren Sie die Ausgabe des Werts in Hexadezimalform: static void Main(string[] args) { int ein; Console.Write("Geben Sie eine ganze Zahl ein: "); ein = Convert.ToInt32(Console.ReadLine()); Console.WriteLine("Hexadezimalzahl: {0:X}", ein); Console.ReadLine(); }
Auf der CD-ROM Das Beispiel finden Sie unter Beispiele/Konsolenprogramme auf der Buch-CD (Projektname K09d).
5 Bringen Sie Ihr Programm zur Ausführung. Bei Eingabe von 492 erhalten Sie für die hexadezimale Anzeige 1EC.
Abbildung 9.3: Darstellung eines Werts als Hexadezimalzahl
Für den Eingabewert 10 zeigt die Ausgabe den hexadezimalen Wert A, was auf jeden Fall richtig ist.
153
154
Kapitel 9
Hinweis Unser vertrautes Dezimalsystem, dem die Basis 10 zugrunde liegt, kommt mit den Ziffern 0 bis 9 aus. Da das Hexadezimalsystem mit der Basis 16 arbeitet, sind neben den 10 Ziffern weitere 6 Zeichen zur Darstellung notwendig. Hierfür verwendet man die Buchstaben A bis F. Die aufsteigende Reihenfolge der »Zahlen« im Hexadezimalsystem ist also: 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F. Daher ist A die hexadezimale Darstellung des dezimalen Werts 10, hexadezimal B steht für dezimal 11 usw. und schließlich hexadezimal F für den Dezimalwert 15. Nach F, dem letzten Ziffernzeichen, wird im Hexadezimalsystem wieder mit 1 angefangen und eine Null angehängt, folglich entspricht hexadezimal 10 dem Dezimalwert 16, hexadezimal 11 dem Dezimalwert 17 usw. Die Zahlen werden also im Hexadezimalsystem nach dem gleichen Schema wie im Dezimalsystem gebildet, nur dass es nicht 10 Ziffern, sondern 16 gibt.
Zum Abschluss noch ein Wort zu den Platzhaltern: Diese bzw. deren Indizes dienen allein zur Identifizierung zugeordneter Werte. Ihre Nummerierung innerhalb eines Ausgabe-Strings muss daher nicht zwingend fortlaufend sein. Das heißt, die Reihenfolge, in der Platzhalter im Ausgabe-String stehen, muss nicht der Liste der angegebenen Werte entsprechen. Console.Write("{2} und {1} und {0}", 3, 2, 1);
Ausgabe: 1 und 2 und 3 Dementsprechend ist es auch möglich, ein und denselben Wert an mehreren Stellen innerhalb eines Ausgabe-Strings einzubauen.
Eine kleine Erfolgskontrolle
Eine kleine Erfolgskontrolle •
Welchen Initialisierungswert erhält die Variable x mit der folgenden Definition? int x = 2 + 3 * 5;
•
Schreiben Sie ein Programm, welches das Produkt zweier vom Benutzer einzugebender Zahlen berechnet und mit einer Genauigkeit von zwei Nachkommastellen auf den Bildschirm ausgibt.
155
Das können Sie schon: Entwicklungsschritte zum lauffähigen C#-Programm Umgang mit der Visual C# Express-IDE Aufbau und grundlegende Syntax von C#-Programmen Arbeiten mit Strings Steuerzeichen Variablen, Konstanten und Datentypen Verarbeiten von Benutzereingaben Typkonvertierung Komplexe Ausdrücke Arithmetische Operatoren Formatieren von Bildschirmausgaben
11 36 48 75 81 87 103 124 139 140 147
Das lernen Sie neu: Einrichten von Programmverzweigungen Behandlung von ungültigen Benutzereingaben Formulieren von Bedingungen Vergleichsoperatoren und logische Operatoren Prioritätsregeln für Operatoren Anhängen eines else-Zweigs Die if-Anweisung Vorzeitiges Abbrechen der Programmausführung
157 157 158 158 162 172 174 175
Das switch-Konstrukt
177
Kapitel 10
Verzweigungen Wenn nichts anderes vorgegeben ist, werden bei der Programmausführung alle Anweisungen so abgearbeitet, wie sie im Quellcode stehen, also von »oben nach unten«. Zur Lösung von komplexen Aufgaben ist es jedoch vielfach erforderlich, in diesen Programmfluss einzugreifen. So etwa, wenn der weitere Ablauf von einer Entscheidung des Benutzers abhängig gemacht werden soll. Für solche Fälle stellt C# eine Reihe von Verzweigungskonstrukten zur Verfügung, mit denen wir uns hier beschäftigen werden. Diese werden immer dann eingesetzt, wenn Anweisungen nur unter bestimmten Bedingungen ausgeführt werden sollen.
158
Kapitel 10
Logische Ausdrücke Wie Sie bereits erfahren haben, wird jeder Teil einer Anweisung, welcher für einen Wert steht, in der Programmierung als »Ausdruck« bezeichnet. Und wie Sie ebenfalls wissen, besitzt jeder Ausdruck einen Rückgabewert (siehe Kapitel 9, Abschnitt »Ausdrücke und arithmetische Operatoren«). Damit ist letztlich der Ergebniswert gemeint, der sich nach Auswertung des Ausdrucks beim Programmlauf ergibt. Ist nun der Ergebnistyp vom Datentyp bool, handelt es sich um einen logischen Ausdruck. Dieser ist also ein spezieller Fall dessen, was wir bisher gemeinhin als Ausdruck bezeichnet haben. Hinweis Mit »Ergebnistyp« ist natürlich der Datentyp des Ergebniswerts (Rückgabewerts) gemeint. Beachten Sie, dass sich der Datentyp eines Ausdrucks immer nach dem Datentyp seines Rückgabewerts richtet. Beispielsweise besitzt in dem Ausdruck 2.5 * 4 der linke Operand den Datentyp double, der rechte ist jedoch vom Datentyp int. Da vor Auswertung eine implizite Konvertierung des rechten Operanden nach double erfolgt (von 4 nach 4.0; siehe Kapitel 8, Abschnitt »Implizite Typumwandlungen«), wird der Ausdruck somit zu 10.0 (nicht 10) ausgewertet. Demnach weist der Ausdruck 2.5 * 4 den Datentyp double auf.
Für die Formulierung logischer Ausdrücke stellt C# verschiedene Vergleichsoperatoren zur Verfügung, welche Sie der folgenden Tabelle entnehmen können. Operator
Bedeutung
==
gleich
!=
ungleich
>
größer als
>=
größer als oder gleich
<
kleiner als
<=
kleiner als oder gleich
Tabelle 10.1: Vergleichsoperatoren
Logische Ausdrücke
Bitte merken Sie sich: • Ausdrücke, welche mit Vergleichsoperatoren gebildet werden, ergeben nach ihrer Auswertung immer einen booleschen Wert. • Der Wertebereich des booleschen Datentyps umfasst ausschließlich die beiden Wahrheitswerte true und false. Daraus folgt, dass Ausdrücke, in denen Vergleichsoperatoren vorkommen, nach Auswertung immer true (für wahr) oder false (für falsch) ergeben. Hinweis Für logische Ausdrücke wird wegen ihres Datentyps auch die Bezeichnung boolescher Ausdruck verwendet.
Wie werden nun boolesche Ausdrücke gebildet? Die Vergleichsoperatoren gehören zur Gruppe der binären Operatoren. Das heißt, sie erfordern einen linken und einen rechten Operanden. Diese werden in Form einer logischen Aussage einander gegenübergestellt. Über die Art des Vergleichs entscheidet dabei das verwendete Symbol. Nehmen wir als einfaches Beispiel den Ausdruck 1 < 2 (»1 kleiner 2«). Dieser wird zu true ausgewertet, da es sich um eine »wahre« Aussage handelt. Eine »falsche« Aussage wie etwa 0 > 3 (»0 größer 3«) dagegen ergibt den Wert false. Beides können Sie leicht nachprüfen, indem Sie sich die Ergebniswerte auf dem Bildschirm anzeigen lassen, z.B.: Console.Write(0 > 3); // Ausgabe: False
Oder Sie weisen den Rückgabewert des Ausdrucks einer booleschen Variablen zu und benutzen diese zur Ausgabe: bool test = 1 < 2; Console.Write(test); // Ausgabe: True
Als Operanden eines logischen Vergleichs dürfen wiederum beliebige Ausdrücke stehen, also z.B. auch Variablenbezeichner.
159
160
Kapitel 10
Achtung Die Rückgabewerte beider Operanden müssen im Datentyp übereinstimmen, da grundsätzlich nur Werte gleichen Datentyps miteinander verglichen werden können. Allerdings erfolgen gegebenenfalls implizite Typumwandlungen nach den in Kapitel 8, Abschnitt »Implizite Typumwandlungen« genannten Regeln.
Angenommen, Sie möchten einen Ausdruck formulieren, welcher true ergibt, wenn eine int-Variable mit dem Bezeichner alter den Wert 30 hat. Dann schreiben Sie: alter == 30
Achtung Denken Sie daran, beim logischen Vergleich auf Gleichheit zwei Gleichheitszeichen zu verwenden (==). Andernfalls handelt es sich um eine Zuweisung.
Folgender Ausdruck wird zu true ausgewertet, falls die Variable einen Wert größer 30 enthält, andernfalls ergibt die Auswertung false. alter > 30
Wie verhält es sich aber, wenn Sie einen Ausdruck formulieren müssen, welcher nur dann true ergibt, wenn der in der Variablen alter gespeicherte Wert sich im Bereich zwischen 30 und 40 bewegt (exklusive). Dann muss der Wert von alter größer sein als 30: alter > 30
und er muss kleiner sein als 40: alter < 40
Das heißt, beide Bedingungen müssen gleichzeitig erfüllt sein. Für solche Fälle – um zwei oder mehrere logische Teilausdrücke zu einem einzigen Gesamtausdruck zusammenzufassen – stellt C# die so genannten logischen Operatoren zur Verfügung (siehe Tabelle):
Logische Ausdrücke
Operator
Bedeutung
&&
logisches Und
||
logisches Oder
!
Negation (logische Verneinung)
Tabelle 10.2: Logische Operatoren
Beim logischen Oder (||) reicht es aus, wenn nur einer der verknüpften Teilausdrücke »wahr« ist. Dann ergibt die Auswertung des logischen Gesamtausdrucks den Wert true. Das logische Oder ist einschließend. Das heißt, der Ergebniswert des gesamten Ausdrucks ist auch dann true, wenn nicht nur einer, sondern beide Teilausdrücke »wahr« sind. Hinweis Das Zeichen | wird mit der Tastenkombination (Strg) + (Alt) + (<) erzeugt.
Umgekehrt ergibt eine Verknüpfung mit && (logisches Und) nur dann einen »wahren« Gesamtausdruck, wenn beide Teilausdrücke »wahr« sind. Für die Umsetzung der Aufgabenstellung benötigen Sie den Operator für das logische Und, da der in der Variablen alter enthaltene Wert nur dann zwischen 30 und 40 liegt, wenn beide Teilausdrücke den Wert true ergeben. Also lautet der zusammengesetzte Ausdruck wie folgt. alter > 30 && alter < 40
Der Operator für die Negation kehrt den Wahrheitswert eines logischen Ausdrucks um. Bei ! handelt sich um einen unären Operator, da er nur einen Operanden erwartet (vgl. Kapitel 9, Abschnitt »Ausdrücke und arithmetische Operatoren«). Ein Beispiel für einen negierten logischen Ausdruck ist !false. Dieser ist mit true äquivalent, beide geben den Wert true zurück. Statt des nahe liegenden Vergleichsoperators != wie in alter != 30 // »alter ungleich 30«
lässt sich entsprechend – mit gleicher Wirkung – auch eine Negation verwenden und Folgendes schreiben: !(alter == 30) // »nicht alter gleich 30«
161
162
Kapitel 10
Achtung Im letzten Ausdruck ist die Klammerung zwingend notwendig, da sich der Operator ! nicht auf die int-Variable alter, sondern auf den gesamten logischen Ausdruck alter == 30 beziehen muss – genau genommen auf dessen Rückgabewert.
Prioritätsstufen von Operatoren Betrachten Sie den folgenden Ausdruck: 5 + alter * 2 < 60 && alter > 0
Bei Auswertung dieses Ausdrucks kommen mehrere Operationen zum Zuge – eine Addition, eine Multiplikation, zwei Vergleichsoperationen und eine logische Verknüpfung (&&). Es muss daher irgendwie geregelt sein, in welcher Reihenfolge diese Operationen ausgeführt werden. Zu diesem Zweck sind alle Operatoren in eine mehrstufige Prioritätsrangfolge eingeordnet. Dabei gilt: • Operatoren mit hoher Priorität werden vor Operatoren mit niedriger Priorität ausgewertet. • Operatoren mit gleicher Prioritätsstufe werden von links nach rechts ausgewertet. Hat ein Operator eine höhere Prioritätsstufe, so bedeutet dies also, dass die durch ihn repräsentierte Operation zuerst ausgeführt wird, wenn er zusammen mit anderen Operatoren in einem Ausdruck bzw. in einer Anweisung auftritt. In der folgenden Tabelle sehen Sie eine Auflistung der bis jetzt besprochenen Operatoren nach ihrer Prioritätsstufe, wobei der am weitesten oben stehende Operator (!) die höchste Priorität besitzt. Operatoren mit gleicher Prioritätsstufe befinden sich in einer Zeile.
Prioritätsstufen von Operatoren
Operator
Name bzw. Kategorie
!
Logische Verneinung
*
/
+
-
<
<=
==
%
Multiplikativ Additiv
!=
>
>=
Relational Gleichheit
&&
Logisches Und
||
Logisches Oder
=
Zuweisung
Tabelle 10.3: Operator-Prioritätsskala
Es ist nicht notwendig, die gesamte Rangfolge der Operatoren auswendig zu können. Einige Orientierungshilfen sind jedoch nützlich: • Arithmetische Operatoren stehen in der Rangfolge weit oben. • Wie in der Mathematik gilt: Punkt vor Strich. Das heißt, die arithmetischen Operatoren * (Multiplikation) und / (Division) binden stärker als ihre Kollegen + (Addition) und - (Subtraktion). Hinzu gesellt sich der Modulo-Operator (%), welcher dieselbe Rangstufe beansprucht wie * und /. Der Ausdruck »Punkt vor Strich« kommt übrigens daher, dass die Operatoren für die Multiplikation und Division in der Mathematik als Punkte geschrieben werden (».« steht für die Multiplikation und »:« für die Division). Die Operatoren für Addition (+) und Subtraktion (-) setzen sich dagegen aus Strichen zusammen. • Der Zuweisungsoperator besitzt von allen Operatoren die niedrigste Prioritätsstufe. • Sie können in jedem Ausdruck durch Setzen von Klammern die vorgegebene Rangfolge ändern. Tipp Regeln Sie im Zweifel die Priorität durch Setzen von Klammern. Mitunter ist dies auch im Hinblick auf eine bessere Lesbarkeit des Codes empfehlenswert.
163
164
Kapitel 10
Bezüglich Vergleichs- und logischer Operatoren gilt: • Die Vergleichsoperatoren besitzen eine höhere Priorität als die logischen Operatoren. • Der Operator && (logisches Und) bindet stärker als der Operator || (logisches Oder). Haben Sie bitte etwas Geduld, falls Ihnen das alles jetzt noch etwas abstrakt erscheint. Sie werden im weiteren Verlauf noch ausreichend Gelegenheit haben, das Formulieren von logischen Ausdrücken zu üben.
Die if-Anweisung Mit dem if-Konstrukt steht dem Programmierer eine einfach zu handhabende, jedoch sehr effektive Möglichkeit zur Implementierung von Programmverzweigungen zur Verfügung. Die Syntax der if-Anweisung in ihrer einfachsten Form lautet if(Bedingung) Anweisung
Die Anweisung beginnt mit dem Schlüsselwort if gefolgt von einer Bedingung. Eine Bedingung ist nichts anderes als ein logischer Ausdruck, der in Klammern steht und im Zusammenhang mit einer Kontrollstruktur Verwendung findet. Kontrollstruktur – was ist das? Hierbei handelt es sich um den Oberbegriff für alle Konstrukte, welche eine Steuerung (Kontrolle) des Programmflusses durch den Programmierer erlauben. Zu den Kontrollstrukturen gehören neben den Verzweigungskonstrukten, welche Thema dieses Kapitels sind, auch Wiederholungskonstrukte – so genannte Schleifen. Mit Letzteren werden Sie sich im nächsten Kapitel beschäftigen.
Wie Sie im Weiteren sehen werden, kommt der Bedingung bei allen Kontrollstrukturen – mit Ausnahme der switch-Anweisung – entscheidende Bedeutung zu.
Die if-Anweisung
Die nächste Anweisung im Code wird nun automatisch als zum if-Konstrukt gehörig interpretiert. Alle weiteren Anweisungen werden vom Compiler wieder »normal« behandelt. Die Funktionsweise des if-Konstrukts ist schnell erklärt. Bei Eintritt in dieses während des Programmlaufs wird zunächst die Bedingung geprüft. • Ergibt die Auswertung des logischen Ausdrucks den Wert true, dann wird die zugehörige Anweisung ausgeführt. Danach wird mit der nächsten Anweisung nach der if-Konstruktion weitergemacht. • Ist der Ergebniswert false, wird die zum if-Konstrukt gehörige Anweisung ignoriert und sofort mit der Anweisung fortgefahren, welche im Code unterhalb der if-Konstruktion steht. Die Ausführung der zum if-Konstrukt gehörigen Anweisung ist also abhängig von der if-Bedingung. Ist die Bedingung falsch, verhält es sich daher so, als sei das if-Konstrukt gar nicht vorhanden.
1 Betrachten Sie das folgende Listing: static void Main(string[] args) { int zahl; Console.Write("Bitte eine Zahl eingeben: "); zahl = Convert.ToInt32(Console.ReadLine()); if(zahl > 10) Console.WriteLine("Zahl ist größer 10"); Console.WriteLine("Dieser Text wird in jedem" + " Fall ausgegeben"); }
Auf der CD-ROM Das Beispiel finden Sie unter Beispiele/Konsolenprogramme auf der Buch-CD (Projektname K10a).
Die Eingabe des Benutzers wird in der int-Variablen zahl festgehalten. Diese Variable wird in der if-Bedingung zur Prüfung herangezogen. Die Bedingung ist wahr, falls in der Variablen ein Wert größer als 10 enthalten ist (zahl > 10). Das heißt, für diesen Fall kommt die zugehörige Anweisung Console.WriteLine("Zahl ist größer 10"); zur Ausführung. Ist die zuvor eingegebene Zahl kleiner oder gleich 10, dann ergibt die Auswertung der if-Bedingung den Wert false und die Anweisung des if-Konstrukts wird beim Programmlauf ignoriert.
165
166
Kapitel 10
2
Laden Sie das Projekt K10a in Ihre Visual C# Express-IDE und führen Sie den Test für eine Eingabezahl kleiner oder gleich 10 durch.
In diesem Fall kommt allein die bedingungsunabhängige Anweisung Console.WriteLine("Dieser Text wird in jedem Fall ausgegeben");
zur Ausführung.
Abbildung 10.1: Eingabezahl kleiner oder gleich 10
3 Führen Sie den Test mit einer Eingabezahl größer als 10 durch. Nun wird zusätzlich die Anweisung des if-Konstrukts abgearbeitet, mit dem Ergebnis, dass der Text Zahl ist größer 10 auf dem Bildschirm ausgegeben wird. Die folgende – bedingungsunabhängige – Anweisung wird natürlich nach wie vor ausgeführt.
Abbildung 10.2: Eingabezahl größer als 10
Die if-Anweisung
Achtung Setzen Sie kein Semikolon hinter die if-Bedingung. Dies führt in der Regel zu logischen Fehlern: if(zahl > 10); Console.WriteLine("Zahl ist größer 10");
Ein Semikolon allein wird als Anweisung angesehen (siehe Kapitel 5, Abschnitt »Anweisungsende«). Das erste Semikolon in obigem Codestück wird daher (als Anweisung) dem if-Konstrukt zugerechnet. Die nachfolgende Ausgabeanweisung wird nun nicht mehr als zum if-Konstrukt gehörig interpretiert. Sie ist somit bedingungsunabhängig. Das heißt, sie wird auf jeden Fall ausgeführt, unabhängig davon, welcher Wert in zahl enthalten ist. Man kann hier davon ausgehen, dass dies vom Programmierer nicht so beabsichtigt war. Die Visual C# Express-IDE gibt lobenswerterweise bei einer solchen Konstruktion eine Warnung aus: »Möglicherweise falsche leere Anweisung«. Da es sich wohlgemerkt um eine Warnung und keine Fehlermeldung handelt, lässt sich das Programm in Verbindung mit solchen Konstruktionen dennoch kompilieren und ausführen.
Nun ein praktisches Beispiel: Ein Käufer soll ab einer Bestellmenge von 100 Stück eines bestimmten Artikels 10 % Rabatt erhalten. Der Stückpreis sei 9,90 Euro.
1 Legen Sie ein neues Projekt an. 2 Definieren Sie eine Variable anzahl vom Datentyp int. static void Main(string[] args) { int anzahl; }
In dieser Variablen werden Sie die Bestellmenge festhalten.
3 Fordern Sie den Benutzer zur Eingabe der Bestellmenge auf: static void Main(string[] args) { int anzahl; Console.Write("Bestellmenge: "); }
167
168
Kapitel 10
4 Richten Sie eine entsprechende Benutzereingabe ein und speichern Sie diese in der Variablen anzahl:
static void Main(string[] args) { int anzahl; Console.Write("Bestellmenge: "); anzahl = Convert.ToInt32(Console.ReadLine()); }
5 Halten Sie den Gesamtpreis (ohne Berücksichtigung eines eventuellen Rabatts) in einer zusätzlichen Variablen fest.
Um den Gesamtpreis zu erhalten, müssen Sie den Stückpreis (9,90 Euro) mit der Bestellmenge multiplizieren: static void Main(string[] args) { int anzahl; double preis; Console.Write("Bestellmenge: "); anzahl = Convert.ToInt32(Console.ReadLine()); preis = 9.9 * anzahl; }
6 Geben Sie das Ergebnis aus: static void Main(string[] args) { int anzahl; double preis; Console.Write("Bestellmenge: "); anzahl = Convert.ToInt32(Console.ReadLine()); preis = 9.9 * anzahl; Console.WriteLine("Gesamtpreis: {0:F} Euro", preis); Console.ReadLine(); }
Soweit wäre Ihr Programm bereits fertig, wenn es nicht noch die Gewährung eines zehnprozentigen Rabatts zu berücksichtigen gäbe. Um den verbilligten Gesamtpreis zu erhalten, muss dieser mit 0,9 multipliziert werden: preis = preis * 0.9;
Die if-Anweisung
7 Fügen Sie diese Anweisung unmittelbar vor der Ausgabe des Gesamtpreises in Ihren Quellcode ein:
static void Main(string[] args) { int anzahl; double preis; Console.Write("Bestellmenge: "); anzahl = Convert.ToInt32(Console.ReadLine()); preis = 9.9 * anzahl; preis = preis * 0.9; Console.WriteLine("Gesamtpreis: {0:F} Euro", preis); Console.ReadLine(); }
Allerdings soll der Rabatt nur für den Fall eingeräumt werden, dass der Käufer mehr als 100 Stück des besagten Artikels abnimmt. Das heißt, die eben genannte Anweisung soll bedingungsabhängig ausgeführt werden (nur, »wenn Bestellmenge größer als 100«). Dies erreichen Sie, indem Sie die Anweisung in eine entsprechende if-Konstruktion einfassen: if(anzahl > 100) preis = preis * 0.9;
8 Vervollständigen Sie Ihr Programm wie beschrieben: static void Main(string[] args) { int anzahl; double preis; Console.Write("Bestellmenge: "); anzahl = Convert.ToInt32(Console.ReadLine()); preis = 9.9 * anzahl; if(anzahl > 100) preis = preis * 0.9; Console.WriteLine("Gesamtpreis: {0:F} Euro", preis); Console.ReadLine(); }
9 Testen Sie Ihr Programm. Für eine Bestellmenge, welche größer ist als 100, wird bei der Berechnung des Gesamtpreises nun ein zehnprozentiger Rabatt berücksichtigt.
169
170
Kapitel 10
Abbildung 10.3: Hier gewährt das Programm Rabatt, da mehr als 100 Artikel bestellt wurden
Wie verhält es sich nun, wenn Sie dem Benutzer zusätzlich die Größe des Rabattbetrags mitteilen möchten, und zwar unmittelbar vor Ausgabe des Gesamtbetrags? Console.WriteLine("Sie erhalten {0:F} Euro Rabatt" , preis * 0.1);
Diese Anweisung soll natürlich auch nur dann zur Ausführung kommen, wenn der Käufer tatsächlich einen Rabatt erhält, wenn also besagte Bedingung zutrifft. Somit haben Sie es nunmehr mit zwei von derselben Bedingung abhängigen Anweisungen zu tun. Statt nun für die zweite Anweisung ein neues if-Konstrukt in den Quelltext zu schreiben, fassen Sie beide Anweisungen in einer if-Konstruktion nach folgendem Muster zusammen: if(Bedingung) { Anweisung(en) }
Sie können auf diese Weise beliebig viele Anweisungen unter einer bestimmten Bedingung zusammenfassen (was übrigens für alle Kontrollstrukturen gilt – mit Ausnahme des switch-Konstrukts). Hinweis Selbstverständlich ist es auch erlaubt, nur eine einzige Anweisung in {} einzuschließen. Dies bewahrt Sie davor, bei eventuellen Änderungen (falls zu ursprünglich einer bedingungsabhängigen Anweisung später noch weitere hinzukommen) das nachträgliche Setzen von Klammern zu vergessen.
Die if-Anweisung
10 Ergänzen Sie Ihr Programm wie beschrieben. static void Main(string[] args) { int anzahl; double preis; Console.Write("Bestellmenge: "); anzahl = Convert.ToInt32(Console.ReadLine()); preis = 9.9 * anzahl; if(anzahl > 100) { preis = preis * 0.9; Console.WriteLine("Sie erhalten {0:F} Euro Rabatt" , preis * 0.1); } Console.WriteLine("Gesamtpreis: {0:F} Euro", preis); Console.ReadLine(); }
Auf der CD-ROM Das Beispiel finden Sie unter Beispiele/Konsolenprogramme auf der Buch-CD (Projektname K10b).
11 Bringen Sie Ihr Programm zur Ausführung. Bei einer entsprechenden Bestellmenge bekommen Sie nun den Rabattbetrag ausgewiesen.
Abbildung 10.4: Bei erfüllter Bedingung werden jetzt zwei Anweisungen ausgeführt
171
172
Kapitel 10
Hinweis Man spricht bei Kontrollstrukturen von Anweisungen (z.B. if-Anweisung, switch-Anweisung), da sie im Quellcode überall dort eingesetzt werden dürfen, wo auch eine einfache Anweisung stehen darf. So gesehen werden Kontrollstrukturen vom Compiler tatsächlich wie einfache Anweisungen behandelt. Folgerichtig ist es auch erlaubt, sie untereinander beliebig zu verschachteln. if(Bedingung1) { … if(Bedingung2) { Anweisung(en) } … }
Beachten Sie, dass dies für alle Kontrollstrukturen gilt und sich auch nicht auf ein und dieselbe Kontrollstruktur beschränkt.
Die if-else-Konstruktion Sie können dem if-Konstrukt bei Bedarf zusätzlich einen else-Teil hinzufügen. Dieser besteht aus dem Schlüsselwort else und einem zugehörigen Anweisungsblock, der nur dann ausgeführt wird, wenn die if-Bedingung den Wert false ergibt. Die Syntax der if-else-Konstruktion lautet: if (Bedingung) { // Anweisung(en) } else { // Anweisung(en) }
Hier wird also auf jeden Fall einer der beiden Anweisungsblöcke ausgeführt. Dabei gelangen die Anweisungen des if-Teils zur Ausführung, wenn die ifBedingung wahr ist, andernfalls die Anweisungen des else-Zweigs. Hier eine Version des letzten Programms unter Hinzufügen eines else-Zweigs:
Die if-Anweisung
static void Main(string[] args) { int anzahl; double preis; Console.Write("Bestellmenge: "); anzahl = Convert.ToInt32(Console.ReadLine()); if(anzahl > 100) { preis = 9.9 * anzahl * 0.9; Console.WriteLine("Sie erhalten {0:F} Euro Rabatt" , preis * 0.1); } else preis = 9.9 * anzahl; Console.WriteLine("Gesamtpreis: {0:F} Euro", preis); Console.ReadLine(); }
Auf der CD-ROM Diese Version finden Sie ebenfalls unter Beispiele/Konsolenprogramme auf der Buch-CD (Projektname K10c).
Beachten Sie: • Die Konstruktion if else wird wie die einfache if-Konstruktion vom Compiler als Einheit betrachtet, also als eine einzige Anweisung. • Auch im else-Zweig einer if-else-Konstruktion ist eine Schachtelung von weiteren Kontrollstrukturen möglich. • Ein else darf niemals alleine (ohne zugehöriges if ) im Quelltext stehen. • Es empfiehlt sich, Anweisungen innerhalb eines Blocks einzurücken. Dadurch wird deutlich erkennbar, welcher else-Teil zu welchem if gehört (siehe Kapitel 5, Abschnitt »Programmierstil«). • Ein else gehört jeweils zum letzten if ohne else. Wenn Sie einen Quellcode lesen müssen, bei dem die Strukturen nicht durch Einrückungen gekennzeichnet wurden, gehen Sie diesen von oben nach unten durch, bis Sie auf das erste else treffen. Dieses rechnen Sie zum letzten if. Dann verfolgen Sie den Code weiter, bis Sie das nächste else erreichen und ordnen dieses dem letzten if zu, das noch ohne else ist, usw.
173
174
Kapitel 10
Stringvergleiche Mit den Operatoren == und != steht Ihnen eine einfache Möglichkeit zum Vergleich von Strings zur Verfügung. Nehmen wir als Beispiel eine Kennwortabfrage. Wenn der Benutzer ein falsches Kennwort eingibt, soll das Programm beendet werden.
1 Legen Sie ein neues Projekt an. 2 Fordern Sie den Benutzer zur Eingabe eines Passworts auf: static void Main(string[] args) { Console.Write("Geben Sie Ihr Kennwort ein: "); }
3 Halten Sie das eingegebene Passwort in einer string-Variablen fest; static void Main(string[] args) { string kenn; Console.Write("Geben Sie Ihr Kennwort ein: "); kenn = Console.ReadLine(); }
Beachten Sie, dass hier keine Typkonvertierung notwendig ist, da der Rückgabewert von ReadLine() denselben Datentyp wie die Variable kenn besitzt. Angenommen, das korrekte Passwort sei xyz. Dann müssen Sie den Wert der Variablen kenn mit dem Literal "xyz" vergleichen. Stimmen beide Werte überein, ist das eingegebene Kennwort richtig: static void Main(string[] args) { string kenn; Console.Write("Geben Sie Ihr Kennwort ein: "); kenn = Console.ReadLine(); if(kenn == "xyz") Console.WriteLine("Willkommen"); }
4 Prüfen Sie die Eingabe, wie es oben gezeigt wird. Wenn das Kennwort falsch ist, soll der Benutzer eine entsprechende Mitteilung erhalten.
5 Ergänzen Sie das if-Konstrukt um einen entsprechenden else-Zweig:
Die if-Anweisung
static void Main(string[] args) { string kenn; Console.Write("Geben Sie Ihr Kennwort ein: "); kenn = Console.ReadLine(); if(kenn == "xyz") Console.WriteLine("Willkommen"); else Console.WriteLine("Falsches Kennwort" + " - Das Programm wird beendet"); Console.WriteLine("..."); }
Die Anweisung Console.WriteLine("..."); soll verdeutlichen, dass das Programm für den authentifizierten Benutzer weiterlaufen soll. Allerdings soll für Benutzer, die ein falsches Kennwort eingegeben haben, der Programmlauf an dieser Stelle zu Ende sein. Um genau das zu erreichen, verwenden Sie den return-Befehl. Anweisung return – was ist das?
Die return-Anweisung bewirkt das sofortige Verlassen einer Methode. Anweisungen, welche in der Methode unterhalb von return stehen, werden nicht mehr abgearbeitet. Da es sich hier um die Methode Main() handelt, ist dies gleichbedeutend mit dem Beenden des Programms. Sie schreiben also an der Stelle, an der die Methode unregelmäßig verlassen werden soll, das Wort return in den Quelltext, gefolgt von einem Semikolon. Es handelt sich schließlich um eine Anweisung und wie Sie wissen, muss jede Anweisung in C# mit einem Semikolon enden. return;
Für Ihr Programm müssen Sie nun folgende Überlegung anstellen: Für den Fall, dass der Benutzer ein falsches Kennwort eingibt, landet die Programmausführung im else-Zweig. Dort erhält er eine entsprechende Mitteilung ("Falsches Kennwort") und genau danach soll für ihn die Programmausführung enden.
6
Sorgen Sie dafür, dass das Programm im else-Zweig unregelmäßig verlassen wird: static void Main(string[] args) { string kenn;
175
176
Kapitel 10
Console.Write("Geben Sie Ihr Kennwort ein: "); kenn = Console.ReadLine(); if(kenn == "xyz") Console.WriteLine("Willkommen"); else { Console.WriteLine("Falsches Kennwort" + " - Das Programm wird beendet"); Console.ReadLine(); return; } Console.WriteLine("..."); // Hier geht's für // den authentifizierten Benutzer weiter Console.ReadLine(); }
Auf der CD-ROM Das Beispiel finden Sie unter Beispiele/Konsolenprogramme auf der Buch-CD (Projektname K10d).
Denken Sie daran, die return-Anweisung mit der Ausgabeanweisung durch Setzen von geschweiften Klammern zu einem Block zusammenzufassen. Andernfalls würde auch bei Eingabe des richtigen Kennworts die Programmausführung mit return beendet werden.
7 Testen Sie Ihr Programm. Geben Sie einmal ein falsches und einmal das richtige Kennwort ein.
Abbildung 10.5: Das Programm wird bei falschem Kennwort durch die »return«Anweisung beendet ...
Abbildung 10.6: ... und bei korrektem Kennwort fortgesetzt
Die switch-Anweisung
Die switch-Anweisung Speziell zur Implementation von Mehrfachverzweigungen bietet C# als weitere Möglichkeit die switch-Anweisung. Zwar können Sie alle Aufgabenstellungen auch mit der if-Anweisung realisieren, für den Benutzer Ihres Programms macht das keinen Unterschied. Dennoch hat die switch-Anweisung ihre Existenzberechtigung. Ein wesentlicher Vorteil ist ihre Übersichtlichkeit. Bei der switch-Anweisung wird zunächst ein Ausdruck ausgewertet. Dabei wird es sich so gut wie nie um einen booleschen Ausdruck handeln. Anhand des Ergebniswerts wird dann entschieden, welcher Programmzweig durchlaufen wird. Die Syntax der switch-Anweisung lautet: switch (Ausdruck) { case Konstante_1: Anweisung(en) break; case Konstante_2: Anweisung(en) break; … case Konstante_n: Anweisung(en) break; default: Anweisung(en) break; }
Die einzelnen Programmzweige werden jeweils mit dem Schlüsselwort case und der Angabe eines Werts gefolgt von einem Doppelpunkt eingeleitet. Dieser Wert muss eine Konstante sein. Es darf an dieser Stelle also kein Variablenname stehen. Die Konstante muss vom selben Datentyp sein wie der oben nach dem Schlüsselwort switch angegebene Ausdruck. Achtung Der Ausdruck des switch-Konstrukts muss wie die Bedingungen der anderen Kontrollstrukturen immer in Klammern stehen.
177
178
Kapitel 10
Nach dem Doppelpunkt schließt sich der Anweisungsteil des entsprechenden case-Zweigs an. Die Stelle vom Schlüsselwort case bis zum Doppelpunkt (case Konstante:) definiert eine so genannte Sprungmarke. Sie wird angesprungen, wenn der Wert der Konstanten mit dem Ergebniswert des switchAusdrucks übereinstimmt. Sobald ein Sprungziel erreicht ist, wird der zugehörige Anweisungsteil abgearbeitet. Die break-Anweisung (break;) bewirkt, dass nach Abarbeitung des angesprungenen Zweigs die switch-Konstruktion verlassen wird. Sie ist daher in jedem Fall die zuletzt ausgeführte Anweisung im switch-Block – den Fall, dass kein Programmzweig zur Ausführung gelangt, einmal ausgenommen. Achtung Die break-Anweisung bewirkt das sofortige Verlassen eines switchKonstrukts. Ihre Wirkung ist daher vergleichbar mit der von return. Allerdings beendet die return-Anweisung die ganze Methode (bzw. das Programm), während die Programmausführung mit dem break-Befehl unmittelbar nach dem switch-Konstrukt fortfährt. Anders als C++ und Java schreibt C# den Gebrauch der break-Anweisung am Ende eines jeden Zweiges (einschließlich des letzten) zwingend vor. Andernfalls erhalten Sie eine Fehlermeldung (in C++ und Java wird, nachdem ein Sprungziel erreicht ist, mit der Programmausführung so lange sequenziell weitergemacht, bis das nächste break oder das Ende der switch-Anweisung erreicht ist, ungeachtet der Tatsache, ob dabei die Anweisungen mehrerer Zweige ausgeführt werden). Falls erforderlich, können Sie die break-Anweisung auch an jeder anderen Stelle des switch-Konstrukts einsetzen (etwa in inneren if-elseVerzweigungen).
Die Angabe einer default-Marke ist optional. Stimmt keine case-Konstante mit dem Wert des switch-Ausdrucks überein, wird – soweit vorhanden – zur default-Marke gesprungen (ist keine default-Marke gesetzt, wird die switch-Anweisung unverrichteter Dinge verlassen und die Programmausführung wird mit der nächsten Anweisung nach dem switch-Konstrukt fortgeführt).
Die switch-Anweisung
Hinweis Die Anweisungsteile der einzelnen Zweige müssen nicht von geschweiften Klammern umschlossen sein, da sie jeweils durch eine case- oder default-Marke eindeutig begrenzt werden (für den Anweisungsteil des letzten Zweigs gilt die schließende geschweifte Klammer der switchAnweisung als unterer Begrenzer).
In der Regel wird als switch-Ausdruck der Bezeichner einer einfachen Variablen stehen. Diese wird somit auf ihren Inhalt geprüft. Lassen Sie uns den Ablauf an folgendem Listing verdeutlichen: static void Main(string[] args) { int number; number = Convert.ToInt32(Console.ReadLine()); switch(number) { case 2: Console.WriteLine("Zwei"); break; case 1: Console.WriteLine("Eins"); break; case 4: Console.WriteLine("Vier"); break; default: Console.WriteLine("Im default-Zweig"); break; } // end switch Console.WriteLine("Ausserhalb von switch"); }
In obigem Programm wird eine einzugebende Ganzzahl (auf eine Eingabeaufforderung haben wir hier verzichtet) in der Variablen number gespeichert. Diese wird im switch-Konstrukt zur Prüfung herangezogen. Spielen wir in Gedanken einige Varianten durch. Angenommen, die Variable number enthält den Wert …
… 1: Dann wird die zweite case-Marke angesprungen (case 1:), da diese case-Konstante (1) mit dem Wert von number übereinstimmt. Die Anweisung Console.WriteLine("Eins"); gelangt zur Ausführung, mit dem Ergebnis, dass der Text Eins auf dem Bildschirm erscheint (zusätzlich wird die Einfüge-
179
180
Kapitel 10
marke in die nächste Zeile gesetzt). Nun wird das switch-Konstrukt mit der break-Anweisung verlassen und mit der nächsten Anweisung nach dem switch-Konstrukt fortgefahren (Console.WriteLine("Ausserhalb von switch");). Hinweis Wie Sie an obigem Listing erkennen können, müssen die Konstanten keinesfalls aufsteigend nach ihrem Wert angeordnet sein (case 2: … case 1: … case 4: …).
… 77: In den case-Marken ist keine Konstante mit diesem Wert angegeben. Folglich wird zur default-Marke gesprungen. Entsprechend kommt die Anweisung Console.WriteLine("Im default-Zweig"); zur Ausführung. Der anschließende break-Befehl bewirkt das Verlassen des switch-Konstrukts. Die Programmausführung landet bei der im Quellcode unterhalb des switch-Konstrukts stehenden Anweisung (Console.WriteLine("Außerhalb von switch");). … 3: Auch dieser Wert stimmt mit keiner Konstanten überein. Es erfolgt ebenfalls ein Sprung zur default-Marke. … 4: Die Anweisungen des dritten Programmzweigs kommen zum Zug (Console.WriteLine("Vier"); und break;). Beachten Sie: • In den einzelnen case-Zweigen dürfen beliebig viele Anweisungen stehen.
•
Eine Verschachtelung von weiteren Kontrollstrukturen ist ebenso möglich.
Häufig wird die switch-Anweisung zur Implementation von Menüs verwendet. Die einzelnen Menüpunkte sind dabei meist mit Zahlen oder Buchstaben gekennzeichnet. Um eine Aktion auszuführen, gibt der Anwender das entsprechende Zeichen ein. Als Beispiel soll ein kleines Rechenprogramm dienen. Dabei wird der Benutzer zur Eingabe zweier Zahlen aufgefordert und kann dann entscheiden, welche Rechenoperation mit diesen Zahlen durchgeführt wird.
1 Legen Sie ein neues Projekt an. 2 Nehmen Sie die Eingabe zweier Zahlen entgegen.
Die switch-Anweisung
Um auch die Eingabe von Zahlen mit Nachkommastellen zu ermöglichen, müssen Sie Variablen vom Datentyp double verwenden, um die beiden Eingabezahlen festzuhalten: static void Main(string[] args) { double zahl1, zahl2; Console.WriteLine("Geben Sie zwei Zahlen ein"); Console.Write("1. Zahl: "); zahl1 = Convert.ToDouble(Console.ReadLine()); Console.Write("2. Zahl: "); zahl2 = Convert.ToDouble(Console.ReadLine()); }
3 Bringen Sie ein Menü zur Anzeige, in dem Sie den Benutzern die Grundrechenarten zur Auswahl stellen:
static void Main(string[] args) { double zahl1, zahl2; Console.WriteLine("Geben Sie zwei Zahlen ein"); Console.Write("1. Zahl: "); zahl1 = Convert.ToDouble(Console.ReadLine()); Console.Write("2. Zahl: "); zahl2 = Convert.ToDouble(Console.ReadLine()); Console.WriteLine("\n(A)ddition"); Console.WriteLine("(S)ubtraktion"); Console.WriteLine("(M)ultiplikation"); Console.WriteLine("(D)ivision\n"); }
Um dem Benutzer deutlich zu machen, was er tun muss, um eine der angebotenen Operationen auszuwählen, wurden die dazugehörigen Anfangsbuchstaben jeweils in Klammern gesetzt. So wird z.B. die Addition durch Eingabe des Großbuchstabens »A« ausgelöst (und einer Bestätigung mit (¢)). Sie benötigen nun eine weitere Variable vom Typ char für die Benutzerauswahl.
4 Nehmen Sie die Benutzerauswahl entgegen: static void Main(string[] args) { double zahl1, zahl2; char c; Console.WriteLine("Geben Sie zwei Zahlen ein"); Console.Write("1. Zahl: "); zahl1 = Convert.ToDouble(Console.ReadLine()); Console.Write("2. Zahl: "); zahl2 = Convert.ToDouble(Console.ReadLine());
181
182
Kapitel 10
Console.WriteLine("\n(A)ddition"); Console.WriteLine("(S)ubtraktion"); Console.WriteLine("(M)ultiplikation"); Console.WriteLine("(D)ivision\n"); Console.Write("Auswahl: "); c = Convert.ToChar(Console.ReadLine()); }
Hinweis Der Bezeichner c steht für »character«, engl. »Zeichen«. Übrigens wird der Datentyp genauso ausgesprochen (char, sprich »character«).
5 Schauen Sie sich das Zwischenergebnis einmal an, bevor Sie weitermachen. Das Programm endet mit der Auswahl.
Abbildung 10.7: Das Auswahlmenü erscheint nach Eingabe der beiden Zahlenwerte
An dieser Stelle muss das Programm nun in Abhängigkeit von der Benutzerauswahl verzweigen. Zur Implementation der verschiedenen Programmzweige verwenden Sie die switch-Anweisung. In dieser prüfen Sie den Inhalt der Variablen c, um zu entscheiden, welcher Anweisungsteil zur Ausführung gelangt: switch(c) { case 'A': break; case 'S': break; case 'M': break; case 'D': break; }
Die switch-Anweisung
Achtung Da die Prüfvariable vom Datentyp char ist, müssen die case-Konstanten ebenfalls diesen Datentyp besitzen.
6 Fügen Sie Ihrem Code die switch-Anweisung hinzu, wie oben dargestellt. 7 Definieren Sie eine double-Variable mit dem Bezeichner ergebnis: static void Main(string[] args) { double zahl1, zahl2, ergebnis; char c; Console.WriteLine("Geben Sie zwei Zahlen ein"); Console.Write("1. Zahl: "); zahl1 = Convert.ToDouble(Console.ReadLine()); Console.Write("2. Zahl: "); zahl2 = Convert.ToDouble(Console.ReadLine()); Console.WriteLine("\n(A)ddition"); Console.WriteLine("(S)ubtraktion"); Console.WriteLine("(M)ultiplikation"); Console.WriteLine("(D)ivision\n"); Console.Write("Auswahl: "); c = Convert.ToChar(Console.ReadLine()); switch(c) { case 'A': break; case 'S': break; case 'M': break; case 'D': break; } }
Nun gehen Sie daran, in den einzelnen case-Zweigen die passenden Anweisungen einzurichten. Sie werden hier lediglich die Berechnungen durchführen und das Ergebnis in der Variablen ergebnis speichern. Am Ende des Programms, außerhalb des switch-Konstrukts, werden Sie dann den Wert der Variablen für die Ausgabe verwenden.
8
Addieren Sie im case-Zweig für die Addition beide Eingabezahlen und weisen Sie das Ergebnis der Variablen ergebnis zu:
183
184
Kapitel 10
static void Main(string[] args) { double zahl1, zahl2, ergebnis; char c; Console.WriteLine("Geben Sie zwei Zahlen ein"); Console.Write("1. Zahl: "); zahl1 = Convert.ToDouble(Console.ReadLine()); Console.Write("2. Zahl: "); zahl2 = Convert.ToDouble(Console.ReadLine()); Console.WriteLine("\n(A)ddition"); Console.WriteLine("(S)ubtraktion"); Console.WriteLine("(M)ultiplikation"); Console.WriteLine("(D)ivision\n"); Console.Write("Auswahl: "); c = Convert.ToChar(Console.ReadLine()); switch(c) { case 'A': ergebnis = zahl1 + zahl2; break; case 'S': break; case 'M': break; case 'D': break; } }
9 Verfahren Sie nun analog mit den anderen Programmzweigen. Sie müssen nur das entsprechende Symbol für die gewünschte Operation verwenden, also z.B. / für die Division: static void Main(string[] args) { double zahl1, zahl2, ergebnis; char c; Console.WriteLine("Geben Sie zwei Zahlen ein"); Console.Write("1. Zahl: "); zahl1 = Convert.ToDouble(Console.ReadLine()); Console.Write("2. Zahl: "); zahl2 = Convert.ToDouble(Console.ReadLine()); Console.WriteLine("\n(A)ddition"); Console.WriteLine("(S)ubtraktion"); Console.WriteLine("(M)ultiplikation"); Console.WriteLine("(D)ivision\n"); Console.Write("Auswahl: ");
Die switch-Anweisung
c = Convert.ToChar(Console.ReadLine()); switch(c) { case 'A': ergebnis = zahl1 + zahl2; break; case 'S': ergebnis = zahl1 - zahl2; break; case 'M': ergebnis = zahl1 * zahl2; break; case 'D': ergebnis = zahl1 / zahl2; break; } }
10 Bringen Sie das Berechnungsergebnis zur Anzeige: static void Main(string[] args) { double zahl1, zahl2, ergebnis; char c; Console.WriteLine("Geben Sie zwei Zahlen ein"); Console.Write("1. Zahl: "); zahl1 = Convert.ToDouble(Console.ReadLine()); Console.Write("2. Zahl: "); zahl2 = Convert.ToDouble(Console.ReadLine()); Console.WriteLine("\n(A)ddition"); Console.WriteLine("(S)ubtraktion"); Console.WriteLine("(M)ultiplikation"); Console.WriteLine("(D)ivision\n"); Console.Write("Auswahl: "); c = Convert.ToChar(Console.ReadLine()); switch(c) { case 'A': ergebnis = zahl1 + zahl2; break; case 'S': ergebnis = zahl1 - zahl2; break; case 'M': ergebnis = zahl1 * zahl2; break; case 'D':
185
186
Kapitel 10
ergebnis = zahl1 / zahl2; break; } Console.WriteLine("Ergebnis: {0:F}", ergebnis); }
Wenn Sie nun versuchen, das Programm zu kompilieren bzw. in Visual C# Express auszuführen, erhalten Sie eine Fehlermeldung: »Verwendung der nicht zugewiesenen lokalen Variablen ergebnis«. Gemeint ist, dass bis zum Zeitpunkt des Lesezugriffs noch keine Wertzuweisung an die Variable ergebnis erfolgt ist. Mit anderen Worten: Es handelt sich um den Versuch, eine Variable zu lesen, welche noch keinen definierten Inhalt hat. Wie Sie wissen, sind solche Zugriffe in C# nicht erlaubt. Nun werden Sie einwenden, dass in den einzelnen case-Zweigen des switchKonstrukts doch Zuweisungen an die Variable stattfinden. Dennoch sind Programmläufe denkbar, in denen die Variable ohne Zuweisung bleibt, nämlich für den Fall, dass der Benutzer ein Zeichen eingibt, welches für die Auswahl nicht vorgesehen ist (also nicht A, S, M oder D). Dann kommt kein Anweisungsteil des switch-Konstrukts zur Ausführung und daher erfolgt in diesem Fall auch keine Zuweisung an die Variable ergebnis. Da Ihr Compiler dies erkennt, will er Sie vor unkontrollierten Zugriffen schützen. Sie könnten den Fehler nun einfach beheben, indem Sie die Variable mit dem Wert 0.0 initialisieren. Allerdings erhält der Benutzer dann bei nicht vorgesehenen Eingaben für die Menüauswahl die Ausgabe Ergebnis: 0,00, was ebenfalls nicht zufrieden stellen kann.
11 Richten Sie für nicht vorgesehene Auswahleingaben in der switch-Anweisung einen default-Zweig ein.
Am Ende des default-Zweiges beenden Sie die Ausführung der Main()-Methode mit return. Damit kommt die Ausgabe des Berechnungsergebnisses für diesen Fall nicht mehr zur Ausführung: static void Main(string[] args) { double zahl1, zahl2, ergebnis; char c; Console.WriteLine("Geben Sie zwei Zahlen ein"); Console.Write("1. Zahl: "); zahl1 = Convert.ToDouble(Console.ReadLine()); Console.Write("2. Zahl: "); zahl2 = Convert.ToDouble(Console.ReadLine()); Console.WriteLine("\n(A)ddition"); Console.WriteLine("(S)ubtraktion");
Die switch-Anweisung
Console.WriteLine("(M)ultiplikation"); Console.WriteLine("(D)ivision\n"); Console.Write("Auswahl: "); c = Convert.ToChar(Console.ReadLine()); switch(c) { case 'A': ergebnis = zahl1 + zahl2; break; case 'S': ergebnis = zahl1 - zahl2; break; case 'M': ergebnis = zahl1 * zahl2; break; case 'D': ergebnis = zahl1 / zahl2; break; default: Console.WriteLine("Ungültige Auswahl " + "(A, S, M oder D)"); return; } Console.WriteLine("Ergebnis: {0:F}", ergebnis); }
Hinweis Da der return-Befehl bezüglich des switch-Konstrukts die gleiche Wirkung besitzt, darf er anstelle von break; verwendet werden. Dass return; nicht nur das switch-Konstrukt, sondern die ganze Methode verlässt, spielt insoweit keine Rolle.
Nun kann es vorkommen, dass für zwei oder mehrere Werte die gleichen Aktionen ausgeführt werden sollen. Das erreichen Sie, indem Sie die entsprechenden case-Marken untereinander setzen, wobei nur die letzte case-Marke einen Anweisungsteil besitzt. Beispielsweise bietet es sich an, das Programm bedienerfreundlicher zu machen, indem neben dem Großbuchstaben auch der entsprechende Kleinbuchstabe akzeptiert wird. Wenn Sie also möchten, dass die Addition nicht nur für die Eingabe A, sondern auch für die Eingabe a durchgeführt wird, dann schreiben Sie:
187
188
Kapitel 10
case 'a': case 'A': ergebnis = zahl1 + zahl2; break;
Beachten Sie, dass es sich trotz mehrerer case-Marken um jeweils nur einen Programmzweig handelt. case-Marken dienen als Sprungziel, ansonsten werden sie ignoriert. Die Verwendung der break-Anweisung ist nur als letzter Befehl eines Anweisungsteils vorgeschrieben. Enthält die Variable c beispielsweise den Wert 'a' (weil der Benutzer bei der Auswahl ein kleines »a« eingegeben hat), wird die zugehörige case-Marke (case 'a':) angesprungen und der nächstliegende Anweisungsteil ausgeführt. Weitere case-Marken werden dabei ignoriert. Der nächstliegende Anweisungsteil beginnt in diesem Fall unmittelbar nach der Marke case 'A':.
12 Richten Sie es ein, dass der Benutzer die von ihm gewünschte Rechenart auch durch Eingeben eines Kleinbuchstabens auswählen kann:
static void Main(string[] args) { double zahl1, zahl2, ergebnis; char c; Console.WriteLine("Geben Sie zwei Zahlen ein"); Console.Write("1. Zahl: "); zahl1 = Convert.ToDouble(Console.ReadLine()); Console.Write("2. Zahl: "); zahl2 = Convert.ToDouble(Console.ReadLine()); Console.WriteLine("\n(A)ddition"); Console.WriteLine("(S)ubtraktion"); Console.WriteLine("(M)ultiplikation"); Console.WriteLine("(D)ivision\n"); Console.Write("Auswahl: "); c = Convert.ToChar(Console.ReadLine()); switch(c) { case 'a': case 'A': ergebnis = zahl1 + zahl2; break; case 's': case 'S': ergebnis = zahl1 - zahl2; break; case 'm': case 'M': ergebnis = zahl1 * zahl2;
Die switch-Anweisung
break; case 'd': case 'D': ergebnis = zahl1 / zahl2; break; default: Console.WriteLine("Ungültige Auswahl " + "(A, S, M oder D)"); Console.ReadLine(); return; } Console.WriteLine("Ergebnis: {0:F}", ergebnis); Console.ReadLine(); }
Auf der CD-ROM Das Beispiel finden Sie unter Beispiele/Konsolenprogramme auf der Buch-CD (Projektname K10e).
13 Testen Sie Ihr Programm.
Abbildung 10.8: Bei ungültiger Auswahl wird ein entsprechender Hinweis angezeigt
189
190
Kapitel 10
Abbildung 10.9: Nach Auswahl einer Rechenart wird das Ergebnis angezeigt
Tipp Wenn Sie möchten, dass die Ausgabe mit Tausender-Trennzeichen erfolgt, dann verwenden Sie im Code das N (anstelle von F) als Formatierungszeichen, also {0:N}.
Im Gegensatz zu den meisten anderen Programmiersprachen erlaubt C# in der switch-Anweisung auch Stringvergleiche. Es dürfte Ihnen nun keine Schwierigkeiten mehr bereiten, das folgende Beispiel nachzuvollziehen: static void Main(string[] args) { string eingabe; Console.Write("Sind Sie noch fit? "); eingabe = Console.ReadLine(); switch(eingabe) { case "ja": case "Ja": case "JA": Console.WriteLine("Das ist schön!"); break; case "nein": case "Nein": case "NEIN": Console.WriteLine("Machen Sie " + "eine kleine Pause!"); break; default: Console.Write("Ich kann Ihre Antwort "); Console.WriteLine("leider nicht verstehen.");
Eine kleine Erfolgskontrolle
break; } }
Auf der CD-ROM Das Beispiel finden Sie unter Beispiele/Konsolenprogramme auf der Buch-CD (Projektname K10f ).
Eine kleine Erfolgskontrolle •
Welchen Rückgabewert liefert folgender Ausdruck? 22 == 2 * 10 || 2.3 < 2.4
•
z sei eine Variable vom Datentyp int. Welche Bildschirmausgabe erzeugt folgendes Codestück, falls z den Wert 9 enthält? if(z <= 0) Console.Write("Die Variable z besitzt "); Console.Write("den Wert " + z);
•
An welcher Stelle ist in folgendem Codestück ein (logischer) Fehler zu vermuten? if(id == 770234); Console.Write("Guten Tag Herr Müller");
• •
•
Was bewirkt die return-Anweisung? Fordern Sie den Benutzer auf, eine ganze Zahl einzugeben. Teilen Sie ihm anschließend mit, ob die eingegebene Zahl gerade oder ungerade war. Speichern Sie die Benutzereingabe in einer Variablen zahl vom Datentyp int. Setzen Sie den Modulo-Operator (siehe Kapitel 9, Abschnitt »Der Modulo-Operator«) zur Formulierung einer passenden if-Bedingung ein. Die Programmausführung landet in einem switch-Konstrukt mit integriertem default-Zweig. Ist der Fall denkbar, dass kein Anweisungsteil innerhalb des Konstrukts zur Ausführung gelangt?
191
Das können Sie schon: Aufbau und grundlegende Syntax von C#-Programmen Arbeiten mit Strings Steuerzeichen Variablen, Konstanten und Datentypen Verarbeiten von Benutzereingaben Typkonvertierung Komplexe Ausdrücke und arithmetische Operatoren Formatieren von Bildschirmausgaben Einrichten von Programmverzweigungen Vergleichsoperatoren und logische Operatoren Prioritätsregeln für Operatoren
48 75 81 87 103 124 139 147 157 158 162
Das lernen Sie neu: Implementation von Wiederholungsabläufen while-Schleife Inkrement und Dekrement do-while-Schleife for-Schleife Gültigkeitsbereich und Lebensdauer von Variablen Im Programm Zufallszahlen erzeugen Referenzvariablen
193 194 197 198 199 202 204 205
Kapitel 11
Wiederholungsanweisungen Oft ist es notwendig, bestimmte Anweisungen wiederholt auszuführen. Dann müssen Sie diese nicht etwa erneut in Ihren Quellcode schreiben. Dies würde in vielen Fällen auch nicht zum gewünschten Ergebnis führen, da die Anzahl der erforderlichen Wiederholungen bei der Entwicklung des Programms oft gar nicht bekannt ist. So ist es z.B. nicht vorhersehbar, ob der Benutzer beim Programmlauf eine bestimmte Aktion abbrechen oder erneut ausführen möchte. Zur Umsetzung von Wiederholungsabläufen stellt C# verschiedene Konstrukte zur Verfügung. Diese zählen wie die Verzweigungsanweisungen zu den Kontrollstrukturen, da sie es ermöglichen, den Programmablauf zu steuern. Wiederholungsanweisungen werden wegen ihres kreisförmigen Verlaufs allgemein Schleifen genannt.
194
Kapitel 11
Die while-Schleife Die while-Schleife erlaubt es, in Abhängigkeit von einer Bedingung zu entscheiden, ob bzw. wie oft ein Anweisungsblock wiederholt ausgeführt werden soll. Es gilt folgende Syntax: while (Bedingung) { Anweisung(en) }
Beginnend mit dem Schlüsselwort while schließt sich eine Bedingung an. Dann folgt die Implementation des Anweisungsteils (beachten Sie die syntaktische – und auch die funktionale – Ähnlichkeit mit der if-Anweisung). Bei Ausführung der while-Schleife wird zunächst die Schleifenbedingung geprüft. Hierbei handelt es sich wie bei der if-Anweisung um einen logischen Ausdruck, der »wahr« oder »falsch« sein kann. Ist er »wahr«, wird der zugehörige Anweisungsteil ausgeführt. Nach jedem Schleifendurchgang wird die Bedingung erneut geprüft. Der Anweisungsteil der while-Schleife wird also so lange wiederholt, wie die Auswertung der Schleifenbedingung den Wert true ergibt. Andernfalls wird die Schleife verlassen und mit der Ausführung der nächsten Anweisung außerhalb der while-Schleife weitergemacht. int number = 1; while(number < 5) { Console.WriteLine("Dies ist der " + "{0}. Schleifendurchgang", number); number = number + 1; }
Obiges Codestück erzeugt die Ausgabe Dies ist der 1. Schleifendurchgang Dies ist der 2. Schleifendurchgang Dies ist der 3. Schleifendurchgang Dies ist der 4. Schleifendurchgang
Bei Eintritt in die while-Schleife hat number den Wert 1, die Schleifenbedingung ist wahr und die bedingungsabhängigen Anweisungen werden ausgeführt. Zunächst wird der Text Dies ist der 1. Schleifendurchgang auf den Bildschirm ausgegeben. Danach wird der Wert von number um den Betrag von 1 erhöht. Im Anschluss daran wird die Schleifenbedingung erneut geprüft. Die Variable number hat nun den Wert 2 und die Bedingung (2 < 5) ist wiederum wahr. Also wird der Anweisungsteil der while-Schleife erneut ausge-
Die while-Schleife
führt und die Zeichenfolge Dies ist der 2. Schleifendurchgang erscheint auf dem Bildschirm. Bei jedem Schleifendurchlauf wird number um 1 hochgezählt (number = number + 1;). Die Abarbeitung der Schleife setzt sich nach dem gleichen Prinzip fort. Die letzte Zeichenfolge, die auf den Bildschirm ausgegeben wird, ist Dies ist der 4. Schleifendurchgang. Sobald number den Wert 5 erreicht, wird die Schleifenbedingung (5 < 5) »falsch« und die while-Schleife verlassen. Bei der while-Schleife kann es vorkommen, dass die zugehörigen Anweisungen kein einziges Mal ausgeführt werden. Das ist der Fall, wenn die erste Auswertung der Schleifenbedingung bereits den Wert false ergibt. Für obiges Beispiel trifft dies zu, falls die Variable number bereits bei Schleifeneintritt den Wert 5 oder einen höheren Wert enthält. Dann werden die Anweisungen des Schleifenrumpfs nie ausgeführt, da bereits die erste Auswertung der Schleifenbedingung den Wert false ergibt: int number = 5; while(number < 5) { // dieser Block wird nie erreicht! Console.WriteLine("Dies ist der " + "{0}. Schleifendurchgang", number); number = number + 1; }
Dies kann aber durchaus so beabsichtigt sein. In der Mehrzahl der Fälle wird die Anzahl der Schleifendurchgänge (bei späteren Programmläufen) je nach Verhalten des Anwenders variieren. Das ist immer der Fall, wenn das Ergebnis der Bedingungsauswertung von einer Eingabe des Benutzers abhängt: double number; Console.Write("Bitte positive Zahl eingeben: "); number = Convert.ToDouble(Console.ReadLine()); while(number < 0) { Console.WriteLine("Sie haben eine " + "negative Zahl eingegeben."); Console.Write("Bitte positive Zahl eingeben: "); number = Convert.ToDouble(Console.ReadLine()); }
Gibt der Anwender sogleich eine positive Zahl ein, dann ist die Schleifenbedingung (number < 0) falsch und die Anweisungen des Schleifenrumpfs werden übersprungen. Besteht die Eingabe aber aus einem negativen Wert, dann wird die Schleifenbedingung »wahr« und der Anwender erhält eine entsprechende
195
196
Kapitel 11
Mitteilung (Console.WriteLine("Sie haben eine " + "negative Zahl eingegeben.");). Nun wird er nochmals aufgefordert, eine Zahl einzugeben, bevor die Schleifenbedingung erneut geprüft wird, usw.
Endlosschleife Einen meist unerwünschten Sonderfall stellt die so genannte Endlosschleife dar. So werden Schleifen genannt, die nie abbrechen, da die Auswertung der Schleifenbedingung stets den Wert true ergibt. Die Endlosschleife beruht in der Regel auf einem Versehen des Programmautors. Lässt man z.B. in obigem Codestück die Eingabeanweisung des Schleifenrumpfs weg, entsteht eine Endlosschleife, falls die Bedingung (number < 0) bei der ersten Auswertung zutrifft. double number; Console.Write("Bitte positive Zahl eingeben: "); number = Convert.ToDouble(Console.ReadLine()); while(number < 0) { Console.WriteLine("Sie haben eine " + "negative Zahl eingegeben."); Console.Write("Bitte positive Zahl eingeben: "); // number = Convert.ToDouble(Console.ReadLine()); }
Die Eingabeanweisung des Schleifenrumpfs ist hier auskommentiert, also als Kommentar gekennzeichnet. Die Auswertung der Schleifenbedingung wird nun nach jedem Schleifendurchgang true ergeben, da sich der Wert von number im Schleifenrumpf nicht ändern kann. Oder betrachten Sie das Einführungsbeispiel. Vergisst man hier, die Variable number hochzuzählen (number = number + 1), entsteht der gleiche Effekt: int number = 1; while(number < 5) { Console.WriteLine("Dies ist der " + "{0}. Schleifendurchgang", number); number = number + 1; // fehlt diese Anweisung, // so entsteht eine Endlosschleife }
Die while-Schleife
Achtung Sie müssen es stets (durch geeignete Implementierung des Schleifenrumpfs) so einrichten, dass sich die Schleifenbedingung nach einer Wiederholung auch ändern kann. Diese Aussage gilt für alle Schleifenkonstrukte.
Inkrement- und Dekrementoperator Hierbei handelt es sich um unäre Operatoren. Als Operanden kommen Variablen numerischen Datentyps infrage. Der Inkrementoperator (++) bewirkt eine Erhöhung des Werts einer Variablen um den Betrag von 1. Der Dekrementoperator (--) verringert den Wert einer Variablen um den gleichen Betrag. Man sagt, eine Variable wird inkrementiert bzw. dekrementiert. Statt zahl = zahl + 1; können Sie also genauso schreiben zahl++;. Analog sind die Anweisungen zahl = zahl - 1; und zahl--; einander gleichwertig. Hinweis Im Einführungsbeispiel hätten wir also statt number = number + 1; auch kürzer number++; schreiben können.
Beide Operatoren gibt es nicht nur in der eben vorgestellten Postfix-Notation, sondern auch in der Präfix-Notation mit führendem Operator (++zahl bzw. --zahl). Dies macht nur einen Unterschied, falls der Ausdruck, den die Operatoren mit ihren Variablen bilden, nicht alleine in einer Anweisung steht. Ist das der Fall, wird bei der Präfix-Notation zuerst inkrementiert bzw. dekrementiert, bevor weitere Operationen mit der Variablen stattfinden. Umgekehrt haben bei der Postfix-Notation andere Operationen Vorrang: int x = 0, y = 3; x = y++; // Postfix-Notation Console.Write(x); // Ausgabe: 3
Hier wird die Variable y erst nach der Zuweisung inkrementiert. Daher erhält x den Wert 3. int x = 0, y = 3; x = ++y; // Präfix-Notation Console.Write(x); // Ausgabe: 4
197
198
Kapitel 11
Hier wird y vor der Zuweisung inkrementiert, sodass die Variable x den Wert 4 zugewiesen bekommt.
Die do-while-Schleife Während bei der while-Schleife die Schleifenbedingung vor jedem Durchlauf geprüft wird, erfolgt die Prüfung bei der do-while-Schleife erst nach jedem Schleifendurchgang. Das hat zur Folge, dass die Schleife auf jeden Fall zumindest einmal durchlaufen wird. do { Anweisung(en) } while (Bedingung);
Zunächst wird der Anweisungsteil der do-while-Schleife abgearbeitet. Im Anschluss daran wird anhand der Bedingung entschieden, ob die Schleife wiederholt werden soll. Achtung Die do-while-Schleife muss mit einem Semikolon abgeschlossen werden.
Hier sehen Sie eine Implementation des letzten Beispiels (Eingabe einer Zahl, die positiv sein soll) in Form einer do-while-Schleife: double number; do { Console.Write("Bitte positive Zahl eingeben: "); number = Convert.ToDouble(Console.ReadLine()); if(number < 0) Console.WriteLine("Sie haben eine " + "negative Zahl eingegeben."); } while(number < 0);
Da der Benutzer in jedem Fall eine Zahl eingeben soll, erscheint hier der Einsatz der do-while-Schleife gegenüber der while-Schleife eleganter, da auf eine doppelte Benutzereingabe verzichtet werden kann. Bereits die erste Eingabe findet im Schleifenrumpf statt. Gibt der Benutzer eine negative Zahl ein, dann wird die if-Bedingung und auch die Schleifenbedingung (number < 0)
Die for-Schleife
»wahr«. In diesem Fall wird die Ausgabeanweisung des if-Konstrukts ausgeführt und die do-while-Schleife erneut durchlaufen. Tipp Denken Sie an die Anwendung der do-while-Schleife immer dann, wenn die Anweisungen des Schleifenrumpfs zumindest ein Mal ausgeführt werden müssen.
Die for-Schleife Der Einsatz der for-Schleife eignet sich besonders dann, wenn die Gesamtzahl der Wiederholungen von vornherein feststeht. Folgerichtig ist die for-Schleife als typische Zählschleife vorgesehen. Zur Syntax: for (Initialisierung; Bedingung; Aktualisierung) { Anweisung(en) }
Der Kopf der for-Schleife präsentiert sich gegenüber den beiden anderen Schleifenarten etwas ungewöhnlich: for (Initialisierung; Bedingung; Aktualisierung)
Hier wird eine zusätzliche Variable (Schleifenvariable oder Laufvariable genannt) eingesetzt, um die Anzahl der Schleifendurchläufe zu steuern. Beachten Sie, dass Sie diese wie alle anderen Variablen vor Gebrauch definieren müssen. Allerdings muss die Schleifenvariable der for-Schleife vom Datentyp int sein. Es ist üblich, für die Schleifenvariable den Bezeichner i (entsprechend j, k usw. bei verschachtelten Schleifen) und damit ausnahmsweise einen sehr kurzen Namen zu verwenden, absolute Pflicht ist es aber nicht. Im Initialisierungsteil setzen Sie die Schleifenvariable auf einen Startwert (z.B. i = 0). Die Initialisierung wird nur ein Mal – bei Eintritt in die for-Schleife – ausgeführt. Die Bedingung wird wie bei der while-Schleife vor jedem Schleifendurchlauf geprüft. Die Anweisung(en) des Schleifenrumpfs werden so lange wiederholt, wie die Auswertung der Schleifenbedingung true ergibt. Zur Formulierung der Bedingung wird folgerichtig die Schleifenvariable herangezogen (z.B. i < 5).
199
200
Kapitel 11
Im Aktualisierungsteil legen Sie fest, wie sich der Wert der Schleifenvariablen nach jedem Durchlauf ändern soll (z.B. i = i+1 oder kürzer i++ ). Die Aktualisierung erfolgt nach jedem Schleifendurchlauf. Beachten Sie, dass die Aktualisierungsanweisung keinesfalls mit einem Semikolon abgeschlossen werden darf! Hinweis Eigentlich lautet die allgemeine Definition des Schleifenkopfs der forSchleife for (Anweisung; Bedingung; Anweisung). Das heißt, für Anweisung könnte theoretisch jede beliebige Anweisung eingesetzt werden. Syntaktisch in Ordnung wäre sogar for (;Bedingung;) (leere Anweisungen vor und nach der Schleifenbedingung). In der Praxis spielen solche Überlegungen jedoch in aller Regel keine Rolle.
Beispiel: int i; for(i = 1; i < 4; i++) { Console.WriteLine("Dies ist der " + "{0}. Schleifendurchgang", i); }
Ausgabe: Dies ist der 1. Schleifendurchgang Dies ist der 2. Schleifendurchgang Dies ist der 3. Schleifendurchgang
Hinweis Da hier der Schleifenrumpf nur eine einzige Anweisung enthält, dürfen Sie die geschweiften Klammern auch weglassen: int i; for(i = 1; i < 4; i++) Console.WriteLine("Dies ist der " + "{0}. Schleifendurchgang", i);
Der Schleifenkopf ist so eingerichtet, dass die for-Schleife bei Programmausführung insgesamt dreimal durchlaufen wird. Bei Eintritt in die for-Schleife
Die for-Schleife
wird zunächst die Variable i auf den Startwert 1 gesetzt, dann wird die Bedingung geprüft (1 < 4) und die Schleife zum ersten Mal durchlaufen. Nach dem ersten Durchlauf wird i um den Betrag von 1 erhöht (i++) und die Bedingung erneut geprüft (2 < 4). Nach jedem Schleifendurchlauf wird der Wert von i um 1 erhöht, sodass i nacheinander die Werte 1, 2, 3 und 4 annimmt. Nach dem dritten Durchgang besitzt i den Wert 4. Nun ergibt die Bedingungsauswertung (4 < 4) den Wert false und die for-Schleife wird verlassen. Beachten Sie: Die Schleifenvariable der for-Schleife wird häufig – wie im Beispiel zu sehen – auch im Anweisungsteil genutzt. for(i = 1; i < 4; i++) { Console.WriteLine("Dies ist der " + "{0}. Schleifendurchgang", i); }
Sie sollten es aber vermeiden, die Schleifenvariable im Schleifenrumpf zu manipulieren. Achtung Der Wert der Schleifenvariablen sollte ausschließlich im Aktualisierungsteil des Schleifenkopfs verändert werden. Führen Sie also im Schleifenrumpf keine Zuweisungen an die Laufvariable durch, denn dadurch verschlechtert sich die Nachvollziehbarkeit des Quellcodes erheblich. Schließlich gibt es dann neben dem Aktualisierungsteil des Schleifenkopfs weitere Stellen, an denen eine Aktualisierung der Laufvariable stattfindet, was eine erhöhte Anfälligkeit für logische Fehler mit sich bringt.
Sie können die Schleifenvariable mit jedem beliebigen Startwert versehen, wenn Sie die Bedingung und die Aktualisierung entsprechend anpassen. Statt for(i = 1; i < 4; i++) hätte man z.B. auch Folgendes schreiben können: for(i = 0; i < 3; i++)
oder auch: for(i = 3; i > 0; i--)
In der letzten Variante wird i nach jedem Schleifendurchlauf dekrementiert (i--).
201
202
Kapitel 11
Achtung Allerdings müssen Sie dann auch die Anweisungen im Schleifenrumpf anpassen, wenn Sie die Laufvariable dort verwenden. Verwenden Sie z.B. für die Laufvariable einen Startwert von 0 und möchten dennoch bei der Ausgabe bei 1 anfangen zu zählen, gehen Sie beispielsweise so vor: for(i = 0; i < 3; i++) { Console.WriteLine("Dies ist der " + "{0}. Schleifendurchgang", i + 1); }
Gültigkeitsbereich von Variablen Sie dürfen die Schleifenvariable auch im Kopf der for-Schleife definieren. Diese ist dann aber nur innerhalb der Schleifenkonstruktion gültig. Das heißt, Sie können außerhalb des Schleifenblocks nicht auf sie zugreifen: for(int i = 1; i < 4; i++) { Console.WriteLine("Dies ist der " + "{0}. Schleifendurchgang", i); } // hier ist i nicht bekannt Console.WriteLine(i); // FEHLER
Mithin ist es an der Zeit, sich über den Gültigkeitsbereich von Variablen Gedanken zu machen. Es gelten folgende Regeln: • Eine Variable ist gültig in dem Block, in dem sie definiert ist, sowie in allen inneren Blöcken. • Eine Variable darf nur innerhalb ihres Gültigkeitsbereichs verwendet werden. Außerhalb ihres Gültigkeitsbereichs verhält es sich so, als sei die Variable gar nicht vorhanden. Hinweis Statt vom Gültigkeitsbereich einer Variablen spricht man auch von deren Sichtbarkeit. Beide Begriffe werden synonym verwendet.
Die for-Schleife
In folgendem Listing ist die Variable a innerhalb des ganzen Methodenrumpfs von Main() gültig. Die Sichtbarkeit der Variablen b dagegen beschränkt sich auf den Schleifenrumpf der while-Schleife: static void Main(string[] args) { int a = 0; while(a < 5) { int b = 0; b++; Console.WriteLine("Wert von b: " + b); a++; } // end while } // end Main()
Auf der CD-ROM Das Beispiel finden Sie unter Beispiele/Konsolenprogramme auf der Buch-CD (Projektname K11a).
Hinweis Variablen, die in einem Methodenblock definiert sind, bezeichnet man als lokal (zu dieser Methode). Wenn ein Programm aus mehreren Methoden besteht, besitzt jede Methode ihre eigenen lokalen Variablen. Diese sind in anderen Methoden nicht bekannt. Mehr dazu erfahren Sie in Kapitel 13.
Mit dem Verlassen des Blocks, in dem eine Variable definiert ist, endet in der Regel auch ihre Lebensdauer. Das heißt, der von der Variablen beanspruchte Speicherplatz wird vom Programm wieder freigegeben. So wird in obigem Beispiel die Variable b mit jedem Schleifendurchlauf neu angelegt und mit dem Wert 0 initialisiert. Daher zeigt die Ausgabe des Variableninhalts (Console.WriteLine("Wert von b: " + b);) stets den Wert 1, obwohl b mit jedem Schleifendurchlauf hochgezählt wird (b++;).
203
204
Kapitel 11
Abbildung 11.1: Die Variable wird bei jedem Schleifendurchgang neu angelegt und initialisiert, was die Ausgabe erklärt
Hinweis Berücksichtigen Sie, dass nach jedem Schleifendurchgang der zugehörige Schleifenblock tatsächlich verlassen wird, auch im Fall einer anschließenden Wiederholung (diese Aussage gilt für alle Schleifenarten).
Zufallszahlen generieren Als Vorlauf zu weiteren Übungen erfahren Sie hier, wie Sie in Ihren Programmen Zufallszahlen erzeugen. Dazu verwenden Sie die Methode Next() der Klasse Random. Diese Methode wird nicht mit dem Klassennamen, sondern mit einem Objektnamen aufgerufen. Ein Objekt der Klasse Random wird von C# jedoch nicht zur Verfügung gestellt. Das heißt, Sie müssen es zunächst erzeugen, bevor Sie die Methode Next() verwenden können. Neue Objekte einer Klasse erzeugen Sie mit dem Schlüsselwort new gefolgt vom Klassennamen. Diesem schließt sich in Klammern eine Parameterliste an: new Random()
Beim Entstehen eines Klassenobjekts wird eine in jeder Klasse definierte spezielle Methode aufgerufen, der so genannte Konstruktor. Da wir den Konstruktor der Klasse Random ohne Parameter aufrufen, bleiben die Klammern hier leer. Allerdings steht der Ausdruck new Random() nicht für das erzeugte Objekt selbst, sondern lediglich für einen Verweis auf dieses. Um wirklich mit dem neuen Objekt arbeiten zu können, benötigen Sie eine so genannte Referenzvariable, welche diesen Verweis aufnimmt. Random zufall;
Zufallszahlen generieren
Bei obiger Anweisung handelt es sich um die Definition einer Referenzvariablen. Dazu müssen Sie wissen, dass jede Klasse ein Datentyp ist. zufall ist ein frei wählbarer Bezeichner für die Referenzvariable. Nun müssen Sie der Referenzvariablen noch den Verweis auf das RandomObjekt zuweisen: zufall = new Random();
Hinweis Natürlich können Sie zufall auch gleich mit dem Verweis auf das Random-Objekt initialisieren: Random zufall = new Random();
Im Weiteren behandeln Sie die Referenzvariable zufall so, als sei sie das Objekt selbst. Um also die Methode Next() zu verwenden, schreiben Sie zufall.Next();
Hinweis Von daher ist es auch nicht falsch zu sagen, »das Objekt zufall« bzw. die Methode Next() »des Objekts zufall«, obwohl zufall ja eigentlich eine Referenzvariable ist (welche einen Verweis auf das wirkliche Objekt enthält).
Die Methode Next() bewirkt die Erzeugung einer ganzen Zufallszahl und gibt diese als int-Wert zurück. Wenn Sie möchten, dass die Zufallszahl in einem bestimmten Bereich liegt, übergeben Sie Next() zwei Werte für die Unter- und Obergrenze: zufall.Next(1, 11);
Der Wert für die Obergrenze wird von der Methode jedoch exklusiv behandelt. Das heißt, die erzeugten Zahlen liegen im Bereich Untergrenze bis Obergrenze minus 1. Obiger Methodenaufruf bewirkt somit, dass die Zufallszahl einen der Werte von 1 bis einschließlich 10 annimmt. Sie sollten den Rückgabewert der Methode noch in einer int-Variablen festhalten: int los; los = zufall.Next(1, 11);
205
206
Kapitel 11
Hier ein vollständiges Beispiel zur Erzeugung und Anzeige einer Zufallszahl: static void Main(string[] args) { int los; Random zufall = new Random(); los = zufall.Next(1, 11); Console.WriteLine("Die geloste Zahl ist " + los); }
Auf der CD-ROM Das Beispiel finden Sie unter Beispiele/Konsolenprogramme auf der Buch-CD (Projektname K11b).
Hier noch einmal das Wesentliche zusammengefasst: • Die Methode Next() ist in der Klasse Random definiert und erzeugt eine Zufallszahl, die als int-Wert zurückgegeben wird. • Next() wird objektbezogen verwendet. Ein Methodenaufruf wie Random.Next(); // FEHLER
•
•
wäre also fehlerhaft. Der Ausdruck new Random() erzeugt ein neues Objekt der Klasse Random und gibt einen Verweis auf dieses zurück. Das Objekt selbst befindet sich in einem separaten Teil des Arbeitsspeichers (dem so genannten Heap) und ist namenlos. Um es im Weiteren ansprechen zu können, ist es daher notwendig, einen Verweis darauf (seine Speicheradresse) in einer Referenzvariablen festzuhalten. Nach Definition und Aufnahme eines Objektverweises wird eine Referenzvariable so verwendet, als handle es sich um das Objekt selbst.
Wertetypen und Referenztypen Im Groben lassen sich Datentypen wie folgt in Gruppen einteilen: • Elementare Datentypen wie int, double, char usw. • Der Datentyp string stellt dabei einen Sonderfall dar, wie wir gleich sehen werden. • Jeder Klassenname beschreibt ebenfalls einen Datentyp. • Im weiteren Sinne handelt es sich bei Arrays ebenfalls um einen Datentyp. Arrays werden Sie im nächsten Kapitel kennen lernen.
Zufallszahlen generieren
C# unterscheidet zwischen Wertetypen und Referenztypen. Zu Letzteren gehören Klassen, Arrays und der Datentyp string. Die anderen elementaren Datentypen sind Wertetypen. Hinweis Häufig werden vereinfachend auch die Variablen bzw. Objekte eines Datentyps als Werte- bzw. Referenztypen bezeichnet. Von daher könnte man bezüglich der Anweisung int a = 0;
nicht nur sagen, der Datentyp der Variablen a (int) sei ein Wertetyp, sondern ebenfalls: »a ist ein Wertetyp«. Gemeint ist damit, dass a eine Variable eines Wertetyps ist.
Bei den Wertetypen werden die Werte in den Variablen direkt abgelegt. Variablen eines Referenztyps erhalten lediglich einen Verweis auf ein Objekt (eine andere Variable), in welchem (welcher) die tatsächlichen Werte abgelegt werden. Als Beispiel für eine Variable eines Referenztyps (Random) sei hier die Variable zufall des letzten Beispiels genannt. Hinweis »Variablen« eines Klassentyps werden Objekte genannt. Ansonsten sind Variablen und Objekte qualitativ einander ähnlich. Genaueres hierzu erfahren Sie in Kapitel 14.
Obige Ausführungen sind gewissermaßen auch als Vorbereitung auf zukünftige Lerninhalte (Arrays, Klassen) gedacht. Sollten Sie noch nicht alles verstanden haben, so ist das kein Grund zur Beunruhigung. Im Allgemeinen sind Referenzvariablen nach Definition und Verweisaufnahme genauso zu behandeln wie Variablen von Wertetypen (allerdings kann das Wissen um die genannten Zusammenhänge gerade bei komplexeren Problemen sehr von Vorteil sein). Dass Referenztypen so gesehen nichts Besonderes sind, zeigt sich schon darin, dass Sie bereits seit geraumer Zeit – ohne es zu wissen – mit Variablen eines Referenztyps arbeiten.
207
208
Kapitel 11
Es handelt sich um den Datentyp string. Wenn Sie eine Variable dieses Typs vereinbaren, schreiben Sie z.B. string myVar;
Wenn string ein Referenztyp ist, muss myVar also eine Referenzvariable sein. Tatsächlich wird mit der Zuweisung myVar = "C#";
zur Aufnahme der Zeichenkette "C#" ein string-Objekt auf dem Heap angelegt und die Referenzvariable myVar erhält einen Verweis auf dieses Objekt. Das alles geschieht automatisch ohne Zutun des Programmierers. Diesbezüglich bildet der Datentyp string eine Ausnahme. Objekte anderer Referenztypen müssen Sie dagegen explizit mit new erzeugen. Hinweis Der Arbeitsspeicher ist in mehrere Bereiche unterteilt. Für den Programmierer interessant ist vor allem der so genannte Stack sowie der bereits erwähnte Heap. Wenn Speicherplatz für ein Datenobjekt in einem der Bereiche reserviert wird, sagt man, das Objekt wird »auf dem Heap bzw. Stack« angelegt. Referenzvariablen werden wie die Variablen eines Wertetyps auf dem Stack und die tatsächlichen Objekte eines Referenztyps auf dem Heap angelegt. Beide Bereiche unterscheiden sich in der internen Speicherverwaltung – worauf wir hier nicht näher eingehen wollen.
Wenn Sie nun eine Neuzuweisung wie z.B. myVar = "C# ist easy";
durchführen, dann wird das alte Objekt auf dem Heap gelöscht (das heißt, der entsprechende Speicherbereich wird wieder freigegeben). Gleichzeitig wird an einer freien Stelle auf dem Heap ein neues Objekt angelegt, welches die neue (größere) Zeichenkette aufnimmt. Die Referenzvariable myVar erhält nun einen Verweis auf dieses neue Objekt. Nun erklärt sich auch, weshalb Variablen des Datentyps string keine feste Speichergröße beanspruchen (siehe Kapitel 7, Abschnitt »Variablendefinition«). Das einzige Limit von Strings sind die Speicherressourcen des Computers, auf dem das Programm läuft.
Realisieren Sie ein Ratespiel
Zur Wiederholung: • Objekte eines Referenztyps werden mit dem Schlüsselwort new erzeugt (gemeint sind nicht die Referenzvariablen, sondern die tatsächlichen Objekte). Ausnahme hierbei sind Objekte des Datentyps string. Diese werden automatisch angelegt. • Klassen, Arrays sowie der Datentyp string sind Referenztypen, die anderen Standarddatentypen sind Wertetypen. Üblicherweise bezeichnet man auch die Objekte bzw. Variablen dieser Datentypen als Referenz- bzw. Wertetypen. • Eine Variable eines Wertetyps speichert den tatsächlichen Wert. • Eine Referenzvariable speichert einen Verweis auf ein anderes Datenobjekt, in dem die Informationen abgelegt sind. • Variablen eines Wertetyps und Referenzvariablen werden auf dem Stack angelegt. • Das eigentliche Datenobjekt, auf das eine Referenzvariable verweist, liegt auf dem Heap.
Realisieren Sie ein Ratespiel Erstellen Sie zur Übung folgendes Programm: Der Benutzer soll eine vom Programm zufällig erzeugte Zahl zwischen 1 und 20 erraten. Er hat hierfür drei Versuche. Teilen Sie ihm nach jedem erfolglosen Versuch mit, ob seine Zahl zu groß oder zu klein war.
1 Legen Sie in Ihrer Visual C# Express-IDE ein neues Projekt an. 2 Implementieren Sie die Auslosung einer Zufallszahl. Gehen Sie so vor, wie im Abschnitt »Zufallszahlen generieren« beschrieben. Halten Sie die Zufallszahl in einer Variablen glueck fest: static void Main(string[] args) { int glueck; Random zufallszahl = new Random(); glueck = zufallszahl.Next(1, 21); }
209
210
Kapitel 11
Auf der CD-ROM Das vollständige Programm finden Sie unter Beispiele/Konsolenprogramme auf der Buch-CD (Projektname K11c).
Beachten Sie, dass die Methode Next() mit den Argumenten 1 und 21 aufgerufen werden muss. Natürlich bringen Sie die Zufallszahl erst am Schluss zur Anzeige, da sie ja für den Benutzer während der Rateversuche nicht sichtbar sein darf. Bemühen Sie sich zunächst um eine einfache Lösung, bevor Sie sich um Wiederholungsabläufe kümmern. Lassen Sie also den Benutzer eine Zahl eingeben und stellen Sie fest, ob sie mit der Zufallszahl übereinstimmt bzw. ob sie kleiner oder größer ist als diese.
3 Richten Sie eine Zahleneingabe ein. Um die Ratezahl festzuhalten, benötigen Sie eine zusätzliche Variable vom Datentyp int. Nennen Sie diese sinnigerweise tipp: static void Main(string[] args) { int glueck, tipp; Random zufallszahl = new Random(); glueck = zufallszahl.Next(1, 21); Console.WriteLine("\nRATESPIEL 1 aus 20\n"); Console.Write("Ihr Tipp: "); tipp = Convert.ToInt32(Console.ReadLine()); }
4 Vergleichen Sie den Tipp mit der Zufallszahl. Sie müssen insgesamt drei Vergleiche anstellen, einmal auf Gleichheit (tipp == glueck) sowie größer als (tipp > glueck) und kleiner als (tipp < glueck). Dementsprechend sind drei Programmzweige einzurichten: static void Main(string[] args) { int glueck, tipp; Random zufallszahl = new Random(); glueck = zufallszahl.Next(1, 21); Console.WriteLine("\nRATESPIEL 1 aus 20\n"); Console.Write("Ihr Tipp: "); tipp = Convert.ToInt32(Console.ReadLine()); if(tipp == glueck)
Realisieren Sie ein Ratespiel
Console.WriteLine("Richtig geraten!"); if(tipp > glueck) Console.WriteLine("Zu groß"); if(tipp < glueck) Console.WriteLine("Zu klein"); }
Alle drei Bedingungen schließen sich gegenseitig aus. Daher ist sichergestellt, dass beim Programmlauf nur ein Zweig zur Ausführung gelangt. Beachten Sie, dass der Einsatz der switch-Anweisung hier nicht infrage kommt, da diese ausschließlich den direkten Wertevergleich unterstützt.
5 Überzeugen Sie sich vom Zwischenergebnis. Zu Testzwecken ist es möglicherweise sinnvoll, vorübergehend eine Ausgabeanweisung zur Anzeige der Zufallszahl in den Quellcode zu schreiben. So können Sie den Test für alle Programmzweige bequem durchführen und die Stimmigkeit der Ausgabe überprüfen: static void Main(string[] args) { int glueck, tipp; Random zufallszahl = new Random(); glueck = zufallszahl.Next(1, 21); Console.WriteLine("\nZufallszahl: " + glueck); Console.WriteLine("\nRATESPIEL 1 aus 20\n"); Console.Write("Ihr Tipp: "); tipp = Convert.ToInt32(Console.ReadLine()); if(tipp == glueck) Console.WriteLine("Richtig geraten!"); if(tipp > glueck) Console.WriteLine("Zu groß"); if(tipp < glueck) Console.WriteLine("Zu klein"); }
211
212
Kapitel 11
Hinweis Auf den Abdruck des Haltebefehls werden wir im Weiteren verzichten. Übrigens kommen Sie innerhalb Ihrer IDE auch ohne Haltebefehl aus, wenn Sie in der Menüleiste unter Debuggen den Eintrag Starten ohne Debuggen wählen (Tastenkombination (Strg) + (F5)). Dann fügt Visual C# Express für das Konsolenfenster automatisch einen Haltebefehl hinzu. Das Verwenden von Console.ReadLine() hat allerdings den Vorteil, dass sich Ihre Programme (die .exe-Datei der jeweiligen Projekte) dann auch aus dem Windows-Explorer heraus starten lassen.
Abbildung 11.2: Testversion des Programms, die zu ratende Zufallszahl wird zwecks Überprüfungsmöglichkeit mit angezeigt
Nun gehen Sie daran, die Wiederholungsabläufe einzurichten. Als Erstes müssen Sie sich überlegen, welche Anweisungen wiederholt werden sollen.
6 Fassen Sie die zu wiederholenden Anweisungen zu einem Block zusammen. Vermutlich sind Sie zum richtigen Ergebnis gekommen: Die Wiederholung muss mit der Eingabe des Tipps einsetzen und sich auf alle Programmzweige erstrecken: static void Main(string[] args) { int glueck, tipp; Random zufallszahl = new Random(); glueck = zufallszahl.Next(1, 21); Console.WriteLine("\nRATESPIEL 1 aus 20\n"); { Console.Write("Ihr Tipp: "); tipp = Convert.ToInt32(Console.ReadLine()); if(tipp == glueck) Console.WriteLine("Richtig geraten!"); if(tipp > glueck) Console.WriteLine("Zu groß");
Realisieren Sie ein Ratespiel
if(tipp < glueck) Console.WriteLine("Zu klein"); } }
Denken Sie daran, die Anweisungen innerhalb des neu entstandenen Blocks nachträglich einzurücken. Tipp Der Code-Editor der Visual C# 2005 Express Edition sollte nach dem Setzen der Klammern ({ und }) die Einrückungen automatisch durchführen. Andernfalls lassen Sie Ihren Quellcode mit Bearbeiten/Erweitert/Dokument formatieren oder der Tastenkombination (Strg) + (E), (D) neu formatieren (siehe Kapitel 5 unter »Hätten Sie gedacht ...«).
7 Verwenden Sie die for-Schleife, um den Anweisungsblock drei Mal zu wiederholen.
Hinweis Es sei darauf hingewiesen, dass prinzipiell auch eine Umsetzung mit der while- oder do-while-Schleife infrage kommt.
Es bietet sich an, die Schleifenvariable i mit dem Startwert 1 zu versehen. Diese gibt dann die aktuelle Wiederholungszahl wieder, was hilfreich für deren Verwendung im Schleifenrumpf ist. static void Main(string[] args) { int glueck, tipp, i; Random zufallszahl = new Random(); glueck = zufallszahl.Next(1, 21); Console.WriteLine("\nRATESPIEL 1 aus 20\n"); for(i = 1; i < 4; i++) { Console.Write("Ihr {0}. Tipp: ", i); tipp = Convert.ToInt32(Console.ReadLine()); if(tipp == glueck) Console.WriteLine("Richtig geraten!"); if(tipp > glueck) Console.WriteLine("Zu groß"); if(tipp < glueck)
213
214
Kapitel 11
Console.WriteLine("Zu klein"); } }
Nun müssen Sie noch den Fall behandeln, dass ein Benutzer bereits vor dem letzten Rateversuch die richtige Zahl tippt, im Beispiel also beim ersten oder zweiten Rateversuch. Dann muss die Schleife sofort abgebrochen werden. Sie könnten dieses Problem nun lösen, indem Sie die Schleifenbedingung um einen Teilausdruck ergänzen (logische Und-Verknüpfung): for(i = 1; i < 4 && tipp != glueck; i++)
Wenn nun die zuletzt geratene Zahl (tipp) mit der Zufallszahl (glueck) übereinstimmt, dann wird die Schleife nicht mehr ausgeführt. Achtung Beachten Sie, dass Sie bei dieser Lösung die Variable tipp mit einem geeigneten Anfangswert versehen müssen. Ansonsten ruft der erste Lesezugriff auf diese Variable (bei der Bedingungsprüfung vor dem ersten Schleifendurchlauf ) eine Ihnen inzwischen bekannte Fehlermeldung hervor (»Verwendung der nicht zugewiesenen lokalen Variablen tipp«). Außerdem muss der Anfangswert so gewählt werden, dass die Teilbedingung tipp != glueck bei der ersten Auswertung auf jeden Fall false ergibt, da in diesem Fall noch keine Benutzereingabe erfolgt ist. Sie sollten also einen Initialisierungswert verwenden, welcher auf keinen Fall mit einer Zufallszahl übereinstimmen kann (z.B. 0). Andernfalls kann es vorkommen, dass die Schleife kein einziges Mal ausgeführt wird. Die Lösung ist daher recht anfällig für logische Fehler.
Es gibt allerdings eine bequemere und sicherere Lösung unter Anwendung eines Befehls, der Ihnen bereits bei der switch-Anweisung begegnet ist: Mit der break-Anweisung können Sie nicht nur ein switch-Konstrukt, sondern auch alle Schleifenarten unregelmäßig, also vorzeitig, verlassen.
Realisieren Sie ein Ratespiel
8 Beenden Sie nach einem erfolgreichen Rateversuch die for-Schleife unverzüglich: static void Main(string[] args) { int glueck, tipp, i; Random zufallszahl = new Random(); glueck = zufallszahl.Next(1, 21); Console.WriteLine("\nRATESPIEL 1 aus 20\n"); for(i = 1; i < 4; i++) { Console.Write("Ihr {0}. Tipp: ", i); tipp = Convert.ToInt32(Console.ReadLine()); if(tipp == glueck) { Console.WriteLine("Richtig geraten!"); break; } if(tipp > glueck) Console.WriteLine("Zu groß"); if(tipp < glueck) Console.WriteLine("Zu klein"); } }
Achtung Der break-Befehl wird ausschließlich in Verbindung mit Schleifen oder der switch-Anweisung verwendet. Auf eine if-Anweisung entfaltet break keinerlei Wirkung. Im Beispiel ist also allein entscheidend, dass die break-Anweisung innerhalb der for-Schleife steht. Allerdings ist es hier aus logischen Gründen von Bedeutung, die break-Anweisung gerade innerhalb des ersten if-Zweigs (if(tipp == glueck)) zu platzieren.
9 Tippen Sie nun selbst. Vielleicht haben Sie Glück und treffen beim ersten oder zweiten Rateversuch.
215
216
Kapitel 11
Abbildung 11.3: Das Ratespiel in Aktion mit einem Treffer beim zweiten Versuch
Tipp Es steht Ihnen frei, dem Benutzer nach drei vergeblichen Rateversuchen die Loszahl mitzuteilen. Fügen Sie die entsprechende Ausgabeanweisung in diesem Fall unterhalb der for-Schleife ein. Allerdings darf diese Anweisung nur dann zur Ausführung gelangen, wenn die Bedingung (tipp != glueck) zutrifft: } // end for if(tipp != glueck) { Console.WriteLine("Schade, die " + "Loszahl war {0}", glueck); } } // end Main()
Zusätzlich ist es jetzt notwendig, die Variable tipp mit einem Initialisierungswert zu versehen (z.B. 0), da Ihr Compiler ansonsten einen unerlaubten Variablenzugriff vermutet.
10 Sorgen Sie dafür, dass der Benutzer das Spiel beliebig oft wiederholen kann. Dazu schachteln Sie eine weitere Wiederholungsanweisung um die forSchleife. Hier bietet sich eine do-while-Schleife an. Beachten Sie, dass die Anweisung für die Erzeugung einer (neuen) Zufallszahl (glueck = zufallszahl.Next(1, 21);) ebenfalls von der Wiederholung umfasst sein muss.
Realisieren Sie ein Ratespiel
Auf der CD-ROM Diese Version des Programms finden Sie unter Beispiele/Konsolenprogramme auf der Buch-CD (Projektname K11d). static void Main(string[] args) { int glueck, tipp, i; char ch; Random zufallszahl = new Random(); do { glueck = zufallszahl.Next(1, 21); Console.WriteLine("\nRATESPIEL 1 aus 20\n"); for(i = 1; i < 4; i++) { Console.Write("Ihr {0}. Tipp: ", i); tipp = Convert.ToInt32(Console.ReadLine()); if(tipp == glueck) { Console.WriteLine("Richtig geraten!"); break; } if(tipp > glueck) Console.WriteLine("Zu groß"); if(tipp < glueck) Console.WriteLine("Zu klein"); } Console.Write("Weiterspielen? (j)a (n)ein "); ch = Convert.ToChar(Console.ReadLine()); } while(ch == 'j' || ch == 'J'); }
Hinweis Bei verschachtelten Schleifenstrukturen bewirkt break; nur das Verlassen der inneren Schleife. In unserem Beispiel bricht zwar die forSchleife mit dem break-Befehl ab, die Programmausführung verbleibt jedoch nach wie vor in der äußeren do-while-Schleife.
217
218
Kapitel 11
Nun kann der Benutzer bei Eingabe des Zeichens j bzw. J und anschließendem (¢) das Spiel wiederholen.
Abbildung 11.4: Die endgültige Version des Ratespiels erlaubt es, das Spiel beliebig oft zu wiederholen
Hätten Sie gedacht ...
Hätten Sie gedacht ... ... dass man in früheren Programmierzeiten so genannte Warteschleifen einsetzte, um ein Programm an bestimmten Stellen anzuhalten? for(int i = 0; i < 1000000000; i++);
Bei den heutigen Prozessorgeschwindigkeiten haben solche Konstrukte allerdings eher geringe Aussicht auf Erfolg. Komfortabler und genauer ist da schon die Sleep()-Methode von C#. Diese ist in der Klasse Thread im Namensraum Threading – dieser wiederum im Namensraum System – definiert. Um die Methode ohne so genannten qualifizierten Zugriff (im vorliegenden Fall sieht dieser so aus: System.Threading.Thread.Sleep()) verwenden zu können, müssen Sie deshalb den Namensraum Threading mit der using-Direktive einbinden. Jetzt können Sie auf die Methode in kürzerer Schreibweise zugreifen. Beim Aufruf der Methode übergeben Sie dieser einfach die gewünschte Wartezeit in Millisekunden: using System.Threading; // ... static void Main(string[] args) { // ... // Thread für 7 Sekunden anhalten Thread.Sleep(7000); // ... } // ...
Probieren Sie doch beides mal aus! Ein kleines Beispiel mit der Sleep()Methode finden Sie unter Beispiele/Konsolenprogramme auf der Buch-CD (Projektname K11e).
219
Das können Sie schon: Arbeiten mit Strings, Steuerzeichen
75
Variablen, Konstanten, Datentypen, Typkonvertierung
87
Verarbeiten von Benutzereingaben
103
Komplexe Ausdrücke und arithmetische Operatoren
139
Formatieren von Bildschirmausgaben
147
Einrichten von Programmverzweigungen
157
Vergleichsoperatoren und logische Operatoren
158
Prioritätsregeln für Operatoren
162
Schleifen
193
Gültigkeitsbereich und Lebensdauer von Variablen
202
Referenzvariablen
205
Das lernen Sie neu: Verwalten größerer Datenmengen
221
Arrays definieren und initialisieren
222
Arrays mit for-Schleifen durchlaufen
224
Arrays sortieren
226
Die foreach-Schleife
227
Kapitel 12
Arrays Wenn Sie in Ihren Programmen größere Datenmengen verwalten müssen, erweist sich der ausschließliche Gebrauch von Variablen, wie Sie diese bisher verwendet haben, als sehr unhandlich. Bei 100 oder mehr Variablen verliert man im Quellcode schnell den Überblick. Überdies müssten Sie dafür sorgen, dass sich die Variablenbezeichner voneinander unterscheiden. Außerdem ergibt sich in vielen Fällen erst zur Laufzeit, wie viele Datenelemente verarbeitet und entsprechend wie viele Variablen benötigt werden. Solche Aufgabenstellungen sind mit gewöhnlichen Variablen nicht lösbar. Für derartige Fälle stellt C# spezielle Datenstrukturen zur Verfügung, so genannte Arrays. Diese erlauben es, eine nahezu unbegrenzte Anzahl von Werten gleichen Datentyps unter einem einzigen Bezeichner anzusprechen. In diesem Kapitel erfahren Sie nicht nur etwas über Arrays, sondern lernen auch eine weitere Schleifenart kennen, welche wir Ihnen bis jetzt vorenthalten haben und die in Verbindung mit Arrays gute Dienste leistet, die foreach-Schleife.
222
Kapitel 12
Arrays definieren Ein Array ist im Grunde eine Zusammenfassung von mehreren Variablen gleichen Datentyps, welche im Speicher nebeneinander liegen. Der Name eines Arrays bezeichnet dabei die Anfangsadresse des Bereichs, in dem die Daten gespeichert sind. Da es sich bei Arrays um Referenztypen handelt (siehe Kapitel 11, Abschnitt »Wertetypen und Referenztypen«), müssen Sie zunächst ein Array-Objekt anlegen. Den Verweis auf dieses weisen Sie einer Referenzvariablen zu. Mit folgender Anweisung vereinbaren Sie ein Array, in dem 100 Integer-Werte abgelegt werden können: int[] myArr = new int[100];
Dabei bewirkt der Ausdruck new int[100] das Anlegen eines Arrays auf dem Heap. Mit der Zahlenangabe legen Sie fest, wie viele Elemente (Variablen) das Array haben soll. Hinweis Objekte auf dem Heap werden bei ihrer Vereinbarung automatisch initialisiert (entsprechend ihres Datentyps mit 0, 0.0 bzw. ""). Die Elemente eines Integer-Arrays besitzen also mit dessen Definition bereits den Anfangswert 0.
Mit int[] myArr definieren Sie eine Referenzvariable zur Aufnahme des Array-Verweises. Achtung Bei Angabe des Datentyps müssen Sie hinter das Schlüsselwort immer die leeren eckigen Klammern [] setzen. Damit machen Sie deutlich, dass es sich um ein Array handelt (dass die Referenzvariable einen Verweis auf ein Array-Objekt aufnehmen soll); int myVar; // Definition einer int-Variablen int[] myArr; // Definition einer Referenzvariablen // zur Aufnahme eines Array-Verweises
Es sei hier noch einmal darauf hingewiesen, dass eine Referenzvariable nach der Zuweisung (hier: des Array-Verweises) so behandelt wird, als enthielte sie
Arrays definieren
das tatsächliche Objekt (Array). Wir werden deshalb im Folgenden so tun, als sei die Referenzvariable myArr das Objekt selbst. Der Zugriff auf die einzelnen Array-Elemente erfolgt, indem Sie hinter den Bezeichner des Arrays eine Indexangabe in eckige Klammern setzen. Der Index beginnt dabei bei 0. Hinweis Die oben genannte Vereinfachung (dass wir uns so verhalten, als hielten wir mit der Referenzvariablen myArr das tatsächliche Objekt in Händen) wird hier in der Ausdrucksweise deutlich: »hinter den Bezeichner des Arrays« müsste eigentlich lauten »hinter den Bezeichner der Referenzvariablen, welche einen Verweis auf das Array enthält«.
Wenn Sie dem ersten Array-Element den Wert 99 zuweisen wollen, schreiben Sie also myArr[0] = 99;
und um das zweite Element mit dem Wert -29 zu versehen myArr[1] = -29;
Um beispielsweise dem vierten Array-Element die Summe der im ersten und zweiten Element enthaltenen Werte zuzuweisen, lautet die entsprechende Zuweisung myArr[3] = myArr[0] + myArr[1]; myArr[3] enthält danach den Wert 70.
Achtung Da die Indizierung bei 0 beginnt, ist der letzte gültige Index Elementzahl minus 1. Die Angabe myArr[99] stellt also den höchsten erlaubten Elementzugriff dar. Es wurde schließlich mit der Array-Definition nur Platz für 100 Integer-Werte reserviert (welche an den Elementpositionen 0 bis 99 abgelegt werden können). Der Ausdruck myArr[100] ist der Versuch, auf ein 101. Element zuzugreifen. Das Gefährliche an solchen unerlaubten Zugriffen liegt darin, dass sie vom Compiler nicht als Syntaxfehler erkannt werden, sondern sich erst zur Laufzeit einstellen (Laufzeitfehler).
223
224
Kapitel 12
Arrays mit der for-Schleife verwalten Arrays lassen sich bequem in for-Schleifen durchlaufen. Hierzu ein Beispiel. Der Benutzer wird dabei zur Eingabe mehrerer Zahlen aufgefordert, welche Sie anschließend in sortierter Reihenfolge ausgeben.
1 Legen Sie in Ihrer IDE ein neues Projekt an. 2 Fragen Sie den Benutzer, wie viele Werte er eingeben möchte: static void Main(string[] args) { Console.Write("Wie viele Werte möchten" + " Sie eingeben? "); }
3 Speichern Sie die Antwort in einer int-Variablen anzahl: static void Main(string[] args) { int anzahl; Console.Write("Wie viele Werte möchten" + " Sie eingeben? "); anzahl = Convert.ToInt32(Console.ReadLine()); }
4 Definieren Sie ein Array mit anzahl Elementen. Dabei machen Sie es sich zunutze, dass als Angabe für die Elementzahl eines Arrays auch Variablenbezeichner stehen dürfen: static void Main(string[] args) { int anzahl; Console.Write("Wie viele Werte möchten" + " Sie eingeben? "); anzahl = Convert.ToInt32(Console.ReadLine()); int[] ein = new int[anzahl]; }
Nun müssen Sie dafür sorgen, dass die Benutzereingaben in dem Array ein abgelegt werden.
Arrays mit der for-Schleife verwalten
Hinweis Um es noch einmal zu betonen: Genau genommen ist ein eine Referenzvariable, welche einen Verweis auf das eigentliche Array-Objekt enthält. Solche Referenzvariablen werden übrigens auch Array-Variablen genannt.
5 Setzen Sie eine for-Schleife auf. Die Anzahl der Schleifendurchläufe muss der Elementzahl des Arrays entsprechen. Es bietet sich an, die Index-Untergrenze 0 des Arrays als Startwert für die Laufvariable zu wählen: static void Main(string[] args) { int anzahl, i; Console.Write("Wie viele Werte möchten" + " Sie eingeben? "); anzahl = Convert.ToInt32(Console.ReadLine()); int[] ein = new int[anzahl]; for(i = 0; i < anzahl; i++) { } }
6 Nun müssen Sie das Array innerhalb der for-Schleife Element für Element durchlaufen. Verwenden Sie für die Indizierung der einzelnen Elemente die Laufvariable i:
static void Main(string[] args) { int anzahl, i; Console.Write("Wie viele Werte möchten" + " Sie eingeben? "); anzahl = Convert.ToInt32(Console.ReadLine()); int[] ein = new int[anzahl]; for(i = 0; i < anzahl; i++) { ein[i]; } }
Die Laufvariable i nimmt nun nacheinander alle ganzzahligen Werte von 0 bis anzahl - 1 an.
225
226
Kapitel 12
Achtung Denken Sie daran, dass der letzte Index, für den der Anweisungsteil der for-Schleife ausgeführt wird, anzahl - 1 (also 99 bei 100 Elementen) sein muss, was mit der Schleifenbedingung i < anzahl gewährleistet ist. Wenn Sie versehentlich als Schleifenbedingung i <= anzahl wählen, bringt dies einen unerlaubten Array-Zugriff mit sich (z.B. ein[100] bei 100 Elementen).
7 Implementieren Sie die Eingabe der Werte und halten Sie diese im Array fest. static void Main(string[] args) { int anzahl, i; Console.Write("Wie viele Werte möchten" + " Sie eingeben? "); anzahl = Convert.ToInt32(Console.ReadLine()); int[] ein = new int[anzahl]; for(i = 0; i < anzahl; i++) { Console.Write("{0}. Wert: ", i+1); ein[i] = Convert.ToInt32(Console.ReadLine()); } }
Nach Verlassen der for-Schleife sind die Eingabewerte im Array enthalten. Diese sollen nun vor der Ausgabe sortiert werden.
Arrays sortieren Um ein Array in aufsteigender Reihenfolge zu sortieren, verwenden Sie die Methode Sort() der Klasse Array. Als Parameter übergeben Sie dieser das zu sortierende Array. Um das Array ein zu sortieren, schreiben Sie also Array.Sort(ein);
Die foreach-Schleife
Hinweis Um ein Array in absteigender Reihenfolge zu sortieren, rufen Sie im Anschluss an die Methode Sort() die Methode Reverse() auf: Array.Reverse(ein);
8 Sortieren Sie die Zahlen in aufsteigender Reihenfolge: static void Main(string[] args) { int anzahl, i; Console.Write("Wie viele Werte möchten" + " Sie eingeben? "); anzahl = Convert.ToInt32(Console.ReadLine()); int[] ein = new int[anzahl]; for(i = 0; i < anzahl; i++) { Console.Write("{0}. Wert: ", i+1); ein[i] = Convert.ToInt32(Console.ReadLine()); } Array.Sort(ein); }
Die foreach-Schleife Um die einzelnen Werte des Arrays auszugeben, könnten Sie nun wiederum eine for-Schleife verwenden: for(i = 0; i < anzahl; i++) { Console.Write(ein[i] + " "); }
Um ein Array im Lesezugriff zu durchlaufen, gibt es jedoch eine bequemere Möglichkeit, die foreach-Schleife: foreach (Datentyp Bezeichner in Arrayname) { Anweisung(en) }
227
228
Kapitel 12
Bei Datentyp Bezeichner handelt es sich um die Definition einer Schleifenvariablen. Diese muss vom Datentyp der Array-Elemente sein. Allerdings fungiert die Schleifenvariable nicht wie bei der for-Schleife als Zählvariable, sondern sie dient dazu, der Reihe nach die Werte des Arrays aufzunehmen. So bekommt diese Variable nach Schleifeneintritt den Wert des ersten Elements zugewiesen. Nach Ausführung des Anweisungsteils erhält die Schleifenvariable für den zweiten Durchlauf den Wert des zweiten Elements usw. Dies setzt sich so lange fort, bis das letzte Element erreicht ist. Auf diese Weise wird jedes Array-Element genau einmal »angefasst«. Dies erfolgt automatisch, ohne dass Sie sich um die Indexgrenzen des Arrays kümmern müssen. Im Schleifenblock kann die Variable dann verwendet werden. Zuweisungen an die Variable sind jedoch nicht gestattet. Das heißt, Sie können mit der foreach-Schleife ein Array nicht verändern, sondern nur lesend auf die Elemente zugreifen. Um die Werte Ihres Arrays auszugeben, schreiben Sie also foreach (int x in ein) { Console.Write(x + " "); }
Achtung Obwohl sich der Gültigkeitsbereich der Schleifenvariablen auf den Block der foreach-Schleife beschränkt, dürfen Sie im übrigen Code der Main()-Methode keine gleichnamige Variable definieren (Syntaxfehler).
Die foreach-Schleife
9 Geben Sie die sortierten Eingabezahlen aus: static void Main(string[] args) { int anzahl, i; Console.Write("Wie viele Werte möchten" + " Sie eingeben? "); anzahl = Convert.ToInt32(Console.ReadLine()); int[] ein = new int[anzahl]; for(i = 0; i < anzahl; i++) { Console.Write("{0}. Wert: ", i+1); ein[i] = Convert.ToInt32(Console.ReadLine()); } Array.Sort(ein); Console.Write("\n Sortiert: "); foreach (int x in ein) { Console.Write(x + " "); } }
Auf der CD-ROM Das Beispiel zu diesem Kapitel finden Sie unter Beispiele/Konsolenprogramme auf der Buch-CD (Projektname K12).
10 Testen Sie Ihr Programm. Die eingegebenen Zahlen werden anschließend in sortierter Reihenfolge angezeigt.
Abbildung 12.1: Ausgabe der Werte in aufsteigender Reihenfolge
229
230
Kapitel 12
Initialisieren von Arrays Es ist auch möglich, ein Array bereits beim Anlegen mit Werten zu versehen. Um beispielsweise ein Array yourArr mit den Werten 11, 22, 33, 44, 55 zu initialisieren, schreiben Sie int[] yourArr = {11, 22, 33, 44, 55};
Ihr Compiler erzeugt dann automatisch (ohne Angabe des Schlüsselworts new) ein Array-Objekt, dessen Elementzahl der Anzahl der angegebenen Werte entspricht. Die obige Anweisung ist in ihrer Wirkung also äquivalent zu int[] yourArr = new int[5]; yourArr[0] = 11; yourArr[1] = 22; yourArr[2] = 33; yourArr[3] = 44; yourArr[4] = 55;
Im Allgemeinen wird man von dieser Möglichkeit aber nur bei kleineren Datenmengen Gebrauch machen.
Eine kleine Erfolgskontrolle
Eine kleine Erfolgskontrolle •
Wo liegt der Fehler? string[] names = new string[7]; for(int i = 0; i <= 7; i++) { names[i] = Console.ReadLine(); }
• • • •
Welchen Vorteil bietet die foreach-Schleife? Nennen Sie eine Einschränkung in Bezug auf die foreach-Schleife. Welche Anweisung können Sie im Quellcode verwenden, um ein Array mit dem Bezeichner myArr zu sortieren? Was bewirkt die folgende Anweisung? double[] numbers = {0.99, 22.3, 55.01, 17.78};
231
Das können Sie schon: Arbeiten mit Strings, Steuerzeichen
75
Variablen, Konstanten, Datentypen, Typkonvertierung
87
Verarbeiten von Benutzereingaben
103
Komplexe Ausdrücke und arithmetische Operatoren
139
Formatieren von Bildschirmausgaben
147
Programmverzweigungen und Schleifen
157
Vergleichsoperatoren und logische Operatoren
158
Prioritätsregeln für Operatoren
162
Gültigkeitsbereich und Lebensdauer von Variablen
202
Referenzvariablen
205
Arrays
221
Das lernen Sie neu: Auslagern von Programmcode in Methoden
233
Methoden definieren und verwenden
234
Wie Methoden untereinander Informationen austauschen
238
Unterschied zwischen Wert- und Referenzübergabe
241
Rückgabewerte von Methoden festlegen
245
Kapitel 13
Methoden Bis jetzt haben Sie sich ausschließlich innerhalb der Methode »Main()« bewegt. Sie erweitern nun Ihren Aktionsradius, indem Sie eigene Methoden definieren und aufrufen. Damit lässt sich die Effizienz bei der Programmierung erheblich steigern. Zum einen können Sie in Ihrem Programm für mehrfach benötigte Teilaufgaben auf bereits fertige Routinen zurückgreifen. Zum anderen gestaltet sich die Codewartung einfacher. Von etwaigen nachträglich an einer Methode durchgeführten Verbesserungen und Korrekturen profitieren alle Teile Ihres Programms, die auf die entsprechende Methode zugreifen.
234
Kapitel 13
Methoden definieren Eine Methode kann erst dann verwendet werden, wenn sie definiert ist. Die Klassen des .NET Framework stellen Ihnen eine Vielzahl von fertigen Methoden zur Verfügung. Auf diese Methoden können Sie direkt zugreifen, also ohne diese erst definieren zu müssen. Sie können ebenso eigene Methoden erstellen. Zu diesem Zweck schreiben Sie die entsprechende Definition in Ihren Quellcode. Im Weiteren können Sie Ihre eigenen Methoden dann genauso verwenden wie die vordefinierten. Eine Methodendefinition kennen Sie bereits, die der Methode Main(). Wie Sie bereits im Zusammenhang mit Main() erfahren haben, besteht auch jede andere Methode aus einem Methodenkopf und einem Methodenrumpf. In Letzterem befinden sich die Anweisungen. Beschäftigen wir uns nun damit, wie der Methodenkopf auszusehen hat. Zunächst benötigt jede Methode einen Bezeichner, durch den sie eindeutig identifiziert werden kann. Wie Sie wissen, sind Bezeichner unter Beachtung der in Kapitel 7 (Abschnitt »Variablendefinition«) genannten Regeln frei wählbar. Dies gilt natürlich auch für die Bezeichner von Methoden, mit Ausnahme von Main(). Dieser Bezeichner ist vorgegeben, da die Methode Main() bei Programmausführung den Einstiegspunkt bildet. Wenn Sie eine Methode definieren, die eine genau festgelegte Bildschirmausgabe bewirken soll, dann können Sie den Methodenkopf von Main() bis auf den Bezeichner erst einmal übernehmen. Nehmen wir eine Methode, die beim Aufruf die Ausgabe ***** erzeugt (ohne anschließenden Zeilenvorschub). Als Bezeichner für die Methode wählen wir SternAus. Dann lautet der Methodenkopf static void SternAus()
und somit das Grundgerüst der Methodendefinition static void SternAus() { }
Methoden definieren
Genau genommen handelt es sich hier bereits um eine vollständige Methodendefinition. Allerdings bewirkt diese Methode beim Aufruf rein gar nichts. Sie müssen natürlich noch entsprechende Anweisungen in den Methodenblock schreiben. Zur Ausgabe von fünf * ist es mit der Anweisung Console.Write ("*****"); getan, also static void SternAus() { Console.Write("*****"); }
Damit ist die Definition der Methode SternAus() abgeschlossen.
1 Legen Sie ein neues Projekt an. 2 Schreiben Sie die Methodendefinition von SternAus() in die Klasse Program. Dabei spielt es keine Rolle, ob Sie die Methode oberhalb oder unterhalb von Main() in den Quelltext schreiben. Wichtig ist nur, dass sich die Methodendefinition im Block von Program befindet: class Program { static void Main(string[] args) { } static void SternAus() { Console.Write("*****"); } }
Achtung Verschachtelte Methodendefinitionen sind nicht erlaubt. Sie dürfen eine Methode nicht im Block einer anderen Methode definieren.
235
236
Kapitel 13
Methoden verwenden Wie rufen Sie die Methode SternAus() nun auf? Im Prinzip genauso wie z.B. die Methode ReadLine(), also mit dem Methodennamen gefolgt von runden Klammern. Natürlich darf das abschließende Semikolon nicht fehlen, da es sich bei einem reinen Methodenaufruf schließlich ebenfalls um eine Anweisung handelt: SternAus();
Nun stellt sich grundsätzlich die Frage, wo ein Methodenaufruf im Quellcode zu notieren ist. Dabei ist zu berücksichtigen, dass die Programmausführung immer in Main() beginnt. Wenn Sie also wollen, dass eine Methode (genauer: die Anweisungen, welche im Block der betreffenden Methode stehen) unmittelbar nach Programmstart ausgeführt wird, dann setzen Sie den Aufruf an den Anfang von Main().
3 Rufen Sie die Methode SternAus() in Main() auf: class Program { static void Main(string[] args) { SternAus(); } static void SternAus() { Console.Write("*****"); } }
4 Überzeugen Sie sich vom Ergebnis. Auf dem Bildschirm sollte nun die Ausgabe ***** erscheinen. Die Wirkung ist dieselbe, als ob die Anweisung Console.Write("*****"); in Main() stünde. Tatsächlich geschieht Folgendes, wenn ein Methodenaufruf während des Programmlaufs zur Ausführung gelangt: • Die Abarbeitung der Anweisungen der aufrufenden Methode wird vorübergehend angehalten. • Es erfolgt ein Sprung in die aufgerufene Methode. Dort werden die Anweisungen, welche im Rumpf dieser Methode stehen, ausgeführt.
Methoden verwenden
• •
Mit Abarbeitung der letzten Anweisung der aufgerufenen Methode wird diese beendet. Das heißt, der Methodenblock der aufgerufenen Methode wird endgültig verlassen. Es erfolgt ein Rücksprung zur aufrufenden Methode an die Stelle, welche dem Methodenaufruf (gemeint ist die entsprechende Anweisung) unmittelbar folgt. Hinweis Statt aufrufende Methode gebraucht man auch die Bezeichnung aufrufende Umgebung oder einfach Aufrufer.
Der Vorteil, bestimmte Anweisungen in Methoden auszulagern, ergibt sich aus der Tatsache, dass Sie die in einem Methodenblock stehenden Anweisungen bei Bedarf immer wieder zur Ausführung bringen können. Sie benötigen dafür nur eine Programmierzeile für den Methodenaufruf. Um z.B. die Ausgabe ***** START *****
zu erzeugen, können Sie die Methode SternAus() in Main() zwei Mal verwenden: static void Main(string[] args) { SternAus(); Console.WriteLine("\nSTART"); SternAus(); }
Tipp Stellen Sie sich Methoden als eigenständige Programmteile vor, auf die Sie bei Bedarf immer wieder zurückgreifen können.
Obwohl Methoden nicht innerhalb anderer Methoden definiert werden dürfen, ist es dennoch möglich, diese verschachtelt aufzurufen. Das heißt Main() könnte eine Methode A aufrufen, welche wiederum eine Methode B aufruft usw. Beachtenswert ist, dass eine solche Kette immer in der Methode Main()
237
238
Kapitel 13
in Gang gesetzt werden muss. Denn während alle anderen Methoden explizit mittels Anweisung (Methodenaufruf ) gestartet werden, geschieht dies mit der Methode Main() automatisch zu Beginn der Programmausführung. In der Praxis wird man sich des Verfahrens, Code in Methoden auszulagern, vor allem dann bedienen, wenn es sich um fest umrissene Teilaufgaben handelt, z.B. die Durchführung komplexer Berechnungen. Auch ist davon auszugehen, dass im Methodenrumpf nicht nur eine, sondern in der Regel mehrere Anweisungen zusammengefasst sind. Allerdings spielt auch die Häufigkeit des Gebrauchs eine gewisse Rolle. Wenn Sie in Ihren Programmen häufig Sternchenreihen zur Anzeige bringen, dann ist es durchaus sinnvoll, diese Ausgabe als Methode einzurichten. Vermutlich würde Sie die Methode SternAus() in dieser Form jedoch nicht ganz zufrieden stellen. Wenn Sie beispielsweise die Ausgabe ********************** Willkommen im Programm **********************
erzeugen möchten, dann verfügen Sie mit der Methode SternAus() nicht über eine praxistaugliche Lösung. Diese bewirkt bekanntlich die Ausgabe von konstant fünf Sternchen. Sie könnten diese Methode natürlich zwei Mal verwenden, um z.B. zehn Sternchen auf den Bildschirm zu bringen, müssten die Ausgabe dann aber entsprechend anpassen, falls etwa 13 Sternchen erwünscht sind. In diesem Fall wäre es effizienter, auf die Methode SternAus() zu verzichten und die Ausgabe auf herkömmliche Weise mit Write() vorzunehmen. Wenn es Ihnen jedoch gelingt, die Methode SternAus() flexibel zu gestalten, dann wird sie sich für den genannten Zweck als durchaus nützlich erweisen.
Parameterübergabe Beachten Sie, dass die Methode SternAus() in der aktuellen Fassung keiner Informationen von außen bedarf. Sie »weiß« auch so, was sie zu tun hat, nämlich genau fünf Sternchen auszugeben. Sie werden die Methode nun aber so einrichten, dass Sie ihr beim Aufruf mitteilen können, wie viele Sternchen sie ausgeben soll und zwar in der Weise, dass z.B. der Methodenaufruf SternAus(10);
Parameterübergabe
die Methode veranlasst, zehn Sternchen auszugeben. Dazu müssen Sie die Methodendefinition entsprechend anpassen. Sorgen Sie zunächst dafür, dass die Methode einen Integer-Wert aufnehmen kann. Wie Ihnen bekannt ist, benötigt man für das Festhalten eines Werts im Code eine Variable, im vorliegenden Falle eine vom Typ int. Variablen, welche beim Methodenaufruf Übergabewerte erhalten sollen, definieren Sie im Kopf der Methode und zwar zwischen den runden Klammern. Wenn Sie für Ihre Variable den Bezeichner anz (steht für Anzahl der gewünschten Sternchen) wählen, dann sieht Ihre Methode erst einmal wie folgt aus: static void SternAus(int anz) { Console.Write("*****"); }
Einige Begriffserklärungen: • Die Variablendefinitionen bzw. Variablen bezeichnet man als Parameter der Methode. Um sie von den Übergabewerten des Methodenaufrufs zu unterscheiden, spricht man auch von formalen Parametern. • Die Gesamtheit der in Klammern angegebenen formalen Parameter heißt Parameterliste. • Die Werte, welche eine Methode beim Aufruf bekommt, werden Übergabewerte, Argumente, (Übergabe-)Parameter oder präziser aktuelle Parameter genannt.
Hinweis Wenn Sie eine Methode so einrichten wollen, dass sie beim Aufruf mehrere Werte erhalten soll, dann trennen Sie die formalen Parameter (Variablendefinitionen) durch Kommata voneinander. Dabei dürfen die einzelnen Parameter auch unterschiedlichen Datentyps sein: static void Methodenname(int a, double b, string c) { … }
Allerdings nimmt die Methode SternAus() in der obigen Form den Übergabewert zwar entgegen, verarbeitet diesen aber nicht im Anweisungsteil. Dies ergibt natürlich wenig Sinn.
239
240
Kapitel 13
Sie müssen die Methode nun so einrichten, dass auch das gewünschte Ergebnis zustande kommt, wenn sie aufgerufen wird. Dabei sorgen Sie mit einer for-Schleife dafür, dass die Anweisung Console.Write("*"); (Ausgabe eines Sternchens) entsprechend oft wiederholt wird. Die Wiederholungszahl muss sich dabei nach dem Übergabewert richten: static void SternAus(int anz) { for(int i = 1; i <= anz; i++) Console.Write("*"); }
Mit der Schleifenbedingung (i <= anz) und dem Startwert der Laufvariablen i legen Sie fest, dass die Anweisung Console.Write("*"); genau anz Mal ausgeführt wird. Die Methode wird also beim Aufruf dem Übergabewert entsprechend viele Sternchen ausgeben.
5 Ändern Sie die Methodendefinition von SternAus() wie beschrieben und testen Sie die Methode in Main():
class Program { static void Main(string[] args) { Console.WriteLine(); SternAus(10); Console.WriteLine("\nKapitel 13"); SternAus(10); Console.WriteLine(); for(int i = 9;i > 0; i--) { SternAus(i); Console.WriteLine(); } } static void SternAus(int anz) { for (int i = 1; i <= anz; i++) Console.Write("*"); } }
Parameterübergabe
Auf der CD-ROM Das Beispiel finden Sie unter Beispiele/Konsolenprogramme auf der Buch-CD (Projektname K13a).
6 Bringen Sie das Programm zur Ausführung. Sie werden feststellen, dass die Methode SternAus() nun wie gewünscht arbeitet. Wenn Sie die obige Main()-Methode übernommen haben (ein einfacherer Test hätte es natürlich auch getan), erhalten Sie die in der Abbildung dargestellte Ausgabe.
Abbildung 13.1: Aufruf der Methode »SternAus()« mit der Anzahl der auszugebenden Sternchen aus einer Schleife heraus
Call by value Beachten Sie, dass die Methoden Main() und SternAus() zwei getrennte Gültigkeitsbereiche beschreiben (siehe Kapitel 11, Abschnitt »Gültigkeitsbereich von Variablen«). Das heißt, lokale Variablen der einen Methode sind in der anderen nicht bekannt. Somit wäre der Fall denkbar, dass in beiden Methoden eine Variable anz definiert ist (mit anderen Worten: ... dass jede Methode ihre eigene Variable anz besitzt). Hinsichtlich der Lebensdauer lokaler Variablen gilt: Sobald ein Methodenblock endgültig verlassen wird, werden die lokalen Variablen dieser Methode gelöscht (bei den formalen Parametern handelt es sich ebenfalls um lokale Variablen). Die Variable anz wird also mit jedem Verlassen der Methode SternAus() gelöscht und mit jedem Aufruf dieser Methode neu angelegt.
241
242
Kapitel 13
Beendet ist eine Methode, wenn die letzte Anweisung im Methodenrumpf abgearbeitet ist. Halten wir noch einmal fest, was bei der obigen Parameterübergabe geschieht: • Beim Aufruf der Methode SternAus() wird ein Integer-Wert übergeben. • In der aufgerufenen Methode entsteht eine Variable (anz). • Die Variable anz bekommt den Übergabewert zugewiesen. Nun ist es möglich, dass beim Methodenaufruf als Argument ein Variablenbezeichner auftritt, z.B. SternAus(zahl); (wobei zahl eine zuvor definierte und zugewiesene Variable vom Datentyp int sei). Dies sollte nichts Neues für Sie sein, da Sie wissen, dass überall, wo im Quellcode ein Wert stehen muss, dieser auch aus einer Variablen herausgelesen werden kann. Lassen Sie uns nun klären, was mit einer im Aufruf bezeichneten Variablen geschieht, wenn der Formalparameter im Rumpf der aufgerufenen Methode verändert wird. Zur Demonstration definieren wir eine Variable anz auch in Main() und verwenden diese dann beim Aufruf von SternAus(). class Program { static void Main(string[] args) { int anz = 7; SternAus(anz); Console.WriteLine("In Main(): " + anz); } static void SternAus(int anz) { anz = 55; Console.WriteLine("In SternAus(): " + anz); } }
Auf der CD-ROM Das Beispiel finden Sie unter Beispiele/Konsolenprogramme auf der Buch-CD (Projektname K13b).
Parameterübergabe
Nach dem weiter oben Dargelegten müsste Ihnen klar sein, dass … • sowohl die Methode SternAus() als auch die Methode Main() eine eigene Variable anz besitzt, • durch den Methodenaufruf SternAus(anz); die Variable anz von Main() nicht als solche der Methode SternAus() bekannt gemacht wird. Der Methode SternAus() wird lediglich eine Kopie des in der Variablen (anz von Main()) enthaltenen Werts übergeben. • die Zuweisung im Block von SternAus() (anz = 55;) an die Variable anz dieser Methode erfolgt, was jedoch keine Auswirkung auf die Variable anz der Methode Main() hat. Das heißt, die Variable anz der Methode Main() wird durch diese Zuweisung nicht verändert. Wenn Sie obiges Programm kompilieren und ausführen, erhalten Sie daher die folgende Ausgabe: In SternAus(): 55 In Main(): 7
Diese Form der Parameterübergabe wird Call by value oder schlicht Wertübergabe genannt.
Call by reference Beachten Sie, dass die Form der Parameterübergabe nur relevant ist für den Fall, dass … • beim Methodenaufruf als Argument kein Literal, sondern der Bezeichner einer Variablen verwendet wird, • im Methodenrumpf der aufgerufenen Methode Zuweisungen an den entsprechenden Formalparameter erfolgen, dieser also tatsächlich verändert wird. Wenn Sie nun beim Methodenaufruf einen Referenztyp verwenden, dann wird ein Verweis auf ein Datenobjekt übergeben. Damit wird das tatsächliche Datenobjekt der aufgerufenen Methode bekannt gemacht. Das bedeutet, Aufrufer und aufgerufene Methode arbeiten mit demselben Objekt. Veränderungen, welche im Rumpf der aufgerufenen Methode stattfinden, besitzen also dauerhafte Wirkung. Diese Art der Parameterübergabe wird Referenzübergabe bzw. Call by reference genannt. In folgendem Beispiel werden beim Aufruf der Methode ZuNull() alle Elemente des Arrays myArr auf den Wert 0 gesetzt. Der Methode sind beim Aufruf entsprechend ihrer Definition zwei Argumente zu übergeben, ein Verweis auf ein Array-Objekt und ein Integer-Wert, welcher für die Array-Obergrenze steht:
243
244
Kapitel 13
class Program { static void Main(string[] args) { int[] myArr = {33, 44, 55, 66, 77}; Console.Write("Vor ZuNull(): "); for(int i = 0; i < 5; i++) Console.Write(myArr[i] + " "); ZuNull(myArr, 4); Console.Write("\nNach ZuNull(): "); for(int i = 0; i < 5; i++) Console.Write(myArr[i] + " "); } static void ZuNull(int[] arr, int ogrenze) { for(int i = 0; i <= ogrenze; i++) arr[i] = 0; } }
Ausgabe: Vor ZuNull(): 33 44 55 66 77 Nach ZuNull(): 0 0 0 0 0
Auf der CD-ROM Das Beispiel finden Sie unter Beispiele/Konsolenprogramme auf der Buch-CD (Projektname K13c).
Die Ausgabe beweist, dass das Array von der Methode ZuNull() nachhaltig verändert worden ist. Wenn Sie möchten, dass auch Wertetypen per Call by reference übergeben werden, müssen Sie in der Methodendefinition vor dem Datentyp des entsprechenden Formalparameters und später im Aufruf das Schlüsselwort ref angeben: class Program { static void Main(string[] args) { int z1 = 109, z2 = 77; Console.WriteLine("\nVor dem Tausch: "
Rückgabewerte von Methoden
+ "z1 = {0}, z2 = {1}", z1, z2); Tausche(ref z1, ref z2); Console.WriteLine("Nach dem Tausch: " + "z1 = {0}, z2 = {1}", z1, z2); } static void Tausche(ref int a, ref int b) { int temp = a; a = b; b = temp; } }
Ausgabe: Vor dem Tausch: z1 = 109, z2 = 77 Nach dem Tausch: z1 = 77, z2 = 109
Auf der CD-ROM Das Beispiel finden Sie unter Beispiele/Konsolenprogramme auf der Buch-CD (Projektname K13d).
Der Aufruf von Tausche() in Main() bewirkt hier, dass die Werte der Variablen z1 und z2 miteinander vertauscht werden.
Rückgabewerte von Methoden Mit den Methoden, die wir in diesem Kapitel definiert und verwendet haben, erfolgte die Kommunikation allenfalls in eine Richtung. Das heißt, die Methoden wurden beim Aufruf mit Informationen versorgt, es wurden aber keine Daten an den Aufrufer zurückgegeben. Wie man Rückgabewerte von Methoden verarbeitet, wissen Sie bereits. Nun erfahren Sie, wie man Methoden so definiert, dass sie einen Rückgabewert liefern. Folgende Methode berechnet die dritte Potenz eines zu übergebenden double-Werts und gibt das Ergebnis auf den Bildschirm aus: static void Pot3(double betrag) { Console.WriteLine(betrag * betrag * betrag); }
245
246
Kapitel 13
Diese Methode schränkt den Programmierer jedoch sehr ein, da jeder Aufruf automatisch mit einer Bildschirmausgabe verbunden ist. Manchmal ist das zwar sicherlich beabsichtigt. In der Mehrzahl der Fälle wird man sich aber von dieser Methode allein das Berechnungsergebnis wünschen, ohne Bildschirmanzeige. Andernfalls ist es nicht möglich, den Rückgabewert einer Methode in einem komplexen Ausdruck weiterzuverarbeiten. Bei Bedarf kann der Programmierer den Rückgabewert (das Berechnungsergebnis) in der aufrufenden Methode immer noch mit Write() bzw. WriteLine() zur Anzeige bringen. Um in der Methodendefinition einen Rückgabewert festzulegen, gehen Sie wie folgt vor: • Schreiben Sie das Schlüsselwort für den Datentyp des Rückgabewerts vor den Bezeichner der Methode. • Sie müssen es im Methodenrumpf so einrichten, dass die zuletzt ausgeführte Anweisung der Methode der return-Befehl ist. Hinter dem Schlüsselwort return geben Sie den Rückgabewert an. Dieser kann durch einen beliebigen Ausdruck des Datentyps repräsentiert werden, den Sie im Methodenkopf angegeben haben. Das heißt natürlich, dass hier auch ein Literal oder ein Variablenbezeichner stehen darf (siehe Kapitel 9, Abschnitt »Ausdrücke und arithmetische Operatoren«). Um die Methode Pot3() mit Rückgabewert – und ohne Bildschirmausgabe – einzurichten, schreiben Sie also wie folgt: static double Pot3(double betrag) { return betrag * betrag * betrag; }
Das Gesagte gilt allerdings nur für Methoden, welche tatsächlich einen Rückgabewert besitzen. Für Methoden ohne Rückgabewert gilt: • Vor dem Methodenbezeichner muss das Schlüsselwort void stehen (void, engl. leer). • Die Verwendung des return-Befehls ist optional. Auf keinen Fall darf dem Schlüsselwort return eine Wertangabe folgen (es ist also return; zu schreiben). Hinweis Die Bedeutung des Schlüsselworts static werden Sie im nächsten Kapitel erfahren.
Rückgabewerte von Methoden
Im Weiteren können Sie die Methode Pot3() verwenden wie jede andere Methode (mit Rückgabewert) auch. Denken Sie daran, dass ein Methodenaufruf im Code gleichzeitig für den Rückgabewert der betreffenden Methode steht (siehe Kapitel 7, Abschnitt »Die Methode ReadLine()«). Ein Aufruf der Methode Pot3() repräsentiert sogar ausschließlich ihren Rückgabewert, da die Methode sonst keine weiteren Aktionen durchführt. Hinweis Tatsächlich entsteht beim Verlassen einer Methode gegebenenfalls eine temporäre, namenlose Variable zur Aufnahme des Rückgabewerts (daher ist auch die Datentypangabe im Methodenkopf notwendig). Diese wird mit der nächsten Anweisung sofort wieder gelöscht. Um den Rückgabewert in der aufrufenden Umgebung dauerhaft festzuhalten, müssen Sie diesen daher sogleich einer Variablen zuweisen: myDouble = Pot3(9.5);
Hier ein Beispiel, in dem die Methode Pot3() verwendet wird: class Program { static double Pot3(double betrag) { return betrag * betrag * betrag; } static void Main(string[] args) { double zahl, kubik; Console.Write("Eingabe: "); zahl = Convert.ToDouble(Console.ReadLine()); kubik = Pot3(zahl); Console.WriteLine("Kubikzahl: {0:N}", kubik); } }
Auf der CD-ROM Das Beispiel finden Sie unter Beispiele/Konsolenprogramme auf der Buch-CD (Projektname K13e).
247
248
Kapitel 13
Im Beispiel wird der Rückgabewert von Pot3() in der Variablen kubik gespeichert und diese dann für die Ausgabe herangezogen. Allerdings könnten Sie hier den Rückgabewert auch gleich in der Ausgabe verwenden: Console.WriteLine("Kubikzahl: {0:N}", Pot3(zahl));
Wenn Sie beim Programmlauf z.B. 19 eingeben, erhalten Sie die Ausgabe 6.859,00.
Abbildung 13.2: Berechnung der dritten Potenz über die selbst definierte Methode »Pot3()«
Hinweis Natürlich können Sie die Methode Pot3() auch in einem komplexen Ausdruck wie z.B. 2 * (Pot3(4.5) + 10) verwenden.
Eine kleine Erfolgskontrolle
Eine kleine Erfolgskontrolle • • • •
Wann spricht man beim Methodenaufruf von Call by value? Was bedeutet Call by reference? Welches Schlüsselwort muss im Kopf einer Methode auf jeden Fall verwendet werden, wenn die Methode ohne Rückgabewert definiert ist? Was können Sie anhand des folgenden Methodenkopfs über die Methode xy() aussagen? static int xy(double a, string b)
•
Welche Ausgabe erzeugt folgendes Programm? class Program { static void B(string myString) { Console.Write(myString); } static void Main(string[] args) { A(); Console.WriteLine("weiter"); } static void A() { Console.Write("Jetzt "); B("geht es "); } }
•
Finden Sie den Fehler in folgender Methodendefinition: static void Quad(int a) { Console.WriteLine("Das Quadrat von {0} ist {1}" , a, a * a); return "ENDE"; }
249
Das können Sie schon: Arbeiten mit Strings, Steuerzeichen
75
Variablen, Konstanten, Datentypen, Typkonvertierung
87
Verarbeiten von Benutzereingaben
103
Komplexe Ausdrücke, Operatoren, Operatorpriorität
139
Formatieren von Bildschirmausgaben
147
Programmverzweigungen und Schleifen
157
Gültigkeitsbereich und Lebensdauer von Variablen
202
Referenzvariablen
205
Arrays
221
Methoden definieren und verwenden
234
Das lernen Sie neu: Klassen definieren
254
Im Code Objekte von Klassen erzeugen
258
Mit Objekten arbeiten
259
Konstruktoren und Destruktoren
267
Bedeutung von static-Elementen
272
Kapitel 14
Klassen und Objekte Das Konzept der objektorientierten Programmierung erlaubt es, Objekte einer realen oder gedachten Wirklichkeit in Ihren Programmen abzubilden. Die objektorientierte Programmierung kommt der menschlichen Denkweise näher als herkömmliche Programmierung und bietet weitere Vorteile, etwa was Wartungsaufwand, Stabilität und Codetransparenz angeht. In diesem Kapitel ist es nun so weit. Jetzt dreht sich alles um Klassen und Objekte. Das Verständnis für die objektorientierte Programmierung braucht erfahrungsgemäß etwas Zeit. Nehmen Sie sich diese und lassen Sie sich bei anfänglichen Schwierigkeiten nicht entmutigen. Mit fortschreitender Übung werden Ihnen die Vorteile der objektorientierten Programmierung nach und nach deutlich werden.
252
Kapitel 14
Vorüberlegungen Für das Verständnis der OOP (Abkürzung für »objektorientierte Programmierung«) ist es sehr wichtig, zwischen Datentypen und Datenobjekten zu differenzieren. • Ein Datenobjekt (dies kann auch eine Variable sein) bezeichnet einen reservierten Platz im Arbeitsspeicher, an dem Informationen abgelegt werden können. • Ein Datentyp charakterisiert ein Datenobjekt, indem er festlegt, wie viel Platz dieses im Arbeitsspeicher beansprucht bzw. welche Art von Werten in einem Datenobjekt dieses Typs abgelegt werden können. Ferner, und dies ist für das Verständnis der OOP sehr bedeutsam, bestimmt ein Datentyp, welche Operationen sich auf Daten dieses Typs ausführen lassen. Beispielsweise ist es möglich, Zeichenketten miteinander zu verknüpfen. Anders sieht es in Verbindung mit Strings dagegen mit Rechenoperationen aus: Diese sind nicht zulässig. Mit anderen Worten: Die Zugehörigkeit eines Datenobjekts zu einem bestimmten Datentyp sagt aus, welche Operationen mit den Daten durchführbar sind. Mit »Daten« sind die an der Speicherstelle (dem Datenobjekt) abgelegten Informationen (Werte) gemeint. Eine Klassendefinition enthält sowohl Datenelemente als auch Methoden, die festlegen, welche Aktionen mit diesen Datenelementen durchgeführt werden können. Die Klassendefinition beschreibt somit einen eigenen Datentyp, legt also fest, wie sich zukünftige Objekte vom Typ dieser Klasse verhalten. Hinweis Die Welt der objektorientierten Programmierung und speziell die von C# und dem .NET hat ihre eigene Sprache. Datenobjekte einer Klasse werden nicht Variablen, sondern Objekte bzw. Instanzen dieser Klasse genannt. Dagegen bezeichnet man die in einer Klasse definierten Datenelemente in C# als »Felder« – und nicht etwa als »Eigenschaften« wie in anderen Programmiersprachen (der Begriff Eigenschaften ist in C# für spezielle Methoden vorgesehen).
Vorüberlegungen
Eine Klassendefinition ist damit sozusagen der Bauplan für Objekte dieser Klasse, genauso wie etwa der Datentyp double die Schablone für zukünftige Variablen dieses Typs darstellt. Entwerfen wir gedanklich eine Klasse Rechteck. Ein Rechteck lässt sich anhand seiner Länge und Breite für die meisten Zwecke ausreichend beschreiben. Also definieren wir in der Klasse ein Datenelement (ein Feld) für die Länge und eines für die Breite. Als Bezeichner für die beiden Felder wählen wir Laenge und Breite. Nun überlegen wir, welche Operationen (Methoden) die Klasse Rechteck mit ihren Datenelementen (den Feldern) ermöglichen soll. Wir entscheiden uns für eine Operation zum Berechnen der Rechtecksfläche sowie eine Operation, welche diese nicht nur berechnet, sondern auch ausgibt. Die Methoden nennen wir BerechneFlaeche() und ZeigeFlaeche(). Somit besitzt jedes Objekt dieser Klasse (oder vereinfacht ausgedrückt: »jedes Rechteck«), das Sie später im Code erzeugen, die Felder Laenge und Breite sowie die Methoden BerechneFlaeche() und ZeigeFlaeche(). Hinweis Eine Klassendefinition stellt in der Regel die programmiersprachliche Wiedergabe eines realen Gebildes dar. Da die Felder die Eigenschaften zukünftiger Objekte beschreiben, erscheint der ursprünglich in der OOP verwendete Begriff Eigenschaft bzw. Attribut für ein Datenelement treffender. Dies wird deutlich, wenn man sagt: »ein Objekt der Klasse Rechteck besitzt die Eigenschaften Laenge und Breite«. Geht man von der C#-Terminologie aus, so ist jedoch zumindest der Begriff Eigenschaft in diesem Zusammenhang nur im landläufigen Sinne zu verstehen. Im fachsprachlichen Sinne sind Eigenschaften in C# spezielle Methoden für den Zugriff auf Felder (dazu später mehr).
Bedenken Sie, dass jedes Rechteck-Objekt seine eigenen Attribute (Felder) und gewissermaßen auch seine eigenen Methoden besitzt. So ist z.B. das eine Objekt 20 cm lang und 11 cm breit. Dann gibt die Methode ZeigeFlaeche() den Wert 220 aus. Für ein Rechteck, welches z.B. 5 cm lang und 10 cm breit ist, bringt die Methode ZeigeFlaeche() dagegen den Wert 50 zur Anzeige. Also muss jede Methode (hier: ZeigeFlaeche()) die speziellen Attribute (Laenge: 5, Breite: 10) ihres Objekts kennen.
253
254
Kapitel 14
Beachten Sie, dass die bloße Definition einer Klasse noch keinerlei Auswirkungen nach sich zieht. Damit steht lediglich ein neuer Datentyp zur Verfügung. Erst wenn der Programmierer im Code Objekte dieses Datentyps (der Klasse) erzeugt, können die Felder und Methoden der Klasse genutzt werden. Nehmen wir zum Vergleich den Datentyp int. Seine bloße Existenz hat noch keine Auswirkung auf Ihre Programme. Dies ändert sich, sobald Sie im Code Variablen dieses Typs definieren. Dann bestimmt der Bauplan int, wie sich diese Variablen verhalten. So kann eine Integer-Variable nur ganzzahlige Werte aufnehmen. Die Variable darf z.B. als Operand an einer arithmetischen Operation beteiligt sein, was etwa bei einer char-Variablen nicht der Fall ist.
Klassendefinition Sie werden nun eine Klasse Rechteck, wie sie oben beschrieben ist, definieren.
1 Legen Sie in Ihrer IDE ein neues Projekt an. Eine Klassendefinition wird durch das Schlüsselwort class und einen Bezeichner für die Klasse eingeleitet. Dem schließt sich der Klassenblock an: class Rechteck { }
2 Fügen Sie Ihrem Projekt eine neue Klasse mit dem Bezeichner Rechteck hinzu. Sie könnten dazu nun im Code der Quelldatei Program.cs die neue Klasse entweder oberhalb oder unterhalb der Klasse Program aufsetzen. Übersichtlicher und bequemer ist es aber, die Klasse in eine eigene Quellcode-Datei auszulagern. Dies lässt in der Visual C# Express-Umgebung sehr einfach mit dem Menübefehl Projekt/Klasse hinzufügen... durchführen. Wählen Sie im folgenden Dialog unter Vorlagen: die Option Klasse, geben Sie in das untere Textfeld für den Namen der Klasse Rechteck ein und klicken Sie dann auf Hinzufügen.
Klassendefinition
Abbildung 14.1: Hinzufügen einer neuen Klasse
Daraufhin wird im Projektmappen-Explorer der Eintrag für die neue QuellcodeDatei Rechteck.cs hinzugefügt. Tipp Um eine Datei im Code-Editor zu bearbeiten, klicken Sie den entsprechenden Eintrag im Projektmappen-Explorer doppelt an oder benutzen Sie die Reiter in der oberen Leiste (im Beispiel »Rechteck.cs« und »Program.cs«), um zwischen den Dateien zu wechseln.
Abbildung 14.2: Visual C# Express nimmt Ihnen viel Arbeit ab, wenn Sie den Code für eine neue Klasse in eine separate Datei auslagern
3 Definieren Sie in der Klasse Rechteck die Felder Laenge und Breite.
255
256
Kapitel 14
Schreiben Sie dazu zwei Variablendefinitionen in den Block Ihrer neuen Klasse (Sie können daraus schließen, dass es sich bei den Feldern einer Klasse im Grunde um nichts anderes handelt als um Variablen). Damit besitzen Ihre Rechtecke die Eigenschaften Länge und Breite: class Rechteck { public double Laenge; public double Breite; }
Das Schlüsselwort public vor der Datentypangabe ist für den direkten Zugriff auf die Datenelemente notwendig. Direkter Zugriff bedeutet, dass Objekte einer Klasse ihre öffentlichen (public) Datenelemente unmittelbar ansprechen können. Das Gegenstück zu public ist private. Auf ein private-Element einer Klasse kann von Klassenobjekten nicht auf direktem Wege zugegriffen werden. Dies ist auch die Voreinstellung. Das heißt, wenn Sie den Zugriffsspezifizierer (public bzw. private) bei der Definition eines Feldes weglassen, gilt dieses als private. Diese Aussagen gelten nicht nur für die Datenelemente einer Klasse, sondern auch für deren Methoden.
4 Richten Sie in der Klasse die Methoden BerechneFlaeche() und ZeigeFlaeche() ein.
Dabei soll die Methode BerechneFlaeche() die Rechtecksfläche als Rückgabewert liefern, während die Methode ZeigeFlaeche() die Rechtecksfläche nur ausgeben soll: class Rechteck { public double Laenge; public double Breite; public double BerechneFlaeche() {return Laenge * Breite;} public void ZeigeFlaeche() { Console.Write("Rechtecksfläche: "); Console.WriteLine(Laenge * Breite); } }
Klassendefinition
Tipp Es widerspricht keinem guten Programmierstil, kleinere Methoden – wie hier BerechneFlaeche() – in Kompaktform zu notieren.
Die Methode BerechneFlaeche() liefert die Rechtecksfläche als Rückgabewert. Die Methode ZeigeFlaeche() bringt diesen zur Anzeige. Diese Methode ist jedoch in ihrer Verwendung sehr einschränkend und wird daher wohl nicht so häufig von Klassenobjekten in Anspruch genommen werden. Von diesem Gesichtspunkt aus gehört BerechneFlaeche() eher zur minimalen Grundausstattung einer Klasse Rechteck, die Methode ZeigeFlaeche() ist eher als komfortabler Zusatz anzusehen. Berücksichtigen Sie aber, dass es keinen Schaden macht, Elemente in die Klasse zu integrieren, auch wenn davon auszugehen ist, dass diese später nur wenig gebraucht werden. Objekte der Klasse werden sowieso nur auf diejenigen Elemente zugreifen, welche zum aktuellen Zeitpunkt tatsächlich benötigt werden. Und wenn Sie die Klasse in einem anderen Programm verwenden, sind Sie vielleicht froh, wenn Ihnen die eine oder andere Methode zur Verfügung steht. Machen Sie sich auch deutlich, dass Sie sich um die Klasse selbst, wenn sie einmal eingerichtet ist, nicht mehr kümmern müssen. Genauso wenig, wie Sie sich z.B. Gedanken machen, wie die vordefinierte Klasse Console eingerichtet ist. Sie verwenden einfach deren Methoden (Write(), WriteLine(), ReadLine()) und fahren damit sehr bequem. Ebenso verhält es sich mit selbst definierten Klassen. Sind diese einmal implementiert, stellen sie für den Programmierer einfach eine willkommene Hilfe bzw. Erweiterung seiner Möglichkeiten dar. Betrachten Sie nun die beiden Methoden ZeigeFlaeche() und BerechneFlaeche(). Fällt Ihnen etwas auf? Warum dürfen diese Methoden die Daten Laenge und Breite im Rumpf verwenden, ohne diese Informationen über die Parameterliste auszutauschen? Die Antwort: Jede Methode einer Klasse kennt ihre Datenelemente. Dabei spielt es keine Rolle, ob die Attribute als private oder public gekennzeichnet sind.
257
258
Kapitel 14
Objekte erzeugen Betrachten wir die Definition der Klasse Rechteck fürs Erste als abgeschlossen. Um die Features der Klasse verwenden zu können, müssen Sie nun daran gehen, ein Objekt dieses Klassentyps (ein Rechteck) zu erzeugen. Wie Sie wissen, werden Klassenobjekte mit dem new-Operator erzeugt. Zusätzlich benötigen Sie eine Referenzvariable zur Aufnahme des Objektverweises.
5 Erzeugen Sie in der Methode Main() ein Rechteck-Objekt: static void Main(string[] args) { Rechteck myRec = new Rechteck(); }
Nun ist im Code ein Rechteck namens myRec existent und Sie können die Elemente seiner Klasse nutzen. Für den Zugriff verwenden Sie den Punkt als Zugriffsoperator, wie Sie das bisher getan haben, um die Elemente vordefinierter Klassen anzusprechen.
6 Setzen Sie Ihr Rechteck auf eine Länge von 5 und eine Breite von 4 (cm): static void Main(string[] args) { Rechteck myRec = new Rechteck(); myRec.Laenge = 5.0; myRec.Breite = 4.0; }
Hinweis Es ist auch möglich, Felder sofort mit eigenen Werten zu initialisieren. class Rechteck { public double Laenge = 1.0; public double Breite = 1.0; … }
Damit hat jedes Rechteck mit seiner Instanzierung eine Länge von 1 und eine Breite von 1 (wenn man im Code Klassenobjekte erzeugt, spricht man vom Instanzieren, analog bezeichnet man Klassenobjekte auch als Instanzen einer Klasse).
Klassendefinition
7 Geben Sie nun mit der Methode ZeigeFlaeche() die Rechtecksfläche aus: static void Main(string[] args) { Rechteck myRec = new Rechteck(); myRec.Laenge = 5.0; myRec.Breite = 4.0; myRec.ZeigeFlaeche(); }
8 Testen Sie Ihr Programm. Sie sollten die Ausgabe Rechtecksfläche: 20 erhalten. Auf der CD-ROM Das Beispiel finden Sie unter Beispiele/Konsolenprogramme auf der Buch-CD (Projektname K14a).
Zugriffsspezifizierer Es steht dem Entwickler einer Klasse grundsätzlich frei, für welche Klassenelemente er den direkten Zugriff von außen erlauben möchte. Das Konzept der objektorientierten Programmierung verfolgt jedoch im Allgemeinen das Ziel, Klassen mit einer gewissen Intelligenz auszustatten, sodass Fehlbedienungen bei der Verwendung der Klasse reduziert werden. Ebenso soll der Code stabil laufen und plausible Ergebnisse produzieren. So wie die Klasse Rechteck derzeit eingerichtet ist, wäre z.B. der Fall denkbar, dass jemand, der in seinem Programm die Klasse Rechteck verwendet, den Attributen Laenge und Breite im Code aus Versehen negative Werte zuweist. Um die Benutzer Ihrer Klasse – also Sie selbst oder andere Programmierer, die Ihre Klasse in ihren Programmen verwenden – vor solchen Fehlern zu bewahren, liegt es nahe, Felder dieser Art als private zu definieren und für den Zugriff entsprechende Methoden einzurichten. Im Anweisungsteil dieser Methoden nehmen Sie dann gewisse Prüfungen und gegebenenfalls Korrekturen vor. Im konkreten Beispiel verhindern Sie die Zuweisung negativer Werte an die Felder Laenge und Breite.
259
260
Kapitel 14
9 Versehen Sie die Felder Laenge und Breite Ihrer Rechteck-Klasse mit dem Zugriffsspezifizierer private:
class Rechteck { private double laenge; private double breite; public double BerechneFlaeche() {return laenge * breite;} public void ZeigeFlaeche() { Console.Write("Rechtecksfläche: "); Console.WriteLine(laenge * breite); } }
Hinweis Microsoft empfiehlt für die Notation von private-Feldern camelCasing, für die Notation von public-Feldern PascalCasing, daher die geänderte Groß- und Kleinschreibung im Listing.
Nun ist der direkte Zugriff auf die Felder laenge und breite nicht mehr möglich: Rechteck myRec = new Rechteck(); myRec.laenge = 9.5; // FEHLER
Sie wollen den Zugriff auf diese Attribute jedoch nicht generell verhindern, sondern nur kontrollieren. Dazu könnten Sie für den Zugriff auf das Feld laenge zwei public-Methoden GetLaenge() und SetLaenge() einrichten. Dabei muss der Rückgabewert der Get-Methode dem Wert des Feldes laenge entsprechen. class Rechteck { private double laenge; private double breite; public double GetLaenge() { return laenge; }
Hätten Sie gedacht ...
public void SetLaenge(double l) { if(l > 0) laenge = l; } public double BerechneFlaeche() {return laenge * breite;} public void ZeigeFlaeche() { Console.Write("Rechtecksfläche: "); Console.WriteLine(laenge * breite); } }
Die Zuweisung im Methodenblock von SetLaenge() findet nur statt, wenn der übergebene Wert größer als 0 ist. Andernfalls zieht ein Aufruf dieser Methode keine Veränderung an dem Attribut laenge nach sich. Damit ist sichergestellt, dass laenge niemals einen negativen Wert annimmt. Beachten Sie, dass die Methode SetLaenge() keinen Rückgabewert benötigt. Alle Datenelemente (hier laenge) sind schließlich innerhalb einer Klasse bekannt und für den Lesezugriff von außen steht jetzt die Methode GetLaenge() zur Verfügung. Hinsichtlich des Feldes breite würden Sie mit entsprechenden Methoden GetBreite() und SetBreite() ebenso verfahren: class Rechteck { private double laenge; private double breite; public double GetLaenge() { return laenge; } public void SetLaenge(double l) { if(l > 0) laenge = l; } public double GetBreite() { return breite;
261
262
Kapitel 14
} public void SetBreite(double b) { if(b > 0) breite = b; } public double BerechneFlaeche() {return laenge * breite;} public void ZeigeFlaeche() { Console.Write("Rechtecksfläche: "); Console.WriteLine(laenge * breite); } }
In Main() müssten Sie dann allerdings statt des direkten Zugriffs myRec.laenge = 5.0; myRec.breite = 4.0;
die entsprechenden Methoden aufrufen: myRec.SetLaenge(5.0); myRec.SetBreite(4.0);
Spezielle Methoden von C# – die Eigenschaften Die gezeigte Vorgehensweise entspricht der anderer objektorientierter Programmiersprachen und ist an und für sich völlig in Ordnung. Für jedes Feld existiert eine Methode zum Lesen und eine für den Schreibzugriff. Letzterer erfolgt dann über einen Methodenaufruf in der Form Objekt.SetMethode(Argument), wobei der neue Wert als Argument übergeben wird. Allerdings sieht C# für den kontrollierten Zugriff auf Felder so genannte Eigenschaften vor. Tatsächlich handelt es sich dabei um spezielle Methoden. Der Vorteil liegt darin, dass der Zugriff über eine Eigenschaft von der Syntax her genauso aussieht wie der direkte Zugriff auf ein Feld. Das Grundgerüst einer Eigenschaft gleicht der Definition eines Feldes plus Anweisungsblock. Natürlich sollte eine Eigenschaft public sein: public double Laenge { }
Klassendefinition
Eigenschaften bestehen eigentlich aus zwei Methoden, einer Get-Methode zum Lesen und einer Set-Methode zum Setzen von Werten. Die Definition gestaltet sich wie folgt: public double Laenge { get { } set { } }
Hinweis Get- und Set-Methoden werden auch kurz Getter bzw. Setter genannt.
Im Getter verfahren Sie nun wie bekannt, Sie geben einfach den Feldwert heraus. Allein der Setter besitzt eine Besonderheit. Hier verwenden Sie den vordefinierten Parameter value, der den zuzuweisenden Wert enthält. Für die Eigenschaft Laenge prüfen Sie natürlich zunächst, ob value positiv ist, bevor Sie die Zuweisung an das Feld laenge durchführen: public double Laenge { get { return laenge; } set { if(value > 0) laenge = value; } }
263
264
Kapitel 14
10 Richten Sie in der Klasse Rechteck die Eigenschaften Laenge und Breite ein. Verhindern Sie die Zuweisung von negativen Werten an die Felder laenge und
breite. Initialisieren Sie die Felder außerdem mit dem Wert 1.0:
class Rechteck { private double laenge = 1.0; private double breite = 1.0; public double Laenge { get {return laenge;} set { if(value > 0) laenge = value;} } public double Breite { get {return breite;} set { if(value > 0) breite = value;} } public double BerechneFlaeche() {return laenge * breite;} public void ZeigeFlaeche() { Console.Write("Rechtecksfläche: "); Console.WriteLine(laenge * breite); } }
Tipp Für Eigenschaften empfiehlt sich oft eine Kompaktschreibweise. Das ausschlaggebende Kriterium bleibt nach wie vor die Übersichtlichkeit des Codes.
Klassendefinition
Hinweis Natürlich könnten Sie es sich innerhalb der Klasse nun aussuchen, ob Sie in den Methoden BerechneFlaeche() und ZeigeFlaeche() – und eventuell in weiteren Methoden, die Sie später noch einrichten werden – die Felder laenge und breite oder die Eigenschaften Laenge und Breite verwenden. In der folgenden Klassendefinition wurde auf letztere Möglichkeit zurückgegriffen: class Rechteck { private double laenge = 1.0; private double breite = 1.0; public double Laenge { get {return laenge;} set { if(value > 0) laenge = value;} } public double Breite { get {return breite;} set { if(value > 0) breite = value;} } public double BerechneFlaeche() {return Laenge * Breite;} public void ZeigeFlaeche() { Console.Write("Rechtecksfläche: "); Console.WriteLine(Laenge * Breite); } }
Allerdings ist es in der Definition einer Klasse durchaus üblich, in den jeweiligen Methoden die Felder direkt anzusprechen. Als Entwickler einer Klasse bewegen Sie sich ja auf sicherem Terrain.
265
266
Kapitel 14
Beachten Sie, dass sich in Main() nichts geändert hat. Für Programmierer, die Ihre Klasse verwenden, präsentieren sich Laenge und Breite wie Felder: myRec.Laenge = 5.0; myRec.Breite = 4.0;
Tipp Bei der Implementation Ihrer Klassen können Sie sich als Richtschnur von einem Grundprinzip der OOP leiten lassen, alle Datenelemente, welche nach außen zugänglich sein sollen, als private zu definieren und für den Zugriff entsprechende öffentliche Methoden zur Verfügung zu stellen. »Nach außen zugänglich« bedeutet natürlich, zugreifbar aus einer anderen Klasse heraus (indem in dieser Objekte instanziert werden, welche dann in irgendeiner Form auf die Daten zugreifen – direkt oder über eine Methode). Es kommt aber auch vor, dass ein Feld ausschließlich für den klasseninternen Gebrauch (in anderen Methoden derselben Klasse) vorgesehen ist. Dann darf seine Information (gemeint ist der Wert des Feldes) in keiner Weise nach außen zugänglich sein (weder direkt noch auf dem Umweg über eine eigens hierfür implementierte Methode).
11 Bringen Sie Ihr Programm zur Ausführung. Sie sollten die gleiche Ausgabe erhalten wie zuvor.
Abbildung 14.3: Die Klasse bietet die gleiche Funktionalität wie vorher, kommt aber nun dem Ideal objektorientierter Programmierung von C# erheblich näher
Auf der CD-ROM Die so erweiterte Version des Beispiels finden Sie unter dem Projektnamen K14b im Ordner Beispiele/Konsolenprogramme auf der Buch-CD.
Klassendefinition
Natürlich steht es Ihnen frei, Ihre Rechteck-Klasse mit anderen Anweisungen zu testen. Versuchen Sie es doch einmal mit negativen Zuweisungen an Laenge und Breite: static void Main(string[] args) { Rechteck myRec = new Rechteck(); myRec.Laenge = -11.0; myRec.Breite = - 7.8; myRec.ZeigeFlaeche(); }
Ausgabe: Rechtecksfläche: 1
Da die Zuweisungen negativer Werte erfolglos bleiben, zeigt die Ausgabe der Rechtecksfläche den Wert 1, was dem Produkt der Initialisierungswerte von laenge und breite entspricht. Auf der CD-ROM Das Beispiel finden Sie unter dem Projektnamen K14c im Ordner Beispiele/Konsolenprogramme auf der Buch-CD.
Konstruktoren und Destruktoren Wir haben an anderer Stelle bereits erwähnt, dass beim Instanzieren eines Klassenobjekts eine besondere Methode aufgerufen wird, der so genannte Konstruktor. Dies gilt auch für den Fall, dass in der Klasse nicht explizit ein Konstruktor definiert ist. Dann wird Ihrer Klasse automatisch ein Standardkonstruktor hinzugefügt. Dieser ist parameterlos und führt auch keinerlei Aktionen durch. In der Regel werden Sie aber einen eigenen Konstruktor einrichten. Hinweis Die Bereitstellung eines Standardkonstruktors ist aus Gründen notwendig, die in der Sprache C# bzw. dem .NET verankert sind. Ansonsten würde der Ausdruck new Rechteck() wegen der Klammern eine Fehlermeldung erzeugen.
267
268
Kapitel 14
Wie sieht nun die Definition eines Konstruktors aus? Ein Konstruktor besitzt einen Anweisungsblock und eine Parameterliste wie jede andere Methode. Was ihn von normalen Methoden unterscheidet, ist Folgendes: • Der Name des Konstruktors entspricht dem Namen der Klasse. • Ein Konstruktor besitzt keinen Rückgabewert (auch nicht void). Beachten Sie außerdem, dass ein Konstruktor immer von außen zugänglich, also public, sein muss. Um in der Klasse Rechteck einen selbst definierten Standardkonstruktor einzurichten, schreiben Sie also public Rechteck() { }
Statt die Felder der Klasse bei der Definition zu initialisieren, werden Sie diese nun im Konstruktor mit Anfangswerten versehen. Dies erfüllt denselben Zweck, da ein Konstruktor ja für jedes Objekt bei seiner Instanzierung zur Ausführung kommt.
12
Richten Sie in Ihrer Rechteck-Klasse einen parameterlosen Konstruktor ein, der die Datenfelder mit dem Wert 1.0 initialisiert: class Rechteck { private double laenge; private double breite; public Rechteck() { laenge = 1.0; breite = 1.0; } public double Laenge { get {return laenge;} set { if(value > 0) laenge = value;} } public double Breite { get {return breite;} set {
Klassendefinition
if(value > 0) breite = value;} } public double BerechneFlaeche() {return laenge * breite;} public void ZeigeFlaeche() { Console.Write("Rechtecksfläche: "); Console.WriteLine(laenge * breite); } }
Das Gegenstück zum Konstruktor ist der Destruktor. Das ist eine Methode, welche zur Ausführung gelangt, wenn ein Objekt wieder gelöscht wird. Das ist der Fall, wenn keine Referenzvariable mehr auf das Objekt verweist (weil der Block, in dem die Referenzvariable definiert ist, verlassen wird und somit deren Lebensdauer endet). Hinweis Es ist auch der Fall möglich, dass mehrere Referenzvariablen mit ein und demselben Objekt verbunden sind. In der Regel wird in Ihren Programmen aber nur eine Referenzvariable auf ein bestimmtes Objekt verweisen. Sobald deren Lebensdauer endet, räumt die Speicherverwaltung des .NET das Objekt auf dem Heap, mit dem die Referenzvariable verbunden ist, automatisch auf. Man spricht in diesem Zusammenhang vom Garbage Collector, was so viel wie Müllabfuhr heißt. Allerdings geht das Freigeben des Speichers (das Entfernen des Objekts) und damit der Aufruf des zugehörigen Destruktors meist nicht synchron mit dem Ungültigwerden der entsprechenden Referenzvariable einher. Der Garbage Collector wartet stattdessen einen »passenden« Zeitpunkt ab.
Es sei darauf hingewiesen, dass sowohl der Konstruktor als auch der Destruktor für jedes Objekt nur ein einziges Mal zur Ausführung gelangt. Die Aufrufe beider Methoden erfolgen automatisch. Sie dürfen keine der beiden Methoden explizit durch Programmierbefehle verwenden. Ein Destruktor ist immer parameterlos und er besitzt auch keinen Rückgabewert. Der Name der Destruktormethode entspricht analog zum Konstruktor dem Klassennamen, wird aber mit einer einleitenden Tilde (~) versehen. Die
269
270
Kapitel 14
Tilde geben Sie mit (Alt_Gr)+(+) ein. Die Definition des Destruktors erfolgt also bezogen auf die Beispielklasse nach folgendem Schema: ~Rechteck() { // ... }
Dem Destruktor kommt wegen des Garbage Collectors in der Praxis nur eine geringe Bedeutung zu, sodass Sie in Ihren Programmen in der Regel auf die Definition eines eigenen Destruktors verzichten werden. Hinweis Beispielsweise gehört es zu den Aufgaben eines C++-Programmierers, Objekte vom Heap wieder zu entfernen (den Speicherplatz wieder freizugeben). Gewöhnlich werden die dafür notwendigen Anweisungen dann im Destruktor eingerichtet.
Überladen von Methoden Wie Sie wissen, müssen Bezeichner innerhalb eines Gültigkeitsbereichs eindeutig sein. Eine Ausnahme von dieser Regel existiert in Bezug auf das Überladen von Methoden. Das Wort »Überladen« bezieht sich eigentlich auf den Bezeichner der Methode. Dieser wird gegebenenfalls überladen, da es möglich ist, mehrere Methoden gleichen Namens zu definieren. Voraussetzung hierfür ist, dass sich die Methoden in Anzahl und/oder Datentyp und/oder Reihenfolge der Parameter unterscheiden. Hinweis Da Bezeichner und Parameterliste zusammen eine Methode eindeutig identifizieren, spricht man von der Signatur einer Methode.
Sie werden von dieser Möglichkeit nun Gebrauch machen, um für die Instanzierung von Rechteck-Objekten alternativ einen zweiten Konstruktor zur Verfügung zu stellen.
Klassendefinition
13 Definieren Sie in der Klasse Rechteck einen Konstruktor, dem beim Aufruf Werte für Länge und Breite übergeben werden: class Rechteck { private double laenge; private double breite; public Rechteck() { laenge = 1.0; breite = 1.0; } public Rechteck(double l, double b) { laenge = l; breite = b; } public double Laenge { get {return laenge;} set { if(value > 0) laenge = value;} } public double Breite { get {return breite;} set { if(value > 0) breite = value;} } public double BerechneFlaeche() {return laenge * breite;} public void ZeigeFlaeche() { Console.Write("Rechtecksfläche: "); Console.WriteLine(laenge * breite); } }
271
272
Kapitel 14
Nun besitzt die Klasse Rechteck neben einem Standardkonstruktor einen Konstruktor, dem Werte für Länge und Breite zu übergeben sind. Sie können es sich nun aussuchen, welchen Konstruktor Sie beim Instanzieren von RechteckObjekten verwenden. Ihr Compiler erkennt anhand des Aufrufs, welcher Konstruktor gemeint ist. Also Rechteck myRec = new Rechteck();
oder Rechteck yourRec = new Rechteck(5.0, 2.5);
Im ersten Fall wird der Standardkonstruktor verwendet, im zweiten Fall der andere. Nur dieser ist in der Lage, die Werte 5.0 und 2.5 entgegenzunehmen.
Statische Klassenelemente Wie Sie bereits in Kapitel 8 (Abschnitt »Literale zur Darstellung von ganzen Zahlen«) erfahren haben, gibt es sowohl Methoden, die sich auf ein Objekt beziehen, als auch Methoden, welche sich auf die Klasse selbst beziehen, in der sie definiert sind. Die einen werden mit dem Objektnamen, die anderen mit dem Klassennamen aufgerufen. Zu den Methoden, die auf die Klasse angewendet werden, gehören beispielsweise die Methoden Write(), WriteLine(), ReadLine(), aber auch etwa die Umwandlungsmethoden der Klasse Convert (siehe Kapitel 8, Abschnitt »Umwandlungsmethoden«). Solche Methoden werden »statisch« genannt. Es gibt nicht nur statische Methoden, sondern auch statische Datenelemente. Wenn Sie in Ihren Klassen statische Elemente (Methoden oder Felder) einrichten, verwenden Sie das Schlüsselwort static. Dieses wird vor dem Datentyp angegeben. Ein Beispiel ist die Methode Main(): static void Main()
Hinweis Die Methode Main() muss statisch sein, da sie vom Betriebssystem ansonsten nicht aufgerufen werden kann.
Zur Demonstration soll die Klasse Rechteck nun so erweitert werden, dass sie die Anzahl der im Programm instanzierten Rechtecke festhält.
Statische Klassenelemente
14
Definieren Sie zu diesem Zweck ein privates statisches Feld anzahl und versehen Sie es mit dem Anfangswert 0: class Rechteck { private double laenge; private double breite; private static int anzahl = 0; public Rechteck() { laenge = 1.0; breite = 1.0; } public Rechteck(double l, double b) { laenge = l; breite = b; } public double Laenge { get {return laenge;} set { if(value > 0) laenge = value;} } public double Breite { get {return breite;} set { if(value > 0) breite = value;} } public double BerechneFlaeche() {return laenge * breite;} public void ZeigeFlaeche() { Console.Write("Rechtecksfläche: "); Console.WriteLine(laenge * breite); } }
273
274
Kapitel 14
Nun müssen Sie dafür sorgen, dass der Wert von anzahl um eins hochgezählt wird, wenn ein neues Rechteck-Objekt im Code entsteht.
15 Inkrementieren Sie das Feld anzahl in den beiden Konstruktormethoden: class Rechteck { private double laenge; private double breite; private static int anzahl = 0; public Rechteck() { laenge = 1.0; breite = 1.0; anzahl++; } public Rechteck(double l, double b) { laenge = l; breite = b; anzahl++; } public double Laenge { get {return laenge;} set { if(value > 0) laenge = value;} } public double Breite { get {return breite;} set { if(value > 0) breite = value;} } public double BerechneFlaeche() {return laenge * breite;} public void ZeigeFlaeche() { Console.Write("Rechtecksfläche: ");
Statische Klassenelemente
Console.WriteLine(laenge * breite); } }
Damit haben Sie sichergestellt, dass anzahl immer die aktuelle Anzahl der instanzierten Objekte enthält.
16 Richten Sie zum Zugriff auf das Feld anzahl eine statische Eigenschaft Anzahl
ein.
Natürlich darf anzahl nur gelesen, nicht aber von außerhalb der Klasse verändert werden. Das erreichen Sie, indem Sie den Setter bei der Definition einfach weglassen: class Rechteck { private double laenge; private double breite; private static int anzahl = 0; public Rechteck() { laenge = 1.0; breite = 1.0; anzahl++; } public Rechteck(double l, double b) { laenge = l; breite = b; anzahl++; } public double Laenge { get {return laenge;} set { if(value > 0) laenge = value;} } public double Breite { get {return breite;} set { if(value > 0)
275
276
Kapitel 14
breite = value;} } public static int Anzahl { get {return anzahl;} } public double BerechneFlaeche() {return laenge * breite;} public void ZeigeFlaeche() { Console.Write("Rechtecksfläche: "); Console.WriteLine(laenge * breite); } }
Nun überzeugen Sie sich davon, dass anzahl korrekt funktioniert.
17
Erzeugen Sie dazu in Main() zwei weitere Rechtecke. Zur Kontrolle geben Sie vorher und nachher den aktuellen Wert von anzahl aus. static void Main(string[] args) { Rechteck myRec = new Rechteck(); myRec.Laenge = 5.0; myRec.Breite = 4.0; myRec.ZeigeFlaeche(); Console.WriteLine("Anzahl der Rechtecke: {0}" , Rechteck.Anzahl); Rechteck yourRec = new Rechteck(); Rechteck herRec = new Rechteck(); Console.WriteLine("Anzahl der Rechtecke: {0}" , Rechteck.Anzahl); }
18 Starten Sie Ihr Programm mit (F5) bzw. (Strg) + (F5). Die ausgegebenen Werte für „Anzahl“ sollten jeweils der aktuellen Anzahl der instanzierten Rechteck-Objekte entsprechen, also 1 und 3.
Namensräume
Abbildung 14.4: Die Beispielklasse wurde um eine statische Eigenschaft erweitert, welche die Anzahl der momentan existierenden Objekte der Klasse darstellt
Auf der CD-ROM Das komplette Beispiel finden Sie unter dem Projektnamen K14d im Ordner Beispiele/Konsolenprogramme auf der Buch-CD.
Namensräume Zum Abschluss wollen wir uns noch in aller Kürze mit den Namensräumen (engl. »namespaces«) beschäftigen. Ein Namensraum beschreibt einen klassenübergreifenden Gültigkeitsbereich. Das heißt, in einem Namensraum können mehrere Klassen zusammengefasst werden. Dabei müssen die Bezeichner für die Klassen nur innerhalb ein und desselben Namensraums eindeutig sein. Beispielsweise haben Sie den vordefinierten Namensraum System von Anfang an verwendet. Dieser wurde mit der Zeile using System; in Ihre Programme eingebunden. In diesem Namensraum sind z.B. alle Datentypen definiert, die Sie verwendet haben, sowie die Klassen Console und Convert. Ohne die Direktive using System; müssten Sie beispielsweise jeden Zugriff auf die Methode WriteLine() der Klasse Console (im Namensraum System) qualifiziert vornehmen, was folgendermaßen aussieht: System.Console.WriteLine();
Die Definition eines Namensraums wird durch das Schlüsselwort namespace eingeleitet: namespace Neu { } Neu ist hier der Bezeichner für den Namensraum. Im zugehörigen Block stehen dann die Klassendefinitionen. Die Verwendung von selbst definierten Namensräumen ist nicht vorgeschrieben und im Allgemeinen nur bei sehr umfangreichen Programmen sinnvoll.
277
278
Kapitel 14
Hinweis Namensräume dürfen auch verschachtelt definiert werden. Beim Zugriff werden die Namensräume verschiedener Ebenen wie gewohnt durch einen Punkt getrennt. Außerdem ist es möglich, Namensräume über mehrere Dateien hinweg zu definieren. Wenn Sie Ihre Rechteck-Klasse in eine separate Datei ausgelagert haben, können Sie z.B. feststellen, dass Visual C# Express beide Klassen (Program und Rechteck) auf diese Weise in einem Namensraum zusammengefasst hat.
Eine kleine Erfolgskontrolle
Eine kleine Erfolgskontrolle • • •
• • •
Was ist ein Konstruktor? Was ist ein Standardkonstruktor? Fügen Sie Ihrer Klasse Rechteck eine nicht statische public-Methode hinzu, welche zwei Rechtecke miteinander vergleicht. Die Methode soll true zurückgeben, falls beide Rechtecke deckungsgleich sind (also die gleiche Länge und Breite besitzen), ansonsten false. Nennen Sie die Methode Vergleiche(). Formulieren Sie eine if-Bedingung zum Vergleich zweier Rechtecke a und b. Benutzen Sie dazu die eben erstellte Methode Vergleiche(). Welche Datenelemente würden Sie in einer Minimaldefinition einer Klasse Konto einrichten, welche ein Kundenkonto einer Bank repräsentieren soll? Eröffnen Sie mit dieser fiktiven Klasse ein neues Konto. Instanzieren Sie also ein Objekt der Klasse Konto (unter Verwendung des Standardkonstruktors der Klasse) und nennen Sie die Referenzvariable meinKonto.
279
Das können Sie schon: Arbeiten mit Strings, Steuerzeichen
75
Variablen, Konstanten, Datentypen, Typkonvertierung
87
Verarbeiten von Benutzereingaben
103
Komplexe Ausdrücke, Operatoren, Operatorpriorität
139
Programmverzweigungen und Schleifen
157
Gültigkeitsbereich und Lebensdauer von Variablen
202
Arrays, Referenzvariablen
205
Methoden definieren und verwenden
234
Klassen definieren
254
Mit Objekten arbeiten
259
Das lernen Sie neu: Entwickeln von GUI-Programmen
281
Windows Forms
282
Spezielle Features der Visual C# Express Edition
283
Toolbox und Eigenschaftenfenster
284
Steuerelemente mit Ereignissen verbinden
293
Kapitel 15
Windows-Anwendungen entwickeln Hier erfahren Sie, wie Sie mit der Visual C# 2005 Express Edition Windows-Anwendungen erstellen – also Applikationen mit GUI-Elementen, etwa Fenstern, Schaltflächen, Menüs usw., wie Sie diese z.B. von den Microsoft Office-Produkten her kennen. Vermutlich werden Sie angenehm überrascht sein, wie einfach es ist, Programme mit einer grafischen Benutzeroberfläche zu versehen – das inzwischen erworbene Programmier-Know-How vorausgesetzt. Sie werden im Folgenden ein Lottospiel entwickeln, wobei Sie Ihre bisher erworbenen Programmierkenntnisse zum Einsatz bringen.
282
Kapitel 15
Der Windows Forms-Designer In der Visual C# 2005 Express Edition bilden Formulare die Basis jeder GUI-Anwendung – daher auch der Name Windows Forms (engl. »form« = »Formular«). Formulare sind nichts anderes als Fenster, worunter auch Zusatzfenster wie Dialogfelder fallen. Auf einer Form platzieren Sie die benötigten Steuerelemente (Textfelder, Schaltflächen, Dialogfelder usw.), welche in ihrer Gesamtheit schließlich das Aussehen Ihrer Anwendung bestimmen. Zu diesem Zweck stellt Ihnen Visual C# Express eine Vielzahl von Hilfsmitteln zur Verfügung, unter anderem eine Palette von fertigen Steuerelementen, mit denen Sie auch bei der Entwicklung professioneller Applikationen auskommen sollten. Hinweis Hinter jedem Steuerelement steht eine Klasse des .NET Framework. Sobald Sie Ihrer Form ein Steuerelement hinzufügen, generiert Visual C# Express im Hintergrund den benötigten Code (z.B. Instanzieren eines Objekts der betreffenden Klasse).
Die vordefinierte Funktionalität der eingefügten Steuerelemente führt in Verbindung mit dem von Ihnen zu entwickelnden Code in der Regel verhältnismäßig schnell zu den gewünschten Lösungen. Hinweis Was hier über die Visual C# 2005 Express Edition gesagt wird, gilt im Wesentlichen auch für Visual Studio .NET. Falls Sie sich also irgendwann die »große« Version von Visual Studio zulegen sollten, werden Sie durch Ihre Erfahrungen mit der Visual C# 2005 Express Edition bestens vorbereitet sein.
1 Legen Sie in Ihrer Visual C# Express-IDE ein neues Projekt an. Wählen Sie als Projektvorlage Windows-Anwendung.
Der Windows Forms-Designer
Abbildung 15.1: Auswahl der Projektvorlage »Windows-Anwendung«
Visual C# Express erzeugt daraufhin unter anderem den Code für das Hauptformular. Die Oberfläche Ihrer IDE sollte sich nun wie in der Abbildung 15.2 zu sehen präsentieren.
Abbildung 15.2: Toolbox, Windows Forms-Designer, Projektmappen-Explorer und Eigenschaftenfenster
283
284
Kapitel 15
Die Toolbox (links im Bild) und das Eigenschaftenfenster (rechts, unterhalb des Projektmappen-Explorers) können Sie gegebenenfalls im Menü mit Ansicht/ Toolbox bzw. Ansicht/Eigenschaftenfenster (oder Klick auf die entsprechenden Symbole in der Symbolleiste) einblenden. Bei der Windows Form, wie sie in der Entwurfsansicht zu sehen ist, handelt es sich um eine visuelle Darstellung des Fensters, wie es beim Starten der Anwendung erscheint. Der entsprechende Code, den Visual C# Express generiert hat, befindet sich in den Dateien Form1.cs und Form1.Designer.cs. Letztere Datei ist standardmäßig nicht sichtbar, kann aber zur Anzeige gebracht werden, indem im Projektmappen-Explorer der Ast bei Form1.cs durch Klicken auf das PlusSymbol (+) geöffnet wird. Diese Aufteilung in zwei Dateien hat den Sinn, dass der automatisch generierte Code vom selbst entwickelten Code getrennt wird, was für mehr Übersicht sorgt. Entsprechend ist die Datei Form1.cs fast leer. Die Entwicklung findet dort statt, gegebenenfalls in weiteren Formularen, wenn vorhanden, außerdem und vor allem in der Klasse Program.cs sowie in weiteren Klassendateien, die einem Projekt hinzugefügt werden. Hinweis Um gegebenenfalls von der Codeansicht in die Entwurfsansicht des Formulars Form1.cs zu gelangen, klicken Sie im Projektmappen-Explorer doppelt auf den Eintrag Form1.cs oder Sie klicken mit der rechten Maustaste darauf und wählen im Kontextmenü den Eintrag Ansicht-Designer.
In der Toolbox besorgen Sie sich nun die passenden Steuerelemente. Für Ihr Lottoprogramm benötigen Sie sechs Textfelder zum Anzeigen der Zahlen und einen Button.
2 Fügen Sie Ihrem Formular ein Textfeld hinzu. Erweitern Sie gegebenenfalls die Kategorie Alle Windows Forms in der Toolbox und klicken Sie auf den Eintrag TextBox. Danach klicken Sie auf die gewünschte Stelle in Ihrem Formular, woraufhin das Textfeld auf dem Formular abgelegt wird. Die Position kann durch Ziehen des Steuerelements noch nachträglich geändert werden.
Der Windows Forms-Designer
Hinweis Ebenso können Sie ein Steuerelement bei gedrückter Maustaste von der Toolbox auf das Formular ziehen.
Jedes Steuerelement – auch das Formular selbst – besitzt bestimmte Eigenschaften. Um diese zu ändern, wählen Sie dieses mit einem Mausklick aus und begeben sich in das Eigenschaftenfenster. Dort können Sie zwischen der Eigenschaften-Ansicht und der Ereignis-Ansicht wählen.
Abbildung 15.3: Eigenschaften-Ansicht des Eigenschaftenfensters
Klicken Sie dazu auf das entsprechende Symbol in der oberen Symbolleiste des Eigenschaftenfensters. In der Eigenschaften-Ansicht sehen Sie in der linken Spalte den Namen der jeweiligen Eigenschaft und in der rechten den dazugehörigen aktuellen Wert, den Sie dort auch neu setzen können. Der untere Teil des Eigenschaftenfensters zeigt eine Beschreibung der betreffenden Eigenschaft.
285
286
Kapitel 15
Hinweis Denken Sie daran, dass jedes Steuerelement eine Instanz der betreffenden Steuerelement-Klasse ist. Wenn Sie bestimmte Eigenschaften (das Wort ist hier im üblichen Sinne gebraucht) ändern, rufen Sie praktisch die entsprechenden Methoden (Eigenschaften, siehe Kapitel 14, Abschnitt »Spezielle Methoden von C# – die Eigenschaften«) auf, welche dann wiederum die entsprechenden Felder ändern, das heißt deren Werte (das bedeutet, Visual C# Express erzeugt im Hintergrund den entsprechenden Code für Sie ).
3 Ändern Sie den Wert für die Eigenschaft Text des Formulars zu »Ihre Lottozahlen«. Klicken Sie dazu im Windows Forms-Designer Ihr Formular an, lassen Sie sich im Eigenschaftenfenster die Eigenschaften-Ansicht anzeigen und setzen Sie die Einfügemarke in die rechte Spalte neben Text. Dort löschen Sie den vorhandenen Eintrag »Form1« und tippen »Ihre Lottozahlen« ein. Damit legen Sie den Text für die Titelleiste des Formulars fest.
4 Setzen Sie den Wert für die Breite des Textfeldes auf 35 Pixel. Erweitern Sie dazu im Eigenschaftenfenster die Eigenschaft Size, nachdem Sie das Textfeld in der Entwurfsansicht ausgewählt haben. Belassen Sie es für Height bei dem Wert 20 und geben Sie für Width den Wert 35 an.
5 Setzen Sie das Attribut Size der Font-Eigenschaft auf den Wert 12. Damit legen Sie für die Darstellung der Lottozahlen eine Schriftgröße von 12 pt fest. Die Eigenschaft Font besteht wie die Eigenschaft Size aus mehreren Attributen. Zusammengesetzte Eigenschaften werden im Eigenschaftenfenster in einer Strukturansicht angezeigt, erkennbar an dem vorangestellten PlusZeichen (+). Um die Werte einzelner Attribute zu ändern, erweitern Sie den Eintrag für die Eigenschaft, indem Sie darauf doppelklicken und verfahren dann wie gerade bei dem Width-Attribut von Size. Bei vielen Eigenschaften, so auch bei der Font-Eigenschaft, kann das Setzen der Attributwerte alternativ über einen Dialog erfolgen, was sehr praktisch ist, um mehrere Attribute in einem Durchgang zu ändern. In diesem Fall wird in der rechten Spalte ein mit Auslassungspunkten beschrifteter Button (»...«) angeboten. Klicken Sie diesen entsprechend an.
Der Windows Forms-Designer
Abbildung 15.4: Der Dialog zum Ändern der Eigenschaft »Font«. Er entspricht dem Dialog aus Textverarbeitungsprogrammen zur Anbringung zeichenorientierter Formatierungen
6 Setzen Sie die Anchor-Eigenschaft auf None. Dies ist notwendig, damit die Textfelder später beim Vergrößern des Anwendungsfensters zentriert bleiben. Klicken Sie im Eigenschaftenfenster in der Spalte neben Anchor auf das Dreiecksymbol und dann jeweils ein Mal auf die grauen Balken, bis kein grauer Balken mehr vorhanden ist.
7 Setzen Sie die TextAlign-Eigenschaft auf Center, damit die Zahl später im Feld zentriert dargestellt wird.
8 Kopieren Sie das vorhandene Textfeld fünf Mal. Dazu wählen Sie es aus, kopieren Sie es mit (Strg) + (C) in die Zwischenablage (oder wählen Sie den Menübefehl Bearbeiten/Kopieren) und fügen Sie es dann fünf Mal in Ihr Formular ein ((Strg) + (V) oder Bearbeiten/Einfügen).
9 Ordnen Sie die sechs Textfelder so an, dass sie mittig in einer Reihe nebeneinander liegen.
Die Namen der Textfelder sollten von links nach rechts textBox1, textBox2, ... textBox6 lauten. Ziehen Sie also zuerst das textBox1-Textfeld nach links, platzieren dann das textBox2-Textfeld daneben usw.
287
288
Kapitel 15
Hinweis Sie finden den Namen eines Steuerelements in der zweite Spalte im Eigenschaftenfenster neben dem eingeklammerten Eintrag (Name). Wie Sie sich vermutlich denken können, handelt es sich dabei um den Bezeichner der Referenzvariablen des betreffenden Steuerelements. Mit diesem werden Sie im Code auch auf die Text-Eigenschaft der einzelnen Textfelder zugreifen (die, wie zu erwarten, den angezeigten Text des Textfelds repräsentiert).
Lassen Sie sich beim Ausrichten der Textfelder von den Ausrichtungslinien unterstützen, den so genannten SnapLines. Außerdem bestätigen Sie zur Ausrichtung die Möglichkeit, die Textfelder (und andere Steuerelemente) auszuwählen und dann die passenden Optionen im Menü Format aufzurufen. Gleichen Sie dabei die horizontalen Abstände zwischen den Rändern der einzelnen Textboxen an (Format/Horizontaler Abstand/Angleichen) und zentrieren Sie diese auf dem Formular (mit Format/Auf Formular zentrieren/Horizontal und Format/Auf Formular zentrieren/Vertikal). Hinweis Um zuverlässig mehrere Steuerelemente gleichzeitig zu markieren, erweitern Sie in der Toolbox den Eintrag Alle Windows Forms und klicken auf Zeiger. Dann klicken Sie in der Entwurfsansicht das erste Steuerelement und – bei gedrückter (Strg)-Taste – nacheinander die anderen an oder Sie wählen im Menü Bearbeiten/Alle Auswählen, sofern es sich um gleichartige Steuerelemente handelt. Des Weiteren besteht die Möglichkeit, bei gedrückter Maustaste einen Auswahlrahmen aufzuziehen, wodurch alle auf diese Weise erfassten Steuerelemente markiert werden.
10 Fügen Sie Ihrem Formular ein Button-Steuerelement hinzu. 11 Ändern Sie die Text-Eigenschaft des Buttons zu »Lottozahlen auslosen«. 12 Setzen Sie die Anchor-Eigenschaft auf None. 13 Legen Sie für den Text des Buttons eine 10-Punkt-Schrift fest und passen Sie die
Größe des Buttons an.
Der Windows Forms-Designer
Setzen Sie z.B. hierfür das Width-Attribut der Size-Eigenschaft auf den Wert 160 und Height auf 30. Tipp Um Größe bzw. Position eines Steuerelements zu ändern, können Sie auch mit gedrückter (ª)- bzw. (Strg)-Taste und den Pfeiltasten arbeiten. Ebenso kann die Größe des Buttons durch Ziehen der Anfasser (den Quadraten an den Begrenzungen des Steuerelements) bei gedrückter Maustaste geändert werden.
14 Platzieren Sie den Button mittig über die Textfelder. Damit ist das Design Ihrer Anwendung im Wesentlichen fertig. Drücken Sie die (F5)-Taste und sehen Sie sich das Ergebnis an.
Abbildung 15.5: Das fertige Formular bei gestartetem Programm
Es steht Ihnen natürlich frei, weitere Verbesserungen wie z.B. farbliche Hervorhebungen (Form-, Button- oder TextBox-Eigenschaften ForeColor bzw. BackColor) hinzuzufügen. Es sei Ihnen an dieser Stelle empfohlen, möglichst viel auszuprobieren, um Ihre IDE noch besser kennen zu lernen.
289
290
Kapitel 15
Tipp Falls Ihnen die Größe des Formulars nicht zusagt, können Sie diese analog zu Steuerelementen durch Ziehen eines der Anfasser bei gedrückter Maustaste ändern. Vergessen Sie nicht, das Formular zunächst mit einem Klick auszuwählen.
Weitere Steuerelemente Die Visual C# 2005 Express Edition stellt Ihnen als Entwickler von Windows-Anwendungen eine Reihe von Steuerelementen zur Auswahl, wie Sie diese als Anwender im Umgang mit professionellen Programmen gewöhnt sind. In der folgenden Tabelle sehen Sie eine Liste derjenigen Steuerelemente, die Sie vermutlich in der nächsten Zeit häufig verwenden werden: Steuerelement
Beschreibung
Label
Beschreibungstext, in der Regel als Beschriftung für andere Steuerelemente gedacht
TextBox
Textfeld zur Eingabe von Text
RichTextBox
Textfeld mit umfangreichen Formatierungsmöglichkeiten
Button
Schaltfläche zum Anklicken
RadioButton
Optionsschaltfläche, die aktiviert oder deaktiviert werden kann
CheckBox
Kontrollkästchen, das aktiviert oder deaktiviert werden kann
MenuStrip
Zeigt eine obere Menüleiste in bekannter Form, auch kaskadiert, an
ToolTip
QuickInfo für andere Steuerelemente
Tabelle 15.1: Steuerelemente mit Beschreibung
RadioButton- und CheckBox-Steuerelemente besitzen eine ähnliche Funktion. Beide stellen Auswahlmöglichkeiten bereit, die ein Benutzer durch Anklicken aktivieren oder deaktivieren kann. Während sich die Optionsfelder eines RadioButton-Steuerelements jedoch gegenseitig ausschließen – das heißt von mehreren vorhandenen Optionen kann vom Benutzer jeweils nur eine ausgewählt werden –, können gleichzeitig mehrere CheckBoxen (Kontrollkästchen) aktiviert sein.
Der Windows Forms-Designer
Hinweis Mehrere RadioButton-Steuerelemente eines Formulars oder in einem so genannten Panel (einem weiteren Steuerelement) zusammengefasste RadioButton-Steuerelemente bilden eine Gruppe. Wenn ein Benutzer ein Optionsfeld innerhalb einer Gruppe aktiviert, werden die anderen Optionsfelder automatisch deaktiviert.
Mit einem MenuStrip richten Sie am oberen Rand einer Form ein Standardmenü ein. Zum Hinzufügen eines MenuStrip-Steuerelements in Ihr Formular gehen Sie in bekannter Weise vor. Ziehen Sie also das Steuerelement auf das Formular oder klicken Sie je ein Mal in der Toolbox auf das MenuStrip-Symbol bzw. den Text MenuStrip und danach an eine beliebige Position innerhalb des Formulars. Danach erscheint im unteren Bereich, dem so genannten Komponentenfach, des Entwurfsfensters ein Eintrag für das MenuStrip-Steuerelement (das Komponentenfach dient zur Aufbewahrung von Steuerelementen, die zur Laufzeit entweder nicht sichtbar sind, z.B. eines Timers, oder die einen festen Platz haben, wie es bei einem Standardmenü der Fall ist; das Komponentenfach ist entsprechend nur im Entwurfsmodus, nicht aber zur Laufzeit sichtbar). Nach Ablegen des MenuStrip-Steuerelements auf dem Formular wird Ihnen oben in der Menüleiste für die erste Auswahlmöglichkeit ein Textfeld angeboten (klicken Sie gegebenenfalls das MenuStrip-Steuerelement im Komponentenfach an, falls diese Anzeige inzwischen nicht mehr sichtbar ist). Sie können sogleich mit der Eingabe des Textes für die erste Auswahlmöglichkeit beginnen (Sie müssen dazu ausdrücklich nicht in die Menüleiste klicken, es genügt tatsächlich, wenn das MenuStrip-Steuerelement aktiv ist. Mit dem ersten Tastendruck wird die Einfügemarke in ein dann sichtbares Textfeld der Menüleiste gesetzt). Beenden Sie die Eingabe mit (¢). Danach erscheint rechts davon ein weiteres Textfeld für die nächste Auswahlmöglichkeit und unterhalb davon ein Textfeld für Auswahlmöglichkeiten der zweiten Ebene – vergleichbar z.B. mit den Menüauswahlen Datei oder Bearbeiten (für die erste Ebene) bzw. Bearbeiten/Kopieren (zweite Ebene), wie sie in fast jedem Windows-Programm vorhanden sind. Durch Drücken von (¢) werden Ihnen jeweils neue leere Felder zum Erstellen weiterer Auswahlmöglichkeiten angeboten. Sobald Sie das Menü vollständig aufgefüllt haben, entziehen Sie dem MenuStrip-Steuerelement den Fokus, indem Sie mit der Maus einfach in einen anderen Bereich des Formulars klicken.
291
292
Kapitel 15
Hinweis Um ein MenuStrip-Steuerelement nachträglich weiter zu bearbeiten, klicken Sie das Steuerelement im Komponentenfach an oder klicken in die obere Menüleiste. Um einem bestimmten Menüeintrag weitere Untereinträge hinzuzufügen, klicken Sie auf den Eintrag der übergeordneten Ebene. Mit (Entf) lassen sich Menüeinträge nachträglich entfernen.
Ein ToolTip-Steuerelement können Sie fast jedem beliebigen anderen Steuerelement zuordnen. Es zeigt eine QuickInfo an, wenn ein Benutzer den Mauszeiger auf das betreffende Steuerelement bewegt und ein paar Augenblicke wartet. Wenn Sie Ihrer Form ein ToolTip-Element aus der Toolbox hinzufügen, erscheint wie beim MenuStrip-Steuerelement ein entsprechendes Symbol im Komponentenfach. Um nun für ein Steuerelement des Formulars eine QuickInfo festzulegen, gehen Sie wie folgt vor: • Wählen Sie das Steuerelement – beispielsweise den Button Ihres Lottoprogramms – aus, welches Sie mit der QuickInfo verbinden möchten. • Legen Sie im Eigenschaftenfenster für den Wert ToolTip auf toolTip1 einen passenden Text fest. Dieser erscheint dann als QuickInfo, wenn ein Benutzer den Mauszeiger auf dem Steuerelement positioniert. • Mit der Eigenschaft ToolTipTitle des ToolTip-Elements können Sie zusätzlich den Titel der QuickInfo bestimmen. Hinweis Wenn Sie ein ToolTip-Element in Ihrer Form ablegen, wird allen sich dort befindlichen Steuerelementen – auch dem Formular selbst – eine Eigenschaft ToolTip auf Name_des_Tooltip_Elementes hinzugefügt. Um für ein bestimmtes Steuerelement eine QuickInfo einzurichten, müssen Sie dieser Eigenschaft im Eigenschaftenfenster lediglich einen Wert zuweisen. Sie benötigen also keinesfalls für jedes Steuerelement ein eigenes ToolTip-Element.
Daneben sei noch das Web-Browser-Steuerelement zum Anzeigen von Webseiten erwähnt, welches in den Beispielen Webbrowser_1 und Webbrowser_2 Verwendung findet (siehe unter Hätten Sie gedacht ... am Schluss des Kapitels).
Steuerelemente mit Code verbinden – Ereignisbehandlungsroutinen
Steuerelemente mit Code verbinden – Ereignisbehandlungsroutinen Um auf unser Lottoprogramm zurückzukommen: Jedem Steuerelement sind eine Reihe von Ereignissen zugeordnet, die beim Programmlauf je nach Benutzerverhalten ausgelöst werden. Für jedes dieser Ereignisse steht eine Methode bereit, die dann ausgeführt wird, wenn das Ereignis beim Programmlauf auftritt. Hinweis Methoden, die auf ein Ereignis reagieren, werden auch Ereignis- oder Eventhandler genannt.
Klickt der Benutzer beispielsweise auf eine Schaltfläche, so löst er damit deren Click-Ereignis aus und die zugehörige Click()-Methode wird ausgeführt. Falls Sie für diese Methode Code hinterlegt haben, geschieht etwas, ansonsten bleibt der Anweisungsteil der Methode leer und das Ereignis hat keine weitere Auswirkung. Hinweis Für einige Aktionen erzeugt Visual C# Express den passenden Code auch automatisch, z.B. für den Schließen-Button in der rechten oberen Ecke eines Formulars.
Die Ereignisse für ein bestimmtes Steuerelement können Sie sich im Eigenschaftenfenster anzeigen lassen, indem Sie dort in die Ereignis-Ansicht wechseln (Klick auf das Blitz-Symbol). Sobald Sie die Einfügemarke auf ein bestimmtes Ereignis setzen, erhalten Sie unten im Eigenschaftenfenster eine Beschreibung, wann dieses Ereignis ausgelöst wird. In Ihrem Programm sollen nun sechs Lottozahlen ausgelost und in die Textfelder geschrieben werden, wenn ein Benutzer auf die Schaltfläche klickt. Sie müssen also Ihre Schaltfläche mit dem Click-Ereignis verbinden, indem Sie für die zugehörige Methode den passenden Code hinterlegen.
293
294
Kapitel 15
15 Führen Sie in der Entwurfsansicht einen Doppelklick auf die Schaltfläche aus. Damit wechseln Sie in das Code-Fenster, wo Ihnen das Grundgerüst der Methode button1_Click() bereits zur Verfügung steht. Hinweis Jedes Steuerelement besitzt ein Standardereignis. Für eine Schaltfläche ist das z.B. das Click-Ereignis, für ein Textfeld ist es z.B. das TextChanged-Ereignis – Letzteres tritt ein, sobald sich der Wert eines Textfeldes ändert. Um für Standardereignisse eine vordefinierte – zunächst leere – Methode anzulegen, genügt der gerade erwähnte Doppelklick auf das jeweilige Steuerelement. Für die anderen Ereignisse legen Sie die entsprechenden (ebenso zunächst leeren) Methoden an, indem Sie im Eigenschaftenfenster den Ereignisnamen doppelt anklicken (auch eine Methode für das Standardereignis kann so alternativ angelegt werden).
Nun sind Ihre Programmierkenntnisse gefragt. Zunächst müssen Sie dafür sorgen, dass im Programm sechs Zufallszahlen ausgelost und festgehalten werden. Im Hinblick auf weitere Zugriffe mittels einer for-Schleife bietet sich für das Speichern der Zahlen ein Array an.
16
Definieren Sie in der Methode button1_Click() ein Array zur Aufnahme von sechs Integer-Zahlen: private void button1_Click(object sender, EventArgs e) { int[] lottozahlen = new int[6]; }
17
Erzeugen Sie sechs Zufallszahlen im Bereich von 1 bis 49 und halten Sie diese in den Elementen des Arrays fest. Verwenden Sie zum einfachen Zugriff auf die ArrayElemente die Laufvariable einer for-Schleife: private void button1_Click(object sender, EventArgs e) { int i; int[] lottozahlen = new int[6]; Random zufallszahl = new Random(); for (i = 0; i < 6; i++) { lottozahlen[i] = zufallszahl.Next(1, 50); } }
Steuerelemente mit Code verbinden – Ereignisbehandlungsroutinen
18 Sortieren Sie das Array: private void button1_Click(object sender, EventArgs e) { int i; int[] lottozahlen = new int[6]; Random zufallszahl = new Random(); for (i = 0; i < 6; i++) { lottozahlen[i] = zufallszahl.Next(1, 50); } Array.Sort(lottozahlen); }
Nun müssen Sie den Fall behandeln, dass Zahlen doppelt ausgelost werden. Lösungsvorschlag: Sie untersuchen zunächst das sortierte Array auf doppelte Zahlen, indem Sie jeweils zwei Nachbarn miteinander vergleichen. Ist das der Fall, so führen Sie die Auslosung nochmals durch. Dies wiederholen Sie so lange, bis eine Auslosung keine doppelten Zahlen enthält. Hinweis Der vorgeschlagene Lösungsweg ist sicher nicht der eleganteste, aber gedanklich leicht nachvollziehbar und einfach zu realisieren. Daher soll diesem hier der Vorzug gegeben werden. Zwar kann es zu Wiederholungen der entsprechenden Anweisungen für die Auslosung während des Programmlaufs kommen, was aber keine merkliche Verschlechterung der Performance Ihrer Anwendung nach sich zieht. (Es steht Ihnen natürlich frei, später eine raffiniertere Lösung zu entwickeln, es ist sogar eine gute Übung.)
295
296
Kapitel 15
19
Untersuchen Sie das sortierte Array auf doppelte Zahlen. Falls doppelte Zahlen vorkommen, setzen Sie eine Variable mit dem Bezeichner doppelt auf den Wert 1: private void button1_Click(object sender, EventArgs e) { int i, doppelt = 0; int[] lottozahlen = new int[6]; Random zufallszahl = new Random(); for (i = 0; i < 6; i++) { lottozahlen[i] = zufallszahl.Next(1, 50); } Array.Sort(lottozahlen); for (i = 0; i < 5; i++) { if (lottozahlen[i] == lottozahlen[i+1]) doppelt = 1; } }
20
Wiederholen Sie die Auslosung für den Fall, dass im Array doppelte Werte enthalten sind.
Denken Sie daran, doppelt bei Schleifenbeginn wieder auf 0 zu setzen. Andernfalls produzieren Sie eine Endlosschleife, falls in der ersten Auslosung doppelte Werte enthalten sind: private void button1_Click(object sender, EventArgs e) { int i, doppelt = 0; int[] lottozahlen = new int[6]; Random zufallszahl = new Random(); do { doppelt = 0; for (i = 0; i < 6; i++) { lottozahlen[i] = zufallszahl.Next(1, 50); } Array.Sort(lottozahlen); for (i = 0; i < 5; i++) { if (lottozahlen[i] == lottozahlen[i+1]) doppelt = 1; } } while (doppelt == 1); }
Steuerelemente mit Code verbinden – Ereignisbehandlungsroutinen
Tipp IntelliSense hilft Ihnen auch beim Einfügen von Codeausschnitten, wobei es unterscheidet, ob Sie ein Codestück nur einfügen oder von einem anderen umschließen lassen wollen. Klicken Sie mit der rechten Maustaste in das Editorfenster und wählen Sie im Kontextmenü Ausschnitt einfügen... bzw. Umschliessen mit... (Tastenkombination (Strg) + (K), (X) bzw. (Strg) + (K), (S)). Beide Befehle sind auch über das Menü Bearbeiten/IntelliSense erreichbar. Um z.B. das Codestück mit der Auslosung, Sortierung und Prüfung wie oben mit der do-while-Schleife zu umgeben, markieren Sie es und verwenden den Befehl Umschließen mit.... Dann bekommen Sie eine Auswahlliste angeboten, in der Sie für die do-while-Schleife den Eintrag do selektieren.
Abbildung 15.6: IntelliSense hilft beim Erzeugen von Konstrukten und kann dabei auch bereits vorhandene Codeteile einbeziehen, sodass sich z.B. vorhandene Anweisungen in eine for-Schleife einbetten lassen
Um die Schleife hinzuzufügen, klicken Sie doppelt auf den Eintrag oder drücken Sie (¢). Danach müssen Sie lediglich noch die Bedingung der do-while-Schleife anpassen.
Nun können Sie die sechs Lottozahlen in aufsteigend sortierter Reihenfolge in die Textfelder schreiben. Für den Zugriff auf die Textfelder verwenden Sie in bekannter Weise den Objektnamen plus Punktoperator plus Name der Eigenschaft. Allerdings müssen Sie die Integer-Zahlen noch in einen String umwandeln (Methode ToString()), bevor Sie diese der Text-Eigenschaft zuweisen.
297
298
Kapitel 15
21 Schreiben Sie die Lottozahlen in die Textfelder: private void button1_Click(object sender, EventArgs e) { int i, doppelt = 0; int[] lottozahlen = new int[6]; Random zufallszahl = new Random(); do { doppelt = 0; for (i = 0; i < 6; i++) { lottozahlen[i] = zufallszahl.Next(1, 50); } Array.Sort(lottozahlen); for (i = 0; i < 5; i++) { if (lottozahlen[i] == lottozahlen[i+1]) doppelt = 1; } } while (doppelt == 1); textBox1.Text = lottozahlen[0].ToString(); textBox2.Text = lottozahlen[1].ToString(); textBox3.Text = lottozahlen[2].ToString(); textBox4.Text = lottozahlen[3].ToString(); textBox5.Text = lottozahlen[4].ToString(); textBox6.Text = lottozahlen[5].ToString(); }
Auf der CD-ROM Das Projekt finden Sie unter Beispiele/Windows-Programme auf der Buch-CD (Projektname Lottospiel).
22
Starten Sie Ihre Applikation und betätigen Sie die Schaltfläche »Lottozahlen auslosen«.
In den Textfeldern sollten Sie die ausgelosten Zahlen in aufsteigender Reihenfolge zu sehen bekommen.
Hätten Sie gedacht ...
Abbildung 15.7: Das Lottoprogramm in Aktion
In der Tabelle sehen Sie eine Übersicht der Standardereignisse für die oben genannten Steuerelemente nebst Beschreibung: Steuerelement
Standardereignis
Beschreibung
Label
Click
Tritt ein, wenn ein Benutzer das Label anklickt
TextBox
TextChanged
RichTextBox
TextChanged
Tritt mit jeder Änderung des enthaltenen Textes ein
Button
Click
Tritt ein, wenn ein Benutzer den Button anklickt
RadioButton
CheckedChanged
CheckBox
CheckedChanged
Tritt ein, wenn sich der Wert der CheckedEigenschaft ändert
MenuStrip
ItemClicked
Tritt ein, sobald ein Benutzer auf einen Menüeintrag klickt
ToolTip
Popup
Tritt unmittelbar vor dem Anzeigen der QuickInfo ein
Tabelle 15.2: Die wichtigsten Steuerelemente und die dazugehörigen Standardereignisse
299
300
Kapitel 15
Die Checked-Eigenschaft von RadioButton- und CheckBox-Steuerelementen besitzt den Wert true, wenn das jeweilige Steuerelement aktiviert ist, andernfalls false (Anzeige True bzw. False im Eigenschaftenfenster). Deren Wert ändert sich also, wenn ein Benutzer die entsprechende Option aktiviert bzw. – bei RadioButtons durch Auswahl einer anderen Option – wieder deaktiviert.
Die Methode »Show()« der Klasse »MessageBox« Eine beliebte Variante, um sich schnell anzeigen zu lassen, wann genau ein bestimmtes Ereignis eintritt, liegt darin, für den jeweiligen Eventhandler Code zum Anzeigen eines Meldungsfensters zu hinterlegen. Nicht nur dazu sollten Sie die Methode Show() der Klasse MessageBox (Namensraum System.Windows.Forms) kennen. Sie ist gewissermaßen das Pendant der Windows Forms zu den Ausgabemethoden Write() und WriteLine() der Console-Klasse. Hinweis Wenn Sie Ihre Konsolenprogramme mit Windows Forms umsetzen, sollten Sie daran denken, die Console.WriteLine()- bzw. Console.Write()-Methoden mit entsprechenden Aufrufen von MessageBox.Show() zu ersetzen, da die Methoden der ConsoleKlasse in diesem Zusammenhang nicht verwendet werden können.
Der Methode Show() der Klasse MessageBox erzeugt ein Meldungsfenster, das standardmäßig ohne Titel und mit einer OK-Schaltfläche erscheint. Hinweis Beides – den Text der Titelzeile sowie Anzahl und Art der Schaltflächen – können Sie nach Bedarf ändern, indem Sie Show() beim Aufruf entsprechende Argumente übergeben. Die Methode ist mehrfach überladen. Eine Beschreibung können Sie in der Online-Hilfe Ihrer Visual C# Express-IDE unter der URL ms-help://MS.VSExpressCC.v80/ MS.NETFramework.v20.de/cpref17/html/ O_T_System_Windows_Forms_MessageBox_Show.htm abrufen (Suchbegriff MessageBox.Show-Methode eingeben, dann aus den Such-
ergebnissen den obersten Eintrag auswählen).
Steuerelemente mit Code verbinden – Ereignisbehandlungsroutinen
Falls Sie z.B. für das Ereignis Popup Ihres ToolTip-Steuerelements – sofern vorhanden – ein Meldungsfenster anzeigen möchten, klicken Sie doppelt auf das entsprechende Symbol im Komponentenfach des Entwurfsfensters und fügen dem Eventhandler den Methodenaufruf MessageBox.Show() hinzu. Dabei übergeben Sie der Methode den passenden Text für das Meldungsfenster. Hinweis Visual C# Express bindet beim Anlegen einer Form automatisch den Namensraum System.Windows.Forms ein, wovon Sie sich in der Code-Ansicht der Quelldatei für Ihr Formular (Form1.cs) überzeugen können. using System.Windows.Forms;
private void toolTip1_Popup(object sender, PopupEventArgs e) { MessageBox.Show("Sie haben gerade das " + "Popup-Ereignis des ToolTips ausgelöst"); }
Abbildung 15.8: Mithilfe der Methode »Show()« der Klasse MessageBox lassen sich auf einfache Weise Meldungsfenster anzeigen
301
302
Kapitel 15
Hätten Sie gedacht ... ... dass es mit der Visual C# 2005 Express Edition nur weniger – in der Minimalausstattung sogar nur einer einzigen – Programmierzeilen bedarf, um einen eigenen Web-Browser zu »entwickeln«? Die Lösung können Sie in der OnlineHilfe Ihrer IDE unter der folgenden URL abrufen: ms-help://MS.VSExpressCC.v80/MS.NETFramework.v20.de/ dv_csexpresscon/html/250618a0-c5f5-4c68-86bf-4fb970220992.htm.
Abbildung 15.9: Ihr eigener Web-Browser
Auf der CD-ROM Das nachgebildete Beispiel finden Sie unter dem Projektnamen Webbrowser_2 im Ordner Beispiele/Windows-Programme auf der BuchCD. Die »einzeilige« Version eines Web-Browsers befindet sich als Webbrowser_1 im gleichen Ordner. Anders als in Webbrowser_2 können Sie in diesem Browser jede beliebige URL eingeben.
303
Anhang – Antworten Antworten zu Kapitel 1 • •
•
Was ist ein Compiler? Ein Compiler ist ein Softwareprogramm, welches den Quellcode übersetzt und ihn damit praktisch in seine endgültige – ausführbare – Form bringt. Worin unterscheiden sich Hochsprachen von maschinennahen Sprachen? Zur Übersetzung eines in einer Hochsprache geschriebenen Quellcodes sind im Allgemeinen mehr interne Übersetzungsschritte notwendig als bei maschinennahen Sprachen. Dagegen bieten Hochsprachen gegenüber maschinennahen Sprachen im Allgemeinen den Vorteil der besseren Lesbarkeit. Auch sind Hochsprachen meist weniger komplex und entsprechend einfacher zu erlernen. Was ist eine IDE? IDE ist die Abkürzung für »Integrated Development Environment«, was »integrierte Entwicklungsumgebung« heißt. Dies ist eine Software, in der alle Werkzeuge zur Programmentwicklung unter einer Benutzeroberfläche zusammengefasst sind.
Antworten zu Kapitel 2 •
•
•
Was bedeutet der Begriff »Laufzeitumgebung«? Wenn ein Programm eine bestimmte Software (abgesehen vom Betriebssystem) benötigt, damit es ausgeführt werden kann, spricht man von einer Laufzeitumgebung. Was hat es mit dem IL-Code auf sich? IL-Code ist die Abkürzung für »Intermediate Language Code«. Dieser bildet gewissermaßen das Endprodukt der C#-Programmierung, ist aber ohne weitere Übersetzung nicht lauffähig. Es handelt sich also um eine Art Zwischencode. Tatsächlich wird der IL-Code mitunter auch als Zwischencode bezeichnet (engl. intermediate – dazwischen liegend). Welcher Compiler erzeugt aus dem Quellcode den IL-Code? Dafür ist der C#-eigene Compiler zuständig, der unter der ausführbaren Datei csc.exe zu finden ist. Dieser Compiler ist ebenfalls Bestandteil des .NET Framework.
304
Anhang – Antworten
•
•
Welche Rolle spielt der Compiler des .NET Framework bei der Ausführung von C#-Programmen? Der Just-in-Time-Compiler des .NET Framework übersetzt beim Programmlauf den IL-Code des C#-Programms in spezifischen Maschinencode, den das jeweilige Betriebssystem verarbeiten kann. Wie lautet eine zusammenfassende Beschreibung der Bedeutung des .NET Framework für die Ausführung von C#-Programmen? Das .NET Framework bildet die Laufzeitumgebung für C#-Programme.
Antworten zu Kapitel 4 •
• •
• •
Nennen Sie eine Anweisung, welche die Bildschirmausgabe ***** einschließlich einer Zeilenschaltung bewirkt. Die Anweisung Console.WriteLine("*****"); bewirkt die Ausgabe ***** und setzt anschließend die Einfügemarke in die nächste Zeile. Woran erkennt der Compiler den Anfang und das Ende eines Methodenrumpfes? An den geschweiften Klammern, { kennzeichnet den Anfang, } das Ende. Welche Anweisung wird beim Programmlauf als Erstes abgearbeitet? Da die Programmausführung immer in Main() beginnt, handelt es sich um die erste Anweisung in dieser Methode, mit anderen Worten um die Anweisung, welche im Methodenrumpf von Main() am weitesten oben steht. Wie wird die Definition einer Klasse eingeleitet? Mit dem Schlüsselwort class. Nennen Sie in hierarchisch aufsteigender Reihenfolge drei Bausteine eines C#-Programms. Anweisung, Methode, Klasse.
Antworten zu Kapitel 6 •
Welche Bildschirmausgabe erzeugt folgende Anweisung? Console.Write(11 + 22 + "33");
•
Die Anweisung bewirkt die Ausgabe 3333. Zunächst erfolgt die mathematische Addition zwischen den Operanden 11 und 22 zu 33. An dieses Ergebnis wird dann der String "33" angehängt. Schreiben Sie ein Programm, welches folgende Ausgabe erzeugt: Das Newline-Zeichen wird mit "\n" dargestellt
Anhang – Antworten
Hier die Main()-Methode: static void Main(string[] args) { Console.Write("Das Newline-Zeichen wird " + "mit \"\\n\" dargestellt"); Console.ReadLine(); }
Antworten zu Kapitel 7 •
zahl sei eine Variable vom Datentyp int. Was bewirkt die folgende Zuwei-
sung? zahl = zahl + 3;
• •
Die Zuweisung bewirkt eine Erhöhung des bisherigen Variablenwerts um den Wert 3. Was bedeutet Initialisieren von Variablen? Eine Variable wird initialisiert, wenn sie bereits bei ihrer Definition mit einem Anfangswert versehen wird. Definieren Sie eine int-Variable mit dem Bezeichner z und initialisieren Sie diese mit dem Wert 0. int z = 0;
•
Worin liegt der Fehler in folgendem Codestück? string name; Console.Write(name);
•
Der Lesezugriff auf eine Variable, welche noch keinen kontrollierten Wert enthält, ist in C# nicht erlaubt. Das bedeutet, zwischen Definition und Ausgabeanweisung muss auf jeden Fall eine Wertzuweisung an die Variable name erfolgen. Erklären Sie die Funktionsweise folgender Anweisung: Console.ReadLine();
•
Die Anweisung bewirkt die Entgegennahme einer Benutzereingabe (einschließlich eines Zeilenvorschubs), ohne diese in einer Variablen zu speichern. Der Einsatz von ReadLine() in dieser Form ist z.B. als Haltebefehl geeignet. Ist 1A ein gültiger Bezeichner? Nein, da das erste Zeichen eines Bezeichners keine Ziffer sein darf.
305
306
Anhang – Antworten
Antworten zu Kapitel 8 • • •
Welchen Datentyp besitzt das Literal 3? Das Literal 3 ist vom Datentyp int. Nennen Sie den Datentyp des Literals 3.0. Datentyp: double Der Datentyp des Literals "3" ist? string
•
Der Datentyp des Literals '3'ist? char
•
Warum verursacht folgendes Codestück beim Kompilieren eine Fehlermeldung? const string name = "Mustermann"; Console.Write("Wie heißen Sie? "); name = Console.ReadLine();
•
Da die Variable name mit dem Schlüsselwort const definiert ist, darf ihr Wert im weiteren Code nicht mehr verändert werden. Die Anweisung name = Console.ReadLine(); ruft daher eine Fehlermeldung hervor. zahl sei eine Variable vom Typ double. Das bedeutet, sie kann ausschließlich Werte von diesem Datentyp aufnehmen. Warum erzeugt folgende Zuweisung dennoch keine Fehlermeldung? zahl = 100;
•
Das Literal 100 ist zwar vom Datentyp int, vor der eigentlichen Zuweisung erfolgt jedoch eine implizite Konvertierung in den Datentyp der Variablen (double). Korrigieren Sie die/den Fehler in folgendem Codestück: int z; Console.Write ("Geben Sie eine Zahl ein: "); z = Console.ReadLine();
Da der Rückgabewert der Methode ReadLine() vom Datentyp string ist, muss dieser erst explizit umgewandelt werden, bevor er einer int-Variablen zugewiesen werden kann. Eine implizite Konvertierung von string nach int erfolgt nicht. Die Lösung lautet also int z; Console.Write ("Geben Sie hier eine Zahl ein: "); z = Convert.ToInt32(Console.ReadLine());
Anhang – Antworten
•
Ist folgende Variableninitialisierung korrekt? int wert = 25.99;
Nein, da C# implizite Konvertierungen von double nach int grundsätzlich nicht durchführt.
Antworten zu Kapitel 9 •
Welchen Initialisierungswert erhält die Variable x mit der folgenden Definition? int x = 2 + 3 * 5;
•
Die Variable erhält den Wert 17 (nicht 25), da wie in der Mathematik »Punkt vor Strich« gilt. Im Ausdruck rechts des Zuweisungsoperators wird daher die Multiplikation 3 * 5 vor der Addition ausgeführt. Schreiben Sie ein Programm, welches das Produkt zweier vom Benutzer einzugebender Zahlen berechnet und mit einer Genauigkeit von zwei Nachkommastellen auf den Bildschirm ausgibt. Eine Lösung könnte wie folgt aussehen: static void Main(string[] args) { double z_1, z_2; Console.Write("Erste Zahl: "); z_1 = Convert.ToDouble(Console.ReadLine()); Console.Write("Zweite Zahl: "); z_2 = Convert.ToDouble(Console.ReadLine()); Console.WriteLine("{0} * {1} = {2:F}" , z_1, z_2, z_1 * z_2); Console.ReadLine(); }
Das entsprechende Projekt finden Sie unter Beispiele/Konsolenprogramme auf der Buch-CD (Projektname K09e).
Antworten zu Kapitel 10 •
Welchen Rückgabewert liefert folgender Ausdruck? 22 == 2 * 10 || 2.3 < 2.4
Der linke Teilausdruck wird zu false ausgewertet, der rechte zu true (false || true). Somit ist der Ergebniswert des Gesamtausdrucks ebenfalls true (bei einer logischen Oder-Verknüpfung ist der Gesamtausdruck »wahr«, wenn mindestens ein Teilausdruck »wahr« ist).
307
308
Anhang – Antworten
•
z sei eine Variable vom Datentyp int. Welche Bildschirmausgabe erzeugt folgendes Codestück, falls z den Wert 9 enthält? if(z <= 0) Console.Write("Die Variable z besitzt"); Console.Write("den Wert " + z);
Ausgabe: den Wert 9 Da die Auswertung der if-Bedingung false ergibt (9 ist nicht kleiner oder gleich 0), wird die erste Ausgabeanweisung übersprungen. Die Belanglosigkeit des Ausgabetexts und das Nichteinrücken der zum if-Konstrukt gehörigen Anweisung dienten hier allein dem Zweck, Sie zu irritieren. Lesbarer ist natürlich if(z <= 0) Console.Write("Die Variable z besitzt "); Console.Write("den Wert " + z);
Allerdings war die Intention des Programmierers wahrscheinlich eine andere und er wollte beide Ausgabezeilen vom Ergebnis der Bedingung abhängig machen. Dann müssen diese allerdings in einen Block gesetzt werden: if(z <= 0) { Console.Write("Die Variable z besitzt "); Console.Write("den Wert " + z); }
•
An welcher Stelle ist in folgendem Codestück ein (logischer) Fehler zu vermuten? if(id == 770234); Console.Write("Guten Tag Herr Müller");
Es ist anzunehmen, dass die Ausgabeanweisung nur für den Fall zur Ausführung gelangen soll, dass die Variable id den Wert 770234 besitzt. Tatsächlich wird diese Anweisung jedoch unabhängig von einer Bedingung in jedem Fall abgearbeitet. Der Grund: Nicht die Ausgabeanweisung, sondern die leere Anweisung (;) wird dem if-Konstrukt zugerechnet. Da eine leere Anweisung keinerlei Wirkung entfaltet, ist das if-Konstrukt if(id == 770234); eigentlich überflüssig. Somit kann davon ausgegangen werden, dass es sich bei dem ersten Semikolon um ein Versehen des Programmautors handelt.
Anhang – Antworten
•
•
Was bewirkt die return-Anweisung? Die return-Anweisung hat das unverzügliche Verlassen einer Methode zur Folge. Da Sie sich bis auf Weiteres ausschließlich innerhalb der Methode Main() befinden, ist dies gleichbedeutend mit dem Ende der Programmausführung. Fordern Sie den Benutzer auf, eine ganze Zahl einzugeben. Teilen Sie ihm anschließend mit, ob die eingegebene Zahl gerade oder ungerade war. Speichern Sie die Benutzereingabe in einer Variablen zahl vom Datentyp int. Setzen Sie den Modulo-Operator (siehe Kapitel 9, Abschnitt »Der Modulo-Operator«) zur Formulierung einer passenden if-Bedingung ein. Eine Zahl ist gerade, wenn sie durch 2 ohne Rest teilbar ist. Daher müssen Sie den Ausdruck zahl % 2 mit dem Wert 0 vergleichen, um festzustellen, ob die Eingabezahl gerade ist: static void Main(string[] args) { int zahl; Console.Write("Geben Sie eine ganze Zahl ein: "); zahl = Convert.ToInt32(Console.ReadLine()); if(zahl % 2 == 0) Console.WriteLine("Die Zahl ist gerade"); else Console.WriteLine("Die Zahl ist ungerade"); Console.ReadLine(); }
•
Das entsprechende Projekt finden Sie unter Beispiele/Konsolenprogramme auf der Buch-CD (Projektname K10g). Die Programmausführung landet in einem switch-Konstrukt mit integriertem default-Zweig. Ist der Fall denkbar, dass kein Anweisungsteil innerhalb des Konstrukts zur Ausführung gelangt? Nein. Sollte keine der angegebenen case-Konstanten mit dem Wert des switch-Ausdrucks übereinstimmen, kommt der Anweisungsteil der default-Marke zur Ausführung.
309
310
Anhang – Antworten
Antworten zu Kapitel 12 •
Wo liegt der Fehler? string[] names = new string[7]; for(int i = 0; i <= 7; i++) { names[i] = Console.ReadLine(); }
• • •
•
Die Schleifenbedingung (i <= 7) ist Ursache für einen unerlaubten ArrayZugriff. Da der Schleifenblock für den Wert 7 von i zur Ausführung gelangt, resultiert daraus der Zugriff names[7]. Das Array ist jedoch für 7 Elemente definiert, das letzte Element wird also mit names[6] angesprochen, da die Zählung bei Arrays stets bei 0 beginnt. Richtig müsste die Schleifenbedingung lauten: i < 7 oder i <= 6. Welchen Vorteil bietet die foreach-Schleife? Die Array-Grenzen werden automatisch erkannt. Der Programmierer braucht sich daher nicht um diese zu kümmern. Nennen Sie eine Einschränkung in Bezug auf die foreach-Schleife. Mit der foreach-Schleife ist es nicht möglich, ein Array zu verändern. Sie ist ausschließlich für den Lesezugriff vorgesehen. Welche Anweisung können Sie im Quellcode verwenden, um ein Array mit dem Bezeichner myArr zu sortieren? Verwenden Sie dazu die Methode Sort() der Klasse Array. Der Methodenaufruf lautet: Array.Sort(myArr); Was bewirkt die folgende Anweisung? double[] numbers = {0.99, 22.3, 55.01, 17.78};
Es handelt sich um Definition und Initialisierung eines Arrays mit vier Elementen. Das Array-Objekt wird auf dem Heap angelegt. Die in Klammern stehenden Werte werden den Elementen des Arrays in der angegebenen Reihenfolge zugewiesen. Die Referenzvariable numbers erhält einen Verweis auf das Array-Objekt.
Anhang – Antworten
Antworten zu Kapitel 13 • •
•
Wann spricht man beim Methodenaufruf von Call by value? Wenn der aufgerufenen Methode ein reiner Wert übergeben wird. Was bedeutet Call by reference? Hierbei wird der aufgerufenen Methode ein Datenobjekt mittels eines Verweises bekannt gemacht. Mit anderen Worten: Die Methode erhält keinen einfachen Wert, sondern den Verweis auf ein Datenobjekt. Es handelt sich also um eine Referenzübergabe. Welches Schlüsselwort muss im Kopf einer Methode auf jeden Fall verwendet werden, wenn die Methode ohne Rückgabewert definiert ist? void
•
Was können Sie anhand des folgenden Methodenkopfs über die Methode xy() aussagen? static int xy(double a, string b)
•
Die Methode xy() erwartet beim Aufruf zwei Argumente, eines vom Datentyp double, das zweite vom Datentyp string. Zudem besitzt die Methode einen Rückgabewert (Datentyp int). Die genaue Wirkungsweise der Methode ließe sich natürlich nur dem Anweisungsteil entnehmen. Welche Ausgabe erzeugt folgendes Programm? class Program { static void B(string myString) { Console.Write(myString); } static void Main(string[] args) { A(); Console.WriteLine("weiter"); } static void A() { Console.Write("Jetzt "); B("geht es "); } }
Die Ausgabe lautet: Jetzt geht es weiter
311
312
Anhang – Antworten
Main() ruft die Methode A() auf, diese wiederum die Methode B() mit dem Argument "geht es ". Nachdem die Anweisung von B() ausgeführt ist, erfolgt der Rücksprung nach A(). Nach Beenden von A() landet der Programmlauf schließlich wieder in Main(). Die Reihenfolge, in der die
•
Methodendefinitionen innerhalb der Klasse stehen, ist dabei nicht von Bedeutung. Das entsprechende Projekt finden Sie unter Beispiele/Konsolenprogramme auf der Buch-CD (Projektname K13f ). Finden Sie den Fehler in folgender Methodendefinition: static void Quad(int a) { Console.WriteLine("Das Quadrat von {0} ist {1}" , a, a * a); return "ENDE"; } Da die Methode im Kopf als void, also ohne Rückgabewert, festgelegt ist, darf die return-Anweisung nur ohne Wertangabe verwendet werden (return;). Allerdings wird die Methode mit Erreichen der schließenden geschweiften Klammer } ohnehin verlassen, sodass der Einsatz des return-Befehls an dieser Stelle überflüssig ist. Denken Sie aber daran,
dass bei Methoden, welche mit Rückgabewert definiert sind, der Gebrauch von return (mit Wertangabe) zwingend vorgeschrieben ist.
Antworten zu Kapitel 14 • • •
Was ist ein Konstruktor? Ein Konstruktor ist eine Methode, welche automatisch beim Instanzieren eines Objekts aufgerufen wird. Was ist ein Standardkonstruktor? Hierbei handelt es sich um einen parameterlosen Konstruktor. Fügen Sie Ihrer Klasse Rechteck eine nicht statische public-Methode hinzu, welche zwei Rechtecke miteinander vergleicht. Die Methode soll true zurückgeben, falls beide Rechtecke deckungsgleich sind (also die gleiche Länge und Breite besitzen), ansonsten false. Nennen Sie die Methode Vergleiche(). Das Rechteck, für das die Methode Vergleiche() aktuell aufgerufen wird, ist der Methode bekannt. Jedoch benötigt Vergleiche() einen Parameter zur Übergabe eines Verweises auf das zu vergleichende Rechteck.
Anhang – Antworten
public bool Vergleiche(Rechteck zwei) { if(laenge == zwei.laenge && breite == zwei.breite) return true; else return false; }
•
Der direkte Zugriff auf Länge und Breite des übergebenen Rechtecks (zwei.laenge bzw. zwei.breite) ist hier erlaubt, da beide Objekte sich in derselben Klasse befinden. Möglicherweise haben Sie die entsprechenden Eigenschaften verwendet (zwei.Laenge bzw. zwei.Breite), was ebenfalls korrekt ist. Formulieren Sie eine if-Bedingung zum Vergleich zweier Rechtecke a und b. Benutzen Sie dazu die eben erstellte Methode Vergleiche(). Diese sieht wie folgt dargestellt aus. Dabei spielt es keine Rolle, für welches Rechteck Sie die Methode aufrufen, sodass sowohl if(a.Vergleiche(b))
als auch Folgendes möglich ist: if(b.Vergleiche(a))
•
•
Welche Datenelemente würden Sie in einer Minimaldefinition einer Klasse Konto einrichten, welche ein Kundenkonto einer Bank repräsentieren soll? Die Klasse Konto sollte auf jeden Fall Datenelemente für Kontostand sowie Kontonummer und Name (bzw. zwei Felder für Vor- und Nachname) des Kontoinhabers besitzen. Zu überlegen wären Felder für Kreditrahmen und Identifikationsnummer (PIN). Eröffnen Sie mit dieser fiktiven Klasse ein neues Konto. Instanzieren Sie also ein Objekt der Klasse Konto (unter Verwendung des Standardkonstruktors der Klasse) und nennen Sie die Referenzvariable meinKonto. Die Anweisung lautet Konto meinKonto = new Konto();
313
315
Glossar Argumente Informationen, die beim Aufruf einer →Methode an diese übergeben werden. Argumente stehen nach dem Methodennamen zwischen zwei runden Klammern, wobei die einzelnen Argumente durch Kommata (,) voneinander getrennt werden. Weitere Bezeichnungen für Argumente sind Übergabewerte, Übergabeparameter bzw. aktuelle →Parameter oder einfach Parameter.
Array Datenstruktur, in der mehrere Werte gleichen Datentyps zusammengefasst und unter einem einzigen Namen mittels Indexangabe ansprechbar sind.
Ausdruck Jeder Teil einer Anweisung, der für einen Wert steht (das gilt mitunter auch für die ganze Anweisung). Komplexe Ausdrücke setzen sich in der Regel aus Literalen, →Variablen, Operatoren und auch Aufrufen von →Methoden (für deren Rückgabewerte) zusammen.
Block Alle Anweisungen, welche zwischen einer öffnenden und der schließenden geschweiften Klammer gleicher Ordnung stehen ({…}).
Compiler Software zum Übersetzen des Quellcodes, bei herkömmlichen Programmiersprachen in Maschinencode. Der Compiler von C# erzeugt einen maschinenunabhängigen Zwischencode, den so genannten Intermediate Language Code (IL-Code).
Debugger Programm, das bei der Anwendungsentwicklung für die Fehlersuche eingesetzt wird. Die Visual C# 2005 Express Edition beinhaltet auch einen integrierten Debugger.
Dekrementieren Vermindern des Werts einer →Variablen um den Betrag eins.
316
Glossar
Destruktor Gegenstück zum →Konstruktor. Die Destruktor-Methode (→Methode) wird aufgerufen, wenn ein Objekt (→Instanz) gelöscht wird. Der Definition eines Dekonstruktors kommt in C# eine nur geringe Bedeutung zu.
Eigenschaften Spezielle →Methoden, die den Zugriff auf →Felder mit der gewohnten Syntax gestatten. Ein Programmierer, der eine →Klasse verwendet, wendet den Bezeichner für eine Eigenschaft im Code praktisch genauso an, als greife er auf ein public-Feld zu. Tatsächlich wird bei Ausführung jedoch die entsprechende Methode – eben die Eigenschaft – aufgerufen.
Ereignisse Zustandsänderungen, auf die Steuerelement-Objekte (→Steuerelement) und auch Formulare reagieren. Jedem Steuerelement-Objekt und auch einem Formular sind spezifische Ereignisse zugeordnet, welche beim Programmlauf dann gegebenenfalls durch ein bestimmtes Benutzerverhalten – z.B. einen Mausklick auf eine Schaltfläche – oder andere Ursachen (z.B. abgelaufene Zeit bei einem Timer) ausgelöst werden.
Eventhandler Spezielle →Methoden, die immer dann ausgeführt werden, wenn ein Steuerelement-Objekt (→Steuerelement) ein bestimmtes →Ereignis meldet. Man spricht auch von Ereignisbehandlungsroutinen oder Ereignishandlern.
Felder In C# die Datenelemente einer →Klasse. Praktisch handelt es sich dabei um →Variablen, die innerhalb einer Klasse definiert sind. Jedes Objekt erhält bei seiner Instanzierung (→Instanz) eine Kopie jedes in der Klasse definierten Feldes, sodass später die aktuell in den Feldern gespeicherten Werte den Zustand eines bestimmten Objekts ausmachen. Für den Zugriff auf die Felder einer Klasse sieht C# spezielle →Methoden vor – die so genannten →Eigenschaften.
IDE Abkürzung für »Integrated Development Environment« (integrierte Entwicklungsumgebung). Software, in welcher alle notwendigen Programmierwerkzeuge unter einer Benutzeroberfläche zusammengefasst sind.
Glossar
Inkrementieren Erhöhen des Werts einer →Variablen um den Betrag eins.
Instanzen (von Klassen) Auch »Objekt (einer Klasse)« genannt. Ein Element im Arbeitsspeicher, das gewissermaßen die reale, »lebendige« und tatsächlich nutzbare Umsetzung einer →Klasse darstellt. Während die Klasse mehr einen abstrakten Bauplan darstellt, der das Wesen späterer nutzbarer Elemente (also Objekte) beschreibt, ist ein Objekt etwas Greifbares, das →Methoden und →Eigenschaften zur Verfügung stellt und über eigene Datenspeicherbereiche verfügt. Das Erzeugen eines Objekts unter Vorlage einer Klasse wird als »Instanzieren« bezeichnet. Ein Objekt wird meist an tatsächlich existierende Dinge angelehnt. Beispielsweise könnte ein Objekt einer Klasse namens »Konto« ein Konto mit einer bestimmten Kontonummer darstellen. Das Objekt enthält dann nicht nur die aktuellen Daten über das Konto, sondern stellt auch Methoden zum Umgang des Kontos zur Verfügung. Die gespeicherten Daten des Objekts sind spezifisch, die Methoden sind bei jedem Objekt gleich. Man kann Objekte auch gewissermaßen als »→Variablen« eines Klassentyps sehen. Genau genommen steht in C# bzw. dem .NET hinter jedem Datenobjekt eine Klasse. Datenobjekte elementarer Datentypen wie int werden dennoch als Variablen bezeichnet.
Kapselung In der objektorientierten Programmierung die Bezeichnung für das Prinzip, den inneren Aufbau einer →Klasse nach außen hin zu verbergen. Die Benutzung der Klasse erfolgt dabei allein über genau definierte Schnittstellen, in der Regel über öffentliche →Methoden (wozu in C# auch die →Eigenschaften als spezielle Methoden gehören). Solange diese Schnittstellen – z.B. Signatur oder Rückgabewert von public-Methoden – nicht geändert werden, haben Änderungen innerhalb der Klasse keinerlei Auswirkungen auf Programme, welche die Klasse verwenden. Dadurch können die Wartungsarbeiten am Code solcher Programmen reduziert werden, was eines der hauptsächlichen Ziele der Kapselung ist. Aber auch der Aspekt, dass der Programmierer keine Kenntnisse darüber haben muss, wie die Klasse intern arbeitet, geht mit der Kapselung einher. Der Programmierer nutzt die Klasse einfach und verlässt sich darauf, dass diese das durchführt, für was sie bestimmt wurde. Details der technischen Umsetzung bleiben im Verborgenen und auf Funktionalitäten, welche negativen Einfluss auf das Verhalten der Klasse haben könnten, etwa bei Fehlanwendung durch den Programmierer, hat der Programmier erst gar
317
318
Glossar
keinen Zugriff. Damit bieten Klassen im Allgemeinen eine höhere Stabilität und Fehlertoleranz, als wenn auf herkömmliche Weise, also nicht objektorientiert programmiert werden würde.
Klassen Eine Art Schablone, die einen eigenen Datentyp beschreibt. Eine Klasse ist sozusagen der abstrakte Bauplan für künftige »→Variablen« – Objekte oder →Instanzen genannt – dieser Klasse (dieses Typs). Die Objekte vereinen Daten mit ihrer Funktionalität. Letztere wird durch die in der jeweiligen Klasse definierten →Methoden bestimmt.
Konkatenation Aneinanderhängen von Zeichenketten mittels des Operators »+« (Konkatenationsoperator).
Konstruktor Spezielle →Methode, die beim Erzeugen eines Objekts (→Instanz) einer →Klasse aufgerufen wird. Die Konstruktormethode kommt also für jedes Objekt genau ein Mal zur Ausführung.
Laufzeitumgebung Eine bestimmte Software (»Umgebung«), die ein Programm benötigt, damit es ausgeführt werden kann. Die Laufzeitumgebung für C#-Programme bildet das .NET Framework.
Literal Steht im Quelltext für einen unveränderlichen Wert.
Namensräume Übergreifender Gültigkeitsbereich, mit dem sich mehrere →Klassen zusammenfassen lassen. Dabei müssen die Bezeichner der Klassen nur in ein und demselben Namensraum eindeutig sein. Es ist also der Fall möglich, dass zwei verschiedene Klassen denselben Namen tragen, vorausgesetzt, die Klassen sind in verschiedenen Namensräumen definiert.
.NET Framework Bildet die →Laufzeitumgebung für C#-Programme.
Glossar
Objekte (von Klassen) →Instanzen einer →Klasse.
Parameter Lokale →Variablen einer →Methode, die beim Aufruf mit den übergebenen Werten (→Argumenten) initialisiert werden. In der Definition einer Methode stehen die Parameter innerhalb der runden Klammern unmittelbar nach dem Bezeichner für die Methode.
Standardkonstruktor Parameterloser (→Parameter) →Konstruktor.
Statische Klassenelemente Beziehen sich nicht auf eine →Instanz der →Klasse, sondern auf die Klasse selbst. Folgerichtig erfolgt der Zugriff auf statische →Methoden sowie statische Datenelemente nicht mit dem Objekt-, sondern mit dem Klassennamen. Der aktuelle Wert eines statischen Datenelements ist für alle erzeugten Instanzen einer Klasse gleich.
Steuerelemente Komponenten von grafischen Benutzeroberflächen, z.B. Schaltflächen, Menüleisten, Kontrollkästchen, Textfelder usw.
System Vordefinierter →Namensraum, in dem eine Reihe von wichtigen →Klassen definiert sind (z.B. die Klasse Console sowie die Klassen für die grundlegenden Datentypen). In den Beispielprogrammen ist der Namensraum System mit der Anweisung using System; eingebunden und damit sind die darin definierten Klassen mit ihren →Methoden verfügbar.
Variable Benannter Ort im Arbeitsspeicher, an dem Werte abgelegt, gelesen und auch verändert werden können.
Windows-Programme Anwendungen, die eine grafische Benutzeroberfläche (engl. »Graphical User Interface«, daher auch GUI-Programme genannt) zur Verfügung stellen.
319
320
Glossar
Zugriffsspezifizierer Angaben in der Klassendefinition, die den Zugriff von →Instanzen einer →Klasse auf deren Elemente (→Felder, →Eigenschaften und →Methoden) regeln. Elemente können auf diese Weise sowohl als öffentlich (public) als auch als nicht öffentlich (private) deklariert werden. Bei nicht öffentlichen Elementen ist kein Zugriff von außerhalb der Klasse möglich. Falls für ein Element der Klasse nichts explizit festgelegt wurde, gilt die Voreinstellung private.
321
Liebe Leserin, lieber Leser, herzlichen Glückwunsch, Sie haben es geschafft. Visual C# 2005 ist Ihnen nun vertraut. Ist es Ihnen nicht viel leichter gefallen, als Sie am Anfang dachten? Genau das ist das Ziel unserer Bücher aus der easy-Reihe. Sie sollen helfen, erfolgreich die ersten Schritte zu gehen, und den Leser auf keinen Fall mit unverständlichem Fachchinesisch überhäufen. Als Lektorin hoffe ich, dass Sie durch das Buch die richtige Unterstützung bekommen haben. Denn für Ihre Zufriedenheit stehen alle Beteiligten mit ihrem Namen: der Verlag, die Autoren, die Druckerei. Aber niemand ist perfekt. Wenn Sie Anregungen zum Buch und zum Konzept haben: Schreiben Sie uns. Und wenn Sie uns kritisieren wollen: Kritisieren Sie uns. Ich verspreche Ihnen, dass Sie Antwort erhalten. Denn nur durch Sie werden wir noch besser. Ich freue mich auf Ihr Schreiben! Brigitte Alexandra Bauer-Schiewek Lektorin Markt + Technik Pearson Education Deutschland GmbH Martin-Kollar-Str. 10-12 81829 München E-Mail:
[email protected] Internet: http://www.mut.de
Stichwortverzeichnis
Stichwortverzeichnis < Operator 158 ! Operator 161 != Operator 158, 174 % Modulo-Operator 143 && Operator 161, 164 * Operator 143 */ Kommentarzeichen 67 .exe-Dateien 13, 14, 57 .NET Framework 12, 18, 22, 23, 24, 25, 26, 57, 89, 90, 234, 267, 282 Installation 27 .NET Framework 2.0 Beta-Versionen 27 .NET siehe .NET Framework / Operator 143 /* Kommentarzeichen 67 // Kommentarzeichen 51, 67 == Operator 158, 160, 174 > Operator 158 >= Operator 158 || Operator 161, 164
A \a Steuerzeichen 83 Addition arithmetische 143, 163 mathematische 93, 111 Additionsoperator 80
Anchor Eigenschaft 287, 288 Anweisung leere 61 Anweisungen 12, 48, 49, 63, 96, 234 Anweisungsende 61, 62, 65 args 50 Argumente 131, 239 Array Klasse 226 Array-Objekt 222, 225, 230 Arrays 206, 220, 221, 222, 223, 224 definieren von 222 initialisieren von 230 sortieren von 226, 295 ASCII-Zeichen 91 Assembler 16 Assemblies 57 Attribute von Eigenschaften 286 Aufruf von Methoden 76, 114 Aufrufer 237 Ausdrücke komplexe 140, 141 logische 158, 159, 160 Ausgabemethoden 300 Ausrichten von Textfeldern 288
B \b Steuerzeichen 83 BackColor Eigenschaft 289 Backspace Steuerzeichen 83 Basic Programmiersprache 53 Befehle siehe Anweisungen Benutzereingaben implementieren von 143, 144 verarbeiten von 86, 88, 103
323
324
Stichwortverzeichnis
Bezeichner 54, 56, 60, 64, 88, 234, 270 für Klassen 277 von Variablen 99, 101, 140, 148 Bezeichnerwahl Regeln 88, 89, 101, 103 bin Verzeichnis 57 Blitz-Symbol des Eigenschaftenfensters 293 Blöcke 62, 63 Blockstruktur verschachtelte 62 bool Datentyp 90, 91 Boolean Klasse 90 break 178, 188 Button Steuerelement 288, 290, 299 byte Datentyp 89, 90
C C Programmiersprache 16, 22, 48, 53, 122 C#-Compiler 22, 25 C#-Programme Aufbau 48, 49, 50, 51, 52 C++ Programmiersprache 16, 50, 53, 126, 178 Call by reference 243 Call by value 241 camelCasing 102, 260 Carriage Return Steuerzeichen 83 case Schlüsselwort 177 case-Konstante 178 case-Marke 179 case-Zweig(e) 178, 186 Casting siehe Cast-Operation Cast-Operation 129, 134, 135 Char Klasse 90, 116 char Datentyp 90, 91, 111
char-Literale 113 CheckBox Steuerelement 290, 299, 300 Checked Eigenschaft 299, 300 CheckedChanged Ereignis 299 class Schlüsselwort 53, 54, 254 Click Ereignis 293, 294, 299 Click() Methode 293 Cobol 16, 53 Codeansicht 284 Codeausschnitte einfügen 297 umschließen 297 Compiler 14, 17, 18, 25, 48, 51, 78, 80, 97, 111, 141, 165, 172, 173, 186, 230 Console Klasse 40, 77, 78, 79, 103, 114, 277 Console.ReadLine() 17, 40, 48, 103, 104, 105, 114, 132, 144 Console.Write() 13, 49, 76, 77, 78, 79, 104, 136, 300 Console.WriteLine() 17, 39, 48, 49, 63, 76, 77, 78, 79, 95, 104, 136, 300 const Schlüsselwort 122 contents.htm Datei 27 Convert Klasse 131, 133, 135, 277 csc.exe 25
D D Formatierungszeichen 151, 152 d Formatierungszeichen 151, 152 Datei ausführbare 13 binärkodiert 16 Datenelemente 252, 256, 257 statische 272
Stichwortverzeichnis
Datentyp(en) 60, 61, 78, 86, 87, 88, 89, 90, 206 boolescher 89, 91 elementare 115, 206 Datenverbindungen 44 Debug Verzeichnis 57 default Marke 178, 179 Zweig 186 Dekrementoperator 197 Designer siehe Entwurfsfenster Destruktoren 267, 269 Division arithmetische 143, 163 Double Klasse 90, 116 double Datentyp 90, 119, 127 do-while-Schleife 198, 199, 297
E Eigenschaften Ansicht 285 spezielle Methoden 252, 262, 264, 286 zusammengesetzte 286 Eigenschaftenfenster 45, 284, 285, 286, 293, 294, 300 else-Zweig 172, 173 Endlosschleife(n) 196, 296 Entwurfsansicht 284, 288 Entwurfsfenster 291, 301 Ereignis-Ansicht 285, 293 Ereignisbehandlungsroutinen 293 Ereignishandler 293 Ereignisse 293, 294 Beschreibung 293 Escape-Sequenzen 83 Eventhandler siehe Ereignishandler explizite Typumwandlung siehe Typumwandlung explizite
F F Formatierungszeichen 149, 150, 151
\f Formatierungszeichen 149, 151 Steuerzeichen 83 false 91, 126, 159, 160 Fehler logische 71 Fehlerliste 41 Fehlermeldungen beim Kompilieren 96, 97, 106, 115, 130, 178 Felder 101, 252, 253, 254, 256, 259, 262, 265, 286 initialisieren von 258 statische 272 Font Eigenschaft 286 foreach-Schleife 227, 228 ForeColor Eigenschaft 289 Form 282, 291, 292 Form1.cs 284, 301 Form1.Designer.cs Datei 284 Formalparameter siehe Parameter formale Format Menü 288 Formatierung des Quelltextes 72 von Bildschirmausgaben 147, 148, 149, 150, 151 Formatierungszeichen 149 Formatzeichen siehe Formatierungszeichen Formfeed Steuerzeichen 83 Formular 282, 284, 285, 286, 291, 292, 293 Formulargröße 290 for-Schleife 199, 200, 224, 225, 227, 294 Funktionen 53
G Garbage Collector 269 Get-Methoden 263 Getter 263 GetType() Methode 113, 115, 116
325
326
Stichwortverzeichnis
Gleitkommawerte 120, 149, 152 Gleitkommazahlen 120 Graphical User Interface 37 Gruppe von Steuerelementen 291 GUI siehe Graphical User Interface Gültigkeitsbereich 270, 277 von Schleifenvariablen 228 von Variablen 102, 202, 203
H
Integrated Development Environment siehe IDE Integrierte Entwicklungsumgebung siehe IDE IntelliSense 41, 297 Intermediate Language Code 22, 25, 26, 57 ItemClicked Ereignis 299
J
Heap 222, 269 Height Attribut 286, 289 Hexadezimalsystem 154 Hochsprachen 16
Java 16, 23, 50, 178 Java Virtual Machine 23, 24 JIT Compiler Manager 26 JITter siehe Just-in-Time-Compiler Just-in-Time-Compiler 26 JVM siehe Java Virtual Machine
I
K
IDE 18, 44, 289 if 164, 165, 166, 167 if-else 172, 173, 178 IL-Code siehe Intermediate Language Code implizite Typumwandlung siehe Typumwandlung implizite Initialisierung von Arrays 230 von Konstanten 122 von Variablen 98 Inkrementoperator 197 Instanzen von Klassen 252, 258, 286 Instanzieren von Objekten 258, 267, 270 int 60 Datentyp 78, 89, 90, 110, 113, 118, 254 Int32 Klasse 90, 116, 118 Int64 Klasse 116, 118 Integer 78 Arrays 222 Datentyp 111 Konstanten 110 Literale 110 Werte 111, 151
Klasse Definition 53, 54 Klassen 52, 53, 63, 114, 250, 251, 252, 253, 277 Definition 254 vordefinierte 77 Kommandozeile 50 Kommentare 67 Kommentarzeichen 51, 67 Kompilieren 13, 14, 15, 18, 36, 51, 56, 64, 97 von C#-Programmen 22, 25 Komponentenfach 291 Konsole(n) 36, 37, 78, 103, 104 Anwendung 38 Fenster 40 Programme 36, 37, 42 Konstante(n) 108, 109, 110, 111 benannte 110, 122, 123 boolesche 126 unbenannte 110 Datentyp 111 Konstruktor Definition 268 Konstruktoren 267 Kontrollkästchen 290 Kontrollstrukturen 63, 91, 172 Konvertierung siehe Typumwandlung
Stichwortverzeichnis
L L Datentyp-Suffix 118 l Datentyp-Suffix 118 Label Steuerelement 290, 299 Laufvariable 199, 201, 202 Laufzeitfehler 71, 132, 152, 223 Laufzeitumgebung 24 Laufzeitverhalten von Programmen 24 Lebensdauer 269 von lokalen Variablen 241 von Variablen 203 Leerzeichen 60 Linux Betriebssystem 17 Literale 110, 111, 112, 113, 115, 119, 127 ganzzahlige 118 Lizenzbedingungen Visual C# 2005 Express Edition Installation 28 logische Verneinung siehe Negation logische long Datentyp 113, 116, 118, 127
M Main() Methode 39, 50, 51, 52, 54, 55, 56, 62, 63, 175, 234, 236, 238, 272 Maschinencode 14, 17, 22, 26 Maschinensprache 14, 26 Meldungsfenster 300 MenuStrip Steuerelement 290, 291, 292, 299 MessageBox Klasse 300, 301 MessageBox.Show() 300, 301 Methode(n) 49, 53, 63, 232, 233, 234, 237, 241, 245, 252, 253, 254, 256 aufgerufene 236 aufrufende 236, 237 Definition von 234, 235
statische 272 überladen (von) 79, 270 vordefinierte 234 Methodenaufruf 239 Methodenblock 62, 63 Methodendefinition siehe Methoden Definition von Methodenkopf 50, 52, 234 Methodenparameter siehe Parameter Methodenrumpf 50, 51, 62, 234 Microsoft SQL Server 2005 27 Modulo Operation 143, 163 Operator 142 MSDN Express Library 29 MSDN Online 29 Multiplikation arithmetische 143, 163
N N Formatierungszeichen 151 \n Steuerungszeichen 83 Formatierungszeichen 151 Namensraum Definition 277, 278 Namensräume 55, 101, 277, 278 selbst definierte 277 namespace Schlüsselwort 277 namespaces siehe Namensräume Negation logische 161 new 208 Operator 258 Schlüsselwort 204, 230 Newline Steuerzeichen 83 Next Generation Windows Services 25 Next() Methode 205, 206 NGWS siehe Next Generation Windows Services
327
328
Stichwortverzeichnis
Notation von Bezeichnern 101, 102, 103 von private-Feldern 260 von public-Feldern 260 Notepad 12
O Objekte instanzieren 258 von Klassen 114, 115, 207, 250, 251, 252, 253 objektorientierte Programmierung 52, 252 Oder logisches 161, 164 OOP siehe objektorientierte Programmierung Operanden 141, 142, 158, 159 Operationen 89 arithmetische 143 Operator(en) 60, 64 arithmetische 140, 141, 142, 163 logische 160, 161 unäre 142, 161 Optionsfelder 290
P Parameter 50, 76, 79, 102, 226 aktuelle 239 der Main()-Methode 56 formale 239, 241 von Methoden 131, 239 Parameterliste 204, 239, 268, 270 Parameterübergabe 238, 242, 243 Pascal 16, 53 PascalCasing 102, 122, 260 Platzhalter 147, 148, 154 Popup Ereignis 299, 301 Portierbarkeit 22 Position von Steuerelementen 289 Postfix-Notation 197 Präfix-Notation 197 Prioritätsskala von Operatoren 163
Prioritätsstufen von Operatoren 162, 164 private Zugriffsspezifizierer 256, 257, 259 Program Klasse 39, 40, 54, 56, 63 Program.cs Datei 44 Programm 15 Programmdatei 13, 14, 57 Programmentwicklung 12 Programmiersprachen case-sensitive 77 objektorientierte 53, 262 typsichere 126 Programmierstil 66, 67 Programmierung objektorientierte 116, 252 Programmverzweigung 25 Projektmappen 39, 43, 44 Projektmappen-Explorer 44, 45, 255, 284 Projektmappenverzeichnis 42 Projektordner 57 Projektvorlagen 38 Prozessor 14, 51 public Zugriffsspezifizierer 256, 257 Punktoperator 77, 297
Q Quellcode 12, 13, 14, 15, 16, 17, 18, 22, 36, 48, 55, 62, 64 Quellcode-Datei 24 Quelltext siehe Quellcode QuickInfo 290, 292, 299
R \r Steuerzeichen 83 RadioButton(s) Steuerelement 290, 291, 299, 300 Random Klasse 204 ReadLine() Methode siehe Console.ReadLine()
Stichwortverzeichnis
readme.htm Datei 27 Referenztypen 206, 207, 222 Referenzübergabe siehe Call by reference Referenzvariablen 205, 222, 269, 288 return Anweisung 175, 187 Befehl 246 Reverse() Methode 227 RichTextBox Steuerelement 290, 299 Rückgabewert der Methode ReadLine() 104 von Ausdrücken 158, 159 von Methoden 104, 105, 117, 131, 245, 246 Runtime Environment siehe Laufzeitumgebung
S Schaltfläche Steuerelement 293 Schleifen 192, 193, 194, 195 Schleifenvariable 199, 201, 202, 213, 228 Schlüsselwörter 41, 53, 60, 64, 88, 90, 91 Seitenvorschub Steuerzeichen 83 Semikolon Anweisungsende 61, 62, 65 Service Pack 26 Set-Methoden 263 Setter 263 setup.exe Installationsdatei 27 Show() Methode 300, 301 Signatur von Methoden 270 Size Eigenschaft 286, 289 Sleep() Methode 219 SnapLines 288
Sort() Methode 226, 227 Speicherbedarf von Datentypen 89 Speicherverwaltung des .NET Framework 269 Standardanwendungstypen 38 Standarddatentypen 115, 116 Standardereignisse 299 von Steuerelementen 294 Standardkonstruktor 267 selbst definierter 268 Standardmenü einrichten 291 static 52, 53 Schlüsselwort 246, 272 Statische Klassenelemente 272 Steuerelemente 282, 284, 285, 288, 290, 293, 299 Namen von 288 Steuerelement-Klassen 286 Steuerzeichen 81, 82, 83, 84, 112 String Klasse 90, 116 string Datentyp 78, 79, 89, 90, 105, 111, 128, 206 Stringkonstanten 78 string-Konstanten 110 string-Literale 110 Strings 60, 78, 89, 97, 110, 124, 147, 252 Stringvergleiche 174 Stringverknüpfung 79, 80, 93, 128 Subtraktion arithmetische 143, 163 switch 172, 177, 178, 187 switch-Ausdruck 178, 179 Syntaxfehler 49, 71, 223 Syntaxregeln 48, 58, 59, 60, 61, 62 System Namensraum 55, 118, 125, 219, 277 System.Windows.Forms Namensraum 300, 301
329
330
Stichwortverzeichnis
T \t Steuerzeichen 83 Text Eigenschaft 286, 288, 297 TextAlign Eigenschaft 287 Textbausteine 60, 61, 64 TextBox Steuerelement 284, 290, 299 TextChanged Ereignis 294, 299 Thread Klasse 219 Threading Namensraum 219 ToBoolean() Methode 131 ToChar() Methode 131 ToDouble() Methode 131, 132, 144 ToInt32() Methode 131 ToInt64() Methode 131 Token siehe Textbausteine Toolbox 45, 284, 291 ToolTip Steuerelement 290, 292, 299, 301 ToolTip auf ToolTip1 Eigenschaft 292 ToolTipTitle Eigenschaft 292 ToString() Methode 131, 133, 297 Trennzeichen 61 true 91, 126, 159, 160, 161 Type Datentyp 117 Typkonvertierung siehe Typumwandlung Typsicherheit von C# 127
Typumwandlung 174 automatische siehe Typumwandlung implizite explizite 125, 129, 130, 131, 132, 133 implizite 124, 125, 126, 127, 128, 158
U Überladen von Methoden 270 Und logisches 161, 164 Unicode 91, 127 Unix Betriebssystem 17 using Schlüsselwort 55 using System 55, 277 using-Direktive 219
V \v Steuerzeichen 83 value 263 Variablen 60, 80, 86, 87, 88, 89, 92, 93, 98, 100, 101, 104, 105, 107, 115, 197 Definition von siehe Variablendefinition lokale 102, 203, 241 Variablenbezeichner siehe Bezeichner von Variablen Variablendefinition 88, 90, 91, 92, 93, 148 Vereinbarung von Variablen 123 Vergleichsoperatoren 158, 159 Verknüpfung von Strings siehe Stringverknüpfung von Zeichenketten siehe Stringverknüpfung Verknüpfungsoperator 79, 80, 97 Verzweigungen 156, 157, 158, 159, 160 Video-Animationen von Microsoft 45 Visual Basic 18 Visual C# 2005 Express Edition 18, 36, 37, 38, 39, 40, 45, 57, 290 Code-Editor 41
Stichwortverzeichnis
Formatierungseinstellungen 72 Hilfe 45 Installation 26, 27, 28, 29, 30, 31, 33 Systemvoraussetzungen 27 Online-Hilfe 300, 302 Projekte 37, 38, 39, 40, 41, 42, 43 Visual C# Express siehe Visual C# 2005 Express Edition Visual Studio 72 Visual Studio .NET 18, 282 Visual Studio 2005 26, 27 void 52, 53, 64 Schlüsselwort 246
W Wahrheitswerte 89, 91 Warnmeldungen beim Kompilieren 106 Warteschleifen 219 Web-Browser erstellen 302 WebBrowser Steuerelement 292 Wert konstante 60 Werte konstante 60, 78, 110 kontrollierte 93 Wertebereich des Datentyps char 91 des Datentyps double 90 des Datentyps int 78, 90, 113 des Datentyps long 113 von Datentypen 89 Wertetypen 206, 207 Wertübergabe siehe Call by value while-Schleife 194, 195 Width Attribut 286, 289 Wiederholungsanweisungen 192, 193, 194, 195 Windows 2000 Betriebssystem 26 Windows Forms 282, 284, 300 Windows Forms-Designer 282, 286
Windows Server 2003 Betriebssystem 26 Windows XP Betriebssystem 26 Windows-Anwendungen 280, 281, 282, 284 Windows-Programme 42 Windows-Programmierung 37 Write() Methode siehe Console.Write() WriteLine() Methode siehe Console.WriteLine()
X X Formatierungszeichen 151, 152 x Formatierungszeichen 151, 152
Z Zählvariable 228 Zeichenkette(n) 60, 78, 80, 89, 110, 252 Konstanten 78 leere 81 Zufallszahlen 204 Zugriffsspezifizierer 259 Zuweisung 93, 94, 95, 124 Zuweisungsoperator 60, 61, 93, 94, 100, 124, 163 Zwischencode 22, 26, 57 Zwischenraumzeichen 64, 97
331
Häufig verwendete Steuerelemente Steuerelement Label TextBox RichTextBox Button RadioButton CheckBox MenuStrip ToolTip
Beschreibung Beschreibungstext, in der Regel als Beschriftung für andere Steuerelemente gedacht Textfeld zur Eingabe von Text Textfeld mit umfangreichen Formatierungsmöglichkeiten Schaltfläche zum Anklicken Optionsschaltfläche, die aktiviert oder deaktiviert werden kann Kontrollkästchen, das aktiviert oder deaktiviert werden kann Zeigt eine obere Menüleiste in bekannter Form, auch kaskadiert, an QuickInfo für andere Steuerelemente
Steuerelemente und die dazugehörigen Standardereignisse Steuerelement Label TextBox RichTextBox Button RadioButton
Standardereignis Click TextChanged TextChanged Click CheckedChanged
CheckBox
CheckedChanged
MenuStrip
ItemClicked
ToolTip
Popup
Beschreibung Tritt ein, wenn ein Benutzer das Label anklickt Tritt mit jeder Änderung des enthaltenen Texts ein Tritt mit jeder Änderung des enthaltenen Texts ein Tritt ein, wenn ein Benutzer den Button anklickt Tritt ein, wenn sich der Wert der Checked-Eigenschaft ändert Tritt ein, wenn sich der Wert der Checked-Eigenschaft ändert Tritt ein, sobald ein Benutzer auf einen Menüeintrag klickt Tritt unmittelbar vor dem Anzeigen der QuickInfo ein
Arithmetische Operationen Operator steht für ... Addition + Subtraktion Multiplikation * Division / Modulo (Rest einer Division) %
Wichtige Datentypen Alias int double char string bool
Größe 32 Bit 64 Bit 16 Bit 16 Bit pro Zeichen 1 Bit
Datentyp Int32 Double Char String Boolean
Vergleichsoperatoren Operator Bedeutung gleich == ungleich != größer als > größer als oder gleich >= kleiner als < kleiner als oder gleich <=
Logische Operatoren Operator Bedeutung logisches Und && logisches Oder || Negation (logische Verneinung) !
Syntax der switch-Anweisung switch (Ausdruck) { case Konstante_1: Anweisung(en) break; case Konstante_2: Anweisung(en) break; … case Konstante_n: Anweisung(en) break; default: Anweisung(en) break; }
Beispiel for-Schleife int i; for (i = 1; i < 5; i++) { Console.WriteLine("{0}. Zeile", i); }
Copyright Daten, Texte, Design und Grafiken dieses eBooks, sowie die eventuell angebotenen eBook-Zusatzdaten sind urheberrechtlich geschützt. Dieses eBook stellen wir lediglich als persönliche Einzelplatz-Lizenz zur Verfügung! Jede andere Verwendung dieses eBooks oder zugehöriger Materialien und Informationen, einschliesslich •
der Reproduktion,
•
der Weitergabe,
•
des Weitervertriebs,
•
der Platzierung im Internet, in Intranets, in Extranets,
•
der Veränderung,
•
des Weiterverkaufs
•
und der Veröffentlichung
bedarf der schriftlichen Genehmigung des Verlags. Insbesondere ist die Entfernung oder Änderung des vom Verlag vergebenen Passwortschutzes ausdrücklich untersagt! Bei Fragen zu diesem Thema wenden Sie sich bitte an:
[email protected] Zusatzdaten Möglicherweise liegt dem gedruckten Buch eine CD-ROM mit Zusatzdaten bei. Die Zurverfügungstellung dieser Daten auf unseren Websites ist eine freiwillige Leistung des Verlags. Der Rechtsweg ist ausgeschlossen. Hinweis Dieses und viele weitere eBooks können Sie rund um die Uhr und legal auf unserer Website
http://www.informit.de herunterladen