• Keine Ergebnisse gefunden

Aufbau eines C-Programms

N/A
N/A
Protected

Academic year: 2022

Aktie "Aufbau eines C-Programms"

Copied!
118
0
0

Wird geladen.... (Jetzt Volltext ansehen)

Volltext

(1)

6 Die Programmiersprache C

6.1 Datentypen und Deklarationen 6.2 Operatoren und Ausdrücke

6.3 Ablaufsteuerung (Kontrollstrukturen) 6.4 Unterprogramme

6.5 Zeiger und komplexe Datenstrukturen 6.6 Dateien, Ein- und Ausgabe

6.7 Weitere Sprachkonstrukte

6.8 Die Umgebung von C-Programmen 6.9 Beispiel

Die Bedeutung der Sprache C

C ist die "Muttersprache" von UNIX

• C ist hochgradig portierbar und standardisiert (ANSI:

X3.159-1989)

• C ermöglicht eine sehr effiziente, maschinennahe Programmierung

• C Schlägt die Brücke von Java zu Assembler- Programmierung

• Aber: C erlaubt viele „unsaubere“ Programmiertricks

Literatur

Karlheinz Zeiner

"Programmieren lernen mit C"

Carl Hanser Verlag 1994

Kernighan/Ritchie

"Programmieren in C"

Carl Hanser Verlag 1990

(2)

6.1 Datentypen und Deklarationen

Es gibt elementare Datentypen, z.B. zur Beschreibung von Zahlen oder einzelnen Zeichen, und es gibt kom- plexe Datentypen, z.B. zur Beschreibung von verkette- ten Listen oder von Datenstrukturen bestehend aus ver- schiedenen Komponenten.

Merke: In C müssen alle Daten vor ihrer Verwendung vereinbart (deklariert) werden, wie in Java, anders als z.B. in Fortran oder Basic.

Ein C-Programm setzt sich aus Vereinbarungen und Anweisungen zusammen. Die Anweisungen definieren die Kontrollstruktur eines Programms. Die Vereinbarun- gen beinhalten die Datenstrukturen, auf denen ein Pro- gramm arbeitet.

Aufbau eines C-Programms

int

main

( int

argc , char *

argv []

Vereinbarungen und Anweisungen }

Hauptprogramm

)

