Kapitel 27 Weitere Sprachdetails
In diesem Kapitel werden verschiedene Sprachdetails
besprochen, u. a. die Verwendung der Main()-Funktion,
die Funktionsweise des Präprozessors und das Schreiben literaler
Werte.
27.1 Die Main-Funktion
 
Die einfachste Version der Main()-Funktion
kennen Sie bereits aus den bisherigen Beispielen:
using System;
class Test
{
public static void Main()
{
Console.WriteLine("Hello, Universe!");
}
}
27.1.1 Zurückgeben eines int-Status
 
Es ist häufig sinnvoll, über die Main()-Funktion
einen Statuswert zurückzugeben. Dies wird erreicht, indem der Rückgabetyp
von Main() als integer deklariert wird:
using System;
class Test
{
public static int Main()
{
Console.WriteLine("Hello, Universe!");
return(0);
}
}
27.1.2 Befehlszeilenparameter
 
Auf die Befehlszeilenparameter einer Anwendung kann
durch Deklarieren der Main()-Funktion mit einem string-Array
als Parameter zugegriffen werden. Die Parameter können anschließend
durch Indizierung des Arrays verarbeitet werden.
using System;
class Test
{
public static void Main(string[] args)
{
foreach (string arg in args)
Console.WriteLine("Arg: {0}", arg);
}
}
27.1.3 Mehrere Main-Funktionen
 
Es ist zu Testzwecken häufig sinnvoll, eine
statische Funktion in eine Klasse aufzunehmen, mit der die Klasse auf
ihre ordnungsgemäße Funktionsweise getestet wird. In C# kann
diese statische Testfunktion als Main()-Funktion geschrieben
werden, was eine automatisierte Prüfung vereinfacht.
Wird während der Kompilierung eine einzelne
Main()-Funktion ermittelt, verwendet der C#-Compiler diese.
Sind mehrere Main()-Funktionen vorhanden, kann die Klasse
mit der gewünschten Main()-Funktion über die
Befehlszeilenoption /main:<Klassenname> angegeben
werden.
// Fehler
using System;
class Complex
{
public static int Main()
{
// Hier Code testen
Console.WriteLine("Console: Passed");
return(0);
}
}
class Test
{
public static void Main(string[] args)
{
foreach (string arg in args)
Console.WriteLine(arg);
}
}
Das Kompilieren dieser Datei mit /main:Complex
führt dazu, dass die Testversion von Main() verwendet
wird; beim Kompilieren mit /main:Test wird die richtige
Version von Main() eingesetzt. Das Kompilieren ohne Befehlszeilenoption
führt zu einem Fehler.
27.2 Vorverarbeitung
 
Beim C#-Präprozessor müssen Sie vor allem beachten, dass dieser
nicht vorhanden ist. Die Funktionen des C/C++-Prozessors stehen entweder
gar nicht oder nur eingeschränkt zur Verfügung. Zu den nicht
verfügbaren Funktionen gehören include-Dateien und die Fähigkeit, Textersetzungen mit #define
durchzuführen. Die Direktive #ifdef und verknüpfte
Direktiven sind vorhanden und werden zur Steuerung der Codekompilierung
eingesetzt.
Durch die Abwesenheit der Makroversion von #define werden die Programme dem
Programmierer verständlicher. Ein Name, der nicht bekannt ist,
muss aus einem der Namespaces kommen, d. h., es müssen keine
include-Dateien durchsucht werden.
Einer der wichtigsten Gründe für diese
Änderung liegt darin, dass durch den Wegfall von Vorverarbeitung
und #include eine einfachere Kompilierungsstruktur erzielt wird, wodurch die Kompilierungsgeschwindigkeit
erheblich gesteigert werden kann1 . Zusätzlich ist es nicht länger nötig, eine separate
Headerdatei zu schreiben und diese mit der Implementierungsdatei zu synchronisieren.
Bei der Kompilierung von C#-Dateien spielt die Kompilierungsreihenfolge der einzelnen Dateien keine Rolle2 , genauso können sich auch alle Dateien in einer großen
Datei befinden. Es ist nicht erforderlich, Deklarationen weiterzuleiten
oder sich über die Reihenfolge von #include-Dateien
Gedanken zu machen.
27.2.1 Vorverarbeitungsdirektiven
 
