Einführung in das Programmieren für Technische Mathematik¶
C & C++¶
- Perfekt für Anwendungen in denen Performance gefragt ist
- Lehrt die inneren Vorgänge im Computer
- Erlaubt Abstraktion
Programmieren für Mathematiker¶
- Das ist keine Programmierkurs der Informatik
- Wenig Softwaredesign
- Wenig Debugging
- Wenige Algorithmen
Wichtige Infos zu EPROG¶
Info-Post im TUWEL-Forum
- Lernziele
- Regeln & Pflichten
- Benotungsschema
- Evaluation & Notenspiegel
TUWEL
- Download der Folien & Übungsblätter
- freiwilliges UE-Material (alte Tests!)
- Termine
- Zoom-Links
Literatur¶
- VO-Folien aus letzen Semestern auf TUWEL
- aktuelle Folien wöchentlich auf TUWEL
formal keine weitere Literatur nötig
mehrere Bücher zum Download auf TUWEL, z.B.
- Ralf Kirsch, Uwe Schmitt Programmieren in C, eine mathematikorientierte Einführung
- Klaus Schmaranz Softwareentwicklung in C / C++
- Online: google.com, stackoverflow.com
Programm¶
Wikipedia: Ein Computerprogramm oder kurz Programm ist eine Folge von Anweisungen, die den Regeln einer Programmiersprache genügen, um auf einem Computer eine bestimmte Funktionalität, Aufgaben- oder Problemstellung bearbeiten oder lösen zu können.
Anweisungen = Deklarationen und Instruktionen
- Deklaration = z.B. Definition von Variablen
- Instruktion = "tue etwas"
BSP: Sei
- BSP: Definiere ,
- BSP: suche einen Telefonbucheintrag
- BSP: berechne den Wert eines Integrals
Algorithmus¶
Wikipedia: Ein Algorithmus ist eine aus endlich vielen Schritten bestehende, eindeutige und ausführbare Handlungsvorschrift zur Lösung eines Problems oder einer Klasse von Problemen.
BSP: Berechne die Lösung eines linearen Gleichungssystems mittels Gauß-Elimination
BSP: Berechne die Nullstelle eines quadratischen Polynoms mittels --Formel
IdR. unendlich viele Algorithmen für ein Problem
IdR. sind Algorithmen unterschiedlich "gut"
- Was "gut" genau heißt erfahren wir später
Programmiersprachen¶
- Grobe Unterscheidung in Interpreter- und Compiler-basierte Sprachen
- Interpreter führt Source-Code zeilenweise
bei der Übersetzung aus
- d.h. Übersetzen & Ausführen ist gleichzeitig
- z.B. Matlab, Java, PHP, Python
- Compiler übersetzt Source-Code in ein
ausführbares Programm (Executable)
- Executable ist eigenständiges Programm
- d.h. (1) Übersetzen, dann (2) Ausführen
- z.B. C, C++, Fortran
- bessere Unterscheidung (siehe Schmaranz)
- imperative Sprachen, z.B. Matlab, C, Fortran
- objektorientierte, z.B. C++, Java, Python
- funktionale, z.B. Lisp
Achtung¶
- C ist Compiler-basierte Programmiersprache
Compilierter Code ist systemabhängig,
- d.h. Code läuft idR. nur auf dem System, auf dem er compiliert wurde
Source-Code ist systemunabhängig,
- d.h. er kann auch auf anderen Systemen compiliert werden.
- C-Compiler unterscheiden sich leicht
- C ist eine sehr alte Sprache (erschienen 1972)
- vielfach weiterentwickelt
- in der VO: ANSI-C = C89 (Standard von 1989)
Wie erstellt man ein C-Programm?¶
- Öffne eine (ggf. neue) Datei
name.c
in einem Editor- Endung
.c
ist Kennung eines C-Programms
- Endung
- Schreibe den sog. Source-Code (= C-Programm)
- Source-Code abspeichern
- Compilieren z.B. mit Eingabe
gcc name.c
in der Shell - Falls Code fehlerfrei, erhält man Executable
a.out
unter Windows:a.exe
- Diese wird durch
a.out
bzw../a.out
gestartet - Compilieren mit
gcc name.c -o output
erzeugt Executableoutput
statta.out
Das erste C-Programm¶
- Zeilennummern und Farben gehören nicht zum Code (sind lediglich Orientierungshilfe auf den Folien)
- Jedes C-Programm besitzt die Zeilen 3 und 6.
- Die Ausführung eines C-Programms startet immer
bei
main()
- egal, womain()
im Code steht - Klammern {...} schließen in C sog. Blöcke ein
1 2 3 4 5 6 | #include <stdio.h>
int main(){
printf("Hello World\n");
return 0;
}
|
Hello World
Das erste C-Programm¶
- Hauptprogramm
main()
bildet immer einen Block - Logische Programmzeilen enden mit Semikolon, vgl. Zeile 4 & 5
printf
gibt Text aus (in Anführungszeichen),\n
macht einen Zeilenumbruch
- Anführungszeichen müssen in derselben Zeile sein
- Zeile 1: Einbinden der Standardbibliothek für Input-Output (später mehr!)
1 2 3 4 5 6 | #include <stdio.h>
int main(){
printf("Hello World!\n");
return 0;
}
|
Hello World!
Das erste C-Programm¶
Achtung¶
- Viele Compiler erlauben das Weglassen von
int
undreturn 0;
- Gut zu wissen falls Sie fremden Code verwenden
1 2 3 4 5 | #include <stdio.h>
main(){
printf("Hello World!\n");
}
|
/tmp/tmpexq2n71l.c: In function ‘main’: /tmp/tmpexq2n71l.c:6:7: error: expected ‘;’ before ‘{’ token 6 | main(){ | ^ | ; [C kernel] GCC exited with code 1, the executable will not be executed
Fehler¶
Syntaxfehler¶
- Syntax = Wortschatz (Befehle) & Grammatik einer Sprache (Was man wie verbinden kann...)
- Syntaxfehler = Falsche Befehle oder Verwendung
- Compiler gibt Fehlermeldung
Laufzeitfehler¶
- Fehler, der erst bei Programm-Ausführung auftritt
- erst bei komplexeren Programmen möglich
- viel schwerer zu finden
- durch sorgfältiges Arbeiten möglichst vermeiden
Beispiel für Syntaxfehler¶
Der Kompiler kann die Funktion printf
nicht finden, da die Bibiliothek stdio.h
nicht eingebunden wurde.
1 2 3 4 | int main(){
printf("Hello World!\n");
return 0;
}
|
/tmp/tmpzwdt271q.c: In function ‘main’: /tmp/tmpzwdt271q.c:2:5: warning: implicit declaration of function ‘printf’ [-Wimplicit-function-declaration] 2 | printf("Hello World!\n"); | ^~~~~~ /tmp/tmpzwdt271q.c:2:5: warning: incompatible implicit declaration of built-in function ‘printf’ /tmp/tmpzwdt271q.c:1:1: note: include ‘<stdio.h>’ or provide a declaration of ‘printf’ +++ |+#include <stdio.h> 1 | int main(){
Hello World!
Beispiel für Syntaxfehler¶
Der Kompiler beschwert sich über das fehlende Semikolon ;
in Zeile 4.
1 2 3 4 5 6 | #include <stdio.h>
int main(){
printf("Hello World!\n")
return 0;
}
|
Hello World!
Die ersten Sprachelemente¶
Variablen¶
- Variable = symbolischer Name für Speicherbereich
- Variable in Math. und Informatik verschieden:
- Mathematik: Sei fixiert
- Informatik:
x = 5
weist x den Wert zu, Zuweisung kann jederzeit geändert werden z.B.x = 7
Variablen-Namen¶
bestehen aus Zeichen, Ziffern und Underscore _
- maximale Länge = 31
- erstes Zeichen darf keine Ziffer sein
Klein- und Großschreibung wird unterschieden
- d.h. Var, var, VAR sind 3 verschiedene Variablen
- Konvention: Namen sind
klein_mit_underscores
Datentypen¶
Bevor man Variable benutzen darf, muss man idR. erklären, welchen Typ Variable haben soll
Elementare Datentypen:
- Gleitkommazahlen (ersetzt , ), z.B.
double
- Integer, Ganzzahlen (ersetzt , ), z.B.
int
- Zeichen (Buchstaben)
char
- Gleitkommazahlen (ersetzt , ), z.B.
int x;
deklariert Variablex
vom Typint
Deklaration¶
- Deklaration = das Anlegen einer Variable
- d.h. Zuweisung von Speicherbereich auf einen
symbolischen Namen & Angabe des Datentyps
- Zeile
int x;
deklariert Variablex
vom Typint
- Zeile
double var;
deklariertvar
vom Typdouble
- Zeile
- d.h. Zuweisung von Speicherbereich auf einen
symbolischen Namen & Angabe des Datentyps
1 2 | int x;
double var;
|
Initialisierung¶
- Durch Deklaration einer Variablen wird lediglich Speicherbereich zugewiesen
- Falls noch kein konkreter Wert zugewiesen:
- Wert einer Variable ist zufällig
- Deshalb direkt nach Deklaration der neuen
Variable Wert zuweisen, sog. Initialisierung
int x;
(Deklaration)x = 0;
(Initialisierung)
- Deklaration & Initialisierung auch in einer Zeile
möglich:
int x = 0;
1 2 | int x = 0;
double var=3.1415;
|
Einbinden der Input-Output-Funktionen (Zeile 1)
printf
gibt Text (oder Wert einer Var.) ausscanf
liest Tastatureingabe ein in eine Variable
Prozentzeichen
%
in Zeile 7/8 leitet Platzhalter ein
1 2 3 4 5 6 7 8 9 | #include <stdio.h>
int main() {
int x = 0;
printf("Input: x=");
scanf("%d",&x);
printf("Output: x=%d\n",x);
}
|
Input: x=4 Output: x=4, 12
Es gibt verschiedene Platzhalter für verschiedene Datentypen.
Datentyp | Platzhalter printf |
Platzhalter scanf |
---|---|---|
int |
%d |
%d |
double |
%f |
%lf |
char |
%c |
%c |
1 2 3 4 5 6 7 8 9 | #include <stdio.h>
int main() {
double x = 0;
printf("Input: x=");
scanf("%lf",&x);
printf("Output: x=%f\n",x);
}
|
Input: x=3.1234 Output: x=3.123400
Beachte
&
beiscanf
in Zeile 7scanf("%d", & x)
- aber:
printf("%d", x)
Wenn man
&
vergisst Laufzeitfehler- Compiler merkt Fehler nicht (kein Syntaxfehler!)
- Sorgfältig arbeiten!
1 2 3 4 5 6 7 8 9 | #include <stdio.h>
int main() {
int x = 0;
printf("Input: x=");
scanf("%d",x);
printf("Output: x=%d\n",x);
}
|
Input: x=3
[C kernel] Executable exited with code -11
Achtung bei double
nicht %lf
vergessen!
1 2 3 4 5 6 7 8 9 | #include <stdio.h>
int main() {
double x = 0;
printf("Input: x=");
scanf("%f",&x);
printf("Output: x=%f\n",x);
}
|
Input: x=3.1415 Output: x=3.141500
Zuweisungsoperator =¶
Das einfache Gleich
=
ist Zuweisungsoperator- Zuweisung immer rechts nach links!
Zeile
x = 1;
weist den Wert auf der rechten Seite der Variablenx
zuZeile
x = y;
weist den Wert der Variableny
der Variablenx
zu- insb. haben
x
undy
danach denselben Wert - d.h. Vertauschen der Werte nur mit Hilfsvariable
- insb. haben
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #include <stdio.h>
int main() {
int x = 1;
int y = 2;
int tmp = 0;
printf("a) x=%d, y=%d, tmp=%d\n",x,y,tmp);
tmp = x;
x = y;
y = tmp;
printf("b) x=%d, y=%d, tmp=%d\n",x,y,tmp);
}
|
a) x=1, y=2, tmp=0 b) x=2, y=1, tmp=1
Arithmetische Operatoren¶
Bedeutung eines Operators kann vom Datentyp abhängen!
Operatoren auf Ganzzahlen:
a=b
,-a
(Vorzeichen)a+b
,a-b
,a*b
,a/b
(Division ohne Rest),a%b
(Divisionsrest)a+=b
etc. gleichbedeutend mita=a+b
Operatoren auf Gleitkommazahlen:
a=b
,-a
(Vorzeichen)a+b
,a-b
,a*b
,a/b
("normale" Division)a+=b
etc. gleichbedeutend mita=a+b
Arithmetische Operatoren¶
Achtung:
2/3
ist Ganzzahl-Division, also Null!Notation für Gleitkommazahlen:
- Vorzeichen
-
, falls negativ - Vorkommastellen
- Dezimalpunkt
- Nachkommastellen
e
oderE
mit ganzzahligem Exponenten (10er Potenz!), z.B.- Wegfallen darf entweder Vor- oder Nach- kommastelle (sonst sinnlos, da kein Wert!)
- Wegfallen darf entweder Dezimalpunkt oder
e
bzw.E
mit Exponent (sonst wird die Zahl als Integer verstanden, vgl.2/3
)
- Vorzeichen
Also:
2./3.
ist Gleitkommadivision (beidouble
ca. auf 16 Stellen genau)
Type Casting¶
- Operatoren können auch Variablen verschiedener Datentypen verbinden
Vor der Ausführung werden beide Variablen auf denselben Datentyp gebracht (Type Casting)
Welchen Datentyp hat
x+y
in Zeile 7, 8?- Den mächtigeren Datentyp, also
double
! - so wie , so ist
double
mächtiger alsint
- Type Casting von Wert
x
aufdouble
- Den mächtigeren Datentyp, also
- Zeile 7: Type Casting, da
double
aufint
Zuweisung- durch Abschneiden, nicht durch Rundung!
1 2 3 4 5 6 7 8 9 10 11 12 | #include <stdio.h>
int main() {
int x = 1;
double y = 2.8;
int sum_int = x+y;
double sum_dbl = x+y;
printf("sum_int = %d\n",sum_int);
printf("sum_dbl = %f\n",sum_dbl);
}
|
sum_int = 3 sum_dbl = 3.800000
Implizites Typecasting¶
Warum Ergebnis in a) und d) ?
2
,3
sindint
2/3
ist Ganzzahl-Division- Werden Variablen verschiedenen Typs durch arith. Operator verbunden, Type Casting auf "gemeinsamen" (mächtigeren) Datentyp
- vgl. Zeile 5, 13, 14
2
istint
,3.
istdouble
2/3.
ergibtdouble
- Der C-Compiler setzt die kanonischen Regeln um:
- Punkt-Rechnung vor Strich-Rechnung
- gleichwertige Operatoren von links nach rechts
- vgl. Zeile 13, 14
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #include <stdio.h>
int main() {
double dbl1 = 2 / 3;
double dbl2 = 2 / 3.;
double dbl3 = 1E2;
int int1 = 2;
int int2 = 3;
printf("a) %f\n",dbl1);
printf("b) %f\n",dbl2);
printf("c) %f\n",dbl3 * int1 / int2);
printf("d) %f\n",dbl3 * (int1 / int2) );
}
|
a) 0.000000 b) 0.666667 c) 66.666667 d) 0.000000
Explizites Type Casting¶
- Kann dem Compiler mitteilen, in welcher Form eine Variable interpretiert
werden muss
- Dazu Ziel-Typ in Klammern voranstellen!
- In Zeile 7, 8, 9: Explizites Type Casting
(jeweils von
int
zudouble
) - In Zeile 6, 8, 9: Implizites Type Casting
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #include <stdio.h>
int main() {
int a = 2;
int b = 3;
double dbl1 = a / b;
double dbl2 = (double) (a / b);
double dbl3 = (double) a / b;
double dbl4 = a / (double) b;
printf("a) %f\n",dbl1);
printf("b) %f\n",dbl2);
printf("c) %f\n",dbl3);
printf("d) %f\n",dbl4);
}
|
a) 0.000000 b) 0.000000 c) 0.666667 d) 0.666667
Fehlerquelle beim Type Casting¶
- Implizites Type Casting sollte man vermeiden!
- d.h. Explizites Type Casting verwenden!
- Bei Rechnungen Zwischenergebnisse in richtigen Typen speichern!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include <stdio.h>
int main() {
int a = 2;
int b = 3;
double dbl = (double) a / b;
int i = dbl;
printf("a) %f\n",dbl);
printf("b) %f\n",dbl*b);
printf("c) %d\n",i);
printf("d) %d\n",i*b);
}
|
a) 0.666667 b) 2.000000 c) 0 d) 0
Einfache Verzweigung¶
Logische Operatoren¶
- Es seien
a,b
zwei Variablen (auch versch. Typs!)- Vergleich (z.B.
a < b
) liefert Wert1
, falls wahr, bzw.0
, falls falsch
- Vergleich (z.B.
Übersicht über Vergleichsoperatoren:
Symbol Bedeutung ==
Gleichheit (ACHTUNG mit Zuweisung!) !=
Ungleichheit >
echt größer <
echt kleiner >=
größer oder gleich <=
kleiner oder gleich Bei Vergleichen stets Klammern setzen (häufig unnötig aber oft besser lesbar)
Weitere logische Operatoren
Symbol | Bedeutung |
---|---|
! |
Nicht |
&& |
und |
|| |
oder |
Logische Verkettung¶
- Warum ist Aussage in Zeile 10 falsch, aber in Zeile 13 wahr?
- Auswertung von links nach rechts:
a > b
ist wahr, also mit1
bewertet1 > c
ist falsch, also mit0
bewertet- Also wird
a > b > c
mit falsch bewertet! - Aussage in 10 ist also nicht korrekt formuliert!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #include <stdio.h>
int main() {
int result = 0;
int a = 3;
int b = 2;
int c = 1;
result = (a > b > c);
printf("a) result=%d\n",result);
result = (a > b) && (b > c);
printf("b) result=%d\n",result);
}
|
/tmp/tmpl7rt034j.c: In function ‘main’: /tmp/tmpl7rt034j.c:10:15: warning: comparisons like ‘X<=Y<=Z’ do not have their mathematical meaning [-Wparentheses] 10 | result = (a > b > c); | ~~^~~
a) result=0 b) result=1
if-else¶
- einfache Verzweigung: Wenn - Dann - Sonst
if (
condition)
statementAelse
statementB- nach
if
steht Bedingung stets in runden Klammern - nach Bedingung steht nie Semikolon
- Bedingung ist falsch, falls sie ist bzw. mit bewertet
wird, sonst ist die Bedingung wahr
- Bedingung wahr statementA wird ausgeführt
- Bedingung falsch statementB wird ausgeführt
- Statement ist
- entweder eine Zeile (d.h. bis zum Semikolon!)
- oder mehrere Zeilen in geschwungenen
Klammern
{ ... }
, sog. Block
else
-Zweig ist optional- d.h.
else statementB
darf entfallen
- d.h.
Beispiel zu if¶
- abhängige Zeilen einrücken (Lesbarkeit!)
- WARNUNG: Nicht-Verwendung von Blöcken
{...}
ist fehleranfällig (Zeile 9--10)- besser: Block (analog zu Zeile 12--14)
- könnte zusätzlich
else
in Zeile 11 schreiben- da
if
's sich ausschließen
- da
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #include <stdio.h>
int main() {
int x = 0;
printf("Input x=");
scanf("%d",&x);
if (x < 0)
printf("x=%d is negative\n",x);
if (x > 0) {
printf("x=%d is positive\n",x);
}
}
|
Input x=3 x=3 is positive
Beispiel zu if-else¶
- Eine Bedingung ist wahr, falls Wert
- z.B. Zeile 15, aber besser:
if ( var2!= 0)
- z.B. Zeile 15, aber besser:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #include <stdio.h>
int main() {
int var1 = -5;
double var2 = 1e-32;
int var3 = 5;
if (var1 >= 0) {
printf("var1 >= 0\n");
}
else {
printf("var1 < 0\n");
}
if (var2) {
printf("var2 != 0, i.e., cond. is true\n");
}
else {
printf("var2 == 0, i.e., cond. is false\n");
}
if ( (var1 < var2) && (var2 < var3) ) {
printf("var2 lies between the others\n");
}
}
|
var1 < 0 var2 != 0, i.e., cond. is true var2 lies between the others
Gerade oder Ungerade?¶
- Programm überprüft, ob eingegebene Zahl x gerade Zahl ist oder nicht
- Man kann Verzweigungen schachteln:
- Einrückungen machen Code übersichtlicher
- formal nicht notwendig, aber trotzdem!
- Abhängigkeiten werden verdeutlicht
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #include <stdio.h>
int main() {
int x = 0;
printf("Input x=");
scanf("%d",&x);
if (x > 0) {
if (x%2 != 0) {
printf("x=%d is odd\n",x);
}
else {
printf("x=%d is even\n",x);
}
}
else {
printf("Error: Input has to be positive!\n");
}
}
|
Input x=3 x=3 is odd
Zwei Zahlen aufsteigend sortieren¶
- Eingabe von zwei Zahlen Zahlen werden aufsteigend sortiert
- ggf. vertauscht
- Ergebnis wird ausgegeben
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #include <stdio.h>
int main() {
double x1 = 0;
double x2 = 0;
double tmp = 0;
printf("Unsorted input:\n");
printf(" x1=");
scanf("%lf",&x1);
printf(" x2=");
scanf("%lf",&x2);
if (x1 > x2) {
tmp = x1;
x1 = x2;
x2 = tmp;
}
printf("Sorted output (ascending order):\n");
printf(" x1=%f\n",x1);
printf(" x2=%f\n",x2);
}
|
Unsorted input: x1=3 x2=1 Sorted output (ascending order): x1=1.000000 x2=3.000000
Innen oder Außen?¶
- liegt im Kreis um mit Radius , falls
- auf Kreisrand, falls
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | #include <stdio.h>
int main() {
double r = 0;
double x1 = 0;
double x2 = 0;
double z1 = 0;
double z2 = 0;
double dist2 = 0;
printf("Radius of the circle: r=");
scanf("%lf",&r);
printf("Center of the circle: x = (x1,x2)\n");
printf(" x1=");
scanf("%lf",&x1);
printf(" x2=");
scanf("%lf",&x2);
printf("Point in the plane: z = (z1,z2)\n");
printf(" z1=");
scanf("%lf",&z1);
printf(" z2=");
scanf("%lf",&z2);
dist2 = (x1-z1)*(x1-z1) + (x2-z2)*(x2-z2);
if ( dist2 < r*r ) {
printf("z is inside the circle\n");
}
else {
if ( dist2 > r*r ) {
printf("z is outside of the circle\n");
}
else {
printf("z lies on the boundary of the circle\n");
}
}
}
|
Radius of the circle: r=3 Center of the circle: x = (x1,x2) x1=0 x2=0 Point in the plane: z = (z1,z2) z1=1 z2=2 z is inside the circle
Gleichheit vs. Zuweisung¶
- Nur Erinnerung:
if (a==b)
vs.if (a=b)
- beides ist syntaktisch korrekt!
if (a==b)
ist Abfrage auf Gleichheit- ist vermutlich so gewollt...
- ABER:
if (a=b)
- weist
a
den Wert vonb
zu - dann Abfrage, ob
- weist
- Letzteres ist schlechter Programmierstil!
- da zwei Aktionen in einer Programmzeile
Strukturierung mit Blöcken¶
Lifetime & Scope¶
Lifetime einer Variable = Zeitraum, in dem Speicherplatz zugewiesen ist = Zeitraum, in dem Variable existiert
Scope einer Variable = Zeitraum, in dem Variable sichtbar ist = Zeitraum, in dem Variable gelesen/verändert werden kann
Scope Lifetime
Globale & Lokale Variablen¶
globale Variablen = Variablen, die globale Lifetime haben (bis Programm terminiert)
- eventuell lokaler Scope
- werden am Anfang außerhalb von main deklariert
lokale Variablen = Variablen, die nur lokale Lifetime haben
- Konvention: erkenne Variable am Namen
- lokale Variablen sind
klein_mit_underscores
- globale Var. haben
auch_underscore_hinten_
- lokale Variablen sind
Blöcke¶
Blöcke stehen innerhalb von
{...}
Jeder Block startet mit Deklaration zusätzlich benötigter Variablen
- Variablen sollten nur am Anfang eines Blocks deklariert werden
Die innerhalb des Blocks deklarierten Variablen werden nach Blockende vergessen (= gelöscht)
- d.h. Lifetime endet
- lokale Variablen
Schachtelung
{..{..}..}
- beliebige Schachtelung ist möglich
- Variablen aus äußerem Block können im inneren Block gelesen und verändert werden, umgekehrt nicht. Änderungen bleiben wirksam.
- d.h. Lifetime & Scope nur nach Innen vererbt
- Wird im äußeren und im inneren Block Variable
var
deklariert, so wird das "äußere"var
überdeckt und ist erst wieder ansprechbar (mit gleichem Wert wie vorher), wenn der innere Block beendet wird.- d.h. äußeres
var
ist nicht im inneren Scope - Das ist schlechter Programmierstil!
- siehe die Beispiele auf den nächsten 2 Folien!
- d.h. äußeres
Einfaches Beispiel¶
- zwei verschiedene lokale Variablen
x
- Deklaration + Initialisierung (Zeile 4, 9)
- unterscheide von Zuweisung (Zeile 6)
1 2 3 4 5 6 7 8 9 10 11 12 13 | #include <stdio.h>
int main() {
int x = 7;
printf("a) %d\n", x);
x = 9;
printf("b) %d\n", x);
{
int x = 17;
printf("c) %d\n", x);
}
printf("d) %d\n", x);
}
|
a) 7 b) 9 c) 17 d) 9
Komplizierteres Beispiel¶
- zwei Variablen mit Name
var0
(Zeile 3 + 18)- Namenskonvention absichtlich verletzt
- zwei Variablen mit Name
var1
(Zeile 6 + 11)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #include <stdio.h>
int var0 = 5;
int main() {
int var1 = 7;
int var2 = 9;
printf("a) %d, %d, %d\n", var0, var1, var2);
{
int var1 = 17;
printf("b) %d, %d, %d\n", var0, var1, var2);
var0 = 15;
var2 = 19;
printf("c) %d, %d, %d\n", var0, var1, var2);
{
int var0 = 25;
printf("d) %d, %d, %d\n", var0, var1, var2);
}
}
printf("e) %d, %d, %d\n", var0, var1, var2);
}
|
a) 5, 7, 9 b) 5, 17, 9 c) 15, 17, 19 d) 25, 17, 19 e) 15, 7, 19
Funktionen¶
Beispiel: Quadrieren¶
Compiler muss Funktion vor Aufruf kennen
- d.h. Funktion vor aufrufender Zeile definieren
Ausführung startet immer bei
main()
Die Variable
x
in Funktionsquare()
und die Variablex
in Funktionmain()
sind verschieden!
1 2 3 4 5 6 7 8 9 10 11 12 | #include <stdio.h>
double square(double x) {
return x*x;
}
int main() {
double x = 0;
printf("Input x = ");
scanf("%lf",&x);
printf("%f^2 = %f\n",x,square(x));
}
|
Input x = 3 3.000000^2 = 9.000000
Funktionen¶
Funktion = Zusammenfassung mehrerer Anweisungen zu einem aufrufbaren Ganzen
output = function(input)
- Eingabeparameter
input
- Ausgabeparameter (Return Value)
output
Warum Funktionen?
- Zerlegung eines großen Problems in überschaubare kleine Teilprobleme
- Strukturierung von Programmen (Abstraktionsebenen)
- Wiederverwertung von Programm-Code
Funktion besteht aus Signatur und Rumpf (Body)
- Signatur = Funktionsname & Eingabe-Ausgabeparameter
- Anzahl & Reihenfolge ist wichtig!
- Rumpf = Programmzeilen der Funktion
Namenskonvention¶
- lokale Variablen sind
klein_mit_underscores
- globale Var. haben
auch_underscore_hinten_
- Funktionen sind
erstesWortKleinKeineUnderscores
Funktionen in C¶
In C können Funktionen
- mehrere (oder keinen) Parameter übernehmen
- einen einzigen oder keinen Rückgabewert liefern
- Rückgabewert muss elementarer Datentyp sein
- z.B.
double
,int
Signatur hat folgenden Aufbau
<type of return value> <function name>(parameters)
- Funktion ohne Rückgabewert:
<type of return value>
=void
- Sonst:
<type of return value>
= Variablentyp parameters
= Liste der Übergabeparameter- getrennt durch Kommata
- vor jedem Parameter Variablentyp angeben
- kein Parameter leere Klammer
()
Funktionen in C¶
- Rumpf ist ein Block
- Rücksprung ins Hauptprogramm mit
return
oder bei Erreichen des Funktionsblock-Endes, falls Funktionstyp =void
- Rücksprung ins Hauptprogramm mit
- Rücksprung ins Hauptprogramm mit
return output
, falls die Variableoutput
zurückgegeben werden soll- Häufiger Fehler: return vergessen
- Dann Rückgabewert zufällig!
- Irgendwann Chaos (Laufzeitfehler!)
Variablen¶
Alle Variablen, die im Funktionsblock deklariert werden, sind lokale Variablen
Alle elementaren Variablen, die in Signatur deklariert werden, sind lokale Variablen
Funktion bekommt Input-Parameter als Werte, ggf. Type Casting!
Call by Value¶
- Dass bei Funktionsaufrufen Input-Parameter in
lokale Variablen kopiert werden, bezeichnet man
als Call by Value
- Es wird neuer Speicher angelegt, der Wert der Eingabe-Parameter wird in diese kopiert
Beispiel: Minimum zweier Zahlen¶
- Programm erfüllt Aufgabenstellung der UE:
- Funktion mit gewisser Funktionalität
- aufrufendes Hauptprogramm mit
- Daten einlesen
- Funktion aufrufen
- Ergebnis ausgeben
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #include <stdio.h>
double min(double x, double y) {
if (x > y) {
return y;
}
else {
return x;
}
}
int main() {
double x = 0;
double y = 0;
printf("Input x = ");
scanf("%lf",&x);
printf("Input y = ");
scanf("%lf",&y);
printf("min(x,y) = %f\n",min(x,y));
}
|
Input x = 3 Input y = 4 min(x,y) = 3.000000
Deklaration von Funktionen¶
Bei vielen Funktionen wird Code unübersichtlich
- Alle Funktionen oben deklarieren, vgl. Zeile 3
- Compiler weiß dann, wie Funktion agiert
- vollständiger Fkt.code folgt, vgl. Zeile 16-23
Alternative Deklaration = Fkt.code ohne Rumpf
double min(double x, double y);
vgl. Zeile 3, 16
in Literatur: Forward Declaration und Prototyp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #include <stdio.h>
double min(double, double);
int main() {
double x = 0;
double y = 0;
printf("Input x = ");
scanf("%lf",&x);
printf("Input y = ");
scanf("%lf",&y);
printf("min(x,y) = %f\n",min(x,y));
}
double min(double x, double y) {
if (x > y) {
return y;
}
else {
return x;
}
}
|
Input x = 3 Input y = 4 min(x,y) = 3.000000
Beispiel zu Call by Value¶
- Die Funktionsparameter werden auf lokale Variablen kopiert
- Später werden wir auch "Call by Reference" kennenlernen
- da wird tatsächlich die Variable selbst (oder eine Referenz auf die Variable) übergeben
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include <stdio.h>
void test(int x) {
printf("a) x=%d\n", x);
x = 43;
printf("b) x=%d\n", x);
}
int main() {
int x = 12;
printf("c) x=%d\n", x);
test(x);
printf("d) x=%d\n", x);
}
|
c) x=12 a) x=12 b) x=43 d) x=12
Type Casting & Call by Value¶
- Type Casting von
int
aufdouble
bei Übergabe
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #include <stdio.h>
double divide(double, double);
int main() {
int int1 = 2;
int int2 = 3;
printf("a) %f\n", int1 / int2 );
printf("b) %f\n", divide(int1,int2));
}
double divide(double dbl1, double dbl2) {
return(dbl1 / dbl2);
}
|
a) 0.000000 b) 0.666667
Type Casting (Negativbeispiel!)¶
- Eigentlich !
- Implizites Type Casting von
double
aufint
durch Abschneiden, denn Input-Parameter sindint
- Implizites Type Casting von
- Achtung mit Type Casting bei Funktionen!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #include <stdio.h>
int isEqual(int, int);
int main() {
double x = 4.1;
double y = 4.9;
if (isEqual(x,y)) {
printf("x == y\n");
}
else {
printf("x != y\n");
}
}
int isEqual(int x, int y) {
if (x == y) {
return 1;
}
else {
return 0;
}
}
|
x == y
Kommentare im Code¶
Kommentarzeilen¶
- werden vom Interpreter/Compiler ausgelassen
- nur für den Leser des Programmcodes
- notwendig, um eigene Programme auch später
noch zu begreifen
- deshalb brauchbar für Übung!
- notwendig, damit andere den Code verstehen
- soziale Komponente der Übung?
extrem brauchbar zum debuggen
- Teile des Source-Code "auskommentieren", sehen was passiert...
- vor allem bei Fehlermeldungen des Parser
Wichtige Regeln:
- nie dt. Sonderzeichen verwenden
- nicht zu viel und nicht zu wenig
- jetzt am Anfang mehr, später weniger!
- bei großen Projekten mit mehreren AutorInnen Name & letzte Änderung kommentieren
- vermeidet das Arbeiten an alten Versionen...
Kommentarzeilen in C¶
Gibt in C zwei Typen von Kommentaren:
- einzeiliger Kommentar
- eingeleitet durch
//
, geht bis Zeilen ende - z.B. Zeile 4
- stammt eigentlich aus C++
- eingeleitet durch
- mehrzeiliger Kommentar
- alles zwischen
/*
(Anfang) und*/
(Ende) - z.B. Zeile 6--9
- darf nicht geschachtelt werden!
- d.h.
/* ... /* ... */ ... */
ist Syntaxfehler
- alles zwischen
- einzeiliger Kommentar
Vorschlag
- Verwende
//
für echte Kommentare - Verwende
/*
...*/
zum Debuggen
- Verwende
1 2 3 4 5 6 7 8 9 10 11 12 | #include <stdio.h>
int main() {
// printf("1 ");
printf("2 ");
/*
printf("3");
printf("4");
*/
printf("5");
printf("\n");
}
|
2 5
Beispiel zu vernünftigem Kommentieren von Code¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | // Exercise 0.1 -- how to comment a program
// author: Michael Feischl
// last modified: 11.03.2020
#include <stdio.h>
double min(double, double);
int main() {
double x = 0;
double y = 0;
// read input x
printf("Input x = ");
scanf("%lf",&x);
// read input y
printf("Input y = ");
scanf("%lf",&y);
// determine and print minimum
printf("min(x,y) = %f\n",min(x,y));
}
// The function min determines the mininum of two
// double values x and y
double min(double x, double y) {
if (x > y) {
// if x > y, then min(x,y) = y
return y;
}
else {
// otherwise, x <= y and min(x,y) = x
return x;
}
}
|
Input x = 3 Input y = 4 min(x,y) = 3.000000
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | // author: Dirk Praetorius
// last modified: 15.03.2020
#include <stdio.h>
// The Euclidean algorithm to compute the greatest
// common divisor (gcd) is based on gcd(a,b) = gcd(a-b,b)
// for a>b and gcd(a,b) = gcd(b,a).
int euclid(int a, int b) {
int tmp = 0;
// iteration gcd(a,b) = gcd(a-b,b), realized
// using division with remainder, until b = 0.
// Then, since it was a==b, it holds gcd = a.
while (b != 0) {
tmp = a%b;
a = b;
b = tmp;
}
return a;
}
int main(){
int a = 200;
int b = 110;
printf("gcd(%d,%d)=%d\n",a,b,euclid(a,b));
}
|
gcd(200,110)=10
Rekursionen¶
Rekursive Funktion¶
- Funktion ist rekursiv, wenn sie sich selber aufruft
- natürliches Konzept in der Mathematik:
- d.h. Rückführung eines Problems auf einfacheres Problem derselben Art
- Achtung:
- Rekursion darf nicht endlos sein
- d.h. Abbruchbedingung für Rekursion ist wichtig
- häufig Schleifen statt Rekursion möglich (später!)
- idR. Rekursion eleganter
- idR. Schleifen effizienter
Beispiel: Faktorielle¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | #include <stdio.h>
// The function factorial computes the factorial
// of a nonnegative integer n recursively
int factorial(int n) {
if (n <= -1) {
// factorial not defined for n <= -1
return -1;
}
else {
if (n > 1) {
// recursive step n! = n*(n-1)!
return n*factorial(n-1);
}
else {
// stopping criterion 0! = 1 = 1!
return 1;
}
}
}
int main() {
int n = 0;
int nfac = 0;
printf("n=");
scanf("%d",&n);
nfac = factorial(n);
if (nfac <= 0) {
printf("Wrong input!\n");
}
else {
printf("%d!=%d\n",n,nfac);
}
}
|
n=5 5!=120
Reihenfolge der Selbstaufrufe am Beispiel Faktorielle¶
- Die blauen Pfeile symbolisieren die Funktionsaufrufe (oben) und die Rückgaben (unten).
- Die grauen Pfeile verdeutlichen, dass es sich oben und unten um die selbe Funktion handelt, aber der Selbstaufruf durch den Rückgabewert ersetzt wird.
Nullstellensuche mit dem Bisektionsverfahren¶
Gegeben: stetiges mit
- Toleranz
Tatsache: Zwischenwertsatz mind. eine Nst
- denn und haben versch. Vorzeichen
Gesucht: mit folgender Eigenschaft
- und
Bisektionsverfahren = iterierte Intervallhalbierung
- Solange wie Intervallbreite
- Berechne Intervallmittelpunkt und
- Falls , betrachte Intervall
- sonst betrachte halbiertes Intervall
- ist schließlich gesuchte Approximation
- Verfahren basiert nur auf Zwischenwertsatz
Terminiert nach endlich vielen Schritten, da jeweils Intervall halbiert wird
Konvergenz gegen Nst. für .
Illustration des Bisektionsverfahrens¶
Beispiel: Bisektionsverfahren¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | #include <stdio.h>
double f(double x) {
return x*x + 1/(2 + x) - 2;
}
double bisection(double a, double b, double tol){
double m = 0.5*(a+b);
if ( b - a <= 2*tol ) {
return m;
}
else {
if ( f(a)*f(m) <= 0 ) {
return bisection(a,m,tol);
}
else {
return bisection(m,b,tol);
}
}
}
int main() {
double a = 0;
double b = 10;
double tol = 1e-12;
double x = bisection(a,b,tol);
printf("Approximate zero of the function x=%g\n",x);
printf("Function value f(x)=%g\n",f(x)); // %g chooses good floating point output
}
|
Approximate zero of the function x=1.30278 Function value f(x)=-5.64659e-13
Mathematische Funktionen¶
Deklaration der math. Funktionen in
math.h
- Input & Output der Funktionen sind vom Typ
double
- Input & Output der Funktionen sind vom Typ
Wenn diese Funktionen benötigt werden
- im Source-Code:
#include <math.h>
- Compilieren des Source-Code mit
zusätzlicher Linker-Option
-lm
, d.h.gcc file.c -o output -lm
erzeugt Executableoutput
- moderne Compiler linken oft automatisch, d.h.,
-lm
nicht nötig
- moderne Compiler linken oft automatisch, d.h.,
- im Source-Code:
Diese Bibliothek stellt u.a. zur Verfügung
- Trigonometrische Funktionen
cos
,sin
,tan
,acos
,asin
,atan
,cosh
,sinh
,tanh
- Exponentialfunktion und Logarithmus
exp
,log
,log10
- Potenz- und Wurzelfunktion
pow
,sqrt
(wobei )- Häufig ist explizite Berechnung effizienter:
- BSP: mittels
x * x * x
berechnen - BSP: über
if ... else
berechnen: falls gerade, sonst - Absolutbetrag
fabs
- Rundung auf ganze Zahlen:
round
,floor
,ceil
- ACHTUNG: In der Bibliothek
stdlib.h
gibt esabs
abs
ist Absolutbetrag fürint
(ggf. type cast!)fabs
ist Absolutbetrag fürdouble
Preprocessor, Compiler & Linker¶
- Ein Compiler besteht aus mehreren Komponenten, die nacheinander abgearbeitet werden
Preprocessor wird intern gestartet, bevor der Source-Code compiliert wird
- Ersetzt Text im Code durch anderen Text
- Preprocessor-Befehle beginnen immer mit
#
und enden nie mit Semikolon, z.B. #define text replacement
- in allen nachfolgenden Zeilen wird der
Text
text
durchreplacement
ersetzt - zur Definition von Konstanten
- Konvention:
GROSS_MIT_UNDERSCORES
- in allen nachfolgenden Zeilen wird der
Text
#include file
- einfügen der Datei
file
- einfügen der Datei
Compiler übersetzt (Source-)Code in Object-Code
- Object-Code = Maschinencode, bei dem symbolische Namen (z.B. Funktionsnamen) noch vorhanden sind
Weiterer Object-Code wird zusätzlich eingebunden
- z.B. Bibliotheken (= Sammlungen von Funktionen)
Linker ersetzt symbolische Namen im Object- Code durch Adressen und erstellt dadurch ein ausführbares Programm, sog. Executable
Bibliotheken & Header-Files¶
(Funktions-) Bibliothek (z.B. math. Funktionen) besteht immer aus 2 Dateien
- Object-Code
- zugehöriges Header-File
Im Header-File steht die Deklaration aller Fktn, die in der Bibliothek vorhanden sind
Will man Bibliothek verwenden, muss man zugehöriges Header-File einbinden
#include header
bindet Header-Fileheader
aus Standardverzeichnis/usr/include/
ein,- z.B.
math.h
(Header-File zur math. Bib.) #include "datei"
bindet Datei aus aktuellem Verzeichnis ein (z.B. Downloads vom Internet)- idR. führt -Compiler
#include <stdio.h>
von allein aus (in zugehöriger Bib. liegt z.B.printf
)
Ferner muss man den Object-Code der Bibliothek hinzulinken
- Wo Object-Code der Bibliothek liegt, muss
gcc
mittels Option-l
(und-L
) mitgeteilt werden - z.B.
gcc file.c -lm
linkt Mathematik Bibliothek (Dateinamelibm.a
) - Standardbibliotheken automatisch gelinkt,
z.B.
stdio
(also keine zusätzliche Option nötig)
- Wo Object-Code der Bibliothek liegt, muss
Elementares Beispiel¶
- Preprocessor-Befehle in 1, 2 ohne Semikolon
- Compilieren mit
gcc sqrt.c -lm
- Vergisst man
-lm
Fehlermeldung des Linkers
In function 'main'
sqrt.c:(.text+0x24): undefined reference to 'sqrt'
collect2: ld returned 1 exit status
1 2 3 4 5 6 7 8 | #include <stdio.h>
#include <math.h>
int main() {
double x = 2.;
double y = sqrt(x);
printf("sqrt(%f)=%f\n",x,y);
}
|
sqrt(2.000000)=1.414214
Code mehrfach ausführen mit Schleifen¶
Schleifen¶
- Schleifen führen einen oder mehrere Befehle wiederholt aus
- In Aufgabenstellung häufig Hinweise, wie
- Vektoren & Matrizen
- Laufvariablen
- Summen
- Produkte
- Text wie z.B. solange bis oder solange wie
- Man unterscheidet
- Zählschleifen
for
: Wiederhole etwas eine gewisse Anzahl oft - Bedingungsschleifen
while, do...while
: Wiederhole etwas solange, wie eine Bdg. gilt oder bis eine Bedingung gilt
- Zählschleifen
Die while-Schleife¶
Formal:
while( condition ) statement
- "Solange wie etwas gilt, tue Folgendes:"
Vor jedem Durchlauf wird
condition
geprüft & Abbruch, falls nicht erfüllt- sog. kopfgesteuerte Schleife
- Eventuell also kein einziger Durchlauf!
statement
kann Block sein
1 2 3 4 5 6 7 8 9 10 11 | #include <stdio.h>
int main() {
int counter = 9;
while (counter > 0) {
printf("%d ",counter);
counter = counter-1;
}
printf("\n");
}
|
9 8 7 6 5 4 3 2 1
Operator ++¶
++a
unda++
sind arithmetisch äquivalent zua=a+1
- Zusätzlich aber Auswertung von Variable
a
- Präinkrement
++a
- Erst erhöhen, dann auswerten
- Postinkrement
a++
- Erst auswerten, dann erhöhen
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include <stdio.h>
int main() {
int a = 0;
int b = 43;
printf("1) a=%d, b=%d\n",a,b);
b = a++;
printf("2) a=%d, b=%d\n",a,b);
b = ++a;
printf("3) a=%d, b=%d\n",a,b);
}
|
1) a=0, b=43 2) a=1, b=0 3) a=2, b=2
Operatoren ++ und --¶
Analog zu
a++
und++a
gibt es- Prädekrement
--a
- Erst verringern, dann auswerten
- Postdekrement
a--
- Erst auswerten, dann verringern
- Prädekrement
Beachte Unterschied in Bedingungsschleife!
1 2 3 4 5 6 7 8 9 10 | #include <stdio.h>
int main() {
int counter = 5;
while (--counter > 0) {
printf("%d ",counter);
}
printf("\n");
}
|
4 3 2 1
Bisektionsverfahren (Wiederholung)¶
Gegeben: stetiges mit
- Toleranz
Tatsache: Zwischenwertsatz mind. eine Nst
- denn und haben versch. Vorzeichen
Gesucht: mit folgender Eigenschaft
- und
Bisektionsverfahren = iterierte Intervallhalbierung
- Solange wie Intervallbreite
- Berechne Intervallmittelpunkt und
- Falls , betrachte Intervall
- sonst betrachte halbiertes Intervall
- ist schließlich gesuchte Approximation
- Verfahren basiert nur auf Zwischenwertsatz
Terminiert nach endlich vielen Schritten, da jeweils Intervall halbiert wird
Konvergenz gegen Nullstelle für .
Bisektionsverfahren (mit Schleife statt Rekursion)¶
- Verwendung von Variablen
fa
undfm
vermeidet doppelte Funktionsauswertung - Schleife ist idR. effizienter als Rekursion!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | #include <stdio.h>
#include <math.h>
double f(double x) {
return x*x + 1/(2 + x) - 2;
}
double bisection(double a, double b, double tol){
double fa = f(a);
double m = 0.5*(a+b);
double fm = 0;
while ( b - a > 2*tol ) {
m = 0.5*(a+b);
fm = f(m);
if ( fa*fm <= 0 ) {
b = m;
}
else {
a = m;
fa = fm;
}
}
return m;
}
int main() {
double a = 0;
double b = 10;
double tol = 1e-12;
double x = bisection(a,b,tol);
printf("Approximate zero of the function x=%g\n",x);
printf("Function value f(x)=%g\n",f(x));
}
|
Approximate zero of the function x=1.30278 Function value f(x)=-1.99352e-12
Euklidischer Algorithmus¶
- Gegeben: zwei ganze Zahlen
Gesucht: größter gemeinsamer Teiler
- engl. greatest common divisor (gcd)
Euklidischer Algorithmus:
- Falls , gilt
- Vertausche und , falls
- Dann gilt , denn:
- Sei Teiler von
- d.h. und mit ,
- also und
- d.h. teilt und
- d.h.
- analog
- Ersetze durch , wiederhole diese Schritte
- Erhalte nach endlich vielen Schritten:
- Falls , wird also pro Schritt um mindestens kleiner
- Nach endlichen vielen Schritten kann also nicht mehr gelten
- d.h. Algorithmus terminiert
Illustration des Euklidischen Algorithmus¶
- Wir berechnen mit dem Euklidischen Algorithms
- Die grauen Linien verdeutlichen, dass die Zwischenergebnisse immer den selben größten gemeinsamen Teiler haben
Euklidischer-Algorithmus¶
- berechnet gcd von
- basiert auf für
- Für terminiert
while
mit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #include <stdio.h>
int main() {
int a = 200;
int b = 110;
int tmp = 0;
printf("gcd(%d,%d)=",a,b);
while (a != b) {
if (a < b) {
tmp = a;
a = b;
b = tmp;
}
a = a-b;
}
printf("%d\n",a);
}
|
gcd(200,110)=10
Euklid-Algorithmus (verbessert)¶
- Kernstück des Euklid-Algorithmus
1 2 3 4 5 6 7 8 | while (a != b) {
if (a < b) {
tmp = a;
a = b;
b = tmp;
}
a = a-b;
}
|
- Erinnerung:
a%b
ist Divsionsrest vona/b
- Euklid-Algorithmus iteriert
a=a-b
bis- d.h. bis
a=a%b
- falls fertig, gilt
a=0
und Ergebnisb
ist größter gemeinsamer Teiler
- d.h. bis
1 2 3 4 5 6 7 8 | while (a != 0) {
if (a < b) {
tmp = a;
a = b;
b = tmp;
}
a = a%b;
}
|
- Divisionsrest erfüllt immer
a%b < b
- d.h. es wird immer vertauscht nach Rechnung
- falls fertig, gilt
b=0
und Ergebnisa
ist größter gemeinsamer Teiler
1 2 3 4 5 | while (b != 0) {
tmp = a%b;
a = b;
b = tmp;
}
|
Beispiel: Euklidischer Algorithmus (verbessert)¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #include <stdio.h>
int main() {
int a = 200;
int b = 110;
int tmp = 0;
printf("ggT(%d,%d)=",a,b);
while (b != 0) {
tmp = a%b;
a = b;
b = tmp;
}
printf("%d\n",a);
}
|
ggT(200,110)=10
Die do-while-Schleife¶
Formal:
do statement while( condition );
- "Tue Folgendes, solange wie etwas gilt:"
Nach jedem Durchlauf wird
condition
geprüft & Abbruch, falls nicht erfüllt- sog. fußgesteuerte Schleife
- Also mindestens ein Durchlauf!
statement
kann Block sein
1 2 3 4 5 6 7 8 9 10 11 | #include <stdio.h>
int main() {
int counter = 5;
do {
printf("%d ",counter);
}
while (--counter>0);
printf("\n");
}
|
5 4 3 2 1
Ein weiteres Beispiel¶
Fibonacci-Folge strebt gegen unendlich
- , und für
Ziel: Berechne erstes Folgenglied mit für gegebene Schranke
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #include <stdio.h>
int main() {
int x0 = 0;
int x1 = 1;
int tmp = 0;
int c = 0;
printf("c=");
scanf("%d",&c);
printf("%d %d ",x0,x1);
do {
tmp = x0 + x1;
x0 = x1;
x1 = tmp;
printf("%d ",x1);
}
while(x1 <= c);
printf("\n");
}
|
c=5 0 1 1 2 3 5 8
"solange wie" vs. "solange bis"¶
break
beendet die aktuelle Schleifewhile
hat Laufbedingungcondition
- d.h. Schleife läuft, solange wie
condition
wahr
- d.h. Schleife läuft, solange wie
Algorithmen haben idR. Abbruchbedingung
done
- d.h. Abbruch, falls
done
wahr - d.h.
condition
Negation vondone
- d.h. Abbruch, falls
einfache Realisierung über Endlosschleife mit
break
- Bedingung in Zeile 11 ist immer wahr!
- Abbruch erfolgt nur durch
break
in Zeile 13
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #include <stdio.h>
int main() {
int a = 200;
int b = 110;
int tmp = 0;
printf("gcd(%d,%d)=",a,b);
// Euclidean algorithm with infinite loop + break
while (1) {
if (b == 0) {
break;
}
else {
tmp = a%b;
a = b;
b = tmp;
}
}
printf("%d\n",a);
}
|
gcd(200,110)=10
Einfache Fehlerkontrolle¶
Motivation¶
- Fakt ist: alle Programmierer machen Fehler
- Code läuft beim ersten Mal nie richtig
- Großteil der Entwicklungszeit geht in Fehlersuche
- "Profis" unterscheiden sich von "Anfängern" im Wesentlichen durch effizientere Fehlersuche
- Syntax-Fehler sind leicht einzugrenzen
- es steht Zeilennummer dabei (Compiler!)
- Tipp: Verwende während des Programmierens zum Syntax-Test regelmäßig (Details später!)
gcc -c name.c
nur Objekt-Codegcc -c -Wall name.c
alle Warnungen
- Laufzeitfehler sind viel schwieriger zu finden
- Programm läuft, tut aber nicht das Richtige
- manchmal fällt der Fehler ewig nicht auf sehr schlecht
Fehler vermeiden!¶
- Programmier-Konventionen beachten
- z.B. bei Namen für Variablen, Funktionen etc.
- Kommentarzeilen dort, wo im Code etwas passiert
- z.B. Verzweigung mit nicht offensichtlicher Bdg.
- z.B. Funktionen (Zweck, Input, Output)
jede Funktion hat nur eine Funktionalität
- jede Funktion einzeln & sofort testen
- Wenn später Funktion verwendet wird, kann ein etwaiger Fehler dort nicht mehr sein!
- d.h. kann Fehler im Programm schneller lokalisieren!
jede Funktionalität hat eigene Funktion
- Programm in überschaubare Funktionen zerlegen!
- nicht alles auf einmal programmieren!
- Achtung: Häufiger Anfängerfehler!
- Möglichst viele Fehler bewusst abfangen!
- Funktions-Input auf Konsistenz prüfen!
- Fehler-Abbruch, falls inkonsistent!
- garantieren, dass Funktions-Output zulässig!
- Funktions-Input auf Konsistenz prüfen!
Bibliothek assert.h¶
- Ziel: Sofortabbruch mit Fehlermeldung, sobald Funktion merkt, dass Input / Output unzulässig
#include <assert.h>
assert(condition)
liefert Fehlerabbruch, fallscondition
falsch- mit Ausgabe der Zeilennummer im Source-Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #include <stdio.h>
#include <assert.h>
void test(int x, int y) {
assert(x<y);
printf("It holds x < y\n");
}
int main() {
int x = 0;
int y = 0;
printf("x = ");
scanf("%d",&x);
printf("y = ");
scanf("%d",&y);
test(x,y);
}
|
x = 6 y = 3
tmp0re3hp7h.out: /tmp/tmpdnqxpqyp.c:5: test: Assertion `x<y' failed. [C kernel] Executable exited with code -6
Beispiel: Euklidischer Algorithmus¶
assert
stellt sicher, dass Input zulässig- d.h. ist notwendig!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | #include <stdio.h>
#include <assert.h>
// The Euclidean algorithm to compute the greatest
// common divisor (gcd) is based on gcd(a,b) = gcd(a-b,b)
// for a>b and gcd(a,b) = gcd(b,a).
int euclid(int a, int b) {
assert(a > 0);
assert(b > 0);
int tmp = 0;
// iteration gcd(a,b) = gcd(a-b,b), realized
// using division with remainder, until b = 0.
// Then, since it was a==b, it holds gcd = a.
while (b != 0) {
tmp = a%b;
a = b;
b = tmp;
}
return a;
}
int main(){
int a=0,b=0;
printf("Input:\na=");
scanf("%d",&a);
printf("b=");
scanf("%d",&b);
printf("gcc(%d,%d) = %d\n",a,b,euclid(a,b));
}
|
Input: a=7 b=5 gcc(7,5) = 1
Testen von Code¶
Motivation¶
- Ariane 5 Explosion ('96)
- Konversion
double
int
- Schaden ca. Mio. Dollar
- Konversion
- Patriot Missile Fehler, Golfkrieg ('91)
- Zeitmessung falsch berechnet & Rundungsfehler
- Untergang Sleipner A offshore Plattform ('91)
- Fehler in der Finite-Elemente-Berechnung
- Kosten ca Mio. EUR
- "kleine BUGs, große GAUs"
Qualitätssicherung¶
- Software entsteht durch menschliche Hand
- Fehler zu machen, ist menschlich!
- Software wird deshalb Fehler enthalten
- Ziel: (Laufzeit-) Fehler finden vor großem Schaden
- Je später Fehler entdeckt werden, desto aufwändiger ist ihre Behebung!
- Schon beim Implementieren auf Qualität achten
- siehe oben: Fehler vermeiden!
- wünschenswert: je 1/3 Zeit für
- Programmieren
- Testen
- Dokumentieren
- wünschenswert: Dokumentation der Tests!
- damit reproduzierbar
- In der Praxis meist viel Programmieren, wenig Testen, noch weniger Dokumentieren ;-)
Testen¶
Testen ist der Prozess, ein Programm mit der Absicht auszuführen, Fehler zu finden!
- Glenford Myers: Art of Software Testing (1979)
Test ist der Vergleich des Verhaltens eines Prg (Ist) mit dem erwarteten Verhalten eines Systems (Soll)
Es ist praktisch nicht möglich, alle Programmfunktionen und
alle möglichen Werte in den Eingabedaten in allen Kombinationen zu testen.- d.h. Tests sind idR. unvollständig!
- Probleme beim unvollständigen Testen
- Tests erlauben nur das Auffinden von Fehlern
- Tests können Korrektheit nicht beweisen
- Fehlerursache ist durch Soll-Ist-Vergleich nicht zwangsläufig klar
- Testfälle können selbst fehlerhaft sein!
- Vorteile beim unvollständigen Testen
- Zeitaufwand vertretbar
- Tests beziehen sich idR. auf "realistischen Input"
- Tests sind idR. reproduzierbar
Arten von Tests¶
strukturelle Tests (für jede Funktion)
- Werden alle Anweisungen ausgeführt oder gibt es toten Code?
- Treten Fehler auf, wenn
if ... else
mit wahr / falsch durchlaufen werden?
funktionale Tests (für jede Fkt. und Programm)
- Tut jede Funktion mit zulässigen Parametern das Richtige? (d.h. Ergebnis korrekt?)
- Tut das Programm (bzw. Teilabschnitte) das Richtige? (d.h. Ergebnis korrekt?)
- Werden unzulässige Parameter erkannt?
- Werden Grenzfälle / Sonderfälle korrekt erkannt und liefern das Richtige?
- Was passiert bei Fehleingaben, d.h. bei Fehler des Benutzers?
Funktionale Tests?¶
- Ziel: Tut Funktion / Programm das Richtige?
- funktionale Tests brauchen Testfälle
- mit bekanntem Ergebnis / Output
- Was sind generische Fälle / Parameter?
- Bei welchen Fällen treten Verzweigungen auf?
- Möglichst viele Verzweigungen abdecken!
- Welche Fälle sind kritisch?
- Zahlen werden im Rechner nicht exakt dargestellt (später mehr!)
- Außerdem keine exakte Arithmetik bei
double
!- jede
double
-Rechnung hat Rechenfehler!
- jede
- Wo können aufgrund solcher Rechenfehler andere Ergebnisse auftreten?
- z.B. Ist ein Punkt auf dem Kreisrand?
- früh mit dem Testen beginnen
- nach Implementierung jeder Funktion!
- nicht erst dann, wenn Prg komplett fertig!
- nach Code-Korrektur alle(!) Tests wiederholen
- deshalb Dokumentation der Tests!
- Ab jetzt in der UE stets: Wie wurde getestet?
- allerdings nur inhaltlich
- d.h. ohne Fehleingaben des Nutzers
Daten speichern in Arrays¶
Vektoren sind 1D-Arrays¶
Deklaration eines Vektors :
double x[N];
x
istdouble
-Vektor
Zugriff auf Komponenten:
x[j]
entspricht- Jedes
x[j]
ist vom Typdouble
Analoge Deklaration für andere Datentypen
int y[N];
y
istint
-Vektor
- ACHTUNG mit der Indizierung der Komponenten
- Indizes in
- idR. Indizes in Mathematik
- Initialisierung bei Deklaration möglich:
double x[3] = {1,2,3};
deklariert den Vektor
- Vektor-Initialisierung nur bei Deklaration erlaubt
- Später zwingend komponentenweises Schreiben!
- d.h.
x[0] = 1; x[1] = 2; x[2] = 3;
ist OK! x = {1,2,3}
ist verboten!
- d.h.
- Später zwingend komponentenweises Schreiben!
Beispiel: Einlesen eines Vektors¶
Ausgabe
double
überprintf
mit Platzhalter%f
Einlesen
double
überscanf
mit Platzhalter%lf
- Es gibt keine Möglichkeit, Vektoren als Ganzes einzulesen / auszugeben!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | #include <stdio.h>
int main() {
double x[3] = {0,0,0};
printf("Read a vector x in R^3 from keyboard:\n");
// read x0
printf("x_0 = ");
scanf("%lf",&x[0]);
// read x1
printf("x_1 = ");
scanf("%lf",&x[1]);
// read x2
printf("x_2 = ");
scanf("%lf",&x[2]);
// print vector (componentwise)
printf("x = (%f, %f, %f)\n",x[0],x[1],x[2]);
}
|
Read a vector x in R^3 from keyboard: x_0 = 3 x_1 = 4 x_2 = 5 x = (3.000000, 4.000000, 5.000000)
Achtung: Statische Arrays¶
- Die Länge von Arrays ist statisch
- nicht veränderbar während Programmablauf
- kann nicht zu erweitert werden
- Programm kann nicht selbständig herausfinden,
wie groß ein Array ist
- d.h. Programm weiß bei Ablauf nicht, dass Vektor Länge hat
- Muss bei der Programmierung beachtet werden!
- Achtung mit Indizierung!
- Indizes laufen in C
- Prg kann nicht wissen, ob
x[j]
definiert istx
muss mindestens Länge haben!- falsche Indizierung ist kein Syntaxfehler!
- sondern ggf. ein Laufzeitfehler!
- Arrays dürfen nicht Output einer Funktion sein!
- Das ist auch kein Problem, denn:
- Arrays werden mit Call by Reference an eine Funktion übergeben!
- Erklärung folgt später ( Pointer!)
Arrays & Call by Reference¶
Man sieht: Call by Reference bei Vektoren!
- Erklärung folgt später ( Pointer!)
in Funktionssignatur darf man Vektorlänge weglassen
- Programmierer für korrekten Zugriff zuständig!
void allByReference(double y[])
auch OK!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #include <stdio.h>
void callByReference(double y[3]) {
printf("a) y = (%f, %f, %f)\n",y[0],y[1],y[2]);
y[0] = 1;
y[1] = 2;
y[2] = 3;
printf("b) y = (%f, %f, %f)\n",y[0],y[1],y[2]);
}
int main() {
double x[3] = {0,0,0};
printf("c) x = (%f, %f, %f)\n",x[0],x[1],x[2]);
// function call modifies the coefficients!
callByReference(x);
printf("d) x = (%f, %f, %f)\n",x[0],x[1],x[2]);
}
|
c) x = (0.000000, 0.000000, 0.000000) a) y = (0.000000, 0.000000, 0.000000) b) y = (1.000000, 2.000000, 3.000000) d) x = (1.000000, 2.000000, 3.000000)
Falsche Indizierung von Vektoren¶
Zeile 7, 10: Falscher Zugriff auf Vektor
x
,- d.h. Laufzeitfehler
- ggf. werden andere Daten überschrieben!
- Trotzdem keine Fehlermeldung/Warnung vom Compiler!
- Für korrekte Indizes sorgt der Programmierer!
- d.h. Laufzeitfehler
Bei kleinen Überschreitungen der Arraylänge oft keine Fehlermeldung. Wenn man aber 100 mit 10000 ersetzt, dann stürzt das Programm ab.
- weil Betriebssystem merkt, dass das Programm "aus dem Ruder läuft"!
- d.h. Programm greift auf Speicherbereiche zu, die ihm nicht zugeordnet / erlaubt sind.
1 2 3 4 5 6 7 8 9 10 11 | #include <stdio.h>
int main() {
int x[3] = {0,1,2};
// this coefficient has no reserved memory!
x[100] = 43;
// nevertheless, writing and reading is possible!
printf("x = (%d, %d, %d), x[%d] = %d\n", x[0],x[1],x[2],100,x[100]);
}
|
x = (0, 1, 2), x[100] = 43
Matrizen¶
- Matrix ist rechteckiges Schema
mit Koeffizienten
- zentrale math. Objekte der Linearen Algebra
Deklaration einer Matrix :
double A[M][N];
Zugriff auf Komponenten:
A[j][k]
entspricht- Jedes
A[j][k]
ist vom Typdouble
zeilenweise Initialisierung bei Deklaration möglich:
double A[2][3] = {{1,2,3},{4,5,6}};
deklariert + initialisiert- Nur bei gleichzeitiger Deklaration erlaubt, vgl. Vektoren
Allgemeine Arrays¶
- Vektor ist ein -dim. Array
- Matrix ist ein -dim. Array
- Ist
type
Datentyp, so deklarierttype x[N];
einen Vektor der Länge- Koeffizienten
x[j]
sind Variablen vom Typtype
- Ist
type
Datentyp, so deklarierttype x[M][N];
eine Matrixx[j]
ist Vektor vom Typtype
(der Länge )- Koeff.
x[j][k]
sind Variablen vom Typtype
- Auch mehr Indizes möglich
type x[M][N][P];
deklariert -dim. Arrayx[j]
ist Matrix vom Typtype
x[j][k]
ist Vektor vom Typtype
(der Länge )- Koeff.
x[j][k][p]
sind Variablen vom Typtype
- etc.
- Arrays dürfen nicht Output einer Funktion sein!
- Arrays werden mit Call by Reference an eine Funktion übergeben!
Zählschleifen¶
Die for
-Schleife¶
for (init.; cond.; step-expr.)
statement- Ablauf einer
for
-Schleife- (1) Ausführen der Initialisierung
init.
- (2) Abbruch, falls Bedingung
cond.
nicht erfüllt - (3) Ausführen von
statement
- (4) Ausführen von
step-expr.
- (5) Sprung nach (2)
- (1) Ausführen der Initialisierung
statement
ist- entweder eine logische Programmzeile
- oder mehrere Programmzeilen als Block
{...}
,
in C ist
for
eigentlich eine Bedingungsschleife- trotzdem immer
for
verwenden, um Statement "eine fixe Anzahl" oft zu wiederholen - gut programmiert = leicht verständlicher Code!
- trotzdem immer
1 2 3 4 5 6 7 8 9 10 11 | #include <stdio.h>
int main() {
int j = 0;
for (j=5; j>0 ; j--) {
printf("%d ",j);
}
printf("\n");
}
|
5 4 3 2 1
Vektor einlesen & ausgeben¶
- Funktionen müssen Länge von Arrays kennen!
- d.h. zusätzlicher Input-Parameter nötig
- Arrays werden mit Call by Reference übergeben!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | #include <stdio.h>
// Read from the keyboard a double vector
// of length dim
void scanVector(double input[], int dim) {
int j = 0;
// iterate over all indices of the vector
for (j=0; j<dim; j++) {
// initialize j-th coefficient
input[j] = 0;
// read j-th coefficient
printf("%d: ",j);
scanf("%lf",&input[j]);
}
}
// Print to the screen a double vector
// of length dim
void printVector(double output[], int dim) {
int j = 0;
// iterate over all indices of the vector
for (j=0; j<dim; j++) {
// print j-th coefficient
printf("%f ",output[j]);
}
printf("\n");
}
int main() {
double x[5];
// recall: call by reference for arrays
scanVector(x,5);
printVector(x,5);
}
|
0: 8 1: 6 2: 5 3: 4 4: 5 8.000000 6.000000 5.000000 4.000000 5.000000
Minimum eines Vektors¶
- Hinweise zur Realisierung (vgl. UE)
- Vektorlänge ist Konstante im Hauptprogramm
- d.h. Länge im Hauptprogramm nicht veränderbar
- aber Input z.B. der Funktion scanVector
- d.h. Funktion arbeitet für beliebige Länge
- Vektorlänge ist Konstante im Hauptprogramm
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | #include <stdio.h>
#define DIM 5
void scanVector(double input[], int dim) {
int j = 0;
for (j=0; j<dim; j++) {
input[j] = 0;
printf("%d: ",j);
scanf("%lf",&input[j]);
}
}
// The function returns the minimum of a double vector
// of length dim
double min(double input[], int dim) {
int j = 0;
// initial guess: the minimum is located at input[0]
double minval = input[0];
// iterate over all entries of the vector
for (j=1; j<dim; j++) {
// if a smaller value is found, update minimum
if (input[j] < minval) {
minval = input[j];
}
}
return minval;
}
int main() {
double x[DIM];
scanVector(x,DIM);
printf("The minimum of the vector is %f\n", min(x,DIM));
}
|
0: 3 1: 4 2: 5 3: 6 4: 7 The minimum of the vector is 3.000000
Beispiel: Summensymbol ¶
Berechnung der Summe :
- Abkürzung
Definiere theoretische Hilfsgröße
Dann gilt
- etc.
Realisierung also durch -maliges Aufsummieren
- ACHTUNG: Zuweisung, keine Gleichheit
S = a_1
S = S + a_2
S = S + a_3
etc.
- ACHTUNG: Zuweisung, keine Gleichheit
Beispiel: Summensymbol ¶
Programm berechnet für .
ACHTUNG: Bei iterierter Summation nicht vergessen, Ergebnisvariable auf Null zu setzen vgl. Zeile 7
- Anderenfalls: Falsches/Zufälliges Ergebnis!
statt
sum = sum + j;
- Kurzschreibweise
sum += j;
- Kurzschreibweise
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include <stdio.h>
int main() {
int j = 0;
int n = 100;
int sum = 0;
for (j=1; j<=n; j++) {
sum = sum+j;
}
printf("sum_{j=1}^{%d} j = %d\n",n,sum);
}
|
sum_{j=1}^{100} j = 5050
Beispiel: Produktsymbol ¶
Programm berechnet Faktorielle für .
ACHTUNG: Bei iteriertem Produkt nicht vergessen, Ergebnisvariable auf Eins zu setzen vgl. Zeile 7
- Anderenfalls: Falsches/Zufälliges Ergebnis!
statt
factorial = factorial*j;
- Kurzschreibweise
factorial *= j;
- Kurzschreibweise
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include <stdio.h>
int main() {
int j = 0;
int n = 5;
int factorial = 1;
for (j=1; j<=n; j++) {
factorial = factorial*j;
}
printf("%d! = %d\n",n,factorial);
}
|
5! = 120
Matrix-Vektor-Multiplikation¶
- Man darf
for
-Schleifen schachteln- Typisches Beispiel: Matrix-Vektor-Multiplikation
- Seien Matrix, Vektor
- Def durch
- Indizierung in C startet bei
- Achtung: In Mathematik Indizierung idR. ab !
- ist also Schreibweise für lineares GLS
- Implementierung
- äußere Schleife über , innere für Summe
- ACHTUNG: Init.
b[j] = 0
nicht vergessen!
1 2 3 4 5 6 | for (j=0; j<M; j++) {
b[j] = 0;
for (k=0; k<N; k++) {
b[j] = b[j] + A[j][k]*x[k];
}
}
|
Matrix spaltenweise speichern 1/2¶
mathematische Bibliotheken speichern Matrizen idR. spaltenweise als Vektor
- , gespeichert als
- , gespeichert als
muss Matrix spaltenweise speichern, wenn ich solche Bibliotheken nutzen will
- diese Bibliotheken (aus historischen Gründen) meist in Fortran programmiert
Beachte: C indiziert ,
- ist -tes Element der -ten Spalte
- also vorher Spalten mit Speicherplätzen
- plus Speicherplätze in der -ten Spalte
gezeigt: mit
BSP. BLAS (basic linear algebra subroutines)
- alles, was man mit Vektoren machen kann...
- elementare Operationen mit Matrizen...
- BSP. LAPACK (linear algebra package)
- lineare Gleichungssysteme lösen
- Eigenwerte berechnen
Matrix spaltenweise speichern 2/2¶
Erinnerung:
- , gespeichert als
- entspricht also mit
Matrix-Vektor-Produkt
- ,
- mit
double A[M][N];
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // 2D-Array Speicherung
for (j=0; j<M; j++) {
b[j] = 0;
for (k=0; k<N; k++) {
b[j] = b[j] + A[j][k]*x[k];
}
}
// 1D-Array spaltenweise Speicherung
for (j=0; j<M; j++) {
b[j] = 0;
for (k=0; k<N; k++) {
b[j] = b[j] + A[j+k*M]*x[k];
}
}
|
MinSort (= Selection Sort)¶
- Gegeben: Ein Vektor
Ziel: Sortiere , sodass
Algorithmus (1. Schritt)
- suche Minimum von
- vertausche und , d.h. ist kleinstes Elt.
- Algorithmus (2. Schritt)
- suche Minimum von
- vertausche und , d.h. zweit kleinstes Elt.
- nach Schritten ist sortiert
- Hinweise zur Realisierung (vgl. UE)
- Länge ist Konstante im Hauptprogramm
- i d.h. ist im Hauptprg nicht veränderbar
- aber ist Inputparameter der Funktion \texttt{minsort}
- i d.h. Funktion arbeitet für beliebige Länge
MinSort¶
- üblich: Kommentar, was eine Funktion tut, bei Vorwärts-Deklaration (Zeile 4--11)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | #include <stdio.h>
#define DIM 5
// read a double vector of length dim from the keyboard
void scanVector(double input[], int dim);
// print a double vector of length dim to the screen
void printVector(double output[], int dim);
// sort a double vector of length dim in ascending order
void minsort(double vector[], int dim);
int main() {
double x[DIM];
scanVector(x,DIM);
minsort(x,DIM);
printVector(x,DIM);
}
void scanVector(double input[], int dim) {
int j = 0;
for (j=0; j<dim; j++) {
input[j] = 0;
printf("%d: ",j);
scanf("%lf",&input[j]);
}
}
void printVector(double output[], int dim) {
int j = 0;
for (j=0; j<dim; j++) {
printf("%f ",output[j]);
}
printf("\n");
}
void minsort(double vector[], int dim) {
int j = 0;
int k = 0;
int argmin = 0;
double tmp = 0;
// iterate over all indices of the vector
for (j=0; j<dim-1; j++) {
// determine minimum in (vector[j],...,vector[dim-1])
// initial guess: minimum is located at vector[j]
argmin = j;
for (k=j+1; k<dim; k++) {
if (vector[argmin] > vector[k]) {
argmin = k;
}
}
// now vector[argmin] is minimum in the remainder
// if needed, swap with j so that vector[j] is mininum
if (argmin > j) {
tmp = vector[argmin];
vector[argmin] = vector[j];
vector[j] = tmp;
}
}
}
|
0: 3 1: 4 2: 5 3: 6 4: 7 3.000000 4.000000 5.000000 6.000000 7.000000
break
und continue
¶
continue
undbreak
imstatement
von Schleifencontinue
beendet aktuellen Durchlaufbreak
beendet nur die aktuelle Schleife
Code ist schlecht programmiertes Beispiel!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #include <stdio.h>
int main() {
int j = 0;
int k = 0;
for (j=0; j<4; ++j) {
if (j%2 == 0) {
continue;
}
for (k=0; k < 10; ++k) {
printf("j=%d, k=%d\n",j,k);
if (k > 1) {
break;
}
}
}
printf("End: j=%d, k=%d\n",j,k);
}
|
j=1, k=0 j=1, k=1 j=1, k=2 j=3, k=0 j=3, k=1 j=3, k=2 End: j=4, k=2
Aufwand von Algorithmen¶
Aufwand eines Algorithmus¶
- wichtige Kenngröße für Algorithmen
- um Algorithmen zu bewerten / vergleichen
- Aufwand = Anzahl benötigter Operationen
- Zuweisungen
- Vergleiche
- arithmetische Operationen
- programmspezifische Operationen nicht gezählt
- Deklarationen & Initialisierungen
- Schleifen, Verzweigungen etc.
- Zählvariablen in Schleifen
- Aufwand wird durch "einfaches" Zählen ermittelt
- Konventionen zum Zählen nicht einheitlich
- in EPROG ist Aufwand für worst case interessant
- d.h. maximaler Aufwand im schlechtesten Fall
- häufig ist auch "mittlerer Aufwand" interessant
(d.h. Erwartungswert des Aufwands)
- mathematisch komplizierter
- deshalb nicht in EPROG
- in EPROG (maximal) benötigte Formeln:
Beispiel: Maximum suchen¶
Beim Zählen wird jede Schleife zu einer Summe!
- d.h.
for
in Zeile 6 ist
- d.h.
Aufwand:
- Zuweisung Zeile 5
- In jedem Schritt der
for
-Schleife Zeile 6--10- Vergleich Zeile 7
- Zuweisung (worst case!) Zeile 8
insgesamt Operationen
wobei Zählvariable nicht in Aufwand eingeht, d.h. nur Statement der
for
-Schleife wird berücksichtigt
1 2 3 4 5 6 7 8 9 10 11 12 13 | double maximum(double vector[], int n) {
int i = 0;
double max = 0;
max = vector[0];
for (i=1; i<n; i++) {
if (vector[i] > max) {
max = vector[i];
}
}
return max;
}
|
Landau-Symbol (= groß-O)¶
- oft nur Größenordnung des Aufwands interessant
Schreibweise für
- heißt
- d.h. es existiert eine Konstante mit
i für .
d.h. wächst höchstens so schnell wie für
- Beispiel: Maximum suchen
- Aufwand für
- häufig entfällt "für "
- dann Grenzwert kanonisch z.B.
- Sprechweise (nur Beispiele):
- Algorithmus hat linearen Aufwand,
falls Aufwand bei Problemgröße
- Maximumssuche hat linearen Aufwand
- Algorithmus hat fastlinearen Aufwand,
falls Aufwand bei Problemgröße
- Die schnellsten Sortieralgorithmen (z.b. Quick-Sort, Merge-Sort) haben fast-linearen Aufwand)
- Algorithmus hat quadratischen Aufwand,
falls Aufwand bei Problemgröße
- Matrix-Vektor Multiplikation hat quadratischen Aufwand
- Algorithmus hat kubischen Aufwand,
falls Aufwand bei Problemgröße
- Gauss Elimination hat kubischen Aufwand
- Algorithmus hat exponentiellen Aufwand falls Ausfwand für bei Problemgröße
- Primfaktorzerlegung mittels Sieb-von-Erastostenes einer -stelligen Zahl hat exponentiellen Aufwand. Die schnellsten Algorithmen (Zahlkörpersieb) sind nicht viel schneller.
- Algorithmus hat linearen Aufwand,
falls Aufwand bei Problemgröße
Matrix-Vektor Multiplikation¶
In jedem Schritt der -Schleife Zeile 6--11
- Zuweisung Zeile 7
- In jedem Schritt der -Schleife Zeile 8--10
- Multiplikationen Zeile 9
- Additionen Zeile 9
- Zuweisung Zeile 9
insgesamt Operationen
- Aufwand
- bzw. Aufwand für
- d.h. quadratischer Aufwand für
1 2 3 4 5 6 7 8 9 10 11 12 | void MVM(double A[], double x[], double b[],
int m, int n) {
int i = 0;
int j = 0;
for (j=0; j<m; j++) {
b[j] = 0;
for (k=0; k<n; k++) {
b[j] = b[j] + A[j+k*m]*x[k];
}
}
}
|
Suchen im Vektor¶
- Aufgabe:
- Suche Index
j
mitvector[j] == value
- Rückgabe
-1
, falls solcher nicht existiert
- Suche Index
in jedem Schritt der -Schleife
- 1 Vergleich
Anzahl der Operationen
- Aufwand im worst case
1 2 3 4 5 6 7 8 9 10 11 12 | int search(int vector[], int value, int n) {
int j = 0;
for (j=0; j<n; j++) {
if (vector[j] == value) {
return j;
}
}
return -1;
}
|
Binäre Suche im sortierten Vektor¶
- Voraussetzung: Vektor ist aufsteigend sortiert
Modifiziere Idee des Bisektionsverfahrens
- d.h. natürliche Suche im Telefonbuch
- Betrachte halben Vektor, falls
vector[j]!= value
Frage: Wieviele Iterationen hat der Algorithmus?
- jeder Schritt halbiert Vektor
- Falls Zweierpotenz, gilt
- dann maximal Schritte
- i je 2 Vergl. + 2 Zuw. + 1 Mult. + 2 Add./Subtr.
Aufwand , d.h. logarithmischer Aufwand
- sog. sublinearer Aufwand
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | int binsearch(int vector[], int value, int n) {
int j = 0;
int start = 0;
int end = n-1;
while (start <= end) {
j = 0.5*(end + start);
if (vector[j] == value) {
return j;
}
else if (vector[j] > value) {
end = j-1;
}
else {
start = j+1;
}
}
return -1;
}
|
Minsort¶
- In jedem Schritt der -Schleife
- Zuweisung
- In jedem Schritt der -Schleife
- Vergleich
- Zuweisung (worst case!)
- jeweils Vergleich
- jeweils Zuweisungen (worst case!)
- quadratischer Aufwand , weil:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | void minsort(int vector[], int n) {
int j = 0;
int k = 0;
int argmin = 0;
double tmp = 0;
for (j=0; j<n-1; j++) {
argmin = j;
for (k=j+1; k<n; k++) {
if (vector[argmin] > vector[k]) {
argmin = k;
}
}
if (argmin > j) {
tmp = vector[argmin];
vector[argmin] = vector[j];
vector[j] = tmp;
}
}
}
|
Aufwand & Rechenzeit¶
Jede Operation kostet Rechenzeit
- d.h. Aufwand korrespondiert zu Rechenzeit
Frage: Welche Rechenzeit kann ich erwarten?
- Antwort kann nur eine relative Größe sein, da Rechner unterschiedlich schnell
- theoretische Voraussagen
- linearer Aufwand
- Problemgröße Operationen
- Problemgröße Operationen
- d.h. Problemgröße Rechenzeit
- quadratischer Aufwand
- Problemgröße Operationen
- Problemgröße Operationen
- d.h. Problemgröße Rechenzeit
- etc.
- linearer Aufwand
BSP. Code braucht Sekunde für
- Aufwand Sekunden für
- Aufwand Sekunden für
- Aufwand Sek. für
Zeitmessung¶
- Wozu Zeitmessung?
- Vergleich von Algorithmen / Implementierungen
- Überprüfen theoretischer Voraussagen
- Bibliothek
time.h
- Datentyp
clock_t
für Zeitvariablen für Ausgabe Typecast nicht vergessen! - Funktion
clock()
liefert Rechenzeit seit Programmbeginn - Konstante
CLOCKS_PER_SEC
zum Umrechnen:Zeitvariable/CLOCKS_PER_SEC
liefert Angabe in Sekunden
- Datentyp
Beispiel: Zeitmessung¶
- einbinden Codes von Folie 115--117 (Zeile 15--17)
- dynamische Vektoren (Zeile 27--29) später!
- ersetzt nur
int vector[NMAX];
- ersetzt nur
auch Test mit Zufallszahlen manchmal sinnvoll
- hier auskommentiert (Zeile 38--44)
Bibliothek
time.h
time(NULL)
gibt Zeit seit 01.01.1970 in Sekunden- Fun fact: im Jahr 2038 reicht der Speicherplatz des Datentyps nicht mehr aus und es kommt zum Überlauf UNIX 2028 Bug (siehe Zahldarstellung im Computer später)
Bibliothek
stdlib.h
srand(time(NULL))
: Initialisierung des Zufallszahlen-GeneratorsRAND_MAX
: maximale Zufallszahl (Konstante)rand()
liefertint
Zufallszahl von bisRAND_MAX
Vektor-Länge wächst
- Zeit wächst linear bzw. quadratisch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | //%cflags:-I data/
//ACHTUNG: Zeile 1 ist nur fuer die korrekte Funktion des interaktiven Skriptums noetig
// include some libraries
#include <stdio.h>
#include <math.h>
#include <time.h>
// include library for dynamic storage allocation
// see line 27-29 below
#include <stdlib.h>
// define minimal and maximal problem size
#define NMIN (int)1e3
#define NMAX (int)1e6
// declaration of codes from previous slides
#include "aufwand_search.c"
#include "aufwand_binsearch.c"
#include "aufwand_minsort.c"
int main() {
int n = NMIN;
int j = 0;
int tmp=0;
clock_t start = 0;
clock_t runtime = 0;
clock_t previous_runtime = 0;
// allocate dynamic vector ==> int vector[NMAX];
printf("*** allocate integer vector, length=%d\n",NMAX);
int* vector = malloc(NMAX*sizeof(int));
// initialize vector with deterministic data
printf("*** initialize vector\n");
for (j=0; j<NMAX; ++j) {
vector[j] = j;
}
/*
// alternative: initialize vector with random numbers
srand(time(NULL));
// fill the vector with non-negative random numbers
printf("*** generate random numbers\n");
for (j=0; j<NMAX; ++j) {
vector[j] = rand();
}
*/
// print the runtime for various vector lengths
printf("*** determine computing times\n");
for (n = NMIN; n <= NMAX; n = 2*n) {
// save start time, call function, determine runtime
start = clock();
tmp = search(vector,-1,n); // linear complexity
//tmp = binsearch(vector,-1,n); // logarithmic compl.
//minsort(vector,n); // quadratic complexity
runtime = clock() - start;
// print runtime (and relative increase of runtime)
if (n == NMIN) {
printf("n=%d, runtime=%1.2f\n",n,
(double) runtime/CLOCKS_PER_SEC);
}
else {
printf("n=%d, runtime=%1.2f, factor=%1.2f\n",
n, (double) runtime/CLOCKS_PER_SEC,
(double) runtime/previous_runtime);
}
previous_runtime = runtime;
}
}
|
/tmp/tmpr2qorgxe.c: In function ‘main’: /tmp/tmpr2qorgxe.c:24:7: warning: variable ‘tmp’ set but not used [-Wunused-but-set-variable] 24 | int tmp=0; | ^~~
*** allocate integer vector, length=1000000 *** initialize vector *** determine computing times n=1000, runtime=0.00 n=2000, runtime=0.00, factor=1.20 n=4000, runtime=0.00, factor=1.83 n=8000, runtime=0.00, factor=1.91 n=16000, runtime=0.00, factor=1.90 n=32000, runtime=0.00, factor=2.10 n=64000, runtime=0.00, factor=1.99 n=128000, runtime=0.00, factor=2.02 n=256000, runtime=0.00, factor=2.04 n=512000, runtime=0.00, factor=2.00
Vergleich von Laufzeit¶
linear | quadratisch | fastlinear | |
---|---|---|---|
search | minsort | binsearch | |
1.000 | 0.00 | 0.00 | 0.00 |
2.000 | 0.00 | 0.00 | 0.00 |
4.000 | 0.00 | 0.01 | 0.00 |
8.000 | 0.00 | 0.06 | 0.00 |
16.000 | 0.00 | 0.25 | 0.00 |
32.000 | 0.00 | 1.03 | 0.00 |
64.000 | 0.00 | 4.12 | 0.00 |
128.000 | 0.00 | 16.55 | 0.00 |
256.000 | 0.00 | 64.31 | 0.00 |
512.000 | 0.00 | 257.25 | 0.00 |
1.024.000 | 0.00 | >18min | 0.00 |
2.048.000 | 0.01 | >72min | 0.00 |
4.096.000 | 0.01 | >4,5h | 0.00 |
8.192.000 | 0.02 | >18h | 0.00 |
16.384.000 | 0.04 | >3d | 0.00 |
32.768.000 | 0.08 | >12d | 0.00 |
65.536.000 | 0.15 | >1,5m | 0.00 |
131.072.000 | 0.29 | >6m | 0.00 |
262.144.000 | 0.60 | >2y | 0.00 |
524.288.000 | 1.18 | >8y | 0.00 |
1.048.576.000 | 2.53 | >32y | 0.00 |
Aufwand: Zusammenfassung¶
- Quadratischer Aufwand für große spürbar
- Fazit: Algorithmen sollen kleinen Aufwand haben
- Ziel der numerischen Mathematik
- nicht immer möglich, oft offene Frage, z.B., Aufwand für Lösen von LGS, oder in der theoretischen Informatik
Pointer¶
Variablen¶
Variable = symbolischer Name für Speicherbereich
- und Information, wie Speicherbereich interpretiert werden muss (Datentyp laut Deklaration)
Compiler übersetzt Namen in Referenz auf Speicherbereich und merkt sich, wie dieser interpretiert werden muss
Pointer¶
- Pointer = Variable, die Adresse eines Speicherbereichs enthält
- Dereferenzieren = Zugriff auf den Inhalt eines
Speicherbereichs mittels Pointer
- d.h. Schreiben eines Wertes an diese Adresse
bzw. Auslesen des dort gespeicherten Wertes
- Beim Dereferenzieren muss Compiler wissen, welcher Var.typ im gegebenen Speicherbereich liegt, d.h. wie Speicherbereich interpretiert werden muss
- d.h. Schreiben eines Wertes an diese Adresse
bzw. Auslesen des dort gespeicherten Wertes
Pointer in C¶
Pointer & Variablen sind in C eng verknüpft:
var
Variable&var
ist zugehöriger Pointerptr
Pointer*ptr
ist zugehörige Variable- insbesondere
*&var
=var
sowie&*ptr
=ptr
Bei Deklaration muss Typ des Pointers angegeben werden, da
*ptr
eine Variable sein soll!int* ptr;
deklariertptr
als Pointer aufint
- d.h.
ptr
speichert eine Adresse, an welcher einint
im Speicher abgelegt ist
Wie üblich gleichzeitige Initialisierung möglich
int var;
deklariert Variablevar
vom Typint
int* ptr = &var;
deklariertptr
und weist Speicheradresse der Variablevar
zu- Bei solchen Zuweisungen muss der Typ von Pointer und Variable passen, sonst passiert Unglück (Laufzeitfehler)!
- I.a. gibt Compiler eine Warnung aus, z.B.
incompatible pointer type
- Diese Warnung nie ignorieren, sondern Code checken, da 99\% Garantie auf Laufzeitfehler!
Analog für andere Datentypen, z.B.
double
Ein elementares Beispiel¶
%p
Platzhalter fürprintf
für Adresse- manchmal sinnvoll zum Finden von Fehlern, dass man Werte von Variablen ausgibt!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #include <stdio.h>
int main() {
int var = 1;
int* ptr = &var;
printf("a) var = %d, *ptr = %d\n",var,*ptr);
var = 2;
printf("b) var = %d, *ptr = %d\n",var,*ptr);
*ptr = 3;
printf("c) var = %d, *ptr = %d\n",var,*ptr);
var = 47;
printf("d) *(&var) = %d,",*(&var));
printf("*&var = %d\n",*&var);
printf("e) &var = %p\n", &var);
}
|
a) var = 1, *ptr = 1 b) var = 2, *ptr = 2 c) var = 3, *ptr = 3 d) *(&var) = 47,*&var = 47 e) &var = 0x7fffc438a6ec
Begrifflichkeiten¶
Call by Value
- Funktionen erhalten Werte der Input-Parameter und speichern diese in lokalen Variablen
- Änderungen an den Input-Parameter wirken sich nicht außerhalb der Funktion aus
Call by Reference
- Funktionen erhalten Variablen als Input ggf. unter lokal neuem Namen
- Änderungen an den Input-Parametern wirken sich außerhalb der Funktion aus
Call by Value / Reference in C¶
- In C gibt es nur Call by Value
- Kann Call by Reference mittels Pointern realisieren
- Vektoren werden mit Call by Reference übergeben
- kein Widerspruch, Erklärung folgt gleich!
Call by Reference in C¶
- Elementare Datentypen werden in C mit
Call by Value an Funktionen übergeben
- z.B.
int
,double
, Pointer
- z.B.
Call by Reference ist über Pointer realisierbar:
Die Funktion
test
ändert den Wert der Variablex
, da mittels Pointery
direkt auf den Speicher zugegriffen wird.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include <stdio.h>
void test(int* y) {
printf("a) *y=%d\n", *y);
*y = 43;
printf("b) *y=%d\n", *y);
}
int main() {
int x = 12;
printf("c) x=%d\n", x);
test(&x);
printf("d) x=%d\n", x);
}
|
c) x=12 a) *y=12 b) *y=43 d) x=43
Beispiel¶
determineMinMax
soll Minimum und Maximum eines Vektors finden- d.h. zwei Rückgabe-Parameter
- in C maximal ein Rückgabe-Parameter erlaubt
- Verwende Pointer für Call by Reference
- mehr als eine Funktionalität = schlechter Stil:
- jede Funktion hat genau eine Funktionalität
- jede Funktionalität erfordert eine Funktion
- oft trotzdem verwendet, weil effizienter:
- hier nur eine Schleife, statt zwei!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | #include <stdio.h>
#include <assert.h>
#define DIM 5
// The function scanVector reads the coefficients
// of a double vector of length dim from the keyboard
void scanVector(double input[], int dim);
// The function determineMinMax returns the minimum
// and maximum of a double vector of length dim. We
// use Call-by-Reference because of 2 return values.
void determineMinMax(double vector[],int dim,
double* min, double* max);
int main() {
double x[DIM];
double max = 0;
double min = 0;
scanVector(x,DIM);
determineMinMax(x,DIM, &min, &max);
printf("min(x) = %f\n",min);
printf("max(x) = %f\n",max);
}
void scanVector(double input[], int dim) {
assert(dim > 0);
int j = 0;
for (j=0; j<dim; ++j) {
input[j] = 0;
printf("%d: ",j);
scanf("%lf",&input[j]);
}
}
void determineMinMax(double vector[],int dim,
double* min, double* max) {
int j = 0;
assert(dim > 0);
// First guess for minimum as well as maximum is the
// first vector coefficient.
// Recall that, e.g., max is double* so that *max is
// a double variable.
*max = vector[0];
*min = vector[0];
// iterate through vector and search for smaller resp.
// larger elements to determine minimum and maximum.
for (j=1; j<dim; ++j) {
if (vector[j] < *min) {
*min = vector[j];
}
else if (vector[j] > *max) {
*max = vector[j];
}
}
}
|
0: 4 1: 3 2: 5 3: 6 4: 7 min(x) = 3.000000 max(x) = 7.000000
Anmerkungen zu Pointern¶
Sind
int*
,double*
Datentypen?-Erfinder sagen Nein!
int *pointer
deklariert Pointer aufint
- im Gegensatz zu:
pointer
ist vom Datentypint*
Verschiedene Sichtweisen sind beinahe äquivalent
Leerzeichen wird vom Compiler ignoriert:
int* pointer
,int * pointer
,int *pointer
*
wird nur auf den folgenden Namen bezogenACHTUNG bei Deklaration von Listen:
int* pointer, var;
deklariert Pointer aufint
und Variable vom Typint
int *pointer1, *pointer2;
deklariert zwei Pointer aufint
ALSO Listen von Pointern vermeiden!
- auch zwecks Lesbarkeit!
Elementare Datentypen¶
Elementare Datentypen¶
Datentyp für Zeichen (z.B. Buchstaben)
char
Datentypen für Ganzzahlen:
int
Datentypen für Gleitkommazahlen:
float
(einfach genaue Gleitk.zahlen, Single)double
(doppelt genaue Gleitkommazahlen)
Alle Pointer gelten als elementare Datentypen
Qualifier (nicht in EPROG!)}
Qualifier (nicht in EPROG)¶
signed
undunsigned
fürchar
undint
long
undshort
fürint
long
fürdouble
- z.B. Datentyp
signed char
,unsigned long int
- siehe https://en.wikipedia.org/wiki/C_data_types
- z.B. Datentyp
Bemerkungen¶
Deklaration und Gebrauch wie bisher
Man kann Arrays & Pointer bilden
Für UE: nur
char
,int
,double
PointerGenaueres zu den Typen später!
Funktionen¶
Elementare Datentypen werden an Funktionen mit Call by Value übergeben
Return Value darf nur
void
oder elementar sein
Arrays¶
Streng genommen, gibt es in C keine Arrays!
- Deklaration
int array[N];
- legt Pointer
array
vom Typint*
an - organisiert ab der Adresse
array
Speicher, umN
-mal einenint
zu speichern - d.h.
array
enthält Adresse vonarray [0]
- d.h.
array
& array[0]
- d.h.
*array
array[0]
- Da Pointer als elementare Datentypen mittels Call by Value übergeben werden, werden Arrays augenscheinlich mit Call by Reference übergeben
- legt Pointer
- Deklaration
Pointerarithmetik:
array+j
& array [j]
- d.h.
*(array + j)
array [j]
- d.h.
Laufzeitfehler!¶
- Ziel:
scanVector
soll Vektor anlegen & initialisieren - Syntax des Programms ist OK, aber Laufzeitfehler!
- Problem: Speicher zu
x
mit Blockende 14 aufgelöst- d.h. Pointer aus 6/13 zeigt auf Irgendwas
- Abhilfe: Call by Reference (vorher!) oder händische Speicherverwaltung (gleich!)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | #include <stdio.h>
#include <assert.h>
double* scanVector(int length) {
assert(length > 0);
double vector[length];
int j = 0;
for (j=0; j<length; ++j) {
vector[j] = 0;
printf("vector[%d] = ",j);
scanf("%lf",&vector[j]);
}
return vector;
}
int main() {
double* x;
int j = 0;
int dim = 0;
printf("dim = ");
scanf("%d",&dim);
x = scanVector(dim);
for (j=0; j<dim; ++j) {
printf("x[%d] = %f\n",j,x[j]);
}
}
|
/tmp/tmpw5npq09t.c: In function ‘scanVector’: /tmp/tmpw5npq09t.c:13:10: warning: function returns address of local variable [-Wreturn-local-addr] 13 | return vector; | ^~~~~~
dim = 4 vector[0] = 3 vector[1] = 4 vector[2] = 5 vector[3] = 6
[C kernel] Executable exited with code -11
Der Befehl sizeof
¶
- Ist
var
eine Variable eines elementaren Datentyps, gibtsizeof(var)
die Größe der Var. in Bytes zurück - Ist
type
ein Datentyp, so gibtsizeof(type)
die Größe einer Variable dieses Typs in Bytes zurück - Ist
array
lokales statisches Array im selben Block, so liefertsizeof(array)
Größe des Arrays in Bytes - Intern sind
ptr
undarray
zweidouble
Pointer und enthalten (= zeigen auf) dieselbe Speicheradresse!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #include <stdio.h>
void printSizeOf(double vector[]) {
printf("sizeof(vector) = %d\n",sizeof(vector));
}
int main() {
int var = 43;
double array[12];
double* ptr = array;
printf("sizeof(var) = %d\n",sizeof(var));
printf("sizeof(double) = %d\n",sizeof(double));
printf("sizeof(array) = %d\n",sizeof(array));
printf("sizeof(ptr) = %d\n",sizeof(ptr));
printSizeOf(array);
}
|
/tmp/tmp644_iya1.c: In function ‘printSizeOf’: /tmp/tmp644_iya1.c:4:40: warning: ‘sizeof’ on array function parameter ‘vector’ will return size of ‘double *’ [-Wsizeof-array-argument] 4 | printf("sizeof(vector) = %d\n",sizeof(vector)); | ^ /tmp/tmp644_iya1.c:3:25: note: declared here 3 | void printSizeOf(double vector[]) { | ~~~~~~~^~~~~~~~
sizeof(var) = 4 sizeof(double) = 8 sizeof(array) = 96 sizeof(ptr) = 8 sizeof(vector) = 8
Dynamische Speicherverwaltung¶
Statische Arrays¶
double array[N];
deklariert statischen Vektorarray
der LängeN
mitdouble
-Komponenten- Indizierung
array[j]
mit array
ist intern vom Typdouble*
- enthält Adr. von
array[0]
, sog. Base Pointer
- enthält Adr. von
- Länge
N
kann während Programmablauf nicht verändert werden
- Indizierung
Speicher des Arrays wird mit Blockende aufgelöst, d.h., kann keine statischen Arrays in Funktionen anlegen und zurückgeben (siehe letztes Beispiel)
Speicher allokieren¶
Jetzt: händische Speicherverwaltung von Arrays
- dadurch Vektoren dynamischer Länge möglich
- Speicher wird erst durch expliziten Befehl aufgelöst
Einbinden der Standard-Bibl:
#include <stdlib.h>
- 3 neue Befehle:
malloc
,free
,realloc
- neue Konstante:
NULL
- 3 neue Befehle:
pointer = malloc(N*sizeof(type));
- allokiert Speicher für Vektor der Länge
N
mit Komponenten vom Typtype
malloc
kriegt Angabe in Bytessizeof
pointer
muss vom Typtype*
sein- Base Pointer
pointer
bekommt Adresse der ersten Komponentepointer[0]
- Base Pointer
pointer
undN
muss sich Programm merken!
- allokiert Speicher für Vektor der Länge
Häufiger Laufzeitfehler:
sizeof
vergessen!Achtung: Allokierter Speicher ist uninitialisiert!
Konvention: Pointer ohne Speicher bekommen den Wert
NULL
zugewiesen- führt sofort auf Speicherzugriffsfehler bei Zugriff
malloc
liefertNULL
, falls Fehler bei Allokation- d.h. Speicher konnte nicht allokiert werden
Speicher freigeben¶
free(pointer)
- gibt Speicher eines dyn. Vektors frei
pointer
muss Output vonmalloc
sein
Achtung: Speicher wird freigegeben, aber
pointer
existiert weiter- Erneuter Zugriff führt (irgendwann) auf Laufzeitfehler
Achtung: Speicher freigeben, nicht vergessen!
- und Pointer auf
NULL
setzen!
- und Pointer auf
Beispiel¶
Ziel:
scanVector
soll Vektor anlegen & einlesen- Vergleiche Beispiel mit statischen Vektoren
Jetzt aber Code OK, da Funktion den Vektor dynamisch mit
malloc
allokiert- Vektor existiert (d.h. Speicher ist reserviert),
bis Basepointer mit
free
freigegeben!
- Vektor existiert (d.h. Speicher ist reserviert),
bis Basepointer mit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | #include <stdio.h>
#include <stdlib.h>
#include <assert.h>
// scanVector allocates a double vector of prescribed
// length and reads the coefficients from the keyboard
double* scanVector(int length) {
int j = 0;
double* vector = NULL;
// ensure that given length is admissible
assert(length > 0);
// allocate memory and ensure that this was successful
vector = malloc(length*sizeof(double));
assert(vector != NULL);
// read coefficients from keyboard
for (j=0; j<length; ++j) {
vector[j] = 0;
printf("vector[%d] = ",j);
scanf("%lf",&vector[j]);
}
// return base pointer of dynamic vector
return vector;
}
// printVector prints vector of given length in the shell
void printVector(double* vector, int length) {
int j = 0;
// ensure that vector and length are admissible
assert(vector != NULL);
assert(length > 0);
// print coefficients to shell
for (j=0; j<length; ++j) {
printf("vector[%d] = %f\n",j,vector[j]);
}
}
int main() {
// always set pointers to NULL, if not bound to memory
double* x = NULL;
int dim = 0;
// read vector length from keyboard
printf("dim = ");
scanf("%d",&dim);
// allocate vector, read coefficients, and print vector
x = scanVector(dim);
printVector(x,dim);
// do not forget to free memory
free(x);
// set pointer x to NULL, since not bound to memory
x = NULL;
}
|
dim = 4 vector[0] = 1 vector[1] = 2 vector[2] = 3 vector[3] = 4 vector[0] = 1.000000 vector[1] = 2.000000 vector[2] = 3.000000 vector[3] = 4.000000
Dynamische Vektoren¶
pointer = realloc(pointer, Nnew*sizeof(type))
- verändert Speicherallokation
- zusätzliche Allokation für
Nnew > N
- Speicherbereich kürzen für
Nnew < N
- zusätzliche Allokation für
- Alter Inhalt bleibt (soweit möglich) erhalten
- Rückgabe
NULL
bei Fehler
- verändert Speicherallokation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | #include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int main() {
int N = 5;
int Nnew = 10;
int j = 0;
int* array = malloc(N*sizeof(int));
assert(array != NULL);
for (j=0; j<N; ++j){
array[j] = j;
}
array = realloc(array,Nnew*sizeof(int));
assert(array != NULL);
for (j=N; j<Nnew; ++j){
array[j] = 10*j;
}
for (j=0; j<Nnew; ++j){
printf("%d ",array[j]);
}
printf("\n");
free(array);
array = NULL;
}
|
0 1 2 3 4 50 60 70 80 90
Bemerkungen¶
- Base Pointer (= Output von
malloc
bzw.realloc
) merken & nicht verändern- notwendig für fehlerfreies
free
undrealloc
- notwendig für fehlerfreies
- bei
malloc
undrealloc
nichtsizeof
vergessen- Typ des Base Pointers muss zum
sizeof
passen!
- Typ des Base Pointers muss zum
- Output von
malloc
undrealloc
aufNULL
checken- sicherstellen, dass Speicher allokiert wurde!
- allokierter Speicherbereich ist stets uninitialisiert
- nach Allokation stets initialisieren
- Länge des dynamischen Arrays merken
- kann Programm nicht herausfinden!
- Nicht mehr benötigten Speicher freigeben
- insb. vor Blockende
...}
, da dann Base Pointer weg
- insb. vor Blockende
- Pointer auf
NULL
setzen, wenn ohne Speicher- Fehlermeldung, falls Programm "aus Versehen"
auf Komponente
array[j]
zugreift
- Fehlermeldung, falls Programm "aus Versehen"
auf Komponente
- Nie
realloc
,free
auf statisches Array anwenden- Führt auf Laufzeitfehler, da Compiler
free
selbständig hinzugefügt hat!
- Führt auf Laufzeitfehler, da Compiler
- Ansonsten gleicher Zugriff auf Komponenten wie
bei statischen Arrays
- Indizierung
array[j]
für
- Indizierung
Anwendung für Pointer: Eine Vektor Bibliothek¶
Aufteilen von Source-Code¶
längere Source-Codes auf mehrere Files aufteilen
Vorteil:
- übersichtlicher
- Bildung von Bibliotheken
- Wiederverwendung von alten Codes
- vermeidet Fehler
gcc name1.c name2.c ...
- erstellt ein Executable aus Source-Codes
- Reihenfolge der Codes nicht wichtig
- analog zu
gcc all .c
- wenn
all .c
ganzen Source-Code enthält
- wenn
- insb. Funktionsnamen müssen eindeutig sein
main()
darf nur 1x vorkommen
Preprocessor, Compiler & Linker¶
Beim Kompilieren von Source-Code werden mehrere Stufen durchlaufen:
- Preprocessor-Befehle ausführen, z.B.
#include
- Compiler erstellt Objekt-Code
- Objekt-Code aus Bibliotheken wird hinzugefügt
- Linker ersetzt symbolische Namen im Objekt-Code durch Adressen und erzeugt Executable
- Preprocessor-Befehle ausführen, z.B.
Bibliotheken = vorkompilierter Objekt-Code
- plus zugehöriges Header-File
Standard-Linker in Unix ist
ld
Nur Schritt (3) fertig, d.h. Objekt-Code erzeugen
gcc -c name .c
erzeugt Objekt-Codename .o
- gut zum Debuggen von Syntax-Fehlern!
Objekt-Code händisch hinzufügen + kompilieren
gcc name.c bib1.o bib2.o ...
gcc name.o bib1.o bib2.o ...
- Reihenfolge + Anzahl der Objekt-Codes ist egal
- Ziel: selbst Bibliotheken erstellen
- spart ggf. Zeit beim Kompilieren
- vermeidet Fehler
Eine erste Bibliothek¶
- Header-File
dynamicvectors.h
zur Bibliothek- enthält alle Funktionssignaturen
- enthält Kommentare zu den Funktionen
- sog. Include Guard: Header-File beginnt mit
#ifndef NAME
#define NAME
Header-File ended mit
#endif
keine erneute Deklaration bei mehrfachen
#include
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #ifndef INCLUDE_DYNAMICVECTORS__
#define INCLUDE_DYNAMICVECTORS__
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
// allocate + initialize dynamic double vector of length n
double* mallocVector(int n);
// free a dynamic vector and set the pointer to NULL
double* freeVector(double* vector);
// extend dynamic double vector and initialize new entries
double* reallocVector(double* vector, int n, int nnew);
// allocate dynamic double vector of length n and read
// entries from keyboard
double* scanVector(int n);
// print dynamic double vector of length n to shell
void printVector(double* vector, int n);
#endif
|
Source-Code¶
- Einbinden des Header-Files (Zeile 1)
#include "..."
mit Angabe des Verzeichnis#include <...>
für Standard-Verzeichnis
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | #include "dynamicvectors.h"
double* mallocVector(int n) {
int j = 0;
double* vector = NULL;
assert(n > 0);
vector = malloc(n*sizeof(double));
assert(vector != NULL);
for (j=0; j<n; ++j) {
vector[j] = 0;
}
return vector;
}
double* freeVector(double* vector) {
free(vector);
return NULL;
}
double* reallocVector(double* vector, int n, int nnew) {
int j = 0;
assert(vector != NULL);
assert(n > 0);
assert(nnew > 0);
vector = realloc(vector,nnew*sizeof(double));
assert(vector != NULL);
for (j=n; j<nnew; ++j) {
vector[j] = 0;
}
return vector;
}
double* scanVector(int n) {
int j = 0;
double* vector = NULL;
assert(n > 0);
vector = mallocVector(n);
assert(vector != NULL);
for (j=0; j<n; ++j) {
printf("vector[%d] = ",j);
scanf("%lf",&vector[j]);
}
return vector;
}
void printVector(double* vector, int n) {
int j = 0;
assert(vector != NULL);
assert(n > 0);
for (j=0; j<n; ++j) {
printf("%d: %f\n",j,vector[j]);
}
}
|
Hauptprogramm¶
- Hauptprogramm bindet Header der Bibliothek ein
- Kompilieren mittels
gcc -c dynamicvectors.c
- erzeugt Object-Code
dynamicvectors.o
- erzeugt Object-Code
gcc dynamicvectors_main .c dynamicvectors.o
- erzeugt Executable
a.out
- erzeugt Executable
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | //%cflags:data/dynamicvectors.o -I data/
//ACHTUNG: Zeile 1 ist nur fuer die korrekte Funktion des interaktiven
// Skriptums noetig (ersetzt gcc ... dynamicvectors.o)
#include "dynamicvectors.h"
int main() {
double* x = NULL;
int n = 10;
int j = 0;
x = mallocVector(n);
for (j=0; j<n; ++j) {
x[j] = j;
}
x = reallocVector(x,n,2*n);
for (j=n; j<2*n; ++j) {
x[j] = 10*j;
}
printVector(x,2*n);
x = freeVector(x);
}
|
0: 0.000000 1: 1.000000 2: 2.000000 3: 3.000000 4: 4.000000 5: 5.000000 6: 6.000000 7: 7.000000 8: 8.000000 9: 9.000000 10: 100.000000 11: 110.000000 12: 120.000000 13: 130.000000 14: 140.000000 15: 150.000000 16: 160.000000 17: 170.000000 18: 180.000000 19: 190.000000
Strings¶
Strings (= Zeichenketten)¶
- Strings =
char
-Arrays, also 2 Definitionen möglich- statisch:
char array[N];
- N = statische Länge
- Deklaration & Initialisierung möglich
char array[] = "text";
- dynamisch (wie oben, Typ: char*)
- statisch:
- Fixe Strings in Anführungszeichen
"..."
- Einzelnes Zeichen in Hochkommata
'.'
- Zugriff auf Teil-Strings nicht möglich!
Achtung bei dynamischen Strings:
- als Standard enden alle Strings
mit Null-Byte
\0
- kann Länge eines Strings dadurch bestimmen!
- Bei statischen Arrays geschieht das automatisch
(also wirkliche Länge
N+1
undarray[N] ='\0'
) - Bei dyn. Strings also 1 Byte mehr reservieren!
- und
\0
nicht vergessen
- als Standard enden alle Strings
mit Null-Byte
An Funktionen können auch fixe Strings (in Anführungszeichen) übergeben werden
- z.B.
printf("Hello World!\n");
- z.B.
Funktionen zur String-Manipulation¶
Wichtigste Funktionen in
stdio.h
sprintf
/sscanf
agieren wieprintf
/scanf
, aber Output / Input = String
zahreiche Funktionen in
stdlib.h
, z.B.atof
: konvertiert Stringdouble
atoi
: konvertiert Stringint
oder in
string.h
, z.B.strchr
,memchr
: Suchechar
innerhalb Stringstrcmp
,memcmp
: Vergleiche zwei Stringsstrcpy
,memcpy
: Kopieren von Stringsstrlen
: Länge eines Strings (ohne Null-Byte)
Header-Files mit
#include <name>
einbinden!Gute Referenz mit allen Befehlen & Erklärungen
Details zu den Befehlen in UNIX mit
man 3 befehl
- sog. UNIX programmer's manual
- z.B. Eingabe
man 3 strcpy
in der Shell
ACHTUNG mit String-Befehlen: Befehle können nicht wissen, ob für den Output-String genügend Speicher allokiert ist ( Laufzeitfehler!)
Beispiel¶
Fixe Strings in Anführungszeichen
"..."
(Zeile 21)- erzeugt statisches Array mit zusätzlichem Null-Byte am Ende
Zugriff auf einzelnes Zeichen eines Strings mit einfachen Hochkommata
'.'
(Zeile 23)Platzhalter für Strings in
printf
ist%s
(Zeile 24)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
char* stringCopy(char* source) {
int length = 0;
char* result = NULL;
// ensure that source string exists
assert(source != NULL);
// determine length and allocate memory
length = strlen(source);
result = malloc((length+1)*sizeof(char));
// copy source string and return copy
strcpy(result,source);
return result;
}
int main() {
char* string1 = "Hello World?";
char* string2 = stringCopy(string1);
string2[11] = '!';
printf("%s %s\n",string1,string2);
free(string2);
string2=NULL;
}
|
Hello World? Hello World!
Funktionspointer¶
Funktionspointer¶
Funktionsaufruf ist Sprung an eine Adresse
- Pointer speichern Adressen
- kann daher Fkt-Aufruf mit Pointer realisieren
Deklaration eines Funktionspointers:
<return value>(*pointer)(<input>);
deklariert Pointerpointer
für Funktionen mit Parametern<input>
und Ergebnis vom Typ<return value>
- Notation entpricht der C-Denkweise:
- Was bekommt man, wenn man
pointer
dereferenziert? Funktionfct
mit Signatur<return value>fct(<input>)
- Was bekommt man, wenn man
Bei Zuweisung müssen Pointer
pointer
und Funktion denselben Aufbau haben- gleicher Return-Value
- gleiche Input-Parameter-Liste
- Aufruf einer Funktion über Pointer wie bei normalem Funktionsaufruf!
Elementares Beispiel¶
- Deklaration eines Funktionspointers in Zeile 13
- Zuweisung auf Fkt.pointer in Zeile 16 + 20
- Fkt.aufruf über Pointer in Zeile 17 + 21
- Achtung: kein dereferenzieren notwendig
(*output)(string)
ist gleichoutput(string)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | #include <stdio.h>
void output1(char* string) {
printf("*%s*\n",string);
}
void output2(char* string) {
printf("#%s#\n",string);
}
int main() {
char string[] = "Hello World";
void (*output)(char* string) = NULL;
// call function output1 via function pointer
output = output1;
output(string);
// call function output2 via function pointer
output = output2;
output(string);
}
|
*Hello World* #Hello World#
Beispiel: Bisektionsverfahren (schon wieder)¶
- Approximation der Nullstelle von
auf dem Intervall auf Genauigkeit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | #include <stdio.h>
#include <assert.h>
#include <math.h>
// bisection returns an approximate root x0 of a
// continuous real-valued scalar function fct with
// fct(a) * fct(b) <= 0, for which the intermediate
// value theorem yields the existence of a root.
//
// bisection takes the function pointer of the function
// fct: [a,b] -> R and a tolerance tol and returns x0
// in (a,b) such that |x0 - x| <= tol, where fct(x) = 0.
double bisection(double (*fct)(double x),
double a, double b, double tol);
// demo function f
double f(double x) {
return x*x + exp(x) - 2;
}
int main() {
double a = 0;
double b = 10;
double tol = 1e-12;
double x = bisection(f,a,b,tol);
printf("Approximate zero of the function x=%1.15e\n",x);
}
double bisection(double (*fct)(double x),
double a, double b, double tol) {
double m = 0;
double fa = 0;
double fm = 0;
// ensure that input is admissible
assert(a < b);
fa = fct(a);
assert(fa*fct(b) <= 0);
// iterate by interval bisection
while ( b-a > 2*tol) {
// compute interval mitpoint and evaluate fct
m = (a+b)/2;
fm = fct(m);
if ( fa*fm <= 0 ) {
// theorem guarantees root in [a,m]
b = m;
}
else {
// theorem guarantees root in [m,b]
a = m;
fa = fm;
}
}
// as loop terminates, x in [a,b] and |b-a| <= 2*tol
// so that |x-m| <= tol for the midpoint m.
return m;
}
|
Approximate zero of the function x=5.372744491739923e-01
Strukturen¶
Deklaration von Strukturen¶
- Funktionen
- Zusammenfassung von verschiedenen Befehlen, um Abstraktionsebenen zu schaffen
Strukturen
- Zusammenfassung von Variablen verschiedener Datentypen zu einem neuen Datentyp
- Abstraktionsebenen bei Daten
Beispiel: Verwaltung der EPROG-Teilnehmer
- pro Student jeweils denselben Datensatz
Semikolon nach Struktur-Deklarations-Block
- erzeugt neuen Variablen-Typ
Student
1 2 3 4 5 6 7 8 9 10 | // declaration of structure and corresponding data type
typedef struct _Student_ {
char* firstname;
char* lastname;
int studentID;
int studiesID;
double final_test;
double kreuzerl;
double exercise;
} Student;
|
Strukturen & Members¶
- Datentypen einer Struktur heißen Members
- Zugriff auf Members mit Punkt-Operator
var
Variable vom TypStudent
- z.B. Member
var.firstname
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | // declaration of structure
struct _Student_ {
char* firstname; // first name
char* lastname; // last name
int studentID; // student ID
int studiesID; // studies ID
double final_test; // points in final test
double kreuzerl; // percentage of Kreuzerl
double exercise; // points in exercises
};
// declaration of corresponding data type
typedef struct _Student_ Student;
main() {
// declaration of variable var of type Student
Student var;
// assign values to members of struct variable
var.firstname = "Dirk";
var.lastname = "Praetorius";
var.studentID = 0;
var.studiesID = 680;
var.final_test = 25.;
var.kreuzerl = 0.7;
var.exercise = 35.;
}
|
Kombinierte Deklaration¶
- gleichzeitige Deklaration von
struct
undtypedef
- anstelle von
struct _Student_ { ... }; typedef struct _Student_ Student;
- einfach
typedef struct _Student_{ ... } Student;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // declaration of structure and corresponding data type
typedef struct _Student_ {
char* firstname;
char* lastname;
int studentID;
int studiesID;
double final_test;
double kreuzerl;
double exercise;
} Student;
main() {
// declaration of variable var of type Student
Student var;
// assign values to members of struct variable
var.firstname = "Dirk";
var.lastname = "Praetorius";
var.studentID = 0;
var.studiesID = 680;
var.final_test = 25.;
var.kreuzerl = 0.7;
var.exercise = 35.;
}
|
Bemerkungen zu Strukturen¶
laut erstem C-Standard verboten:
- Struktur als Input-Parameter einer Funktion
- Struktur als Output-Parameter einer Funktion
- Zuweisungsoperator (
=
) für gesamte Struktur
in der Zwischenzeit erlaubt, aber trotzdem:
- idR. Strukturen dynamisch über Pointer
- Zuweisung (= Kopieren) selbst schreiben
- Zuweisung (
=
) macht sog. shallow copy
Shallow copy:
- nur die oberste Ebene wird kopiert
- d.h. Werte bei elementaren Variablen
- d.h. Adressen bei Pointern
- also: Kopie hat (physisch!) dieselben dynamischen Daten
Deep copy:
- alle Ebenen der Struktur werden kopiert
- d.h. alle Werte bei elementaren Variablen
- plus Kopie der dynamischen Inhalte (d.h. durch Pointer adressierter Speicher)
Strukturen: Speicher allokieren¶
- Also Funktionen anlegen
newStudent
: Allokieren und InitialisierenfreeStudent
: Freigeben des SpeicherscloneStudent
: Vollständige Kopie der Struktur inkl. dyn. Felder, z.B. Memberfirstname
(sog. deep copy)copyStudent
: Kopie der obersten Ebene exkl. dynamischer Felder (sog. shallow copy)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | Student* newStudent() {
// allocate memory for struct variable as before.
// Do not forget to use sizeof within malloc!
Student* pointer = malloc(sizeof(Student));
assert( pointer != NULL);
// dereferencing of the pointer yields a struct variable
// of type Student. The use of brackets is mandatory!
(*pointer).firstname = NULL;
(*pointer).lastname = NULL;
(*pointer).studentID = 0;
(*pointer).studiesID = 0;
(*pointer).final_test = 0.;
(*pointer).kreuzerl = 0.;
(*pointer).exercise = 0.;
return pointer;
}
|
Strukturen & Pfeiloperator¶
- Im Programm ist
pointer
vom TypStudent*
- Zugriff auf Members, z.B.
(*pointer).firstname
- Bessere Schreibweise dafür
pointer->firstname
- Bessere Schreibweise dafür
- Strukturen nie statisch, sondern stets
dynamisch
- Verwende gleich
student
für TypStudent*
- Verwende gleich
- Funktion
newStudent
lautet besser wie folgt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | // declaration of structure and corresponding data type
typedef struct _Student_ {
char* firstname;
char* lastname;
int studentID;
int studiesID;
double final_test;
double kreuzerl;
double exercise;
} Student;
// allocate and initialize new student
Student* newStudent() {
Student* student = malloc(sizeof(Student));
assert(student != NULL);
student->firstname = NULL;
student->lastname = NULL;
student->studentID = 0;
student->studiesID = 0;
student->final_test = 0.;
student->kreuzerl = 0.;
student->exercise = 0.;
return student;
}
|
Strukturen: Speicher freigeben¶
- Freigeben einer dynamisch erzeugten
Struktur-Variable vom Typ
Student
- Achtung: Zugewiesenen dynamischen Speicher
vor Freigabe des Strukturpointers freigeben
- hier:
firstname
,lastname
- alle anderen Members sind elementar
- hier:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // free memory allocation
Student* delStudent(Student* student) {
assert(student != NULL);
// delete firstname, if memory has been allocated
if (student->firstname != NULL) {
free(student->firstname);
}
// delete lastname, if memory has been allocated
if (student->lastname != NULL) {
free(student->lastname);
}
// delete memory of struct variable and return NULL
free(student);
return NULL;
}
|
Shallow Copy¶
- Kopieren einer dynamisch erzeugten
Struktur-Variable vom Typ
Student
- Kopieren der obersten Ebene einer Struktur exklusive dynamischen Speicher (Members!)
- ACHTUNG: Original und Shallow-Kopie teilen sich die dynamischen Felder
- d.h. physisch derselbe
firstname
undlastname
- Änderung
firstname
vom Original ändert auchfirstname
der Shallow-Kopie- Anwendung von
freeStudent
auf Kopie und Shallow-Kopie liefert Laufzeitfehler
- Anwendung von
- Nach erstem Aufruf ist bereits Speicher von
firstname
undlastname
freigegeben
- d.h. physisch derselbe
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // shallow copy of student
Student* copyStudent(Student* student) {
Student* copy = newStudent();
assert(student != NULL);
// note: for pointes, only adresses are copied
copy->firstname = student->firstname;
copy->lastname = student->lastname;
// copy of elementary data
copy->studentID = student->studentID;
copy->studiesID = student->studiesID;
copy->final_test = student->final_test;
copy->kreuzerl = student->kreuzerl;
copy->exercise = student->exercise;
return copy;
}
|
Deep Copy¶
- Kopieren einer dynamisch erzeugten
Struktur-Variable vom Typ
Student
- Vollständige Kopie, inkl. dynamischem Speicher
- Achtung: Zugewiesenen dynamischen Speicher mitkopieren
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | // deep copy of student
Student* cloneStudent(Student* student) {
Student* copy = newStudent();
int length = 0;
assert( student != NULL);
// full copy of firstname
if (student->firstname != NULL) {
length = strlen(student->firstname);
copy->firstname = malloc((length+1)*sizeof(char));
assert(copy->firstname != NULL);
strcpy(copy->firstname, student->firstname);
}
// full copy of lastname
if (student->lastname != NULL) {
length = strlen(student->lastname);
copy->lastname = malloc((length+1)*sizeof(char));
assert(copy->lastname != NULL);
strcpy(copy->lastname, student->lastname);
}
// copy of elementary data
copy->studentID = student->studentID;
copy->studiesID = student->studiesID;
copy->final_test = student->final_test;
copy->kreuzerl = student->kreuzerl;
copy->exercise = student->exercise;
return copy;
}
|
Arrays von Strukturen¶
- Ziel: Array mit Teilnehmern von EPROG erstellen
- keine statischen Arrays verwenden,
sondern dynamische Arrays
- Studenten-Daten sind vom Typ
Student
- also intern verwaltet mittels Typ
Student*
- also Array vom Typ
Student**
- Studenten-Daten sind vom Typ
- Zugriff auf Members wie vorher
participant[j]
ist vom TypStudent*
- also z.B.
participant[j]->firstname
1 2 3 4 5 6 7 | // declare array
Student** participant = malloc(N*sizeof(Student*));
// allocate memory for participants
for (j=0; j<N; ++j){
participant[j] = newStudent();
}
|
Schachtelung von Strukturen¶
Mitarbeiterdaten strukturieren
- Name, Wohnadresse, Büroadresse
Für
employee
vom TypEmployee*
employee->home
Pointer aufAddress
- also z.B.
employee->home->city
- Achtung beim Allokieren, Freigeben, Kopieren
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | typedef struct _Address_ {
char* street;
char* number;
char* city;
char* zip;
} Address;
typedef struct _Employee_ {
char* firstname;
char* lastname;
char* title;
Address* home;
Address* office;
} Employee;
|
Strukturen & Mathematik¶
Abstand zweier Punkte im ¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | #include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <math.h>
typedef struct _Vector3_ {
double x;
double y;
double z;
} Vector3;
Vector3* newVector3(double x, double y, double z) {
Vector3* v = malloc(sizeof(Vector3));
assert(v != NULL); // ensure proper memory allocation
v->x = x;
v->y = y;
v->z = z;
return v;
}
Vector3* delVector3(Vector3* v) {
assert(v != NULL); // ensure that vector v exists
free(v);
return NULL;
}
double dist(Vector3* v, Vector3* w) {
assert(v != NULL); // ensure that vector v exists
assert(w != NULL); // ensure that vector w exists
return sqrt( (v->x - w->x)*(v->x - w->x)
+ (v->y - w->y)*(v->y - w->y)
+ (v->z - w->z)*(v->z - w->z) );
}
int main() {
Vector3* v = newVector3(1,1,1);
Vector3* w = newVector3(1,2,3);
printf("dist(x,y) = %f\n", dist(v,w));
v = delVector3(v);
w = delVector3(w);
}
|
dist(x,y) = 2.236068
Strukturen und Vektoren¶
- Datentyp zur Speicherung von
- Dimension vom Typ
int
- Datenfeld zur Speicherung von
double
- Dimension vom Typ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | #ifndef INCLUDE_VECTOR__
#define INCLUDE_VECTOR__
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <math.h>
// declaration of new data type Vector
typedef struct _Vector_ {
int n; // Dimension
double* entry; // Vector coefficients
} Vector;
// allocate and initialize new vector of length n
Vector* newVector(int n);
// free storage of allocated vector and return NULL
Vector* delVector(Vector* X);
// return length of a vector
int getVectorN(Vector* X);
// return coefficient Xi of vector X
double getVectorEntry(Vector* X, int i);
// assign new value to coefficient Xi of vector X
void setVectorEntry(Vector* X, int i, double Xi);
// some example functions...
Vector* inputVector();
double normVector(Vector* X);
double productVector(Vector* X, Vector* Y);
#endif
|
Allokieren eines Vektors¶
- Funktion bekommt Länge des Vektors
- allokiert Struktur, weist Dimension zu
- allokiert und initialisiert Datenfeld
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | Vector* newVector(int n) {
int i = 0;
Vector* X = NULL;
assert(n > 0);
X = malloc(sizeof(Vector));
assert(X != NULL);
X->n = n;
X->entry = malloc(n*sizeof(double));
assert(X->entry != NULL);
for (i=0; i<n; ++i) {
X->entry[i] = 0;
}
return X;
}
|
Freigeben eines Vektors¶
- Datenfeld freigeben
- Struktur freigeben
NULL
zurückgeben
1 2 3 4 5 6 7 | Vector* delVector(Vector* X) {
assert(X != NULL);
free(X->entry);
free(X);
return NULL;
}
|
Zugriff auf Strukturen¶
- Es ist guter (aber seltener) Programmierstil, auf Members einer Struktur nicht direkt zuzugreifen
- Stattdessen lieber für jeden Member
set
undget
schreiben- Wenn kein
set
, dann Schreiben nicht erlaubt! - Wenn kein
get
, dann Lesen nicht erlaubt!
- Wenn kein
- Dieses Vorgehen erlaubt leichte Umstellung der Datenstruktur bei späteren Modifikationen
- Dieses Vorgehen vermeidet Inkonsistenzen der Daten und insbesondere Laufzeitfehler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | int getVectorN(Vector* X) {
assert(X != NULL);
return X->n;
}
double getVectorEntry(Vector* X, int i) {
assert(X != NULL);
assert((i >= 0) && (i < X->n));
return X->entry[i];
}
void setVectorEntry(Vector* X, int i, double Xi){
assert(X != NULL);
assert((i >= 0) && (i < X->n));
X->entry[i] = Xi;
}
|
Beispiel: Vektor einlesen¶
- Einlesen von und eines Vektors
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | Vector* inputVector() {
Vector* X = NULL;
int i = 0;
int n = 0;
double input = 0;
printf("Length of the vector n=");
scanf("%d",&n);
assert(n > 0);
X = newVector(n);
assert(X != NULL);
for (i=0; i<n; ++i) {
input = 0;
printf("x[%d]=",i);
scanf("%lf",&input);
setVectorEntry(X,i,input);
}
return X;
}
|
Beispiel: Euklidische Norm¶
- Berechne für
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | double normVector(Vector* X) {
double Xi = 0;
double norm = 0;
int n = 0;
int i = 0;
assert(X != NULL);
n = getVectorN(X);
for (i=0; i<n; ++i) {
Xi = getVectorEntry(X,i);
norm = norm + Xi*Xi;
}
norm = sqrt(norm);
return norm;
}
|
Beispiel: Skalarprodukt¶
- Berechne für
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | double productVector(Vector* X, Vector* Y) {
double Xi = 0;
double Yi = 0;
double product = 0;
int n = 0;
int i = 0;
assert(X != NULL);
assert(Y != NULL);
n = getVectorN(X);
assert(n == getVectorN(Y));
for (i=0; i<n; ++i) {
Xi = getVectorEntry(X,i);
Yi = getVectorEntry(Y,i);
product = product + Xi*Yi;
}
return product;
}
|
Zahldarstellung im Computer¶
Speichereinheiten¶
- 1 Bit = 1 b = kleinste Einheit, speichert oder
- 1 Byte = 1 B = Zusammenfassung von 8 Bit
- 1 Kilobyte = 1 KB = 1024 Byte
- nächste 2er Potenz an
- 1 Megabyte = 1 MB = 1024 KB
- 1 Gigabyte = 1 GB = 1024 MB
- 1 Terabyte = 1 TB = 1024 GB
Speicherung von Zahlen¶
- Zur Speicherung von Zahlen wird je nach Datentyp fixe Anzahl an Bytes verwendet
- Konsequenz:
- pro Datentyp gibt es nur endlich viele Zahlen
- d.h. es gibt jeweils größte und kleinste Zahl!
Ganzzahlen¶
- Mit Bits kann man Ganzzahlen darstellen
- Standardmäßig betrachtet man
- entweder alle ganzen Zahlen in
- oder alle ganzen Zahlen in
Integer-Arithmetik¶
exakte Arithmetik innerhalb
Überlauf: Ergebnis von Rechnung
Unterlauf: Ergebnis von Rechnung
Integer-Arithmetik ist i.d.R. Modulo-Arithmetik
- d.h. Zahlenbereich ist geschlossen
- liefert
- liefert
- nicht im C-Standard festgelegt!
- d.h. Zahlenbereich ist geschlossen
Beispiel: unsigned 4-bit Integer¶
- 4 Speicherplätze für 0/1 16 verschiedene Zahlen ()
Formel:
a_3 a_2 a_1 a_0
mit0 0 0 0
0 0 0 1
0 0 1 0
0 0 1 1
1 0 0 0
1 1 1 1
Addition wie im 10er System (mit Übertrag falls Ergebnis )
0 0 0 1
+0 1 0 1
=0 1 1 0
()
Beispiel: 4-bit Integer¶
4 Speicherplätze für 0/1 16 verschiedene Zahlen ()
0 0 0 0
0 1 1 1
Beobachtung:
0 1 1 1
+1 0 0 1
=0 0 0 0
( Übertrag bleibt übrig)- daher:
1 0 0 1
1 0 0 0
1 1 1 1
- daher:
- Daher:
0 1 1 1
+0 0 0 1
=1 0 0 0
(Überlauf )
Beispiel: Integer in C¶
1 2 3 4 5 6 7 8 9 10 11 12 13 | #include <stdio.h>
int main() {
int j = 0;
int n = 8*sizeof(int); // number bits per int
int min = 1;
// compute 2^(n-1)
for (j=1; j<n; ++j) {
min = 2*min;
}
printf("n=%d, min=%d, max=%d\n",n,min,min-1);
}
|
n=32, min=-2147483648, max=2147483647
2 Milliarden sind nicht viel!¶
- Achtung: Millarden
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include <stdio.h>
int main() {
int n = 1;
int factorial = 1;
do {
++n;
factorial = n*factorial;
printf("n=%d, n!=%d\n",n,factorial);
} while (factorial < n*factorial);
printf("n=%d, n!>%d\n",n+1,n*factorial);
}
|
n=2, n!=2 n=3, n!=6 n=4, n!=24 n=5, n!=120 n=6, n!=720 n=7, n!=5040 n=8, n!=40320 n=9, n!=362880 n=10, n!=3628800 n=11, n!=39916800 n=12, n!=479001600 n=13, n!=1932053504 n=14, n!>-653108224
Variablentypen short
, int
, long
¶
Bits Ganzzahlen
In C sind
short
,int
,long
mit Vorzeichen- d.h. ganze Zahlen in
Ganzzahlen durch zusätzliches
unsigned
- d.h. ganze Zahlen in
- z.B.
unsigned int var1 = 0;
- Es gilt stets
short
int
long
long long
- Standardlängen:
- 2 Byte (
short
) - 4 Byte (
int
) - 8 Byte (
long long
, seit C99)
- 2 Byte (
- In der Regel gilt
int
=long
- Standardlängen:
- https://en.wikipedia.org/wiki/C_data_types
- Für die UE nur
int
verwenden
Variablentypen char
¶
char
ist Ganzzahl-Typ, idR. 1 ByteZeichen sind intern Ganzzahlen zugeordnet
- idR. ASCII-Code
- siehe z.B. http://www.asciitable.com/
- ASCII-Code eines Buchstabens erhält man durch einfache
Hochkommata
- Deklaration
char var = 'A';
weistvar
ASCII-Code des BuchstabensA
zu
- Deklaration
Platzhalter eines Zeichens für
printf
undscanf
%c
als Zeichen%d
als Ganzzahl
ACHTUNG:
'A'
erzeugtchar
,"A"
erzeugtchar*
1 2 3 4 5 6 7 8 | #include <stdio.h>
int main() {
char var = 'A';
printf("sizeof(var) = %d\n", sizeof(var));
printf("%c %d\n",var,var);
}
|
sizeof(var) = 1 A 65
Gleitkommazahlen¶
Gleitkommadarstellung 1/3¶
SATZ: Zu existieren
- Vorzeichen
- Ziffern
- Exponent sodass gilt
Darstellung ist nicht eindeutig, da z.B.
- geometrische Reihe:
- z.B.
Bemerkungen zum Dezimalsystem¶
- Satz gilt für jede Basis
- Ziffern dann
- Dezimalsystem ist übliches System
- , , , ,
- Nichteindeutigkeit im Dezimalsystem:
Bemerkungen zum Binärsystem¶
Mit sind gekürzte Brüche genau dann als endliche Summe darstellbar (nicht als unendliche Reihe), wenn der Nenner Zweierpotenz ist:
- hat Nenner mit Zweierpotenz
- Eindeutigkeit der Primfaktorzerlegung
- formaler Beweis = freiwillige Übung
z.B. keine exakte Darstellung für für
Definition von Gleitkommazahlensystemen¶
Gleitkommazahlsystem
- endliche Teilmenge!
- Mantissenlänge
- Exponentialschranken
hat Darstellung mit
- Vorzeichen
- Ziffern mit oder
- normalisierte Gleitkommazahl ()
- denormalisierte Gleitkommazahl ()
- Exponent mit
Darstellung von ist eindeutig (Übung!)
- Ziffer muss nicht gespeichert werden
- implizites erstes Bit
Arithmetik für Gleitkommazahlen¶
- Ergebnis
Inf
,-Inf
bei Überlauf (oder1./0.
)- (
Inf
), (-Inf
)
- (
Ergebnis
NaN
, falls nicht definiert (z.B.0./0.
)- not a number (
NaN
)
- not a number (
Arithmetik ist approximativ, nicht exakt
Genauigkeit nimmt ab wenn Zahlen größer werden, d.h., nur relative Fehler sind sinnvoll.
- Beispiel:
Schlechte Kondition¶
Eine Aufgabe ist numerisch schlecht gestellt bzw. schlecht konditioniert, falls kleine Änderungen der Daten auf große Änderungen im Ergebnis führen
- z.B. hat Dreieck mit gegebenen Seitenlängen einen rechten Winkel?
- z.B. liegt gegebener Punkt auf Kreisrand?
Implementierung sinnlos, weil Ergebnis zufällig!
bessere Fragestellungen:
- Hat ein Dreieck mit gegebenen Seitenlängen einen Winkel ?
- Liegt ein Punkt in einem -Schlauch um den Kreisrand?
Rechenfehler¶
- Aufgrund von Rechenfehlern darf man Gleitkommazahlen nie auf Gleichheit überprüfen
- Statt prüfen, ob Fehler klein ist
- z.B. mit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #include <stdio.h>
#include <math.h>
int main() {
double x = (116./100.)*100.;
printf("x=%f\n",x);
printf("floor(x)=%f\n",floor(x));
if (x==116.) {
printf("There holds x==116\n");
}
else {
printf("x = %.15f\n",x);
}
}
|
x=116.000000 floor(x)=115.000000 x = 115.999999999999986
Variablentypen float
, double
¶
float
ist idR. einfache Genauigkeit nach IEEE-754-Standard- 4 Byte = 32 Bit
- 32b = 8b Exp. + 23b Mantisse + 1b Vorz.
- sog. single precision
- ca. 7 signifikante Dezimalstellen
- 4 Byte = 32 Bit
double
ist idR. doppelte Genauigkeit nach IEEE-754-Standard- 8 Byte = 64 Bit
- 64b = 11b Exp. + 52b Mantisse + 1b Vorz.
- sog. double precision
- ca. 16 signifikante Dezimalstellen
- 8 Byte = 64 Bit
Platzhalter in
printf
:%a.bf
gibta
Stellen (inklusive '.') und davonb
Nachkommastellen aus%e
gibt Zahlen in Exponentialdarstellung aus
1 2 3 4 5 6 7 | #include <stdio.h>
int main() {
double x = 2./3.;
float y = 2./3.;
printf("%17.6f, %e\n", x, x);
printf("%16.15f, %.15e\n",y, y);
}
|
0.666667, 6.666667e-01 0.666666686534882, 6.666666865348816e-01