{ void

argc und argv stellen die Verbindung des Programms zu seiner Umwelt her. Hier wird die Anzahl der Argu- mente und ein Vektor mit den Werten der Argumente von der Kommandozeile an das C-Programm überge- ben. Sie sind zwar im ANSI-Standard nicht beschrieben, haben sich aber als de-facto-Standard etabliert.

(3)

Vereinbarungen in C – Programmen (1)

Vereinbarungen

Konstantenvereinbarung Typenvereinbarung

Variablenvereinbarung

Routinenvereinbarung

Vereinbarungen in C-Programmen (2)

Merke: Die im Syntaxdiagramm angegebene Reihenfol- ge der Vereinbarungen ist nicht zwingend vorgeschrie- ben. Es ist jedoch sinnvoll, sich an diese Reihenfolge zu halten.

In C gibt es zwei Arten von Vereinbarungen:

• Vereinbarungen, die Speicherplatz reservieren (Defi- nitionen), z.B. Variablenvereinbarungen, Routinen- definitionen

• Vereinbarungen, die eine Variable/einen Daten- typ/eine Routine beschreiben (Deklarationen) z.B. Typvereinbarungen, Prototypen von Routinen.

(4)

Typvereinbarung

Typvereinbarung

typedef Typ Typname ;

,

typedef dient zur Definition eigener Datentypen als Ableitung aus bestehenden Datentypen.

Beispiel

typedef int alter;

alter lebensalter, firmenbestand;

Vordefinierte Datentypen in C

einfacher Typ

strukturierter Typ

Zeigertyp Typ

Hinweis: strukturierte Typen und Zeigertypen werden wir später erklären.

(5)

Einfache Datentypen

einfacher Typ

elementarer Datentyp

Aufzählungstyp

Elementare Datentypen

elementare Datentypen

signed

char

unsigned

int

short

long

float

double

long double int int

Elementare Datentypen sind die Grundbausteine aller anderen Datentypen.

(6)

Beschreibung der elementaren Datentypen

integer

• unsigned int = natürliche Zahlen inclusive 0, als Bi- närzahlen abgespeichert.

• (signed) int = Ganzzahlen, in der Regel in Zweier- komplementdarstellung abgespeichert.

Beispiel:

Bits unsigned signed

000 0 0

001 1 1

010 2 2

011 3 3

100 4 -4

101 5 -3

110 6 -2

111 7 -1

N = Anzahl Bits

Wertebereich unsigned: 0 bis 2N - 1 Wertebereich signed: -2N-1 bis 2N-1 – 1

Ganze Zahlen unterschiedlicher Länge

char/short/long int

Datentypen für ganze Zahlen unterschiedlicher Länge.

Es gilt:

len(char) ≤ len(short) ≤ len(int) ≤ len(long)

In der Regel belegt char 8 Bits (1 Byte), short 16 Bits (2 Bytes) und int 32 Bits (4 Bytes). Dies ist jedoch ab- hängig vom Compiler.

(7)

Zeichen (1)

Der Datentyp char

Für den Datentyp char gilt, daß der Wertebereich aus diskreten Einzelwerten besteht, die einer Ordnungsrela- tion unterliegen. Zeichen werden in einem Byte gespei- chert und auf ganze Zahlen abgebildet.

Im ASCII-Code entspricht beispielsweise '0' der Zahl 48, 'A' der Zahl 65 und 'a' der Zahl 97.

Für Steuerzeichen gibt es in C eine Ersatzdarstellung.

Der backslash (\) wird dabei als Escape-Zeichen be- zeichnet, welches "die normale Interpretationsebene verlässt".

Beispiele

\n steht für Zeilenvorschub

\g steht für Glocke

\t steht für Tabulator.

Zeichen (2)

Die Ordnung der Zeichen untereinander (die Kollati- onsfolge) ist in C implementierungsabhängig. Der Standard fordert lediglich, dass die Ziffern 0 – 9 aufstei- gend und lückenlos angeordnet sind sowie dass die Großbuchstaben und die Kleinbuchstaben in alphabeti- scher Reihenfolge angeordnet sind. Eine Ordnung zwi- schen der Menge der Großbuchstaben und der Menge der Kleinbuchstaben ist beispielsweise nicht vorge- schrieben.

(8)

Gleitkommazahlen (1)

float/double/long double

Die Datentypen float, double und long double werden zur Darstellung von reellen Zahlen verwendet. Der im Prinzip kontinuierliche Zahlenbereich der reellen Zahlen kann im Computer nicht vollständig dargestellt werden, da hierzu unendlich viele Codewörter benötigt würden.

Man kann nur rationale Zahlen mit einer begrenzten An- zahl von Stellen darstellen. Dazu werden reelle Zahlen ins Dualsystem übertragen und in Gleitpunktdarstellung mit Vorzeichen, Mantisse und Exponent gespeichert.

Beispiel

5.75 = 4 + 0 + 1 + 0.5 + 0.25

= 1*22 + 0*21 + 1*20 + 1*2-1 + 1*2-2

= 101.11

= 0.10111 * 23

Gleitkommazahlen (2)

Eine Gleitkommazahl erfordert also die maschineninter- ne Codierung

• der Mantisse

• des Vorzeichens der Mantisse

• des Exponenten

• des Vorzeichens des Exponenten.

Dazu gibt es verschiedene Konventionen; die bekannte- ste ist die von IEEE (Standard 754).

Folgende Grenzwerte müssen von einem C-Compiler mindestens eingehalten werden:

Typ kleinste Zahl größte Zahl Genauigkeit float

double

1E-37 1E-37

1E+37 1E+37

1E-5 1E-9

(9)

Wahrheitswerte

Jede ganze Zahl x kann in C einen Wahrheitswert dar- stellen. Dabei gilt per Konvention:

x = 0 impliziert x = false

x ≠ 0 impliziert x = true

Falls ein Wahrheitswert auf eine ganze Zahl abgebildet werden soll, gilt:

false entspricht x = 0

true entspricht x = 1

Aufzählungstyp (enumeration type) (1)

Eine Aufzählung ist eine Folge von Zeichenkonstanten, denen vom Compiler jeweils ein Integer-Wert zugeord- net wird.

Syntax

enum Typname { Enum-

erator } ;

, Aufzählungstyp

Beispiel

enum fruit {apple = 7, pear, orange = 3, lemon, peach}

Nun gilt: apple = 7, pear = 8, orange = 3, lemon = 4, pe- ach = 5.

Merke:

Aufzählungen werden wie Konstanten behandelt. Die mehrfache Verwendung desselben Integerwertes ist möglich, aber die Enumeratoren müssen eindeutig sein.

(10)

Konstantenvereinbarung

In C gibt es zwei Möglichkeiten, Konstanten zu verein- baren:

• const-Vereinbarung

Die const-Vereinbarung definiert eine Variable, die nur gelesen und nicht zugewiesen werden darf. Die Verwendung zur Definition der Länge einer anderen Datenstruktur (z.B. einer Vektorlänge) ist deshalb syntaktisch nicht erlaubt.

• #define-Klausel

#define suchtext ersatztext

Die #define-Klausel definiert sogenannte Compiler- Konstanten. Beim Übersetzen wird im gesamten Pro- grammcode der Suchtext durch den Ersatztext er- setzt. Wenn man also Suchtext als Konstantenname und Ersatztext als Konstante ansieht, hat man eine Compiler-Konstante. Bei #define können als Kon- stanten auch Ausdrücke eingesetzt werden.

Beispiel

#define max_length 1000 int a [max_length];

Syntax für Konstante in C

Konstanten-Vereinbarung

const Konstanten-

name

Konstante

Konstante

Zahl

Zeichen

Zeichenkette

Typ =

;

#define-Klausel

#define Suchtext Ersatztext

(11)

Beispiele für Konstantenvereinbarungen (1)

Zahlen

const float pi = 3.14159;

const int ganze_Zahl = 79;

const int hexa_Zahl = 0x13;

Zeichen

const char anfang = "a";

Zeichenkette

const char alphabet = "abc";

Variablenvereinbarung

Variablenvereinbarung

Typ Variablen-

name

;

,

Initiali- sierung

Beispiele

int anz_kinder;

float groesse;

char initiale_vorname, initiale_nachname;

Konstante vom Typ der Variablen

= Initialisierung

Beispiel int a = 5;

(12)

6.2 Operatoren und Ausdrücke

Ausdruck (expression)

• Verarbeitungsvorschrift zur Ermittlung eines Wertes

• besteht aus Operanden und Operatoren

• wichtigste Ausdrücke: arithmetische und logische (boole'sche) Ausdrücke

Beispiele

int i = 5, j = 2, k =23;

float x = 2.0, y = 5.5;

double d = 2.4;

Ausdruck Resultat i / j

k % i*j k – 7 % 5

x*y -i y / x y % x

d / 2

2 6 21 6.0 2.75 nicht erlaubt

1.2

Regeln

• Bei der Auswertung gelten, wie üblich, Vorrangregeln, z.B. Punktrechnung vor Strichrechnung

• a / b ergibt für int a,b wiederum einen int-Wert, nämlich den Ganzzahl-Anteil der Division

• a % b (die modulo-Funktion) ist auf float und dou- ble nicht erlaubt

• bei Mischung von int und float/double in einem Ausdruck ist das Resultat vom Typ float/double.

(13)

Der L-Wert (1)

Ein L-Wert ist ein Ausdruck, der einen benannten Spei- cherbereich bezeichnet. L-Werte sind Objekte, denen Ergebnisse von Operationen zugeordnet werden kön- nen. Ein Beispiel für einen L-Wert ist ein Variablenname mit geeignetem Typ und passender Speicherklasse.

Manche Operatoren erwarten L-Werte als Operanden, manche liefern einen L-Wert als Resultat.

Beispiele

&lvalue Adressoperator

*expression Inhaltsoperator (liefert L-Wert)

Der L-Wert (2)

L-Wert

Bezeichner

Bezeichner

Bezeichner

Bezeichner Bezeichner

Ausdruck

Ausdruck L-Wert

L-Wert

*

( )

->

.

[ ]

(14)

Arithmetische Operatoren

+ * / %

arithmetischer Ausdruck

Ausdruck 1 arithm. Operatoren Ausdruck 2

arithmetischer Operator

Beispiel a+b

(x-y) / (z%5)

Zuweisungsoperator

Zuweisung

L-Wert Zuweisungs-

operator Ausdruck ;

Zuweisungsoperator

+ - * / % >> <<

=

Beispiele a += 5 l[a] = 17+4 a = b = c = 7

(15)

Unäre Ausdrücke

Unärer Ausdruck

unärer arithmetischer Ausdruck

Postfix-Arithmetik

Prefix-Arithmetik

Inhalts- Operator

Cast- Operator

Sizeof- Operator

Unärer arithmetischer Ausdruck

Syntax

unärer arithmetischer Ausdruck

+ - ! ~

Ausdruck

*

+ (a+b) - (x*y)

Vorzeichen

!a logische Negation (NOT)

*a Inhaltsoperator

~a bitweise Negation

L-Wert Adreßoperator

&

Beispiel

&y

(16)

Inkrement/Dekrement (1)

Syntax

Postfixarithmetik

L-Wert

++

-- Prefixarithmetik

L-Wert ++

--

Inkrement/Dekrement (2)

Postinkrement

Wert der Variablen wird erst nach Auswertung des Aus- drucks, in dem die Variable vorkommt, erhöht.

Preinkrement

Wert der Variablen wird vor Auswertung des Ausdrucks, in dem die Variable vorkommt, erhöht.

Beispiel für das Postinkrement int a, b;

a = 1;

b = a++;

/* b=1, a=2 */

Beispiel für das Preinkrement int a,b;

a = 1;

b = ++a;

/* b=2, a=2 */

Analog für das Dekrement.

(17)

Typkonversionsoperator (type cast)

Syntax

cast - Operator

( Typname ) Ausdruck

In C gibt es keine strenge Bindung der Variablen an ihre Datentypen! Treffen in einer Operation Operanden mit unterschiedlichem Datentyp zusammen, so wandelt C nach definierten Regeln implizit um. Ist die Umwand- lungsregel nicht offensichtlich (z.B. int ->float), so sollte explizit konvertiert werden, um anzuzeigen, wie die Konvertierung erfolgen soll.

Beispiel int a;

double x;

a = (int) (5 * x);

Der sizeof - Operator

Syntax

sizeof - Operator

Typname

Ausdruck

sizeof ( )

C verwendet den unären Operator sizeof, um die Anzahl von Bytes zu bestimmen, die zur Speicherung eines Objektes benötigt werden.

Beispiele int a;

sizeof (char) liefert in der Regel 1 sizeof (a*2) liefert sizeof (int)

(18)

Vergleichsoperatoren (1)

Syntax

Vergleich

Gleichheitsoperator

Relationsoperator

Gleichheitsoperator

Ausdruck 1 Ausdruck 2

==

Relationsoperator

Ausdruck 1

Ausdruck 2

>

< < = >=

!=

Vergleichsoperatoren (2)

Die Rangordnung der Relationsoperatoren ist kleiner als die der arithmetischen Operatoren.

Die Rangordnung der Gleichheitsoperatoren ist kleiner als die der Relationsoperatoren.

Das Ergebnis von Vergleichsoperatoren ist ein Wahr- heitswert.

Beispiele a ==b y >= z

(19)

Logische Operatoren

Syntax

logische Verknüpfung

Ausdruck 1 Ausdruck 2

&&

Die Präzedenz von && ist höher als die von ||, aber bei- de haben einen niedrigeren Rang als unäre, arithmeti- sche und Vergleichsoperatoren. Lediglich die Zuwei- sungsoperatoren haben einen noch niedrigeren Rang

Der Komma-Operator

Syntax

Komma - Ausdruck

Ausdruck 1 , Ausdruck 2

Zwei Ausdrücke werden nacheinander von links nach rechts ausgeführt. Datentyp und Resultat des Ausdrucks sind vom Typ und Wert von Ausdruck 2.

Beispiel

for ( s = 0, i = 0; i< n; i++) s += x [i];

(20)

Bitoperatoren (1)

Die kleinste adressierbare Speichereinheit in C ist ein Byte (char). Es ist aber möglich, auch einzelne Bits zu manipulieren. Dies erfolgt mit Hilfe der Bitoperatoren.

Bitweise logische Operatoren

Aktion Symbol

Bitweises Komplement ~ Bitweises AND

&

Bitweises OR |

Bitweises XOR ^

Shift-Operatoren

LEFTSHIFT <<

RIGHTSHIFT >>

Bitoperatoren (2)

Der Operator ~ ist als einziger Bitoperator unär (einstel- lig). Er bildet das bitweise Komplement.

Beispiel

x = (int) 5 (dezimal)

⇒ x = 0x0000000000000101 (hexadezimal)

⇒ ~x = 0x1111111111111010 (hexadezimal)

Die anderen Operatoren sind binäre (zweistellige) Ope- ratoren und arbeiten, wie in der Aussagenlogik definiert.

Beispiel

x = 0x1100; y = 0x1010;

x ^ y ergibt 0x0110 (XOR-Operator)

(21)

Bitoperatoren (3)

Die Shiftoperatoren verschieben den gesamten Bits- tring um n Bits nach rechts oder links. So lässt sich z.B.

auch leicht eine 2er-Multiplikation/Division implementie- ren.

Beispiel

x = 0x0101 (= 5 dezimal)

x << 1 ergibt 0x1010 (=10 dezimal)

Merke: Es kommt leicht zu Verwechselungen von & mit

&&. Dies kann fatale Auswirkungen haben!

Beispiel

unsigned short int x = 2; /* Länge = 1 Byte */

x && 1 liefert als Ergebnis 0 oder 1 zurück, abhängig von x (Wert ≠ 0 wird als Wahr- heitswert true interpretiert)

x & 1 liefert

00000010 00000001 00000000

Ausdruck

einfacher Ausdruck Ausdruck

unärer Ausdruck

arithmetischer Ausdruck Zuweisung

Vergleich

logische Verknüpfung

Bit - Ausdruck

Komma - Ausdruck

bedingter Ausdruck

(22)

Bedingter Ausdruck

Syntax

Ausdruck 1 ? Ausdruck 2 : Ausdruck 3

bedingter Ausdruck

Der Code ist äquivalent zu

x = (y < z) ? y : z; if (y < z) x = y;

else

x = z;

Beispiel

max = (a < b) ? b : a;

Präzedenzregeln

Die Präzedenzregeln (Rangordnung der Operatoren) sind (wie üblich) an die Gepflogenheiten der Mathematik angelehnt.

• Geklammerte Ausdrücke zuerst

• Ungeklammerte Ausdrücke gemäß vier Präzedenz- klassen:

1.) NOT

2.) Multiplikationsoperatoren 3.) Additionsoperatoren 4.) Vergleichsoperatoren

