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