ModProg 15-16, Vorl. 12

Werbung
ModProg 15-16, Vorl. 12
Richard Grzibovski
Jan. 20, 2016
1 / 41
Übersicht
Übersicht
1 Rekursive Funktionen
2
3
Suchalgorithmen
Sortieralgorithmen
Insertionsort
Quicksort
4
Zeiger auf Funktionen
5
Bibliotheksfunktionen zum Sortieren und Suchen
6
Nichttriviale Deklarationen
7
Mehrere Quelldateien
2 / 41
Rekursive Funktionen
Als Rekursion (lat. recurrere ”zurücklaufen”) bezeichnet man die
Technik in Mathematik, Logik und Informatik, eine Funktion durch
sich selbst zu definieren.
In C sind rekursive Funktionen erlaubt, d.h. eine Funktion darf sich
selbst aufrufen.
3 / 41
Beispiel: Fakultät
Definition: sei n ∈ N, dann n! = n · (n − 1) · . . . · 2 · 1, 0! = 1.
1
2
3
4
5
6
7
8
9
10
unsigned long fak ( unsigned long n ) {
unsigned long i , f=1;
for ( i=2;i<=n ; i++) f∗=i ;
return f ;
}
unsigned long fakr ( unsigned long n ) {
if ( n<=1) return 1 ;
return n∗ fakr ( n −1);
}
4 / 41
Beispiel: Binomialkoeffizienten
Definition: sei n, k ∈ N, n ≥ k ≥ 0, dann
n!
n
=
k
k!(n − k)!
Naive Implementierung:
1
2
3
unsigned long bink ( unsigned long n , unsigned long k ) {
return fak ( n ) / fak ( k ) / fak ( n−k ) ;
}
Bessere Methode wäre, die Rekursion zu verwenden:
n
n−1
n−1
=
+
k
k −1
k
5 / 41
Beispiel: Binomialkoeffizienten
1
2
3
4
5
6
7
8
9
n
k
=
n−1
k −1
+
n−1
k
int binomial ( int n , int k ) {
if ( n < k ) {
return 0 ;
}
if(0== n | | 0== k | | n == k ) {
return 1 ;
} else
return binomial ( n−1, k−1) + binomial ( n−1, k ) ;
}
6 / 41
Rekursion: allgemeine Bemerkungen
Bei Implementierung ist darauf zu achten, dass
die Rekursion stets auf einen elementar zu behandelnden Fall
führt bzw. auf einen von mehreren solcher Fälle,
alle elementar lösbaren Fälle, die eintreten können, auch
korrekt behandelt werden.
Rekursive Algorithmen können mit Hilfe rekursiver Funktionen
implementiert werden. Sehr oft sind alternative iterative
Realisierungen möglich, d.h. solche, die Schleifen verwenden.
7 / 41
Suchalgorithmen
Eine häufig zu lösende Aufgabe besteht darin herauszufinden, ob
ein bestimmter Wert in einem Feld (oder einer Liste) vorkommt
und wenn ja, wo.
Die einfachste Vorgehensweise besteht natürlich darin, das Feld
von vorne beginnend abzusuchen, wie im folgenden Beispiel für ein
Feld mit int-Komponenten gezeigt:
1
2
3
4
5
6
int einfachsuche ( int ∗feld , int len , int wert ) {
int i ;
for ( i =0; i < len ; i ++)
if ( feld [ i ] == wert ) break ;
return i ;
}
Die Vorgehensweise hat die folgenden Eigenschaften:
Positiv: Die Methode funktioniert für unsortierte Felder und
Listen.
Negativ: Die Laufzeit wächst linear mit der Feldlänge an.
8 / 41
Suchalgorithmen
Ist das Feld aufsteigend sortiert, so kann man die Suche mit Hilfe
der folgenden Strategie wesentlich schneller durchführen:
Beispiel: Binäre Suche (engl. binary search) in einem aufsteigend
sortierten Feld .
1
Vergleiche den gesuchten Wert mit dem in der Mitte des
Feldes.
2
Bei Gleichheit: n/2 ist die gesuchte Stelle. Abbruch.
Andernfalls:
3
Ist der gesuchte Wert größer, wiederhole das Verfahren für das
Teilfeld rechts von der Feldmitte.
Ist der gesuchte Wert kleiner, wiederhole das Verfahren für das
Teilfeld links von der Feldmitte.
bis die Feldlänge 0 ist. In diesem Fall befindet sich der Wert
nicht im Feld.
9 / 41
Suchalgorithmen
Durch vollständige Induktion kann man zeigen:
Op(n) = c(1 + log2 (n)).
Damit gilt Op(n) = O(log2 n), d.h die binäre Suche hat
logarithmische Komplexität.
Ein Beispiel aus dem Alltag für die Anwendung dieses Verfahrens
ist das Suchen in Telefonbüchern.
10 / 41
Sortieralgorithmen: Problemstellung
Gegeben: Ein Feld F der Länge n von Zahlen.
Zu finden: Eine Permutation F 0 von F , wobei die Einträge in F 0
sortiert sind.
Beispiel:
F= 4 3 8 1 5
F’= 1 3 4 5 8
11 / 41
Sortieralgorithmen: Sonderfälle
Bemerkung: Jedes Feld der Länge 1 ist bereits sortiert.
Definition
Ein Feld der Länge n heißt ein Z-Feld, wenn die ersten n − 1
Einträge sortiert sind.
Bemerkung: Jedes Feld der Länge 2 ist ein Z-Feld.
Algorithmus: Sortierung eines Z-Feldes der Länge n ≥ 2
1
Setze i := n.
2
Wenn i == 1, gehe nach Schritt 4.
Wenn F (i) < F (i − 1),
3
(a) vertausche F (i) und F (i − 1)
(b) setze i := i − 1.
(c) Wiederhole Schritt 2.
4
Ende.
12 / 41
Sortieralgorithmen: Sonderfälle
Algorithmus: Sortierung eines Z-Feldes der Länge n ≥ 2
1
Setze i := n.
2
Wenn i == 1, gehe nach Schritt 4.
Wenn F (i) < F (i − 1),
3
(a) vertausche F (i) und F (i − 1)
(b) setze i := i − 1.
(c) Wiederhole Schritt 2.
4
Ende.
Beispiel:
1 3 5 8 4
1 3 4 5 8
13 / 41
Sortieralgorithmen: Sonderfälle
Algorithmus: Sortierung eines Z-Feldes der Länge n ≥ 2
1
Setze i := n.
2
Wenn i == 1, gehe nach Schritt 4.
Wenn F (i) < F (i − 1),
3
(a) vertausche F (i) und F (i − 1)
(b) setze i := i − 1.
(c) Wiederhole Schritt 2.
4
Ende.
Laufzeitkomplexität:
Im schlimmsten Fall n − 1 Vergleiche und
n − 1 Vertauschoperationen ⇒ Op(n) = 2(n − 1) = O(n),
im besten Fall 1 Vergleich ⇒ Op(n) = 1.
14 / 41
Insertionsort
Algorithmus: Insertionsort
Sortierung eines Feldes der Länge n ≥ 2.
1 Für i := 2 bis n
Sortiere das Z-Feld {F (1) . . . F (i)}
P
Laufzeitkomplexität: Op(n) = ni=2 (Z-Sort(i))
Im schlimmsten Fall Op(n) = n(n − 1) = O(n2 ),
im besten Fall Op(n) = (n − 1) = O(n).
15 / 41
Insertionsort: Beisplel
i
F
4 3 8 1 5
2
4 3 8 1 5
3
3 4 8 1 5
4
3 4 8 1 5
5
1 3 4 8 5
1 3 4 5 8
16 / 41
Quicksort
Algorithmus: Quicksort
Sortierung eines Feldes F = {fi }ni=1 .
1
Felder mit n < 2 sind bereits sortiert.
2
Wähle ein Vergleichselement (auch Pivotelement genannt) fk
aus dem Feld aus.
3
Teile das Feld auf in Elemente {≤ fk } und {> fk }
F := [{fi ∈ F̃ : fi ≤ fk }, fk , {fi ∈ F̃ : fi > fk }],
wobei F̃ = F \ {fk }.
4
Wende den Algorithmus rekursiv auf die Teilfelder links und
rechts des Pivotelements an.
Bezeichne:
[{fi ∈ F̃ : fi ≤ fk }, fk , {fi ∈ F̃ : fi > fk }] = teilung(F , k).
17 / 41
Quicksort: Teilung
[{fi ∈ F̃ : fi ≤ fk }, fk , {fi ∈ F̃ : fi > fk }] = teilung(F , k).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int teilung ( ft ∗ feld , int beg , int end , int pIdx ) {
int tIdx , i ;
ft pVal ;
pVal= feld [ pIdx ] ;
vertausche ( feld [ pIdx ] , feld [ end ] ) ; // piv --> end
tIdx = beg ;
for ( i=beg ; i<end ; i++) {
if ( feld [ i ] <= pVal ) {
vertausche ( feld [ i ] , feld [ tIdx ] ) ;
tIdx++;
}
}
vertausche ( feld [ tIdx ] , feld [ end ] ) ; // piv --> tIdx
return tIdx ;
}
18 / 41
Quicksort: Teilung: Beisplel
3 7 8 5 2 1 9 5 4
piv−>end
3 7 8 5 2 1 9 5 4
for−Schleife
beg=0
pIdx=4
end=8
3 7 8 4 2 1 9 5 5
3 4 2 7 8 1 9 5 5
piv−>tIdx
3 4 2 1 5 7 9 8 5
Ergebnis
3 4 2 1 5 5 9 8 7
19 / 41
Quicksort: Implementierung
1
2
3
4
5
6
7
8
9
void quicksort ( ft ∗ feld , int beg , int end ) {
if ( end > beg ) {
int pIdx ;
pIdx = beg + ( end − beg ) / 2 ;
pIdx = teilung ( feld , beg , end , pIdx ) ;
quicksort ( feld , beg
, pIdx − 1 ) ;
quicksort ( feld , pIdx + 1 , end
);
}
}
Laufzeitkomplexität:
Im schlimmsten Fall Op(n) = O(n2 ),
im besten Fall, auch im durchschnittlichen Fall,
Op(n) = O(n log n).
20 / 41
Quicksort: Bemerkungen
Existieren iterative Implementierungen von Quicksort
Es gibt Algorithmen, beispielsweise Heapsort, deren Laufzeit
auch im schlimmsten Fall durch O(n log n) beschränkt sind.
In der Praxis wird aber trotzdem Quicksort eingesetzt, da
angenommen wird, dass bei Quicksort der schlimmste Fall nur
sehr selten auftritt und im mittleren Fall schneller als
Heapsort ist. Dies ist jedoch noch Forschungsgegenstand.
Quicksort wurde ca. 1960 von C. Antony R. Hoare in seiner
Grundform entwickelt und seitdem von vielen Forschern
verbessert.
21 / 41
Zeiger auf Funktionen: Motivation
Beispiel 1: Sei f : [a, b] 7→ R eine stetige Funktion mit
f (a)f (b) < 0. Dann gibt es eine Nullstelle x0 (d.h. f (x0 ) = 0).
Wir haben ein Algorithmus implementiert, der die Nullstelle mit
gegebener Genauigkeit ε findet (Übungsblatt 3, Übung 3).
double finde_nullstelle(double a, double b, double eps);
Die Funktion f ist als
double func(double x);
implementiert.
Es wäre schön, den Bisektionalgorithmus unabhängig von func zu
implementieren:
double finde_nullstelle(“funktion f ” , double a, double b,
double eps);
Somit könte man einmal implementieren und lebenslang für alle
Funktionen benutzen.
22 / 41
Zeiger auf Funktionen: Motivation
Beispiel 2: Sei f : [a, b] 7→ R eine beschränkte Funktion. Man
kann ein Algorithmus
implementieren, der das Integral
Rb
I (f , a, b) = a f (x)dx approximiert:
N
b−aX
f
I (f , a, b) ≈
N
i=1
b−a
a+i
N
.
double integriere(double a, double b, int N);
Die Funktion f ist als
double func(double x);
implementiert.
Es wäre schön, den Integrationsalgorithmus unabhängig von func
zu implementieren:
double integriere(“funktion f ” , double a, double b, int N);
Somit könte man einmal implementieren und lebenslang für alle
Funktionen benutzen.
23 / 41
Zeiger auf Funktionen: Motivation
Beispiel 3: Die Sortieralgorithmen, die wir betrachten haben,
brauchen nur zwei Operationen:
1
Vertauschoperation: vertauscht Feldelemente fi und fj .
2
Vergleichoperation: ist das Feldelement fi größe als das,
kleiner als das oder gleich dem Feldelement fj .
Wenn man die zwei Operationen für ein Feld hat,
kann man das Feld sortieren.
Da die Feldeinträge in Speicher liegen, ist die
Vertauschoperation äquvivalent zum Kopieren. Man braucht
nur die Größen von fi (oder von fj ) zu wissen.
Eine Funktion, die die Vergleichoperation durchgeführt, muss
man dem Sortieralgorithmus als Argument übergeben.
24 / 41
Zeiger auf Funktionen: Deklaration
Zunächst zur Deklaration eines Funktionszeigers:
Ruckgabetyp (∗ Zeigername)(Liste der Funktionsparametertypen);
Betrachten wir folgende einfache Beispiele:
double (∗ fptr)(double );
fptr ist ein Zeiger auf eine Funktion, die ein Argument vom
Typ double entgegennimmt und einen double-Wert
zurückliefert.
char∗ (∗ fptr2)(int, int);
fptr2 ist ein Zeiger auf eine Funktion, die zwei Argumente
vom Typ int übernimmt und einen String (Zeiger auf char)
zurückliefert.
Die häufigste Anwendung von Funktionszeigern besteht darin, dass
man mit ihrer Hilfe Funktionen als Argumente an andere
Funktionen übergeben kann.
25 / 41
Zeiger auf Funktionen: Beispiel
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<s t d i o . h>
#include<math . h>
void zeige_fwert ( double x , double ( ∗ funk ) ( double ) ) ;
double SQR ( double x ) ;
void zeige_fwert ( double x , double ( ∗ funk ) ( double ) ) {
printf ( "Wert fur x= %e : %.15e\n" , x , ( ∗ funk ) ( x ) ) ;
}
double SQR ( double x ) {
return x∗x ;
}
int main ( ) {
zeige_fwert ( 2 . 0 , & SQR ) ;
zeige_fwert ( 2 . 0 , & sin ) ;
return 0 ;
}
26 / 41
Zeiger auf Funktionen: Bemerkung
Bemerkung
Bei vielen C-Compilern (auch bei GCC) verhalten sich
Funktionszeiger wie Feldbezeichner. Ein Funktionsname ist
gleichzeitig auch Zeiger auf die Funktion. Der C-Standard stellt
dies den Compilerautoren frei. Im Zweifel sollte man jedoch streng
zwischen Funktionsnamen und -zeigern unterscheiden.
27 / 41
Zeiger auf Funktionen: Bemerkung
Der Funktionsname allein – ohne ”()” – steht für die Adresse der
Funktion im Code-Teil des Programms:
Funktionsname == Zeiger auf Funktionsanfang
Im Falle der Deklarationen (Prototyp-Angaben)
double sin(double x);
int
f(double x, char c);
können die Aufrufe
x = sin(3.5);
k = f(2.9, ’a’);
auch durch
x = (∗sin)(3.5);
k = (∗f)(2.9, ’a’);
ersetzt werden (und umgekehrt).
28 / 41
Zeiger auf Funktionen: Beispiel
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<s t d i o . h>
#include<math . h>
void
zeige_fwert ( double x , double ( ∗ funk ) ( double ) ) ;
double SQR ( double x ) ;
void zeige_fwert ( double x , double ( ∗ funk ) ( double ) ) {
printf ( "Wert fur x= %e : %.15e\n" , x , funk ( x ) ) ;
}
double SQR ( double x ) {
return x∗x ;
}
int main ( ) {
zeige_fwert ( 2 . 0 , SQR ) ;
zeige_fwert ( 2 . 0 , sin ) ;
return 0 ;
}
29 / 41
Bibliotheksfunktionen zum Sortieren und Suchen
void qsort(void ∗anfang, size_t laenge, size_t groesse,
int (∗verglfunkzgr)(const void ∗, const void ∗));
Die Funktion ist in <stdlib.h> deklariert und ist für beliebige
Datentypen einsetzbar.
qsort sortiert ein Feld ab der Stelle, auf die anfang zeigt, in
aufsteigender Reihenfolge und führt die Sortierung für laenge
Einträge durch, deren Datengröße jeweils groesse Bytes
beträgt.
Der Größenvergleich wird mit Hilfe der Funktion durchgeführt,
auf die verglfunkzgr zeigt. Der Rückgabewert der
Vergleichsfunktion muss sich wie folgt verhalten: er ist
negativ, wenn das erste Argument kleiner als das zweite ist,
0, wenn beide Argumente gleich sind,
positiv, wenn das erste Argument größer als das zweite ist.
30 / 41
Bibliotheksfunktionen zum Sortieren und Suchen
void ∗bsearch(const void ∗wert, const void ∗anfang,
size_t laenge, size_t groesse,
int (∗verglfunkzgr)(const void ∗, const void ∗));
Die Funktion ist in <stdlib.h> deklariert und ist für beliebige
Datentypen einsetzbar.
bsearch sucht in einem aufsteigend sortierten Feld nach dem
Wert, auf den wert zeigt. Die Suche beginnt ab der Stelle im
Feld, auf die anfang zeigt und umfasst laenge Einträge ,
deren Datengröße jeweils groesse Bytes beträgt.
Auch hier wird zum Vergleich die Funktion verwendet, auf die
verglfunkzgr zeigt. Für deren Rückgabewert gilt natürlich
das bereits zur Funktion qsort Gesagte.
bsearch gibt einen Zeiger auf einen passenden Feldelement
oder NULL, wenn keine Übereinstimmung gefunden wird.
31 / 41
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<s t d i o . h>
#include<math . h>
#include< s t d l i b . h>
int intvergl ( const void ∗ a , const void ∗ b ) {
int A , B ;
A =∗(( int ∗ ) a ) ;
B =∗(( int ∗ ) b ) ;
return A − B ;
}
int main ( void ) {
int i , laenge =6;
int feld [ ] = {3 , 1 , 7 , 4 , 8 , 2 } ;
printf ( "Vor Sortieren :\n" ) ;
for ( i=0;i<laenge ; i++) printf ( " %i" , feld [ i ] ) ;
printf ( "\n" ) ;
qsort ( feld , laenge , sizeof ( int ) , intvergl ) ;
printf ( "Nach Sortieren :\n" ) ;
for ( i=0;i<laenge ; i++) printf ( " %i" , feld [ i ] ) ;
printf ( "\n" ) ;
return 0 ;
}
32 / 41
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
...
...
int main ( void ) {
int i , laenge =6, ∗wert , ∗ pos ;
int feld [ ] = {3 , 1 , 7 , 4 , 8 , 2 } ;
printf ( "Vor Sortieren :\n" ) ;
for ( i=0;i<laenge ; i++) printf ( " %i" , feld [ i ] ) ;
printf ( "\n" ) ;
qsort ( feld , laenge , sizeof ( int ) , intvergl ) ;
printf ( "Nach Sortieren :\n" ) ;
for ( i=0;i<laenge ; i++) printf ( " %i" , feld [ i ] ) ;
printf ( "\n" ) ;
wert=&i ;
printf ( " Geben Sie eine ganze Zahl ein:" ) ;
scanf ( "%i" , wert ) ;
pos=bsearch ( wert , feld , laenge , sizeof ( int ) , intvergl ) ;
if ( pos==NULL )
printf ( "%i nicht gefunden .\n " , ∗ wert ) ;
else
printf ( "%i an %i. Stelle im Feld .\n " ,
∗ wert , pos−feld +1);
return 0 ;
33 / 41
}
Nichttriviale Deklarationen
Wir entwickeln eine nichttriviale Deklaration Schritt für Schritt:
1
char fzgr;
fzgr ist ein Zeichen.
2
char ∗fzgr;
fzgr ist ein Zeiger auf ein Zeichen, also ein String.
3
char ∗∗fzgr;
fzgr ist ein Doppelzeiger auf ein Zeichen, also ein Zeiger auf
einen String.
4
char ∗∗fzgr(char ∗, const char ∗);
fzgr ist eine Funktion, die zwei Stringargumente
entgegennimmt (wobei das zweite Argument geschützt ist)
und einen Zeiger auf einen String zurückliefert.
5
char ∗(∗fzgr)(char ∗, const char ∗);
fzgr ist ein Zeiger auf eine Funktion, die zwei
Stringargumente entgegennimmt (wobei das zweite Argument
geschützt ist) und einen String zurückliefert.
34 / 41
Nichttriviale Deklarationen
Wir entwickeln eine nichttriviale Deklaration Schritt für Schritt:
4
char ∗∗fzgr(char ∗, const char ∗);
fzgr ist eine Funktion, die zwei Stringargumente
entgegennimmt (wobei das zweite Argument geschützt ist)
und einen Zeiger auf einen String zurückliefert.
5
char ∗(∗fzgr)(char ∗, const char ∗);
fzgr ist ein Zeiger auf eine Funktion, die zwei
Stringargumente entgegennimmt (wobei das zweite Argument
geschützt ist) und einen String zurückliefert.
6
char ∗ (∗(∗fzgr)(void))(char ∗, const char ∗);
fzgr ist ein Zeiger auf eine Funktion, die kein Argument
besitzt und einen Zeiger auf eine Funktion zurückliefert, die
wiederum zwei Stringargumente entgegennimmt (wobei das
zweite Argument geschützt ist) und einen String zurückliefert.
35 / 41
Deklarationen lesen mit der ”Schneckenmethode”
Um kompliziertere Deklarationen bzw. Typvereinbarungen zu
verstehen, ist die so genannte Schnecken- oder Spiralmethode sehr
hilfreich:
Beginnend mit dem Bezeichner des Datenobjekts bzw. -typs
arbeitet man sich spiralförmig gegen den Uhrzeigersinn von
innen nach außen.
Entscheidend für das Gelingen ist, dass man zu Beginn darauf
achtet, ob unmittelbar rechts vom Bezeichner ein
Klammernpaar steht, denn diese haben ja Vorrang vor *. Ist
dies der Fall, so beginnt die Schnecke nach unten rechts.
Ist der Bezeichner mit einem * in Klammern
zusammengefasst, so handelt es sich um einen Zeiger und die
Schnecke beginnt nach oben links.
36 / 41
Deklarationen lesen mit der ”Schneckenmethode”
37 / 41
Mehrere Quelldateien
C sieht vor, dass der Quelltext eines Programms aus mehreren
Dateien bestehen darf. Es sprechen u. a. folgende Gründe für die
Aufteilung des Quelltextes auf mehrere Dateien:
Übersichtlichkeit: Jeder Quelltextbaustein (z.B. die
Definition einer Funktion) wird in einer eigenen Datei
gehalten. Diese wird in den allermeisten Fällen recht kurz sein.
Mehrköpfige Entwicklungsteams möglich: Jede/r
Entwickler/in übernimmt die Implementierung eines oder
mehrerer Bausteine des Programms.
Wiederverwendbarkeit: Einzelne Quelltextbausteine können
– sofern sie hinreichend allgemein programmiert sind – in
anderen Projekten ohne Veränderung eingesetzt werden.
Leichte Wartung: An einzelnen Quelltextdateien können
Verbesserungen und Korrekturen vorgenommen werden, ohne
dass andere Dateien hiervon betroffen sind.
38 / 41
Mehrere Quelldateien: Allgemeine Aspekte
Eine wichtige Regel für die Verteilung des Quellcodes vorweg:
Nur eine Quelltextdatei darf main enthalten.
Damit ein lauffähiges Programm entsteht, muss eine
Quelltextdatei main enthalten.
Eine Funktion kann sich nicht über mehrere Dateien
erstrecken.
Ansonsten ist man hinsichtlich der Verteilung des Quelltextes
weitgehend frei. Es empfiehlt sich jedoch, die folgenden Richtlinien
zu befolgen:
Deklarationen von Funktionen und globalen Variablen fasst
man in eigenen Headerdateien (Dateiendung .h) zusammen.
Definitionen von Funktionen erfolgen in eigenen
Quelltextdateien mit Endung .c. Dabei können mehrere kurze
Funktionsdefinitionen in einer Datei erfolgen.
39 / 41
Mehrere Quelldateien: Allgemeine Aspekte
Die Übersetzung erfolgt in zwei Stufen:
1
Die einzelnen C-Quelltextdateien werden in einem ersten
Schritt jeweils mit
gcc −c dateiname.c
in Objektdateien übersetzt (somit wird dateiname.o
erzeugt).
2
Anschließend folgt das Linken zu einem ausführbaren
Programm, wobei ggf. erforderliche Bibliotheken
berücksichtigt werden, die jeweils mit einer eigenen -l-Option
angegeben werden:
gcc −o Programmname Liste der Objektdateien [ −lBibliothek]
40 / 41
Mehrere Quelldateien: Beispiel
f_null.c
#include<stdio.h>
#include"f_null.h"
double finde_nullstelle(
double (*func)(double),
double a, double b,
double eps) {
...
return nstelle;
}
f_null.h
double finde_nullstelle(
double (*func)(double),
double a, double b,
double eps);
prog.c
#include<stdio.h>
#include"f_null.h"
double fn(double y) {
return y*y−0.5;
}
int main(void) {
...
x=finde_nullstelle(fn,
0,1,1e−6);
...
return 0;
}
gcc −c f_null.c
ergibt f_null.o
gcc −c prog.c
ergibt prog.o
gcc −o prog prog.o f_null.o −lm
ergibt prog
41 / 41
Zugehörige Unterlagen
Herunterladen