Martin Sulzmann
Eine Liste von Funktionsprototypen. Funktionsprototypen spezifizieren wie die einzelnen Funktionen verwendet werden. Alternative Name für Funktionsprototyp sind Funktionsdeklaration oder auch Funktionssignatur.
int func1(int, float);
void func2(int, float);
int func3();
void func4();
void
seinprintf
Beachte
In C/C++ immer call-by-value (Wertübergabe).
In Java
call-by-value im Fall von primitive Datentypen
call-by-reference im Fall von Objekten (Instanzen von Klassen).
int inc(int x) {
x++;
return x;
}
void test1() {
int y,z;
y = inc(3+4);
z = inc(y);
}
Die Auswertungsschritte im einzelnen.
Betrachte y = inc(3+4)
:
Auswertung Argument 3+4
liefert 7
.
Aufruf inc(7)
liefert 8
.
Bindung von y
an den Wert 8
.
Betrachte z = inc(y)
:
Auswertung von y
liefert Wert 8
.
Aufruf von inc(8)
liefert Wert 9
.
Bindung von z
an den Wert 9
.
Betrachte Strukturen (= "public classes").
struct position {
int x;
float y;
};
void f(struct position p) {
p.x++;
// p.x == 2
}
void test2 () {
struct position q;
q.x = 1;
q.y = 2.0;
f(q);
// q.x == 1
}
Betrachte f(q)
:
Aufruf von Funktion f
. Bindung von formalen Parameter an die Werte von q
.
Innerhalb von f
, inkrementiere x Position.
Nach Rücksprung aus f
, Werte von q
sind unverändert!
Gleiches Verhalten im Falle von Beispiel 1 (da primitiver Datentyp int
).
Unterschied im Fall von Beispiel 2. In Java, call-by-reference im Fall von Objekten komplexer Klassen. Struktur position
ist eine komplexe "public class".
Deshalb in Java. Nach Rücksprung aus f
gilt: q.x == 2
!
Betrachte
3+4
Dies ist ein arithmetischer Ausdruck dessen Auswertung liefert den Wert 7
.
Betrachte
int x;
x = 1;
Der Variablen x
wird der Wert 1
zugewiesen. Im Detail bedeutet dies:
Die rechte Seite der Zuweisung x = 1
wird ausgewertet (liefert hier 1
).
Dieser Wert wird dann x
zugewiesen.
Betrachte
int x, y;
x = 1;
y = x + 2;
Die rechte Seite x + 2
wird ausgewertet.
In einem Zwischenschritt wird x
ausgewertet. Dies liefert den Wert 1
.
In einem weiteren Zwischenschritt wird der Ausdruck 1 + 2
ausgewertet. Dies liefert den Wert 3
.
Der Wert 3
wird y
zugewiesen.
Betrachte
struct position p,q;
p.x = 1;
p.y = 2.0;
q = p;
Strukturen sind auch Werte. Der Wert von p
ist {1,2.0}
. Eine Struktur vom Typ struct position
bei der x
den Wert 1
und y
den Wert 2.0
hat.
Betrachte die Zuweisung q = p
.
Rechte Seite wird ausgwertet. Liefert {1,2.0}
.
Dieser Wert wird q
zugewiesen. D.h. wird kopieren p
Elementweise nach q
.
In C/C++ immer call-by-value (Wertübergabe).
D.h. Funktionsargumente werden immer ausgewertet und der Wert wird an die Funktion übergeben.
Dies gilt für all Typen von Argumenten (ints, Strukturen, ...).
Java verwendet call-by-value für primitive Typen (int, float, ...). Für komplexe Typen (Objekte) wird call-by-reference verwendet. Call-by-reference bedeutet:
Nicht der Wert, sondern eine Referenz (wo sich die Variable im Speicher befindet wird übergeben).
Dadurch kann der Aufrufende (callee) den Wert gespeichert in der Variable ändern.
C/C++ erlaubt es Referenzen direkt darzustellen und zu manipulieren. Referenzen werden mit Hilfe von Zeigern (Pointern) und Zeigeroperationen emuliert.
Das ganze an unserem Beispiel.
void f2(struct position* p) {
(*p).x++;
}
void test3() {
struct position q;
q.x = 1;
q.y = 2.0;
f2(&q);
}
struct position* p
deklariert p
als Referenz auf einen Wert vom Typ struct position
. Anstatt Referenz sagen wir auch, dass p
ein Zeiger ist.
Um auf den eigentlichen Wert (hier eine Struktur) zuzugreifen, muss p
dereferenziert werden.
Dies geschieht via der Notation *p
.
Der Zugriff auf das Element x
geschieht via (*p).x
.
Wieso nicht *p.x
? Wird interpretiert als *(p.x)
da .
stärker bindet als der Dereferenzierungsoperator *
.
Funktion f2
erwartet eine Referenz.
q
ist der Wert und &q
liefert die Referenz auf q
.
Zusammengefasst:
struct position* p
Deklaration einer Referenz auf einen Wert mit Typ struct position
.
Zugriff auf den Wert via *p
.
Falls q
eine Struktur position, liefert &q
die Referenz.
In C++ gibt es eine "direktere" Notation für call-by reference.
void f3(struct position &p) {
p.x++;
}
int main () {
struct position q;
q.x = 1;
q.y = 2.0;
f3(q);
return 0;
}
Parameter deklariert als call-by reference, siehe void f3(struct position &p)
Keine Berechnung der Adresse bei Aufruf notwendig.
Effektiv wird call-by reference durch Zeiger implementiert.
Zusammenfassung obiger Programmfragmente. Zusätzlich betrachten wir Varianten von inc
welche Referenzparameter verwenden.
#include <stdio.h>
int inc(int x) {
x++;
return x;
}
void test1() {
int y,z;
y = inc(3+4);
z = inc(y);
}
// inc Variante mit zusaetzlichem Referenzparameter
void inc_a(int x, int* y) {
x++;
*y = x;
}
// inc nur mit Referenzparameter
void inc_b(int* x) {
(*x)++;
}
void test1_a() {
int y,z;
inc_a(3+4, &y);
inc_a(y, &z);
}
void test1_b() {
int y,z;
y = 3+4;
inc_b(&y);
inc_b(&y);
z = y;
}
struct position {
int x;
float y;
};
void f(struct position p) {
p.x++;
// p.x == 2
}
void test2 () {
struct position q;
q.x = 1;
q.y = 2.0;
f(q);
// q.x == 1
if(q.x == 1)
printf("\n q unveraendert");
}
void f2(struct position* p) {
(*p).x++;
}
void test3 () {
struct position q;
q.x = 1;
q.y = 2.0;
f2(&q);
if(q.x != 1)
printf("\n q hat sich geaendert");
}
int main() {
struct position p,q;
p.x = 1;
p.y = 2.0;
q = p;
printf("\n %d %f", p.x, p.y);
printf("\n %d %f", q.x, q.y);
test1();
test1_a();
test1_b();
test2();
test3();
return 1;
}
// Deklaration, Vorwärtsreferenz
int f(int);
int g(int);
// Definition
int f(int x) {
if(x==1) return g(x);
return 1;
}
int g(int y) {
if(y==0) return f(y+1);
return 2;
}
Wieso inferriert der Kompiler nicht automatisch diese Vorwärtsreferenzen? Zum Teil historisch bedingut. Aus Effizienzgründen versuchte man ursprünglich den Quelltext in einem Durchgang ("pass") zu kompilieren.
#include <stdio.h>
int count(void) {
static int i = 0; // statische Variable
i++;
return i;
}
int main () {
printf("Aufruf Nr. %d \n", count());
printf("Aufruf Nr. %d \n", count());
printf("Aufruf Nr. %d \n", count());
return 0;
}
Funktionsbezeichner: static
, extern
, inline
static void f(void) {}
extern void f(void);
inline void g(void) {}
static
nur sichtbar im gleichen File
extern
global sichtbar
inline
Ersetzung Funktionsaufruf durch Funktionsrumpf
Beispiel.
inline int inc(int x) { return x+1; }
int main() {
int y = 1;
int z;
z = inc(y+3); // Rechte Seite ersetzt durch "(y+3)+1"
}
Beachte:
inline
sollte nur auf "kleine", Performanzkritische Funktionen angewandt werden. Wieso? Durch inline
spart man sich den Funktionsaufruf (die Parameterübergabe, anlegen von lokalen Variablen etc) aber der Programmcode wird dupliziert.
inline
kann nur auf Funktionen und statische Methoden (in C++) angewandt werden. Wieso? Der Compiler muss statisch (= zur Compilezeit) Zugriff auf den Programmcode der Funktion haben. Dies ist nur für Funktionen und statische Methoden (in C++) möglich, nicht aber für virtuelle Methoden (in C++).
Bisher ein File, nicht realisitisch für grössere Projekte
Aber: C hat kein Modulkonzept
Konvention:
Deklaration (Schnittstelle) in Header File (.h)
Definition (Implementierung) in Source File (.c)
// fact.h Header mit Funktions-prototyp
int fact(int n);
// fact.c Source
#include "fact.h"
int fact(int n) {
return n ? n*fact(n-1) : 1;
}
// fact_main.c Anwendung
#include <stdio.h>
#include "fact.h"
int main () {
printf("fact(3)=%d \n", fact(3));
return 0;
}
Beachte:
Im .h
File stehen in der Regel nur Deklarationen.
.c
File "inkludiert" die Deklaration. Dadurch wird garantiert, dass die Deklaration mit der Definition übereinstimmt.
#include
sucht nach File mit dem Namen.
#include "file.h"
sucht nach file.h
ausgehend vom aktuellen Projektordner.
#include <file.h>
sucht nach file.h
unter Berücksichtung von Umgebungsvariablen (dadurch können Bibliotheken lokalisert werden) und sucht erst dann ausgehend vom aktuellen Projektordner.
Ablegen von .c
und .h
in jeweilige Ordner
Automatischer "build" Prozess
Kompilierung der .c Files .o Objekt Files
Linker baut "main"
Im Regelfall Abhängigkeiten definiert durch ein makeFile
fact.c
$gcc.exe -c fact.c -o fact.o
fact_main.c
, linke dazu fact.o
$gcc.exe fact.o fact_main.c
Ziel : Quellen
Aktion
Makefile
:CC=gcc.exe
fact_main.exe : fact_main.o fact.o
$(CC) fact.o fact_main.o -o fact_main.exe
fact_main.o: fact_main.c
$(CC) -c fact_main.c
fact.o: fact.c fact.h
$(CC) -c fact.c
make Makefile
Präprozessor Direktive/Befehle am Anfang einer Zeile, Start mit #
Ersetzungsbefehle
#include and #define
#ifdef, #ifndef, #endif, ...
Effektiv ist der Präprozessor ein Textmanipulationsprogramm.
Alle Präprozessor Direktive/Befehle in einem File werden ausgeführt und erst dann wird das File compiliert.
#include
#include "filename"
#include <filename>
#include ...
mit dem Inhalt von filename."..."
startet Suche von der aktuellen (directory) Position.<...>
durchsucht erst vorgegebene Ordner.#define
(Macro)Form
#define Name Ersetzung
Reine Vorwärts-ersetzung
Ersetzung muss innerhalb einer Zeile sein.
Macros dürfen Parameter haben. Aber verschieden von Funktionsaufruf!
#include <stdio.h>
#define PI 3.141592654
#define MAX(A,B) ((A)>(B)?(A):(B))
#define SQUARE(X) X*X
int main() {
int x, y;
x = 1;
y = 2;
printf("%d \n", SQUARE(x+y));
printf("%d \n", SQUARE((x+y)));
return 1;
}
Zwei Standardbeispiele
Wechsel zwischen verschiedenen Versionen (ohne das auskommentiert werden muss)
#include <stdio.h>
#define VERSION_STABLE
// Stabile Variante
#ifdef VERSION_STABLE
int square(int x) {
return x * x;
}
#endif
// Experimentelle Variante
#ifndef VERSION_STABLE
int square(int x) {
if (x == 1) return 1;
return x * x;
}
#endif
int main() {
printf("%d", square(3));
}
Falls ein Header zweimal inkludiert wird, wird er auch zweimal verarbeitet.
In der Regel unproblematisch weil C mehrfache Deklarationen erlaubt. Z.B.
int x;
int x;
Zweimal den gleichen Header verarbeiten ist aber sicher Zeitverschwendung.
Im Header (vor allem in C++) befinden sich aber auch oft Definitionen. Dann wird es problematisch.
Wie können wir verhindern, dass ein Header nur einmal inkludiert wird? Wir wenden folgenden Trick an.
#ifndef __FACT_HEADER__
#define __FACT_HEADER__
int fact(int n);
#endif
#include <stdlib.h>
http://www.cplusplus.com/reference/clibrary/cstdlib/
#include <ctype.h>
http://www.cplusplus.com/reference/clibrary/cctype/
#include <string.h>