• bei gleicher Präzedenz erfolgt die Abarbeitung von links nach rechts

Beispiele

a) (3 <= 8 * 8 + 4) || (9 / 3 + 4 * 3 <= 10) = (3 <= 68) || (15 <=

10)

=1 || 0

=1 (TRUE)

b) 3 - 8 + 4 *2 - 9 / 2 % 3*2 + 1 = 3 - 9 / 2 % 3 * 2 + 1

=4 – 2

=2

c) !a && b || c entspricht ((!a) && b) || c

(23)

Überlauf/Unterlauf bei ganzen Zahlen

Auch ganze Zahlen (integers) haben wegen der be- grenzten Wortlänge des Computers einen beschränkten Wertebereich (z.B. 32 Bits). Ein Ganzzahl-

Überlauf/Unterlauf tritt bei Verlassen des Wertebereichs auf.

Sei z eine ganze Zahl (also vom Datentyp int).

Dann gilt:

min ≤ z ≤ max , min < max,

wobei min und max den zulässigen Wertebereich für z begrenzen.

Beispiele für Überläufe

Merke: Damit bei arithmetischen Operationen das Er- gebnis korrekt ist, müssen auch alle Zwischenresultate innerhalb des zulässigen Wertebereiches bleiben!

Sei -min = max = 1000.

700 + 400 - 200 80 * 20 / 4

erzeugen Überläufe in Zwischenresultaten, wenn sie von links nach rechts ausgewertet werden. Solche Überläufe können unter Umständen durch Klammerung vermieden werden:

700 + (400 - 200) 80 * (20 / 4)

Manche Compiler kümmern sich bereits selbst um eine solche Änderung der Reihenfolge der Auswertung. Man kann sich jedoch nicht darauf verlassen!

Man könnte sagen: Das Assoziativgesetz gilt nicht mehr, wenn Bereichsgrenzen überschritten werden.

(24)

Operatoren (1)

Rang Art Symbol

in der Logik/

Arith- metik

Ope- rator in C

Name Beispiel

1 ( ) ( ) Klammern (a+b)

1 monadisch [ ] [ ] Array/

Vektor

a[ ]

1 monadisch -> Kompo-

nente

a-> b

1 monadisch . Kompo-

nente

a . b 2 monadisch/

arithmetisch

++ Addition von 1

a++

monadisch/

arithmetisch

-- Subtraktion von 1

--b 2 monadisch/

logisch

¬ ! Negation !treu

2 monadisch/

logisch

NOT ~ Bitkom-

plement

~y

2 monadisch (type) typecast (int) x

2 monadisch sizeof sizeof si-

zeof(int)

Operatoren (2)

2 monadisch/

arithmetisch

+ + Plusvorzei-

chen

+7 monadisch/

arithmetisch

- - Minusvorzei-

chen

-7

2 monadisch * Verweis *a

2 monadisch & Adresse &a

3 dyadisch/

arithmetisch

* * Multiplikatin 3*4

dyadisch/

arthimetisch/

/ / Division 3 / 4

dyadisch/

arithmetisch

DIV / ganzz. Divi- sion

3 / 4 = 0 dyadisch/

arithmetisch

MOD % ganzz. Rest 3%4 = 3 4 dyadisch/

arithmetisch

+ + Addition 3+4

dyadisch/

arithmetisch

- - Subtraktion 3-4

5 dyadisch/

logisch

LSHIFT << Linksshift um Bitpositionen

x<<5 5 dyadisch/

logisch

RSHIFT >> Rechtsshift um Bitposi- tionen

x>>5

(25)

Operatoren (3)

6 dyadisch < < Relation- soperatoren

a>b

dyadisch ≤ <=

dyadisch > >

dyadisch ≥ >=

7 dyadisch ==

=!

==

=!

Gleichheits- operatoren

a==b 8 dyadisch/ lo-

gisch

^ & Logisches

`und` auf Bits

x & y

9 dyadisch/ lo- gisch

XOR ^ Logisches

`XOR` auf Bits

x ^ y

10 dyadisch/ lo- gisch

∨ | Logisches

`oder` auf Bits

x | y