Es werden folgende Vorverarbeitungsdirektiven unterstützt:
Direktive
|
Beschreibung
|
#define-Bezeichner
|
Definiert einen Bezeichner. Beachten Sie, dass kein
Wert gesetzt werden kann; dieser kann nur definiert werden. Bezeichner
können auch über die Befehlszeile definiert werden.
|
#undef-Bezeichner
|
Hebt die Definition für einen Bezeichner auf.
|
#if-Ausdruck
|
Code in diesem Abschnitt wird kompiliert, wenn der
Ausdruck wahr (true) ist.
|
#elif-Ausdruck
|
Else-If-Struktur. Wenn die vorstehende
Direktive nicht verwendet wurde und der Ausdruck wahr ist, wird der
Code in diesem Abschnitt kompiliert.
|
#else
|
Wenn die vorstehende Direktive nicht verwendet wurde,
wird der Code in diesem Abschnitt kompiliert.
|
#endif
|
Markiert das Ende eines Abschnitts.
|
Hier ein Verwendungsbeispiel:
#define DEBUGLOG
using System;
class Test
{
public static void Main()
{
#if DEBUGLOG
Console.WriteLine("In Main – Debug Enabled");
#else
Console.WriteLine("In Main – No Debug");
#endif
}
}
#define und #undef müssen
in einer Datei einem Abschnitt mit »richtigem Code« vorangestellt
sein, sonst tritt ein Fehler auf. Das vorgenannte Beispiel kann folgendermaßen
umgeschrieben werden:
// Fehler
using System;
class Test
{
#define DEBUGLOG
public static void Main()
{
#if DEBUGLOG
Console.WriteLine("In Main – Debug Enabled");
#else
Console.WriteLine("In Main – No Debug");
#endif
}
}
Präprozessorausdrücke
In Präprozessorausdrücken können
folgende Operatoren eingesetzt werden:
Operator
|
Beschreibung
|
! ex
|
Ausdruck ist wahr, wenn ex falsch ist.
|
ex == value
|
Ausdruck ist wahr, wenn ex gleich value.
|
ex !== value
|
Ausdruck ist wahr, wenn ex ungleich
value.
|
ex1 && ex2
|
Ausdruck ist wahr, wenn sowohl ex1
als auch ex2 wahr sind.
|
ex1 || ex2
|
Ausdruck ist wahr, wenn entweder ex1
oder ex2 wahr ist.
|
Mit Hilfe von Klammern können Ausdrücke
gruppiert werden:
#if !(DEBUGLOG && (TESTLOG || USERLOG))
Wenn TESTLOG oder USERLOG
definiert ist und DEBUGLOG definiert ist, ist der Ausdruck
innerhalb der Klammern wahr und wird dann durch ! negiert.
27.2.2 Andere Präprozessorfunktionen
 
Neben den Funktionen #if und #define
können einige weitere Präprozessorfunktionen verwendet werden.
#warning und #error
#warning und #error ermöglichen
die Ausgabe von Warnungen oder Fehlern während der Kompilierung.
Sämtlicher Text, der auf #warning oder #error
folgt, wird ausgegeben, wenn der Compiler die betreffende Zeile erreicht.
Für einen Codeabschnitt wäre Folgendes
denkbar:
#warning Check algorithm with John
Bei Kompilierung dieser Zeile würde die Zeichenfolge
»Check algorithm with John« ausgegeben.
#line
Mit #line kann der Programmierer den
Namen der Quelldatei und die Zeilennummer angeben, die bei der Ermittlung
eines Fehlers durch den Compiler ausgegeben werden sollen. Diese Methode
wird typischerweise bei maschinengeneriertem Quellcode eingesetzt, damit die ausgegebenen
Zeilen mit einem anderen Benennungs- oder Nummernsystem synchronisiert
werden können.
27.3 Lexikalische Details
 
Die lexikalischen Details der Sprache befassen sich
mit Dingen, die auf Einzelzeichenebene von Bedeutung sind. Hierzu zählen
beispielsweise das Schreiben numerischer Konstanten, Bezeichner und
andere Lowleveleinheiten der Sprache.
27.3.1 Bezeichner
 
