Programmierung für Mathematiker

Werbung
Programmierung für Mathematiker
Prof. Dr. Thomas Schuster
M.Sc. Dipl.-Phys. Anne Wald
31.05.2017
Strukturen: Motivation
Situation: Mit Funktionen verfügen wir über ein wirksames Mittel, um Programmcode
sinnvoll zu gliedern und Komplexität zu verbergen. Für Daten haben wir bisher kein
solches Konstrukt!
Beispielproblem: Adressdatenbank
Eine Adresse (in Deutschland) besteht aus
Straße
Hausnummer
Stockwerk
Postleitzahl
Ort
Zeichenkette
positive Ganzzahl (Vorsicht: „21a“ nicht darstellbar!)
positive Ganzzahl (optional)
positive Ganzzahl
Zeichenkette
Umsetzung: jede Informationseinheit wird in einem entsprechenden Feld gespeichert,
und die Einträge mit gleichen Feldindices gehören zueinander.
char **strassen, unsigned *hausnummern, unsigned *stockwerke, unsigned
*postleitzahlen, char **orte
Nachteile:
Unlogische Gruppierung von Daten, die miteinander nichts zu tun haben
Zuordnung über den Feldindex ist fehleranfällig
Strukturen
Bessere Lösung: jede Adresse ist wie eine Variable ansprechbar, deren Elemente die
einzelnen Informationseinheiten sind. Dazu gibt es in C das Konzept der Strukturen.
Definition
Eine Struktur ist die Zusammenfassung einer bestimmten Anzahl von Daten
(möglicherweise) verschiedenen Typs zu einer Einheit, die mit einem festgelegten
Namen angesprochen werden kann.
Deklaration
eines Strukturtyps:
struct Etikett;
Teilt dem Compiler mit, dass es einen Strukturtyp mit einem bestimmten Etikett
(engl. structure tag) gibt. Muss außerhalb von main stehen.
einer Strukturvariablen:
struct Etikett Variablenname;
Teilt dem Compiler mit, dass eine Variable eines bestimmten Namens gibt, die
vom Typ struct Etikett ist.
Definition eines Strukturtyps:
struct Etikett {Codeblock};
Legt fest, welche Komponenten (engl. members) zu einer Struktur dieses Typs
gehören. Muss außerhalb von main stehen (meistens direkt nach oder sogar statt der
Deklaration).
Strukturen
Anwendung auf das Beispielproblem:
1
struct Adresse;
// Deklaration des Strukturtyps "Adresse"
struct Adresse
{
char *strasse;
unsigned hausnr;
int stock;
unsigned plz;
char *ort;
};
// Definition der Struktur
2
3
4
5
6
7
8
9
10
// 0 = "keine Angabe"
11
12
13
14
15
16
17
18
19
int main(void)
{
// Deklaration einer Variablen vom Typ "struct Adresse"
struct Adresse meine_adr;
printf("sizeof(struct Adresse) = %lu\n", sizeof(struct Adresse));
printf("alternativ: sizeof(meine_adr) = %lu\n", sizeof(meine_adr));
return 0;
}
Die Definition macht in diesem Beispiel die Deklaration überflüssig.
Anzahl und Namen der Komponenten der Struktur sind mit der Definition
festgelegt und im Nachhinein nicht mehr veränderbar.
Hinter der schließenden geschweiften Klammer muss ein Semikolon stehen!
„struct Adresse“ kann wie ein gewöhnlicher Datentyp behandelt werden.
Strukturen: Zugriff auf Komponenten
Die Komponenten einer Struktur werden mit ihrem Namen angesprochen, im
Gegensatz zum Zugriff über Indices bei Feldern. Das Pendant zum Index-Operator
„[]“ ist der Strukturkomponenten-Operator „.“.
Verwendung:
Strukturvariable.Komponentenname
Greift auf die Komponente des entsprechenden Namens einer zuvor deklarierten
Strukturvariable zu.
Der .-Operator hat wie [] höchste Priorität, insbesondere höher als *, &, ++,
Cast und arithmetische Operatoren.
Beispiel:
meine_adr.strasse = "Stuhlsatzenhausweg";
meine_adr.hausnr = 103;
meine_adr.stock = 0;
meine_adr.plz = 66123;
meine_adr.ort = "Saarbruecken";
printf("%s %u\n", meine_adr.strasse,
meine_adr.hausnr);
printf("%u %s\n", meine_adr.plz,
meine_adr.ort);
Ausgabe:
Stuhlsatzenhausweg 103
66123 Saarbruecken
Strukturen: Weitere Eigenschaften
Initialisierung bei Deklaration: wie bei statischen Feldern in geschweiften
Klammern
struct Adresse meine_adr = {"Stuhlsatzenhausweg", 103, 0, 66123,
"Saarbruecken"};
Strukturen können andere Strukturen enthalten (Schachtelung)
struct Anschrift
{
char *nachname;
char *vorname;
struct Adresse adresse;
};
Initialisierung:
struct Anschrift meine_anschrift = {"mit Biergarten",
"Restaurant",
{"Stuhlsatzenhausweg", 103, 0, 66123, "Saarbruecken"} };
Zugriff auf geschachtelte Strukturen: z. B.
meine_anschrift.adresse.hausnr
Achtung: Zu tiefe Schachtelung macht Code unlesbar!
Strukturen: Weitere Eigenschaften
Zeiger auf Strukturen:
struct Etikett *pointer
Deklariert einen Zeiger auf den Datentyp struct Etikett
Für den Zugriff (*pointer).Komponente auf Komponenten mit Hilfe des
Pointers gibt es die vereinfachte Schreibweise pointer->Komponente
Strukturen können einen Zeiger auf den eigenen Typ enthalten.Dazu müssen
jedoch Deklaration und Definition getrennt werden:
struct Anschrift;
struct Anschrift
{
char *nachname;
char *vorname;
struct Adresse adresse;
struct Anschrift *li_nachbar;
struct Anschrift *re_nachbar;
} meine_anschrift_;
Zugriff auf Nachbarelement: z. B.
Bemerkung: In diesem Programmcode
wird in Verbindung mit der Definition
der Struktur Anschrift eine globale
Instanz meine_anschrift_ deklariert.
meine_anschrift_.li_nachbar->hausnr
Anwendung: Verkettung von Daten, z. B. Listen oder Bäume
Strukturen: Weitere Eigenschaften
Zeiger auf Strukturen können an Funktionen als Parameter übergeben und als
Rückgabewert zurückgegeben werden.
Strukturen, die andere Strukturen oder dynamische Speicherobjekte enthalten,
sollten nicht mehr „von Hand“, sondern mit speziell dafür vorgesehenen
Funktionen (Erstellung, Manipulation von Komponenten, Kopie, Löschung, . . . )
verwaltet werden
; Robuste Programme, Anfänge der „Objektorientierten Programmierung“
Vorsicht Strukturen können nur komponentenweise verglichen werden. Der
Vergleich
struct Vektor u,v;
if (u==v)
printf("Vektoren stimmen überein");
führt zu der Fehlermeldung: invalid operands to binary ==
Strukturen: Weitere Eigenschaften
Vorsicht: Bei Zuweisung wird nur die oberste Ebene betrachtet.
1
2
3
4
5
6
struct Vektor
{
int dim;
char *label;
int *koord;
};
7
8
9
10
11
int main(void)
{
struct Vektor u = {2, "Ursprung", NULL}, e1;
u.koord = (int *) malloc(u.dim * sizeof(int));
12
e1 = u;
// Shallow copy
e1.label = "1. Einheitsvektor";
//umbiegen des Zeigers e1.label
e1.koord[0] = 1;
13
14
15
16
17
printf("%s u = (%d,%d), ", u.label, u.koord[0], u.koord[1]);
printf("%s e1 = (%d,%d)\n", e1.label, e1.koord[0], e1.koord[1]);
18
19
20
21
22
23
}
free(u.koord);
return 0;
Ausgabe: Ursprung u = (1,0), 1. Einheitsvektor e1 = (1,0)
; Es wurden sämtliche Variablenwerte (= Adressen bei Zeigern!) kopiert, nicht
jedoch das dynamische Feld! (Woher soll der Compiler auch davon wissen?)
Typendefinition
Mit Hilfe des Schlüsselworts typedef lassen sich in C eigene Datentypen definieren.
Syntax: typedef AlterDatentyp NeuerDatentyp;
Beispiel 1: primitive Datentypen
typedef unsigned long int size_t;
Anwendung: architekturunabhängige Programmierung
Beispiel 2: in Verbindung mit Strukturen
typedef struct Adresse Adresse;
Im folgenden Code kann statt „struct Adresse“ einfach „Adresse“ geschrieben
werden. Folge: erhöhte Lesbarkeit
Die Typendefinition lässt sich mit der Strukturdefinition verbinden:
typedef struct _Adresse_ Adresse;
struct _Adresse_
{
char *strasse;
unsigned hausnr;
int stock;
unsigned plz;
char *ort;
};
oder äquivalent:
typedef struct
{
char *strasse;
unsigned hausnr;
int stock;
unsigned plz;
char *ort;
} Adresse;
Beispiel: Erstellen/Kopieren eines Vektors
1
2
3
4
5
6
7
8
9
10
Vektor *neuerVektor(int dim, char *label, int *koord)
{ Vektor *vec=(Vektor *) malloc(sizeof(Vektor));int i;
if(vec==NULL)
printf("Zu wenig speicher");
vec->dim = dim;
vec->koord = (int *) malloc(dim*sizeof(int));
for (i=0;i<dim;i++)
vec->koord[i] = koord[i];
vec->label = dupliziereString(label);
return vec;}
11
12
13
14
15
16
17
18
19
20
char *dupliziereString(char *string)
{char *clone = NULL;
if (string)
{
if ( !(clone = (char *) malloc(sizeof(char)*(strlen(string)+1))))
printf("Zu wenig Speicher!");
strcpy(clone,string);
}
return clone;}
Ausschnitt aus dem Hauptprogramm:
int dimension=2;
koord=(int*) malloc(dimension*sizeof(int));
char ulabel[]="Ursprung";
struct Vektor *u=neuerVektor(dimension,ulabel,koord); //neuen Vektor erzeugen
struct Vektor *e1=neuerVektor(u->dim,u->label,u->koord); //Vektor kopieren
Beispiel: komplexe Zahlen
Deklarationen und Definitionen
1
2
3
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
4
5
6
7
8
9
typedef struct
{
double re;
double im;
} Complex;
10
11
12
13
Complex *newComplexPolar(double radius, double angle);
Complex *complexProduct(Complex *z1, Complex *z2);
//--------------------------------------------------------
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Complex *newComplexPolar(double radius, double winkel)
{
Complex *z = (Complex *) malloc(sizeof(Complex));
z->re = radius * cos(winkel);
z->im = radius * sin(winkel);
return z;
}
//-------------------------------------------------------Complex *complexProduct(Complex *z1, Complex *z2)
{
Complex *z = (Complex *) malloc(sizeof(Complex));
z->re = z1->re*z2->re - z1->im*z2->im;
z->im = z1->re*z2->im + z1->im*z2->re;
return z;
}
Beispiel: komplexe Zahlen
Hauptprogramm
1
2
3
4
5
int main(void)
{
Complex a = {1.0, 1.0};
Complex *b = newComplexPolar(2.0, 3*M_PI/4.0);
Complex *c = complexProduct(&a, b);
6
printf("a = %lf+%lfi\n", a.re, a.im);
printf("b = %lf+%lfi\n", b->re, b->im);
printf("a*b = %lf+%lfi\n", c->re, c->im);
c->re = 0;
c->im = 2.5;
b = complexProduct(&a, c);
printf("a*c = %lf+%lfi\n", b->re, b->im);
7
8
9
10
11
12
13
14
15
16
}
return 0;
Ausgabe:
a
b
a*b
a*c
= 1.000000+1.000000i
= -1.414214+1.414214i
= -2.828427+0.000000i
= -2.500000+2.500000i
Felder von Strukturen
1
2
3
4
5
int main(void)
{
int j, n = 10;
Complex *dyn_vek = (Complex *) calloc(n, sizeof(Complex));
Complex stat_vek[10];
6
for (j=0; j<n; j++)
{
dyn_vek[j].re = 2.0 * j;
dyn_vek[j].im = 0.5 * j;
stat_vek[j].re = 2.0 * j;
stat_vek[j].im = 0.5 * j;
}
printf(" dyn_vek[7] = %lf+%lfi\n", dyn_vek[7].re, dyn_vek[7].im);
printf("stat_vek[7] = %lf+%lfi\n", stat_vek[7].re, stat_vek[7].im);
7
8
9
10
11
12
13
14
15
16
17
18
19
}
free(dyn_vek);
return 0;
Ausgabe:
dyn_vek[7] =
stat_vek[7] =
14.000000+3.500000i
14.000000+3.500000i
; Statische und dynamische Felder von Strukturen können wie gewohnt verwaltet
werden.
Allgemeine Merkregeln für Strukturen
Strukturen dienen dazu, Einzelinformationen zu einer sinnvollen Einheit
zusammenzufassen.
Häufig sind Strukturen Abbilder von Konzepten aus dem Alltag oder von
mathematischen Objekten (siehe Beispiele zu Adressen und komplexen Zahlen).
Geschachtelte Strukturen sind möglich und oft sinnvoll, bergen aber die Gefahr,
zu große Komplexität zu erzeugen und das Ziel größerer Ordnung zu verfehlen.
Zudem steigt das Risiko von Programmierfehlern (s. shallow-copy -Problem).
Strukturen, die andere Strukturen oder dynamische Speicherobjekte enthalten,
sollten nicht mehr „von Hand“, sondern mit speziell dafür vorgesehenen
Funktionen verwaltet werden (Erstellung, Manipulation von Komponenten, Kopie,
Löschung, . . . ) ; Anfänge der „Objektorientierten Programmierung“
Zur Erhöhung der Lesbarkeit sollten Strukturen immer mit einem typedef als
neuer Datentyp definiert werden.
Zur Namensgebung hat sich folgende Konvention durchgesetzt:
ErsterBuchstabeGrossOhneUnterstriche für eigene typedef-Datentypen
_VorneUndHintenMitUnterstrichen_ für Strukturen, die nicht direkt, sondern
nur in Verbindung mit typedef verwended werden.
Beispiel:
typedef struct _Adresse_ Adresse;
Verwendung nur über Adresse, niemals über struct _Adresse_
Funktionszeiger: Vorüberlegungen
Funktionsaufrufe sind bis jetzt im Code mit Name explizit angegeben
(„hartcodiert“)
Folge: Zur Compilierzeit muss bekannt sein, welche Funktion eine bestimmte
Aufgabe erfüllen soll.
Beispielszenario: Wir haben einen Sortieralgorithmus geschrieben, der ein Feld
von Zahlen sortiert. Dabei wollen wir flexibel bestimmen können, nach welchem
Vergleichskriterium sortiert werden soll (größer, kleiner, 5. Ziffer in
Dezimaldarstellung größer, Quersumme kleiner, . . . ).
Mit den bisherigen Mitteln müsste im Algorithmus nach Vergleichskriterien
unterschieden werden, z. B. mittels if-else oder switch.
Alternativ müsste für jedes Vergleichskriterium eine separate Funktion
geschrieben werden.
Einleuchtendere Herangehensweise:
Es gibt einen allgemeinen Sortieralgorithmus, der das Feld nach einer
bestimmten Methode durchsucht und beim Vergleich zweier Elemente
irgendein (variables!) Vergleichskriterium heranzieht.
Zum Vergleich gibt es eine Reihe von Vergleichsfunktionen, die getrennt
vom Sortierverfahren deklariert und definiert sind.
Beim Aufruf des Sortieralgorithmus wird eine der Vergleichsfunktionen als
Parameter mit angegeben.
Genau dieses Verhalten lässt sich mit Funktionszeigern erzeugen!
Funktionszeiger:Deklaration
Datentyp (*FktZeigerName)(Parameter(typ)liste);
Deklariert einen Zeiger auf eine Funktion, welche die Signatur
Datentyp Funktion(Parameter(typ)liste)
besitzt.
Beispiele:
void (*InitFkt)(double *, double);
Zeiger auf eine Funktion, die einen double *- und einen double-Parameter
nimmt und nichts (void) zurückgibt.
double (*ZufGen)(void)
Zeiger auf eine Funktion, die keine Parameter nimmt und einen double-Wert
zurückgibt.
double *(*NeuesFeld)(unsigned)
Zeiger auf eine Funktion, die einen unsigned-Parameter nimmt und einen Pointer
auf double zurückgibt.
Funktionszeiger: Beispiel
Deklaration und Definition von zwei Anzeigevarianten
1
#include <stdio.h>
2
3
4
void anzeigeVariante1(char *text);
void anzeigeVariante2(char *text);
5
6
//--------------------------------------------------
7
8
9
10
11
12
void anzeigeVariante1(char* text)
{
printf("\n %s\n", text);
return;
}
13
14
//--------------------------------------------------
15
16
17
18
19
20
21
22
void anzeigeVariante2(char* text)
{
printf("\n **********************************");
printf("\n * %-30s *\n", text);
printf(" **********************************\n");
return;
}
Funktionszeiger: Beispiel
Hauptprogramm
1
2
3
int main(void)
{
void (*AnzFkt)(char *);
4
AnzFkt = anzeigeVariante1;
AnzFkt("Test Variante 1");
5
6
7
AnzFkt = anzeigeVariante2;
AnzFkt("Test Variante 2");
8
9
10
11
12
}
return 0;
Ausgabe:
Test Variante 1
**********************************
* Test Variante 2
*
**********************************
; Vor dem Funktionsnamen in der Zuweisung steht kein &-Operator. Wie bei
statischen Feldern hätte er keine Auswirkung.
; Beim Aufruf der Funktion via Pointer wird kein *-Operator benötigt. Die
Schreibweise (*AnzFkt)(...) wäre äquivalent, nicht jedoch *AnzFkt(...)!
Funktionszeiger: Bemerkungen
Bei Zuweisungen mit Funktionspointern ist unbedingt darauf zu achten, dass die
Signaturen von Pointer und Funktion übereinstimmen. Alles andere führt zu
unkontrollierbarem Programmverhalten! Vor Fehlern dieser Art warnt der
Compiler bestenfalls.
Deklarationen von Zeigern auf Funktionen mit langer Parameterliste werden leicht
unleserlich (vor allem bei Funktionen mit Funktionszeigern als Parameter):
void funktion(int a, double b,
double *(*f)(double, int, int, double *, double *));
int main(void)
{
double *(*fp)(double, int, int, double *, double *);
fp = testfkt; // testfkt sei passend deklariert
funktion(42, 3.14, fp);
return 0;
}
Ein typedef schafft Abhilfe:
typedef double *(*MeineFkt)(double, int, int, double *, double *);
void funktion(int a, double b, MeineFkt f);
Deklaration der Funktionspointer-Variable in main: MeineFkt fp;
Rangfolge von Operatoren (Überblick)
Priorität
Priorität 1
Operator
()
[]
. , ->
Priorität 2
!
++ , −−
−,+
Assoziativität
linksassoziativ
Array/Index-Operator
Member-Zugriff
Logische Negation
Unäres Plus, unäres Minus
Adress-Operator
*
Dereferenzierung
*,/
%
rechtsassoziativ
Inkrement, Dekrement
&
(type)
Priorität 3
Bedeutung
Funktionenaufruf
Cast
Multiplikation, Division
linksassoziativ
Modulo
Priorität 4
+,−
Priorität 6
<, <=, >, >=
Priorität 7
== , ! =
gleich, ungleich
linksassoziativ
Priorität 11
&&
logisches UND
linksassoziativ
Priorität 12
||
logisches ODER
linksassoziativ
Plus, Minus
linksassoziativ
kleiner, ...
linksassoziativ
Merkregeln und lesen von Deklarationen
Merke:
Rechtsassoziativ sind lediglich: Zuweisungsoperatoren, Bedingungsoperator (? :)
und unäre Operatoren.
Sinnvolle Klammerungen können die Lesbarkeit von Code deutlich erhöhen!
int * (*Funkzeiger)();
↑ ↑ ↑
↑
4
2
3
1
Resultat:
Funkzeiger ist ein Zeiger (1) auf eine Funktion (2) mit leerer Parameterliste, die einen
Zeiger (3) auf Integer (4) zurückgibt.
Publikumsfrage
Was deklarieren die folgenden Statements?
double *f(double *, int);
; Funktion, die als Parameter einen double * und einen int nimmt und einen
double * zurückgibt.
double (*f)(double *, int);
; Zeiger auf eine Funktion, die als Parameter einen double * und einen int nimmt
und einen double zurückgibt.
double *(*f)(double *, int);
; Zeiger auf eine Funktion, die als Parameter einen double * und einen int nimmt
und einen double * zurückgibt.
double *g[20];
; g ist Feld mit 20 Einträgen vom Typ double *.
Funktionszeiger: Beispiel Sortierverfahren
In stdlib.h ist die folgende Sortierfunktion deklariert („quicksort“):
void qsort(void *base, size_t nmemb, size_t size,
int(*compar)(const void *, const void *));
Parameter:
base Zu sortierendes Feld eines (noch) nicht festgelegten Datentyps
nmemb Anzahl der Feldelemente
size Größe eines Feldelements in Byte
compar Zeiger auf eine (Vergleichs-)Funktion, die zwei void *-Zeiger auf zu
vergleichende Elemente als Parameter nimmt und einen int
zurückgibt.
Interpretation: compar repräsentiert eine mathematische
Ordnungsrelation „“ auf einer Menge M, d. h. für zwei beliebige
Werte a, b ∈ M gilt entweder a b, b a oder beides. Die zu
vergleichenden Elemente von base stammen aus M.
Der Rückgabewert von compar ist -1 (a b), 0 (Gleichheit) oder +1
(b a), wobei a dem ersten und b dem zweiten Parameter von
compar entspricht.
Funktionszeiger: Beispiel Sortierverfahren
Anwendung:
1
2
#include <stdio.h>
#include <stdlib.h>
3
4
int unsign_qsumme_kleiner(const void *pa, const void *pb);
5
6
7
8
9
int main(void)
{
unsigned z[5] = {23, 511, 10100, 8, 333};
qsort(z, 5, sizeof(unsigned), unsign_qsumme_kleiner);
10
11
12
13
14
}
printf("Sortiertes Feld:\n");
printf("%d %d %d %d %d\n", z[0], z[1], z[2], z[3], z[4]);
return 0;
15
16
17
18
19
int unsign_qsumme_kleiner(const void *pa, const void *pb)
{
unsigned a = *((unsigned *) pa), b = *((unsigned *) pb);
unsigned qs_a = a % 10, qs_b = b % 10;
20
while (a /= 10)
qs_a += a % 10;
21
22
23
while (b /= 10)
qs_b += b % 10;
24
25
26
27
28
}
return (qs_a < qs_b)? -1 : (qs_b < qs_a)? +1 : 0;
Funktionszeiger: Beispiel Sortierverfahren – Codeanalyse
Deklaration und Definition der Sortierfunktion
unsign_qsumme_kleiner soll die Quersumme von unsigned-Werten vergleichen.
Die Signatur muss int fkt(const void *, const void *) sein.
Im Funktionsrumpf werden die Parameter pa und pb zunächst als unsigned *
gecastet, anschließend dereferenziert und die Werte in unsigned-Variablen a
und b geschrieben. Exemplarisch für pa:
unsigned a = *( (unsigned *) pa );
In den Schleifen werden die Quersummen von a und b berechnet und in qs_a
bzw. qs_b gespeichert.
Beachte: while (a /= 10) führt zuerst Ganzzahl-Division durch und prüft
anschließend, ob das Ergebnis ungleich 0 ist (in diesem Fall besteht a noch nicht
aus einer einzigen Dezimalziffer).
Die return-Zeile verwendet die verkürzte Fallunterscheidung
(Bedingung)? Wert_Falls_Ja : Wert_Falls_Nein;
Beispiel:
absx = (x > 0)? x : -x;
speichert genau wie
if (x > 0) absx = x; else absx = -x;
den Betrag von x in absx.
Funktionszeiger: Beispiel Sortierverfahren – Codeanalyse
Hauptprogramm
Das Feld z mit 5 unsigned-Einträgen soll aufsteigend bzgl. der Quersumme
sortiert werden.
Dazu wird qsort mit den Parametern z (base), 5 (nmemb), sizeof(unsigned)
(size) und unsign_qsumme_kleiner (compar) aufgerufen:
qsort(z, 5, sizeof(unsigned), unsign_qsumme_kleiner);
Ausgabe:
Sortiertes Feld:
10100 23 511 8 333
Fazit: Wie qsort genau funktioniert, ist hier völlig unerheblich. Entscheidend ist,
dass die Funktion ein Feld anhand einer gegebenen Vergleichsroutine sortiert.
Kommandozeilenargumente
Bisher:
$ gcc -o ProgName C_Code.c
$ ./ProgName
.
.
.
Neu: Komandozeilenargumente
Die main-Funktion lässt sich auch mit zwei Parametern aufrufen.
Vollständige Deklaration von main:
int main(int argc, char *argv[])
argc argument counter: Anzahl der beim Programmaufruf übergebenen Argumente,
einschließlich des Programmnamens. Erst wenn argc > 1 ist, werden tatsächlich
Parameter übergeben.
argv argument vector: Feld von Strings
Bemerkung: Die Namen argc und argv (auch üblich: args) sind Konvention und
nicht zwingend festgelegt. Möglich (aber wenig sinnvoll) wäre auch
int main(bla, blubb)
Kommandozeilenargumente
Beispiel: argumente.c
1
#include <stdio.h>
2
3
4
5
int main(int argc, char *argv[])
{
int i;
6
printf("Anzahl der Argumente: %d\n\n", argc);
7
8
for (i=0; i<=argc; i++)
printf("Argument %d: %s\n", i, argv[i]);
9
10
11
12
13
}
return 0;
Ausgabe: Der Aufruf ./argumente arg1 arg2 ––help +~ 125.777 -99 liefert
Anzahl der Argumente: 7
Argument
Argument
Argument
Argument
0:
1:
2:
3:
./argumente
arg1
arg2
--help
Argument
Argument
Argument
Argument
4:
5:
6:
7:
+~
125.777
-99
(null)
Kommandozeilenargumente
Beachte:
Parameter werden durch Leerzeichen getrennt. Ausdrücke wie -o Ausgabe
werden als zwei separate Argumente aufgefasst.
Jedes Argument wird als Zeichenkette in argv gespeichert. Die Reihenfolge der
Strings in argv entspricht dabei der Reihenfolge auf der Kommandozeile.
Werden Zahlen als Argumente übergeben, müssen diese mit Hilfe der
entsprechenden Umwandlungsfunktionen (z. B. atoi oder atof) in ein
Zahlenformat konvertiert werden.
Mit String-Vergleichsfunktionen (z. B. strncmp) lässt sich beispielsweise prüfen,
ob ein Programm mit einem bestimmten Optionsparameter aufgerufen wurde.
Da jedoch die Reihenfolge der Optionsargumente festgelegt ist, benötigen
Programme mit vielen Optionen einen flexibleren Ansatz zur Auswertung der
Kommandozeile (→ Parser).
Kommandozeilenargumente: Beispiel
1
2
#include <stdio.h>
#include <stdlib.h>
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int main(int argc, char *argv[]){
double x, y, z;
if (argc < 4){
// Es fehlen Argumente
printf("\nKorrekter Aufruf: ");
printf("%s zahl1 op zahl2\n", argv[0]);
return 1;
}
x = atof(argv[1]);
y = atof(argv[3]);
switch (argv[2][0]) {
case '+':
z = x + y; break;
case '-':
z = x - y; break;
case 'x':
case '*':
z = x * y; break;
case '/':
z = x / y; break;
default:
printf("\nFalscher Operator! ABBRUCH!\n");
return -1;
}
27
28
29
30
}
printf("\n%s %s %s = %lf", argv[1], argv[2], argv[3], z);
return 0;
Kommandozeilenargumente
$ gcc -o berechne taschenrechner.c
$ ./berechne 2 + 5
2 + 5 = 7.000000
$ ./berechne 2 x 5
2 x 5 = 10.000000
$ ./berechne 2 / 5
2 / 5 = 0.400000
$ ./berechne 2 /
Korrekter Aufruf: ./berechne zahl1 op zahl2
Herunterladen