11 dyadisch/

logisch

∧ && logisches 'und'

a && b 12 dyadisch/

logisch

∨ || logisches

'oder'

a || b

13 ?: bedingter

Ausdruck

a ? c : b

Operatoren (4)

14 =

+=

-=

*=

/=

%=

&=

^=

|=

<<=

>>=

Wertzu- weis-ung

15 , Kommaope-

rator

s=0, i=0

(26)

6.3 Ablaufsteuerung (Kontroll struktren)

Sequenz von Anweisungen (Hintereinander- Ausführung)

Selektion

if ... else

switch ...

Iteration

while ...

do... while ...

for ...

6.3.1 Anweisungen

Ein Programm besteht aus einer Folge von Anweisun- gen.

Anweisung

leere Anweisung Zuweisung Routinen-Aufruf Anweisungsblock Iterations-Anweisung Auswahl-Anweisung Anweisung mit Marke

Sprung-Anweisung

(27)

Wertzuweisung

("Ergibt - Anweisung", assignment statement)

• Berechnung des rechts stehenden Ausdrucks

• Wertzuweisung an die links stehende Variable

Syntax

Zuweisung

L-Wert Zuweisungs-

operator Ausdruck ;

Merke: Der Ausdruck sollte stets denselben Datentyp haben wie die Variable; wenn dies nicht sowieso der Fall ist, sollte ein Typecast programmiert werden!

Beispiel

summe += 4; /*identisch zu summe = summe +4;*/

produkt *= i; /*identisch zu produkt = produkt * i;*/

Da in C Zuweisungen syntaktisch selbst Ausdrücke sind, sind "Mehrfachzuweisungen" (verkettete Zuwei- sungen) in C möglich!

Beispiele

a = b = c = 0.0;

if ((c=getc(datei))!= EOF) ...

[Man beachte die Klammerung wegen der Vorrangregel]

Die Initialisierung mehrerer Werte in einer Anweisung und die Verwendung als Operand in einem Aufruf sind typische Anwendungen.

(28)

Wertzuweisung ungleich Vergleichsoperator!

i = i + 1; ist eine Wertzuweisung an i;

Neuer Wert ist alter Wert + 1

i == i + 1 ist ein boole'scher Ausdruck, der immer falsch ist!

Syntaktisch ist auch dieser Ausdruck kor- rekt. Er kann ersetzt werden durch 0 (fal- se).

Häufige Ursache von Programmierfehlern!

Beispiele für Wertzuweisungen

Die Variablen seien wie folgt deklariert:

int i, j, k;

double x, y, z;

int fertig;

i = j / k + i;

z = x / y;

fertig = (z < j) && ! (x == y);

i = j / x; implizite Typkonvertierung! */

i = z / x + 15; implizite Typkonvertierung */

fertig = 1 && x; zugewiesen wird 0 oder 1! */

(29)

Folgen von einfachen Anweisungen (Sequenz)

Folge von Anweisungen, sequentiell aufgeschrieben, getrennt durch ";"

Anweisung

Sequenz

Beispiele

a = b; x = y + 3; z = 17;

a = 3 * 87 - 5;

wahr = (x < 5) || (y > 20);

Anweisungsblock

Anweisungsblock

Deklaration Anweisungs- Sequenz

{ }

Ein Anweisungsblock kann überall da stehen, wo eine einfache Anweisung stehen kann, insbesondere

in Abhängigkeit von einer Bedingung

im Rumpf einer Schleife.

Der gesamte ausführbare Teil eines C-Programms kann als Anweisungsblock aufgefasst werden.

In C kann zu Beginn jedes Anweisungsblocks eine Va- riablendeklaration eingefügt werden. Die hier deklarier- ten Variablen sind jedoch nur innerhalb dieses Blockes gültig (lokale Variable).

(30)

6.3.2 Bedingte Anweisung

if-Anweisung

if Ausdruck Anweisung

else Anweisung

( )

Anmerkungen zur bedingten Anweisung

Anmerkung 1

Als "Anweisung" tritt häufig ein Anweisungsblock der Form

{

Anweisung;

Anweisung;

. .

Anweisung;

}

auf, die dann als Ganzes von der Bedingung abhängt.

Anmerkung 2

Eine bedingte Anweisung der Form if Ausdruck Anweisung;

Anweisung;

else Anweisung;

ist syntaktisch falsch!

(31)

Geschachtelte if - Anweisungen

Die Zugehörigkeit der else-Klausel zur if-Klausel wird üblicherweise durch Einrückung deutlich gemacht. Die Einrückung ist aber nicht signifikant für den Compiler!

Regel

Ein else gehört immer zu dem letzten if, das noch keine else - Klausel hat.

Beispiel: Knobelspiel

Zwei Spieler geben unabhängig voneinander gleichzei- tig je eine nicht-negative ganze Zahl an (etwa durch Ausstrecken von Fingern auf Kommando oder durch verdecktes Aufschreiben).

Nennen beide Spieler die gleiche Zahl, so endet das Spiel unentschieden; andernfalls gewinnt, falls die Summe der genannten Zahlen gerade ist, der Spieler, der die kleinere Zahl genannt hat, und sonst (falls also die Summe ungerade ist) derjenige, der die größere Zahl genannt hat.

(32)

Erster Entwurf

/* PROGRAMM knobelspiel */

void main () {

/* Variablen deklarieren */

/* ... */

if (eingabe fehlerhaft) {fehlermeldung}

else

{Entscheidung}

}

Schrittweise Verfeinerung

Die Entscheidung lässt sich schreiben als if {eingaben gleich}

{unentschieden}

else {ermittle sieger}

Der Sieger kann ermittelt werden mit if {summe gerade}

{kleinerer siegt}

else {grösserer siegt}

Um anzugeben, welcher der beiden gewonnen hat, kann man den Sieg des Spielers mit der kleineren Zahl be- schreiben als

if {erster spieler kleinere zahl}

{erster spieler siegt}

else {zweiter spieler siegt}

Der Sieg des Spielers mit der größeren Zahl wird be- schrieben als

if {erster spieler grössere zahl}

{erster spieler siegt}

else {zweiter spieler siegt}

(33)

Programm "Knobelspiel" in Pseudo-Code

/* PROGRAMM knobelspiel */

/* Variablendeklaration */

void main () { {eingabe}

if {eingabe fehlerhaft}

{fehlermeldung}

else

if {eingaben gleich}

{unentschieden}

else

if {summe gerade}

if{erster Spieler kleinere zahl}

{erster spieler siegt}

else {zweiter spieler siegt}

else {summe ungerade}

if {erster spieler grössere zahl}

{erster spieler gewinnt}

else {zweiter spieler siegt}

} /* main */

(In unserer Notation im Pseudocode ist vereinbarungs- gemäß die Einrückung signifikant.)

Programm "Knobelspiel" in C

/* PROGRAMM knobelspiel */

void main () { int k,l;

printf ("Bitte die beiden Knobelwerte einge- ben:");

scanf ("%d \n", &k); scanf ("%d \n", &l);

/* "call by reference", Erklärung folgt später */

if (( k < 0) || (l < 0))

printf ("unzulässige eingabe");

else

if (k == l)

printf ("unentschieden");

else

if (((k + l) %2) !=0) /*ungerade Summe*/

if k < l)

printf ("1. spieler siegt");

else printf ("2. spieler siegt");

else

if (k > l)

printf ("1. spieler siegt");

else printf ("2. spieler siegt");

} /* main */

(34)

6.3.3 Mehrfach-Selektion (Fallunterscheidung)

Die Mehrfachselektion ist eine Auswahlanweisung mit mehreren Alternativen. Es wird untersucht, ob ein Aus- druck einen aus einer Liste von mehreren konstanten ganzzahligen Werten besitzt, dann wird entsprechend verzweigt.