Ein Bezeichner ist ein Name, der für ein Programmelement
verwendet wird, z. B. eine Variable oder eine Funktion.
Bezeichner müssen mit einem Buchstaben oder
einem Unterstrich beginnen, der verbleibende Teil des Bezeichners kann
auch Zahlen enthalten3 . Unicode-Zeichen können über \udddd angegeben
werden, wobei dddd den Hexadezimalwert des Unicode-Zeichens
angibt.
Bei der Verwendung von Code, der in einer anderen
Sprache geschrieben wurde, kann es sich bei einigen Namen um C#-Schlüsselwörter
handeln. Zum Schreiben solcher Namen kann vor dem Namen ein at-Zeichen
(@) eingefügt werden. Hierdurch wird C# darüber informiert,
dass es sich nicht um ein Schlüsselwort, sondern um einen Bezeichner
handelt.
Ebenso kann das @-Zeichen zur Verwendung von Schlüsselwörtern als
Bezeichner eingesetzt werden:
class Test
{
public void @checked()
{
}
}
Diese Klasse deklariert eine Mitgliederfunktion
namens checked.
Das Verwenden dieser Funktion zur Gleichstellung
von Bezeichnern mit den integrierten Bezeichnern wird nicht empfohlen,
da es Verwirrung stiften kann.
Schlüsselwörter
Schlüsselwörter sind reservierte Wörter,
die nicht als Bezeichner eingesetzt werden können. In C# gibt es
folgende Schlüsselwörter:
abstract
|
ba4se
|
bool
|
break;
|
byte
|
case
|
catch
|
char
|
checked
|
class
|
const
|
continue;
|
decimal
|
default:
|
delegate
|
do
|
double
|
else
|
enum
|
event
|
explicit
|
extern
|
false
|
finally
|
fixed
|
float
|
for
|
foreach
|
goto
|
if
|
implicit
|
in
|
int
|
interface
|
internal
|
is
|
lock
|
long
|
namespace
|
new
|
null
|
object
|
operator
|
out
|
override
|
params
|
private
|
protected
|
public
|
readonly
|
ref
|
return
|
sbyte
|
sealed
|
short
|
sizeof
|
static
|
string
|
struct
|
switch
|
this
|
throw
|
true
|
try
|
typeof
|
uint
|
ulong
|
unchecked
|
unsafe
|
ushort
|
using
|
virtual
|
void
|
while
|
|
27.3.2 Literale
 
