Globale Vereinbarungen (in module-Blöcken)C-Programm:
globale Variablen, Unterprogramme
Hauptprogramm (program)
lokale Vereinbarungen
ausführbare Befehle (Zuweisungen, Unterprogrammaufrufe)
Preprocessor-DirektivenDie ausführbaren Befehle werden in beiden Sprachen im Prinzip sequentiell, d.h. der Reihe nach, bzw. durch Schleifen, Verzweigungen usw. gesteuert, abgearbeitet. F9x kennt darüber hinaus auch noch parallele Konstruktionen. Ein augenfälliger Unterschied sind die mit dem #-Zeichen beginnenden Preprocessor-Direktiven, die es nur in C gibt und die typischerweise am Beginn, aber auch an beliebigen anderen Stellen des Programms auftreten können. Der Preprocessor ist ein Filter, den das Programm passiert, bevor es an den eigentlichen Compiler weitergegeben wird. Mit Hilfe von Preprocessor-Direktiven können Konstanten und Macros definiert, Teile des Programms maschinenspezifisch ein- oder ausgeblendet, zusätzlicher Code inkludiert werden, usw. Jedes F- oder C-Programm muß genau ein Hauptprogramm enthalten. Der Beginn desselben ist der sogenannte Entry Point, an dem die Ausführung des Programms beginnt, wenn es in den Computer geladen wird, und der natürlich eindeutig sein muß. In F wird der Beginn des Hauptprogramms durch das Schlüsselwort (Keyword) program markiert; in C, wo nicht zwischen Haupt- und Unterprogrammen unterschieden wird, durch den Beginn einer Funktion mit dem reservierten Namen main. Sowohl in F als auch in C können Vereinbarungen, Anweisungen usw. im "freien Format" geschrieben werden, d.h. an beliebiger Stelle in einer Zeile. In C muß jedoch jede Variablendeklaration oder Anweisung durch einen Strichpunkt abgeschlossen werden, z.B. weist
Globale Vereinbarungen
globale Variablen, Unterprogramme
Hauptprogramm (Funktion main)
lokale Vereinbarungen
ausführbare Befehle (Zuweisungen, Funktionsaufrufe)
x=y+z;x die Summe von y und z zu. In F lautet dieselbe Anweisung einfach
x=y+zLogisch zusammengehörende Befehle, sogenannte Blöcke, werden in F durch keyword ... end keyword zusammengefaßt, in C durch Paare geschlungener Klammern, {...}. So werden z.B. Beginn und Ende des Hauptprogramms in F folgendermaßen gekennzeichnet
program namein C aber durch
lokale Vereinbarungen
ausführbare Befehle
...
end program name
int main(...) {Dabei muß in F der (Programm)-Name in den Zeilen program und end program übereinstimmen. Eine Besonderheit von C ist, daß an die Funktion main aus dem Betriebssystem die Kommandozeilen-Argumente als Parameter übergeben werden können. Die logischen Blöcke werden meist durch Einrücken sichtbar gemacht. Um wieviel eingerückt wird und ob in C die geschlungenen Klammern in eigenen Zeilen stehen, ist Sache des persönlichen Geschmacks.
lokale Vereinbarungen
ausführbare Befehle
...
}
Die Default-Längen (2, 4 oder 8 Bytes) der Integer- und Floating Point (real)-Zahlen sind implementationsabhängig. In F und F9x gibt es aber die Möglichkeit, den konkreten Datentyp mittels einer gewünschten numerischen Mindestgenauigkeit maschinenunabhängig festzulegen. Für naturwissenschaftlich-technische Anwendung ist das Fehlen komplexer Zahlen in C manchmal störend.
F C integer byte
int
longreal float
doublecomplex character char logical pointer *
17, +58 oder -3ganze und
238.4 oder -1.4708gültige Floating Point-Zahlen. Zur Darstellung sehr großer oder kleiner Werte gibt es auch noch die wissenschaftliche Notation
6.022e+23wobei e±n für einen Faktor 10±n steht. Komplexe Werte, die allerdings nur in F zur Verfügung stehen, werden als Paare von Real- und Imaginärteilen in runden Klammern geschrieben, also z.B.
(0.0,1.0)für die imaginäre Einheit i. Zeichenketten-Konstanten werden in beiden Sprachen zwischen doppelte Hochkommas gesetzt
"Das ist ein Text."Einer der subtilen Unterschiede zwischen F und C besteht allerdings darin, daß in Fortran eine primitive Character-Variable, wenn sie entsprechen definiert wird, einen beliebig langen String enthalten kann, in C aber nur ein einzelnes Zeichen, das als Literal zwischen einfachen Hochkommas steht, also z.B.
'a'Strings aus mehr als einem Zeichen sind in C stets Arrays. Die Schreibweise der zwei möglichen logischen Werte, "wahr" und "falsch", in F ist
.true.bzw.
.false.(also mit je einem Punkt vor und hinter dem Wert).
wozu im Fall von F noch Attribute kommen können. So definiert z.B.F C integer::i,j,k int i,j,k; real::x,y,z float x,y,z;
integer,parameter::imax=100keine Variable, sondern eine benannte Konstante, d.h. in diesem Fall, daß vom Compiler überall im Programm der Name imax durch den Wert 100 ersetzt wird. (In C würde man einen solchen "Parameter" typischerweise durch eine Preprocessor-Direktive
#define IMAX 100definieren.)
write(unit=*,fmt="(format)") expression_listDabei wird mit dem Schlüsselwort unit im allgemeinen Fall der "Kommunikationskanal" (ein ganzzahliger Ausdruck, dessen Wert z.B. mit einem File assoziiert sein kann) angegeben. Der Kanal "*" steht für Ausgabe auf den Schirm. Das zweite Argument fmt enthält in einer Zeichenkette in runden Klammern eine Beschreibung des Formats, in dem die Ausgabe erfolgen soll. Legt man keinen Wert auf eine bestimmte Formatierung, so kann man sie mit Hilfe von fmt=* auch dem Compiler überlassen. expression_list gibt schließlich in einer durch Beistriche getrennten Liste von Ausdrücken die eigentlich auszugebenden Daten an. Für die Ausgabe aus einem C-Programm auf den Schirm benützt man die printf-Funktion
printf("format",expression_list);wobei format und expression_list wieder die Formatbeschreibung (allerdings ohne runde Klammern) und die Liste von auszugebenden Werten sind. In C fehlt leider das praktische "*"-Format.
(Die letztere Form von Klartextausgabe, die in F9x nach wie vor gültig ist und bleibt, wurde um das Jahr 2000 aus nicht nachvollziehbaren Gründen aus F gestrichen, sodaß in F seither die Ausgabe von Zeichenketten-Konstanten etwas umständlich mit Hilfe eines a-Platzhalters im Format und der Zeichenkette in der Werteliste erfolgen muß.) Einzelformate können durch Klammern in Gruppen zusammengefaßt und mit Wiederholungsfaktoren versehen werden, z.B.
in ganze Zahl mit n Stellen fn.m Kommazahl mit n Stellen insgesamt, davon m nach dem Komma esn.m Exponentialformat mit n Stellen insgesamt, davon m nach dem Komma an, a Zeichenkette mit n Stellen bzw. angepaßter Länge trn n Leerstellen / neue Zeile ""text"" text wird wörtlich ausgegeben
In C wird jeglicher Text in der Formatbeschreibung format wörtlich ausgegeben, ausgenommen Einzelformatbeschreibungen (Platzhalter), die mit dem "%"-Zeichen beginnen und wieder den Elementen in der Werteliste entsprechen
3i5 = i5,i5,i5 2(tr1,f8.3) = tr1,f8.3,tr1,f8.3
Da man jedenfalls den Typ der auszugebenden Ausdrücke spezifizieren muß, gibt es zwar kein Äquivalent zum "*"-Format von F, aber es genügt als Abkürzung auch schon %d, %f, usw., ohne explizite Angabe von Stellen. Ein wichtiger Unterschied zwischen dem write- und dem printf-Befehl besteht darin, daß in F am Ende jeder formatierten Ausgabe automatisch eine neue Zeile begonnen wird. Will man das z.B. in einer Eingabeaufforderung unterdrücken, kann man die advance="no"-Option verwenden. So druckt etwa
%nd ganze Zahl mit n Stellen %nld ganze Zahl vom Typ long mit n Stellen %n.mf Kommazahl vom Typ float mit n Stellen, davon m nach dem Komma %n.mlf Kommazahl vom Type double mit n Stellen, davon m nach dem Komma %n.me Exponentialformat mit n Stellen, davon m nach dem Komma %n.mg äquivalent f oder e, je nach Größe der Zahl %ns, %s Zeichenkette mit n Stellen bzw. angepaßter Länge \n neue Zeile
write(unit=*,fmt="("" x="")",advance="no")den Text
x=auf den Bildschirm und bleibt dann mit dem Cursor hinter dem Gleichheitszeichen stehen. (Diese Konstruktion ist zwar korrektes F9x, müßte für einen neueren F-Compiler aber in der Form
write(unit=*,fmt="(a)",advance="no") " x="geschrieben werden.) Im Gegensatz dazu muß in C der Zeilenvorschub am Ende einer Zeile explizit mit Hilfe von \n angegeben werden. Soll also z.B. der Wert der float-Variablen x gedruckt und dann (für die nächste Ausgabe) eine neue Zeile begonnen werden, kann man das mit
printf("x=%f\n",x);erreichen.
read(unit=*,fmt="(format)") variable_listbzw.
scanf("format",variable_list);Dabei ist variable_list eine durch Beistriche getrennte Liste von Variablen, denen die eingegebenen Werte zugewiesen werden. Da man sich bei der Eingabe von der Tastatur im allgemeinen an keine starre Formatierung binden möchte, gibt es in F auch für den read-Befehl das "*"-Format. In diesem Fall können die einzulesenden Werte beliebig eingegeben werden; es muß nur jeweils ein Wert vom nächsten durch ein geeignetes Trennzeichen (meist ein Beistrich) unterschieden werden. Sollen also z.B. den drei (entsprechend deklarierten) real-Variablen x, y und z die Werte 1.25, 201.5 und -33.429 zugewiesen werden, so kann das durch den Befehl
read(unit=*,fmt=*) x,y,zund die Eingabe von
1.25,201.5,-33.429von der Tastatur erfolgen. In C würde man dieselbe Eingabe mit
scanf("%f,%f,%f",&x,&y,&z);in die float-Variablen x, y und z einlesen. Dieses Beispiel illustriert auch einen der gravierendsten Unterschiede zwischen F und C: Während in F Parameter an Prozeduren (wie die Ein- und Ausgabebefehle) per Adresse übergeben werden, erfolgt in C die Parameterübergabe per Wert (s. später). Das hat zur Folge, daß bei einer Eingabe, wodurch ja der Wert einer Variablen verändert werden soll, die Adresse der Variablen übergeben werden muß. Das wird in C erreicht, indem man dem Namen der Variablen das "&"-Zeichen voranstellt.
[label :] do count =start,end [,increment ]Dabei ist der Schleifenzähler count eine ganzzahlige Variable, die mit start initialisiert und zu der nach jedem Schleifendurchlauf increment addiert wird. Der Block von Befehlen (body) zwischen do und end do wird so oft ausgeführt als der Schleifenzähler end nicht überschritten wird (bzw. unterschritten wird, wenn increment negativ ist). Ist diese Abbruchbedingung schon vor dem ersten Schleifendurchlauf erfüllt, wird die Schleife überhaupt nicht ausgeführt. Wenn increment nicht angegeben ist, wird count bei jedem Durchlauf um 1 erhöht. Die Parameter start, end und increment können ganzzahlige Ausdrücke sein. Um bei längeren oder geschachtelten Schleifen Beginn und Ende leichter identifizieren zu können, kann der Schleifenbeginn mit einem Namen label markiert werden, der dann aber auch beim schließenden end do angegeben werden muß. Aus einer do-Schleife springt man mit dem Befehl
body
...
end do [label ]
exit [label ]Die Ausführung des Programms wird dann mit dem auf das Schleifenende folgenden Befehl fortgesetzt. Der Befehl
cycle [label ]hingegen bewirkt, daß nur der momentane Schleifendurchlauf abgebrochen und die Schleife mit der nächsten Iteration fortgesetzt wird. Durch Angabe von label kann man auch aus bzw. an das Ende von (außerhalb liegenden) geschachtelten Schleifen springen. Eine spezielle Form der Schleife ist die Endlosschleife
[label :] doNatürlich muß eine Endlosschleife irgendwann mit Hilfe von exit verlassen werden. Die analogen Konstruktionen in C sind for-Schleifen. Ihre allgemeine Form ist
body
...
end do [label ]
for(init_expr; test_expr; incrmt_expr) {Im Gegensatz zu F ist man hier nicht an die Verwendung von expliziten (oder gar ganzzahligen) Schleifenzählern gebunden, sondern init_expr, test_expr und incrmt_expr können Ausdrücke oder Zuweisungen allgemeiner Art sein. Der Initialisierungsausdruck init_expr (meist eine Zuweisung) wird vor Beginn der Schleife durchgeführt, der Abbruchausdruck test_expr (meist ein logischer Test) vor jedem Schleifendurchlauf und der Iterationsausdruck incrmt_expr (i.a. wieder eine Zuweisung) nach jedem Schleifendurchlauf. Bei Bedarf dürfen einer oder mehrere dieser Ausdrücke auch weggelassen werden. So kann man z.B. mit
body
...
}
for(;;) {eine Endlosschleife erzeugen. Aus einer for-Schleife springt man mit
body
...
}
break;Durch den Befehl
continue;wird nur der momentane Schleifendurchlauf abgebrochen, dann aber mit der nächsten Iteration fortgesetzt. break und continue sind also analog zu exit und cycle von F. Allerdings kann man bei break und continue in C keine Labels angeben.
if(condition_1) thenHier werden der Reihe nach die Bedingungen condition_1, condition_2 usw. geprüft und der erste Block von Befehlen (und nur dieser) ausgeführt, für den die Bedingung erfüllt ist. Trifft keine der Bedingungen zu, wird der else-Block ausgeführt. Eine if-Konstruktion kann auch nur aus den if... else...end if-Blöcken bzw. einem einzigen if...end if-Block bestehen. In C funktioniert die if-Konstruktion genauso wie in F und hat die Gestalt
block_1
...
else if(condition_2) then
block_2
...
...
else
else_block
...
end if
if(condition_1) {Auch hier dürfen die else if- und else-Zweige fehlen. Die Bedingungen müssen in F logische Ausdrücke sein, die letztlich den Wert .true. oder .false. ergeben. In C können es Ausdrücke allgemeiner Art sein, die einen numerischen Wert [0 (als "falsch" interpretiert) oder ungleich 0 (als "wahr" interpretiert)] ergeben. In der Praxis haben die Bedingungen jedoch in beiden Sprachen fast immer die Form numerischer Vergleiche oder Tests der Gleichheit von Zeichen (in F auch von Zeichenketten). In F stehen zum Bilden logischer Ausdrücke außerdem noch Variablen vom Typ logical zur Verfügung. Die numerischen Vergleichsoperatoren sind in F und C fast identisch
block_1
...
} else if(condition_2) {
block_2
...
...
} else {
else_block
...
}
Logische Ausdrücke können mit Hilfe der folgenden Operatoren verknüpft bzw. negiert werden
F C Bedeutung < < kleiner > > größer <= <= kleiner gleich >= >= größer gleich == == gleich /= != ungleich
(Auch hier sind in F die Punkte wieder Bestandteil des Operators.)
F C Bedeutung .or. || oder .and. && und .not. ! nicht
+, -, * und /In F gibt es zusätzlich noch den Potenzierungsoperator "**", d.h. für xy schreibt man
x**ywobei der Exponent auch nicht-ganzzahlig sein darf (also wäre etwa x**0.5 die Quadratwurzel aus x). Arithmetische Ausdrücke werden in der gewohnten Weise ausgewertet: Potenzierung (so vorhanden) hat Vorrang vor Multiplikation und Division, die ihrerseits Vorrang vor Addition und Subtraktion haben. Will man eine andere Reihenfolge der Auswertung erreichen, so kann man das durch Klammerung von Teilausdrücken mit runden Klammern (...) erzwingen. Zusätzlich stehen in beiden Sprachen eine ganze Reihe von "eingebauten" mathematischen Funktionen mit z.T. identischen Namen zur Verfügung. Die wichtigsten sind
F | C | Anmerkung |
abs(x) | abs(i), fabs(x) | |
acos(x) | acos(x) | |
acosh(x) | ||
aimag(z) | Imaginärteil | |
asin(x) | asin(x) | |
asinh(x) | ||
atan(x) | atan(x) | |
atan2(y,x) | atan2(y,x) | Hauptwert des Arguments von x+iy |
cmplx(x,y) | Konversion in komplexe Zahl x+iy | |
conjg(z) | komplexe Konjugation | |
cos(x) | cos(x) | |
cosh(x) | cosh(x) | |
erf(x), erfc(x) | (komplementäre) Errorfunktion | |
exp(x) | exp(x) | |
int(x) | Konversion in ganze Zahl (mit Abrunden nach 0) | |
j0(x), j1(x), jn(n,x) | Besselfunktion 1. Art | |
log(x), log10(x) | log(x), log10(x) | Logarithmus zur Basis e bzw. 10 |
max(x1,x2,...) | Maximum von x1, x2,... | |
min(x1,x2,...) | Minimum von x1, x2,... | |
modulo(x,y) | fmod(x,y) | x mod y |
nint(x) | Konversion in nächste ganze Zahl | |
pow(x,y) | xy | |
real(x) | Konversion in Typ real | |
sin(x) | sin(x) | |
sinh(x) | sinh(x) | |
sqrt(x) | sqrt(x) | |
tan(x) | tan(x) | |
tanh(x) | tanh(x) | |
y0(x), y1(x), yn(n,x) | Besselfunktion 2. Art |
function function_name (arg_1, arg_2,...) result(result)Hier ist function_name der Name der Funktion, arg_1, arg_, ... die Parameterliste und result der Name einer lokalen Variablen, der irgendwo in der Prozedur der Funktionswert zugewiesen werden muß. Die Parameter müssen durch das Attribut intent(in) als in der Prozedur unveränderlich gekennzeichnet werden. Ein analoges intent(out) bei der Deklaration von result kann entfallen, da es sich von selbst versteht, daß es möglich sein muß, den Funktionswert zu setzen (überschreiben). Dieselbe Konstruktion hat in C die Gestalt
type_1,intent(in)::arg_1
type_2,intent(in)::arg_2
...
type::result
...
result=...
...
return
end function function_name
type function_name (type_1 arg_1, type_2 arg_2,...) {Der Typ des Funktionswertes kann hier gleich vor dem Namen der Funktion angegeben werden. Ebenso können die Parameter bereits in der Parameterliste deklariert werden. Der im Ausdruck expression berechnete eigentliche Funktionswert wird durch den return-Befehl an das rufende Programm zurückgegeben. Es ist also in C nicht notwendig, für den Funktionswert extra eine Variable anzulegen.
...
return expression ;
}
module module_nameDa alle Variablen, Funktionsnamen usw. eines Moduls außerhalb desselben zunächst unbekannt sein sollen (dies wird durch eine private-Direktive erreicht), muß man alles, was für das rufende Programm sichtbar sein soll, explizit mit Hilfe von public-Attributen "sichtbar" machen. Will man also beispielsweise eine Funktion f(x) verwenden, dann müßte das entsprechende Modul mindestens folgende Definitionen enthalten
Deklarationen und Definitionen
...
[contains
Funktionen und Unterprogramme
...]
end module module_name
module module_nameAußerdem müßte im rufenden Programm (bzw. in dem Modul, in dem eine rufende Prozedur steht) dieses Modul im Deklarationsteil mit einer use-Direktive
...
private
...
public::f
...
contains
...
function f(x) ...
...
end function f
...
end module module_name
use module_nameeingebunden werden Die sogenannten "eingebauten" Funktionen und Prozeduren von F sind Bestandteil des Sprachstandards. Der Compiler hat daher von vornherein die notwendigen Informationen, um in der Compilationsphase zu prüfen, ob z.B. Funktionen mit der richtigen Anzahl und dem korrekten Typ von Argumenten aufgerufen werden. Ebenso werden beim Linken diese Prozeduren automatisch in den Systembibliotheken gesucht. Um das Einbinden von Modulen, die Angabe von Bibliotheken beim Linken usw. muß man sich also nur bei selbstgeschriebenen Prozeduren kümmern. Im Gegensatz dazu sind in C auch so grundlegende Funktionen wie Ein- und Ausgabe oder die mathematischen Funktionen nicht Bestandteil der Sprache, sondern nur in Form von Bibliotheken vorhanden, die von Implementation zu Implementation leicht variieren können. Damit der Compiler prüfen kann, ob z.B. Funktionsaufrufe formal korrekt durchgeführt werden, muß man ihm zusätzliche Informationen zur Verfügung stellen. Diese Informationen stehen in den "Header-Files", die typischerweise am Beginn des Programms mit Hilfe von include-Direktiven vom Preprocessor eingefügt werden. Will man also z.B. sowohl die Ein- und Ausgabe- als auch die mathematischen Funktionen von C verwenden, so müßten mit
#include <stdio.h>die Header-Files stdio.h und math.h eingefügt werden. Zusätzlich kann es noch notwendig sein, beim Linken die entsprechenden Bibliotheken anzugeben. Welche Header-Files und welche Bibliotheken man angeben muß, kann man u.U. der man-Page der verwendeten Funktion entnehmen. Die wichtigesten Header-Files stehen im Filesystem in /usr/inlude (oder einem von dessen Unterverzeichnissen), die wichtigsten Bibliotheken in /usr/lib.
#include <math.h>