Syntax der Mehrfach-Selektion

Mehrfachauswahl

switch ( Ausdruck ) { Listenelement }

Listenelement

Marke Anweisung break ;

default

:

Marke

case Konstante

Die default-Klausel kann dabei maximal einmal am En- de der Liste verwendet werden.

(35)

Beispiel für switch

void main () {

typedef enum {sieben, acht, neun, zehn, bube, dame, könig, as} spielkartentyp ;

spielkartentyp karte;

int wert;

switch (karte) {

case sieben : wert = 7;

break;

case acht : wert = 8;

break;

case neun : wert = 9;

break;

case zehn :

case bube :

case dame :

case könig : wert = 10;

break;

case as : wert = 11;

break;

default : wert = 0;

} }

Abarbeitung der switch-Anweisung

Die Anweisungen in einem switch -Block werden se- quentiell abgearbeitet. Die break-Anweisung bewirkt ein Verlassen des switch-Blocks. Einen case-Block ohne eine break-Anweisung abzuschließen bewirkt, dass alle darauffolgenden case-Blöcke bis zu einem break oder dem Ende der switch-Anweisung ausge- führt werden.

Beispiel

int x = 0, y;

switch (x) {

case 0 : y = 1;

case 1 : y = 2; break;

case 2 : y = 3; break;

default : }

Der Benutzer wollte y den Wert 1 zuweisen. So aber er- hält y den Wert 2!

(36)

6.3.4 Iteration (Wiederholungsanweisung)

Es gibt in C drei Varianten von Iterationsanweisungen:

while-Schleife

do-Schleife

for-Schleife

while-Schleife

Bedingungsschleife

while ( Ausdruck ) Anweisung

Merke: Die Abbruchbedingung wird jeweils vor der Ausführung der Anweisungen geprüft.

(37)

do-Schleife

Anweisung while Ausdruck

do-Schleife

do ( ) ;

Merke: Die Abbruchbedingung wird jeweils nach der Ausführung der Anweisungen geprüft.

for-Schleife ("Zählschleife") (1)

Die for-Schleife sieht allgemein wie folgt aus:

Zählschleife

for Ausdruck 1

Ausdruck 2

( ;

;

Ausdruck 3 ) Anweisung

(38)

for-Schleife ("Zählschleife") (2)

Es gelten folgende Regeln:

Ausdruck1 wird nur einmal berechnet und dient zur In- itialisierung der Schleife (in der Regel zur Anfangszu- weisung an Kontrollvariable).

Ausdruck2 ist ein logischer Ausdruck (boolean), in der Regel eine Abbruchbedingung. Er wird vor jedem Schleifendurchlauf neu berechnet. Die for-Schleife bricht ab, wenn Ausdruck2 = 0 (false) ist.

Ausdruck3 wird ebenfalls vor jedem Schleifendurch- lauf neu berechnet. Er dient zur Veränderung von Schleifenvariablen für den neuen Durchlauf.

Analogie zwischen for- und while-Schleife

Eine for-Schleife der Form

for ( expr1; expr2; expr3) Anweisungsblock;

ist äquivalent zu einer while-Schleife der Form expr1;

while (expr2) {

Anweisungsblock;

expr3;

}

Eine for-Schleife ist in der Regel immer dann sinnvoll, wenn die Anzahl der Wiederholungen im voraus be- kannt ist.

(39)

break und continue in Schleifen (1) continue bewirkt, dass der Anweisungsblock im Inne- ren der Schleife nicht weiter abgearbeitet wird, sondern sofort mit dem nächsten Schleifendurchlauf begonnen wird.

Beispiel

/* negative Elemente werden nicht bearbeitet */

#define N 10 int i, a[N];

for (i=0; i < N; i++) {

if (a[i] < 0) continue;

/* Bearbeitung positiver Elemente */

...

}

break und continue in Schleifen (2) break bewirkt, dass die gesamte Schleife verlassen wird, nicht nur der aktuelle Schleifendurchlauf.

Beispiel

Im obigen Beispiel bewirkt break an der Stelle von continue, dass beim Auftreten des ersten negativen Elements die Bearbeitung des gesamten Arrays abge- brochen wird.

(40)

6.4 Unterprogramme

Unterprogramme dienen zur Modularisierung von Programmen. Unterprogramme werden deklariert und aufgerufen. Man unterscheidet traditionell zwei Arten von Unterprogrammen

Prozeduren

Führen einen Teil der Arbeit des aufrufenden Pro- gramms durch.

Ergebnisse werden in der Regel in Form von Pa- rametern zurückgegeben.

Funktionen

führen die Berechnung von Funktionswerten im mathematischen Sinn durch.

Ergebnisse werden (gegebenfalls zusätzlich zu Pa- rametern) durch den Funktionsaufruf zurückgelie- fert; der Wert des Ergebnisses tritt an die Stelle des Funktionsnamens im umgebenden Ausdruck.

In C gibt es nur einen Typ von Unterprogrammen, näm- lich Funktionen. Ergebniswerte von Funktionen können in C jedoch ignoriert werden, so dass eine C-Funktion sich wie eine Prozedur verhält.

Deklaration von Unterprogrammen (1)

Unterprogrammvereinbarung

Unterprogrammkopf Unterprogrammrumpf

Unterprogrammkopf

Typ- name

Routinen-

name ( Parameter-

liste )

Unterprogrammrumpf

Vereinbarungs- folge

Anweisungs-

folge ; }