Literale stellen die Möglichkeit dar, Werte
für Variablen zu schreiben.
Boolesche Literale
Es gibt zwei boolesche Literale: true
und false.
Ganzzahlige Literale
Ganzzahlige Literale werden einfach durch das Schreiben
der numerischen Werte geschrieben. Ganzzahlige Literale, die klein genug
sind, um in den int-Datentyp zu passen4 , werden als integer-Werte behandelt, wenn sie zu groß sind, werden sie
als kleinste Typen von uint, long oder ulong
erstellt.
Hier einige Beispiele für ganzzahlige Literale:
123
-15
Ganzzahlige Literale können auch im Hexadezimalformat dargestellt werden, indem vor der Konstante ein
»0x« eingefügt wird.
0xFFFF
0x12AB
Reale Literale
Reale Literale werden für die Typen float,
double und decimal verwendet. Float-Literale
weisen ein nachgestelltes »f« oder »F« auf, double-Literale
ein »d« oder »D«, an decimal-Literale
wird ein »m« oder »M« angehängt. Reale Literale
ohne Typenzeichen werden als double-Literale interpretiert.
Die exponentielle Notierung wird durch das Anhängen
eines »e« und des Exponenten an den literalen Wert ausgedrückt.
Beispiele:
1.345 // double-Konstante
-8.99e12F // float-Konstante
15.66m // decimal-Konstante
Zeichenliterale
Ein Zeichenliteral ist ein einzelnes, von einfachen
Anführungszeichen umschlossenes Zeichen, beispielsweise ’x’.
Es werden folgende Escapesequenzen unterstützt:
Escapesequenz
|
Beschreibung
|
\’xe »_
|
Einfaches Anführungszeichen
|
\\"
|
Doppeltes Anführungszeichen
|
\\
|
Umgekehrter Schrägstrich (Backslash)
|
\0
|
Null
|
\a
|
Alert (Warnung)
|
\b
|
Rückschritt
|
\f
|
Vorschub
|
\n
|
Neue Zeile
|
\r
|
Wagenrücklaufzeichen
|
\t
|
Tabulator
|
\v
|
Vertikaler Tabulator
|
\xdddd
|
Zeichen dddd, wobei d für eine Hexadezimalstelle
steht
|
Zeichenfolgenliterale
Zeichenfolgenliterale werden als eine in doppelte
Anführungszeichen eingeschlossene Zeichensequenz dargestellt, beispielsweise
»Hello«. Innerhalb von Zeichenfolgen werden alle Zeichenescapesequenzen
unterstützt.
Zeichenfolgen dürfen nicht mehrere Zeilen umfassen,
der gleiche Effekt kann jedoch durch eine Verkettung erzielt werden\:
string s = \"What is your favorite color?\" +
\"Blue. No, Red. \";
Bei der Codekompilierung wird eine einzige Zeichenfolgenkonstante
erstellt, die aus zwei verketteten Zeichenfolgen besteht.
Wörtliche Zeichenfolgen
Wörtliche Zeichenfolgen ermöglichen das
einfachere Angeben einiger Zeichenfolgen.
Wenn eine Zeichenfolge einen umgekehrten Schrägstrich
enthält, beispielsweise einen Dateinamen, kann mit Hilfe einer
wörtlichen Zeichenfolge die Unterstützung für Escapesequenzen
deaktiviert werden. Statt der Zeile\:
string s = \"c\:\\Program Files\\Microsoft Office\\Office\";
kann folgende Zeile verwendet werden\:
string s = @\"c\:\Program Files\Microsoft Office\Office\";
Die wörtliche Zeichenfolgensyntax ist demnach
nützlich, wenn der Code durch ein Programm generiert wird und die
Inhalte der Zeichenfolgen nicht eingeschränkt werden können.
Innerhalb dieser Zeichenfolgen können alle Zeichen dargestellt
werden, es müssen jedoch einige der doppelten Anführungszeichen
mehrfach verwendet werden\:
string s = @\"She said, \"\"Hello\"\"\";
Darüber hinaus können in der wörtlichen
Zeichenfolgensyntax geschriebene Zeichenfolgen mehrere Zeilen umfassen,
vorhandene Leerzeichen (Leerzeichen, Tabulatoren, neue Zeilen) werden
hierbei beibehalten.
using System;
class Test
{
public static void Main()
{
string s = @\"
C\: Hello, Miss?
O\: What do you mean, 'Miss'?
C\: I'm Sorry, I have a cold. I wish to make a complaint.\";
Console.WriteLine(s);
}
}
27.3.3 Kommentare
 
In C# werden einzeilige Kommentare durch einen doppelten
Schrägstrich gekennzeichnet, der Anfang und das Ende eines mehrzeiligen
Kommentars wird mit /* und */ markiert.
// Dies ist ein einzeiliger Kommentar
/*
* Hier mehrzeiligen Kommentar einfügen
*/
C# unterstützt darüber hinaus eine besondere
Art Kommentar, um die Dokumentation mit dem Code zu verknüpfen.
Diese Kommentare werden in Kapitel 31, C# im Detail, im Abschnitt
zur XML-Dokumentation beschrieben.
1
Als ich zum ersten Mal eine Kopie des Compilers
auf meinem System installierte, gab ich ein einfaches Beispiel ein und
kompilierte dieses. Die Kompilierung wurde sehr schnell ausgeführt
– so schnell, dass ich einen Fehler vermutete und einen Entwickler
um Hilfe bat. Der Compiler ist sehr viel schneller als ein C++-Compiler
(je sein könnte).
2
Abgesehen davon, dass die Ausgabedatei automatisch
mit dem Namen der zuerst kompilierten Datei versehen wird.
3
Es ist tatsächlich etwas komplizierter als
hier dargestellt, da C# über Unicode-Unterstützung verfügt.
Kurz gesagt, bei den Buchstaben kann es sich um ein beliebiges Unicode-Zeichen
handeln, und alle Zeichen außer dem Unterstrich (_) können
kombiniert werden. Eine vollständige Beschreibung erhalten Sie
in der C#-Sprachreferenz, die unter http://msdn.microsoft.com/vstudio/nextgen/technology/csharpdownload.asp
heruntergeladen werden kann.
4
Siehe Kapitel 3, Schnelleinstieg in C#, Abschnitt
»Grundlegende Datentypen«.
|