{

(41)

Deklaration von Unterprogrammen (2)

Unterprogramme sollten im Deklarationsteil eines Pro- gramms, im Anschluss an die Variablen-Deklarationen vereinbart werden.

Es sollte immer ein Typ für das Unterprogramm (und damit für den zurückgelieferten Wert) angegeben wer- den, auch wenn der C-Compiler dies nicht verlangt.

Wird kein Typ angegeben, geht der Compiler vom Typ

"int" aus.

Die return-Anweisung

Die Rückgabe von Ergebnissen einer Funktion erfolgt in der Funktion durch die Anweisung return:

;

return-Anweisung

return Ausdruck

Nach dem Schlüsselwort return kann ein beliebiger Ausdruck stehen. Das Ergebnis der Funktion ist das Er- gebnis der Auswertung dieses Ausdrucks.

Merke: Eine Routine muss keinen Resultatwert liefern.

Ein leeres return bzw. die abschließende geschweifte Klammer beenden die Routine und geben 0 zurück (normales Routinenende). Eine Routine sollte jedoch aus Gründen der Klarheit immer eine return-

Anweisung besitzen.

(42)

Aufruf von Unterprogrammen (1)

Routinenaufruf

Variable Routinenname

aktueller Parameter (

,

)

, ...

;

=

Der Aufruf ohne Zuweisung an eine Variable ist, syn- taktisch gesehen, eine Anweisung (statement); dies entspricht der traditionellen Prozedur. Solche Unterpro- gramme werden mit dem Typ void vereinbart.

Der Aufruf mit Zuweisung an eine Variable ist, syntak- tisch gesehen, ein Ausdruck (expression); dies ent- spricht der traditionellen Funktion, bei der ein Wert zu- rückgegeben wird.

Aufruf von Unterprogrammen (2)

Beispiele

void mache_garnichts () {

}

double square_root (double value) {

return (sqrt(value));

}

Aufrufe:

double a, b;

...

mache_garnichts();

...

b = 4.0;

a = square_root(b);

...

(43)

Korrespondenz zwischen formalen und aktuellen Parametern

Aktuelle und formale Parameter sollten in der Anzahl übereinstimmen.

Aktuelle und formale Parameter sollten im Typ über- einstimmen.

Aktuelle und formale Parameter entsprechen einander in der Reihenfolge, in der sie in der Vereinbarung und im Aufruf auftreten (Stellungsparameter).

Variable Parameterlisten

Es dürfen beim Aufruf eines Unterprogramms auch mehr Parameter übergeben werden als vereinbart sind.

Dazu ist in der Deklaration ' ...' als letzter Parameter notwendig. Dies ist sinnvoll, wenn vorab nicht bekannt ist, wieviele Parameter übergeben werden sollen.

Beispiel:

Die später noch einzuführende Funktion printf ist fol- gendermaßen deklariert:

int printf (const char *format , ... ) Sie gibt u.a. den Inhalt beliebig vieler Variablen auf dem Bildschirm aus.

Achtung

Es dürfen nie weniger Argumente als vereinbart über- geben werden, da ansonsten der Effekt des Aufrufs un- definiert ist.

(44)

Beispiel für schwierige Semantik

int i;

void test (int k; int j) { k = k + 1;

j = 3 i;

return;

} /*test*/

main () { int a[3];

a[0] = 1; a[1] = 2; a[2] = 3;

i = 1;

test (i, a[i]);

return;

}

Fragen bzgl. i und a[i]:

Wie wird auf den aktuellen Parameter zugegriffen?

• indem innerhalb der Routine Speicherplatz angelegt und der Wert dorthin kopiert wird?

• indem direkt auf den Speicherplatz der Variablen im Hauptprogramm zugegriffen wird?

• indem der Parameter-Ausdruck bei jeder Benutzung neu berechnet wird?

Call - by - value (1)

• Argumente werden bei Routinenaufruf in die Routine auf lokalen Speicherplatz kopiert.

• Berechnung der Parameterwerte bei Aufruf der Routi- ne.

• Alle Operationen innerhalb der Routine werden auf dem lokalen Speicherplatz ausgeführt.

Also:

Keine Auswirkungen außerhalb der Prozedur.

Nur geeignet für Eingangsparameter.

(45)

Call - by - value (2)

int i, a[3];

void test (int k, int j) { k = k + 1;

j = 3 * a[i];

return;

} /*test*/

main () { a[0] = 1;

a[1] = 2;

a[2] = 3;

i = 1;

test (i, a[i]);

return;

}

i a[0] a[1] a[2]

1 1 2 3

Call - by - reference (1)

• bei Eintritt in die Routine wird die Speicheradresse des Parameters berechnet.

• alle Operationen innerhalb der Routine werden direkt auf den so berechneten Speicheradressen ausge- führt.

Also: Änderungen des Parameterinhaltes ändern die Umgebung. Geeignet für Eingangs- und Ausgangs- parameter, d.h. damit können Daten der aufrufenden Routine geändert werden!

(46)

Call - by - reference (2)

int i, a[3];

void test (int *k, int *j) { *k = *k + 1;

*j = 3 * a[i];

return;

} /*test*/

main () { a[0] = 1;

a[1] = 2;

a[2] = 3;

i = 1;

test (&i, &a[i]);

return;

}

i a[0] a[1] a[2]

2 1 9 3

Call - by - name (1)

Ausdrücke sind als aktuelle Parameter erlaubt.

Operationen werden auf den Original-Speicherplätzen außerhalb der Routine ausgeführt.

Erneute Auswertung des Ausdrucks bei jeder Verwen- dung innerhalb des Unterprogramms!

Also: Der Call-by-name führt dann zu anderen Werten als der Call-by-reference, wenn mehrere Parameter übergeben werden, die voneinander abhängen.

Nicht empfehlenswert, da oft sehr schwer verständlich.

(47)

Call - by - name (2)

int i, a[3];

void test (int *k, int *j) { *k = *k + 1;

*j = 3 * a[i];

return;

} /*test*/

main () { a[0] = 1;

a[1] = 2;

a[2] = 3;

i = 1;

test (&i, &a[i]);

return;

}

Call-by-name ist in C nicht möglich! Dazu müsste "*j"

im Unterprogramm "test" erst nach der Anweisung

"* k=*k+1" erneut ausgewertet werden, d.h. da "*j" für

"a[i]" steht und "i= * k" ist, wäre "*j= a [2]". Damit würde das Programm folgende Ergebnisse liefern:

a[0] a[1] a[2]

2 1 2 9

Binderegeln in C (1)

Es gibt nur Call-by-value in C.

Call-by-reference kann durch den Referenzoperator * dargestellt werden.

Parameterklasse Übergaberegel aktueller Para- meter

Wertparameter Referenzpara- meter

Routinenpara- meter

Call-by-value call-by-reference call-by-reference

Ausdruck

Variablenreferenz Routinenreferenz

(48)

Binderegeln in C (2)

Parameterliste

,

Wertparameter

Routinen-

referenzparameter Variablen-

referenzparameter

Binderegeln in C (3)

) Wertparameter

Variablenreferezparameter

Typname Parametername

Typname * Parametername

Routinenreferezparameter

Typname ( * Routinenname

( )

* Parametertyp

(49)

Binderegeln in C (4)

Beispiele

a) Call-by-value

float Betrag (float x) {

if (x >= 0)

return(x);

else

return(-x);

}

Aufgerufen wird die Funktion z.B. durch y = Betrag (3.14 * z);

Binderegeln in C (5)

b) Call-by-reference

void Tausch (int *a, int *b) {

int hilf;

hilf = *a;

*a = *b;

*b = hilf;

return;

}

Aufgerufen wird die Funktion z.B. durch Tausch(&x, &y);

(wobei x und y integer sind)

(50)

Binderegeln in C (6)

c) Routinen als Parameter

(*F)(float) sei eine beliebige reellwertige Funktion, bei der (*F)(a)<0 und (*F)(b)>0 ist

.

Das Unterprogramm "Nullstelle" berechnet nun nach der Methode der Intervallhalbierung eine Null- stelle von (* F).

float Nullstelle(float(*F)(float), float a, float b) {

float mittelpunkt = 0.0;

while (fabs((*F)(mittelpunkt) > (1e-10))) {

mittelpunkt = (a+b)/2;

if ( (*F)(mittelpunkt) < 0) a = mittelpunkt;

else

b = mittelpunkt;

}

return (mittelpunkt);

}

Binderegeln in C (7)

Mittelpunkt a

F(x)

b

Achtung: Bei der Angabe der Routine (*F) als Para- meter muss auf die Klammern geachtet werden, da man sonst eine Funktion angibt, die einen Zeiger auf ein float zurück gibt, und nicht einen Funktions- zeiger.

(51)

Standardfunktionen in C (1)

In der Standard include-Datei <math. h> vereinbarte mathematische Standardfunktionen:

Aufruf Para- metertyp

Ergebnistyp Bedeutung abs (x)

labs (x) fabs (x) ceil (x)

floor (x)

sin (x) cos (x) exp (x) log (x) sqrt (x) atan (x) pow (x,y)

integer long float double

double

double double double double double double double

integer long float double

double

double double double double double double double

Betrag eines Integers Betrag eines Longs Betrag eines Floats kleinster ganzzahliger Wert,der nicht kleiner als x ist

größter ganzzahliger Wert,der nicht größer als x ist

Sinusfunktion Cosinusfunktion Exponentialfunktion 10er- Logarithmus Wurzel einer Zahl Arcustangensfunktion x hoch y

Standardfunktionen in C (2)

In der Standard include-Datei <string. h> vereinbarte Standardfunktionen zur Handhabung von Zeichenket- ten:

Aufruf Bedeutung

char *strcpy(s,ct) char *strncpy(ct,n) char *strcat(s,ct) char *strncat(s,ct,n) int strcmp(cs,ct) int strncmp(cs,ct,n) char *strstr(cs,ct) size_t strlen(cs)

Kopiert Zeichenkette ct in Vektor s Kopiert höchstens n Zeichen aus ct in s

Hängt Zeichenkette ct an s hinten an Hängt höchstens n Zeichen an

Vergleicht Zeichenketten cs und ct Vergleicht höchstens n Zeichen von cs und ct

Liefert Zeiger auf erste Kopie von ct in cs

Liefert Länge von cs

(52)

Gültigkeitsbereich von Namen

Wenn Variablen, Konstanten usw. innerhalb einer Pro- zedur oder Funktion denselben Namen haben wie Va- riablen, Konstanten usw. außerhalb, muss klar definiert sein, welches Objekt jeweils gemeint ist. Es gilt:

Jede Vereinbarung eines Namens hat nur in dem Block Gültigkeit, in dem sie vorgenommen wird.

Also:

Ein Name bezieht sich immer auf die am nächsten lie- gende Deklaration.

Namen müssen innerhalb eines Blockes eindeutig sein.

Die Deklaration muss der Verwendung vorangehen.

Ein innerhalb eines Blockes vereinbarter Name heißt lokal.

Ein außerhalb des Blockes vereinbarter Name heißt global.

Blockstruktur

In C kann jeder Anweisungsblock am Beginn eigene Deklarationen enthalten. Ansonsten gibt es Vereinba- rungen außerhalb von Routinen ("globale") und zu Be- ginn von Routinen.

UP 1

UP 2

UP n

Block 4 Block 1

Block 2

Hauptprogramm

. . .

Block 3

Block 5

Achtung:

In ANSI-C können Routinen nicht geschachtelt werden.

Insbesondere darf auch das Hauptprogramm keine Un- terprogramme enthalten. Es können jedoch Anwei- sungsblöcke geschachtelt werden.

(53)

Regeln für die Sichtbarkeit von Vereinbarungen

• Sichtbarkeit von äußeren Blöcken nach innen

• keine Sichtbarkeit von innen nach außen

keine gegenseitige Sichtbarkeit für Blöcke derselben Schachtelungstiefe

Beispiel

int a,b,c

function g int x,y,z

main int x,y

b2 int c,d,e b1

int d,e,f

Gültigkeitsbereich vs. Lebensdauer

Der Gültigkeitsbereich eines Namens umfasst den Block, in dem der Name deklariert ist.

Die Lebensdauer eines Objekts umfasst den Block, in dem es definiert ist: es existiert nur so lange, wie An- weisungen des zugehörigen Blocks ausgeführt werden.

Das Laufzeitsystem von C legt beim Eintritt in einen Block den Speicherplatz für die dort lokal benötigten Objekte an und gibt ihn beim Verlassen des Blocks wie- der frei.

Eine Ausnahme hierzu bildet die static-Deklaration:

Wird eine Variable innerhalb einer Funktion als static deklariert, so behält sie ihren Wert auch nach Beendi- gung der Funktion. Bei erneutem Aufruf hat sie den Wert vom vorigen Verlassen der Prozedur.

Beispiel: static int alert;

Merke:

Objekte, die nur innerhalb eines Blocks benötigt werden, sollten innerhalb dieses Blocks vereinbart werden. Dies verbessert die Übersichtlichkeit und vermeidet Sei- teneffekte.

(54)

Rekursion in C (1)

Da für alle Call-by-Value-Parameter und für alle lokalen Variablen Speicherplatz beim Prozedureintritt dyna- misch angelegt wird, können Funktionen und Prozedu- ren in C rekursiv aufgerufen werden.

Die Anweisungen innerhalb eines Prozedurrumpfes be- ziehen sich dabei jeweils auf die lokalen Variablen und Parameter.

Bei der Rückkehr aus der Rekursion findet die Funktion bzw. Prozedur dann jeweils wieder die alten Werte vor.

Rekursion in C (2)

Beispiel

int fak (int k) {

if (k==0)

return (1);

else

return (k * fak (k-1));

}

1 * 1 1

1 * fak (0) 2 * fak (1)

2 * 1 3 * fak (2)

3 * 2 6

(55)

Rekursion und Kellerspeicher (Stack)

Bei jedem rekursiven Aufruf wird ein neuer Speicherbe- reich für die lokalen Variablen und Parameter angelegt.

Zugleich werden die lokalen Variablen und Parameter aus der nächsthöheren Rekursionsstufe unzugänglich.

Daher lässt sich der Speicher für Unterprogrammdaten als Kellerspeicher (Stack) organisieren. Dies geschieht auch in den C-Laufzeitsystemen.

Beim Umsetzen einer Rekursion in eine Iteration muss der Kellerspeicher oft vom Programmierer angelegt und verwaltet werden.

: k : 3 k : 2 k : 1 k : 0

}

globaleVariablen

Hauptspeicher: Keller /Stack

Seiteneffekte

Auswirkungen von Funktionen und Prozeduren, die nicht unmittelbar aus der intendierten Semantik hervor- gehen, heißen Seiteneffekte. Sie treten meist auf im Zusammenhang mit

• Funktionsaufrufen und globalen Variablen

• verschachtelten Zuweisungen Beispiel: x = 0;

v = --x - (x=4);

• Makros

Merke:

Externe, global gültige Variablen sind nur für große, den Kern eines Programms bestimmende Datenstrukturen sinnvoll. Grundsätzlich sollten alle in einer Routine ver- wendeten Variablen entweder lokal sein, oder als Para- meter übergeben werden.

(56)

Beispiel für einen Seiteneffekt (1)

/* Globale Variablen */

int r;

float s, wert;

float hoch (float x, int y) { float u, v;

u = 1;

v = x;

r = y;

while (r > 0) {

if (r % 2) /*r ist ungerade? */

u = u * v;

v = v * v; / * v -Quadrat * / r = r/2;

}

return (u);

}

(Fortsetzung nächste Seite)

Beispiel für einen Seiteneffekt (2)

main () {

printf ("Bitte ein Zahlenpaar eingeben; mit

<ENTER> beenden: \n");

scanf ("%f", &s);

scanf ("%d", &r);

wert = hoch (s, r);

printf ("%f hoch %d ist %f \n", s, r, wert);

return;

}

Eine Eingabe von 2 7

erzeugt eine Ausgabe von

"2 hoch 0 ist 49"

(57)

6.5 Zeiger und komplexe Datenstruk- turen

1. Zeiger

Ein Zeiger (pointer) ist ein Verweis auf ein anderes Da- tenobjekt.

Beispiel

Ted

Fred

Adam

Mary

Eva

Null Null Null

Null Null

Null

Implementierung von Zeigern (1)

Der Speicher einer Maschine unter dem Betriebssystem UNIX ist in fortlaufend nummerierte Speicherzellen auf- geteilt, wobei die Nummer die Adresse der Speicher- zelle darstellt.

Ein Zeiger wird implementiert durch Abspeichern der Speicheradresse des anderen Objektes, auf das ver- wiesen werden soll.

(58)

Implementierung von Zeigern (2)

Beispiel

40 "Ted" Adr("Fred")=80 Adr("Mary")=160 •

• •

80 "Fred" Adr("Adam")=80 Null •

• •

100 "Adam" Null Null •

• •

160 "Mary" Null Adr("Eva")=180 •

• •

180 "Eva" Null Null

Syntax in ANSI C (1)

Deklaration:

typ *

,

Variable

;

Zeiger- deklaration

Beispiele int *a;

struct person { char name[20];

struct person * vater, * mutter;

};

(59)

Syntax in ANSI C (2)

Werte, die ein Zeiger annehmen kann:

spezielle Adresse NULL (Zeiger, der nirgendwo hin- zeigt)

(in <stdio.h> definiert)

positive Ganzzahl (Maschinenadresse im Speicher des Systems)

Merke

Der Typ der Datenelemente, auf die gezeigt wird (Be- zugstyp), ist aus der Deklaration des Zeigertyps ersicht- lich!

Mit der Deklaration "void *" kann ein unspezifischer (ge- nerischer) Zeiger (ohne Typ) generiert werden. Solche Zeiger dürfen jedoch nicht selbst zum Zugriff verwendet werden, sondern können nur als Platzhalter für Argu- mente in Funktionen dienen.

Operationen auf Zeigern (1)

a) Wertzuweisung

Einem Zeiger kann der Wert eines anderen Zeigers zu- gewiesen werden.

Beispiel

typedef int ganze_zahl;

typedef (ganze_zahl *) zeiger_auf_ganze_zahl;

ganze_zahl a;

zeiger_auf_ganze_zahl p1, p2;

...

{ ...

. .

p1

p2

(60)

Operationen auf Zeigern (2)

p2 = p1;

/* p2 zeigt nun auf das selbe Objekt wie p1 */

...

}

. .

p1

p2

Es ist auch möglich, absolute Speicheradressen zuzu- weisen.

Beispiel

p1 =(zeiger_auf_ganze_zahl) 1501;

/* Typecast vermeidet Compiler-Warnung */

Merke

Es ist gefährlich, absolute Speicheradressen zu ver- wenden, da die Einteilung des Speichers i.d.R. nicht be- kannt ist.

Operationen auf Zeigern (3)

b) Unärer Adreßoperator &

Einem Zeiger kann die Adresse eines Objektes zuge- wiesen werden.

Beispiel int c;

int *p;

...

{

c

.

p ...

p = &c;

c

p

/* p zeigt nun auf die Adresse von c */

}

(61)

Operationen auf Zeigern (4)

c) Unärer Inhaltsoperator *

Einem Objekt kann der Inhalt eines anderen Objektes zugewiesen werden, auf das ein Zeiger zeigt. (derefe- rencing/indirection)

Beispiel int c1, c2;

int *p;

...

{

c1 = x

c2 = y

p

...

p = &c1;

c1 = x

c2 = y

p

Operationen auf Zeigern (5)

...

c2 = *p;

c1 = x c2 = x

p ...

*p = 5;

c1 = 5 c2 = x

p ...

} Merke

Die unären Adreßoperatoren * und & haben höheren Vorrang als dyadisch arithmetrische Operatoren.

(62)

Operationen auf Zeigern (6)

Beispiel

y = *p + 1 inkrementiert den Wert, auf den p zeigt, um 1 und weist das Ergebnis y zu

*p += 1 inkrementiert den Wert, auf den p zeigt, um 1

++*p ebenso (++(*p)) (*p)++ ebenso

*p++ inkrementiert p (die Adresse) (es wird von rechts nach links zusammengefaßt) (*(p++))

Operationen auf Zeigern (7)

d) Direktes Einrichten eines neuen Datenobjekts, auf das der Zeiger zeigt

void *malloc (size_t size)

(size_t ist ein Datentyp, der architekturspezifisch für

"1Byte" steht.) Diese Funktion dient zum Allokieren von Speicherplatz der angegebenen Größe und liefert die Anfangsadresse des allokierten Blocks als generic- Pointer.

type *p;

...

p = (type *) malloc (sizeof (type));

malloc wird dazu verwendet, Speicherplatz für ein Da- tenobjekt des Bezugstyps von p (Bezugsvariable) einzu- richten und läßt p auf diesen Speicherplatz zeigen.

Beispiel:

int *p,

p = Null; •

p = (int *) malloc (sizeof (int)); p

Datenobjekt vom Typ int

(63)

Operationen auf Zeigern (8)

Der unäre Operator sizeof wird dabei verwendet, um die Größe des zu allokierenden Speicherplatzes anzuge- ben.

sizeof Objekt liefert die Größe des angegebe- nen Speicherobjekts (z.B. Va- riable) in "Byte".

sizeof (Typname) liefert die Größe des angegebe- nen Typs in "Byte".

z.B. sizeof (char) = 1

Zu Allokierung mehrerer gleichartiger Objekte am Stück gibt es eine spezielle Funktion:

void * calloc (size_t nitems, size_t size) calloc reserviert für "nitems" Objekte der Größe "size"

Speicherplatz.

Diese Funktion ist insbesondere für das dynamische Er- zeugen eines Vektors interessant.

Beispiel

p = calloc (7, sizeof (int));

Operationen auf Zeigern (9)

e) Löschen eines Datenobjekts, auf das der Zeiger zeigt

free (p);

Der Speicherplatz für das Datenobjekt, auf das p zeigt, wird freigegeben. Der Wert von p ist anschließend un- definiert.

Merke

Die Lebensdauer von Bezugsvariablen wird vom Pro- grammierer explizit durch malloc und free bestimmt. Sie ist nicht an die Blockstruktur eines Programms gebun- den. Die Lebensdauer der Zeigervariablen selbst folgt dagegen der dynamischen Blockstruktur, wie die aller anderen Variablen auch.

(64)

Operationen auf Zeigern (10)

f) Vergleich von Zeigern

Alle Vergleichsoperatoren sind auch auf Zeigern mög- lich, aber nicht immer ist das Ergebnis sinnvoll.

Wenn p1 und p2 auf Elemente im gleichen linearen Adreßprogramm zeigen, dann sind Vergleiche wie ==,

!=, <, >, <=, >= etc sinnvoll.

Insbesondere ist dies bei einem Vektor von Elementen der Fall.

Beispiel:

Seien p1 und p2 Zeiger auf Elemente eines Vektors (array oder calloc).

p1 < p2 gilt, wenn p1 auf ein früheres Element im Vektor zeigt als p2

Sind p1 und p2 jedoch nicht Elemente des gleichen Vektors, ist das Ergebnis i.d.R. nicht sinnvoll.

Operationen auf Zeigern (11)

g) Arithmetik mit Zeigern

Addition und Subtraktion ganzzahliger Werte ist auf Zei- gern erlaubt.

Beispiel

p += n setzt p auf die Adresse des n-ten Objektes nach dem Objekt, auf das p momentan zeigt, (nicht n-tes Byte!)

p++ setzt p auf die Adresse des nächsten Objek- tes

p-- setzt p auf die Adresse des vorherigen Ob- jektes

Merke

Alle anderen Operationen mit Zeigern sind verboten:

wie Addition, Multiplikation, Division oder Subtraktion zweier Zeiger, Bitoperationen oder Addition von Gleit- punktwerten. (Leider lassen manche Compiler solche Operationen trotzdem zu. Sie sind aber nicht standard- konform und sollten deshalb auch nicht eingesetzt wer- den.)

Abgesehen von "void *" sollte ohne explizite Umwand- lungsoperatoren kein Zeiger auf einen Datentyp an ei- nen Zeiger auf einen anderen Datentyp zugewiesen werden.

Referenzen

ÄHNLICHE DOKUMENTE

3 Jahre nach Ablauf des Kalenderjahrs, in dem alle aus den beendeten Überlas- sungsverhältnissen sich ergebenden Rechte und Verpflichtungen abgewickelt sind. ee)

fi ndet Ihr wei tere Informati onen zum Wettbewerb. zum

Um festzustellen zu können, ob ein Vokal eingetippt wurde, steht jeder Vokal nach einer case –Anweisung. Verwendung von break hier genau

[r]

Es ist zu beachten, dass wenn eine Methode der Basisklasse redefi- niert wird auch nicht mehr ohne weiteres auf die überladenen Funktio- nen der redefinierten Funktion

Die Bundesregierung sollte darauf hinarbeiten, dass Deutschland in den kommenden Jahren zu den Die deutsche Wirtschaft boomt, Forschung und Entwicklung haben in den ver­..

Das Zeichen ‘%d’ steht nun als Ersatz für dezimale Zei- chen in der eingelesenen Zeichenkette, deren Wert an die Variablen note1-3 übergeben

 Eliminierung globaler Variablen (fast) immer zu empfehlen, manchmal aber mit Arbeit verbunden.. ohne