Grundlagen der Programmiersprache C für Studierende der Naturwissenschaften Teil 2: Numerische Datentypen und Verwendung von Variablen Patrick Schreier Abteilung für Angewandte Mathematik Vorlesung vom 27. April 2015 Gliederung Rechnen mit Variablen Darstellung von Integer-Datentypen Gleitpunktzahlen Ausdrücke Bedingte Anweisungen Einfache Verzweigung Berechnung der Quadratwurzel Quelltext (Quadratwurzel) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <math.h> #include <stdio.h> int main(void) { /* declare floating point variable */ double x; /* read from user input */ printf ("x = "); scanf("%lf", &x); /* print value of x and square root */ printf ("x = %f\n", x); printf ("sqrt(x) = %f\n", sqrt(x)); return 0; } Variablen I Variable = symbolischer Name für einen Speicherbereich. I Variablen in Mathematik und Informatik sind verschieden: I I I Mathematik: Sei x ∈ R fixiert x Informatik: int x definiert eine Variable vom Type int. x = 5 weist x den Wert 5 zu. Jede Variable muss vor Ihrer Verwendung vereinbart werden. Eine Vereinbarung besteht aus Angabe von Namen und Typ der Variablen. Namen: Namen identifizieren Variablen in eindeutiger Weise. Typen: Der Typ einer Variablen legt fest, welche Werte in der Variablen gespeichert werden können und welche Operationen auf Ihnen möglich sind. Der Datentyp int I Der Datentyp int ist vorgesehen, um ganze Zahlen (engl. integers) darzustellen: . . . , − 2, − 1, 0, 1, 2, . . . I Integer-Datentypen haben einen endlichen Wertebereich, d. h. Variablen vom Typ int können nicht beliebig (betragsmäßig) große Werte annehmen. I Neben dem Typ int gibt es eine Reihe weiterer Integer-Datentypen: char, short int, unsigned int, long int, ... Sie unterscheiden sich hinsichtlich ihres darstellbaren Wertebereichs und ihres Speicheraufwands. Der Datentyp double I Gleitpunktzahlen oder auch Gleitkommazahlen (engl. floating point numbers) sind eine Teilmenge der rationalen Zahlen. Arithmetische Operationen auf Gleitkommazahlen sind nicht exakt, sondern mit einem kleinen Fehler behaftet. I Gleitpunktzahlen werden in einem der folgenden Typen abgespeichert: float double long double I Wir beschäftigen uns heute ausschließlich mit dem Typ double. Bezeichner Konstruktion von Bezeichnern: I Bezeichner dürfen aus Buchstaben, Ziffern und dem Unterstrich bestehen. I Das erste Zeichen darf keine Ziffer sein. I Reservierte Schlüsselwörter dürfen nicht verwendet werden. Bemerkungen: I I Zwischen Groß- und Kleinschreibung wird unterschieden, d. h. value und Value sind verschiedene Bezeichner. Bezeichner dürfen beliebig lang sein. Der Standard garantiert allerdings nur, dass mindestens 31 Zeichen signifikant sind (in der Praxis heute irrelevant). Reservierte Schlüsselwörter auto default float register struct volatile break do for return switch while case double goto short typedef char else if signed union const enum int sizeof unsigned continue extern long static void Tabelle : Reservierte Schüsselwörter in ANSI C89 inline _Bool _Complex _Imaginary Tabelle : Reservierte Schüsselwörter in ISO C99 Beispiele für Bezeichner Betrachten wir einige Beispiele: x, i, n, ... Okay Sehr kurze Variablennamen sind üblich in arithmetischen Ausdrücken oder in Schleifen. Aussagekräftige Namen helfen allerdings beim Programmieren und Debuggen. _a_very_long_long_identifier Okay Sehr lange Bezeichnern sind allerdings auch unpraktisch (mögliche Schreibfehler, Zeilenlänge). größe Falsch Der Umlaut ö und der Buchstabe ß sind keine erlaubten Zeichen. case Falsch Bei case handelt es sich um ein reserviertes Schlüsselwort. Deklaration von Variablen Die Deklaration entspricht der Zuweisung von einem Speicherbereich auf einen symbolischen Namen. Die Grösse des Speicherbereichs entspricht dem Typ der Variable. Syntax: typename identifier; wobei I typename den Namen eines Typs (bisher: int oder double) bezeichnet, I identifier ein gültiger Bezeichner sein muss. Beispiele: int a; double x; Gleichzeitige Deklaration mehrerer Variablen I Mehrere Variablen desselben Typs können auch in nur einer Zeile definiert werden. Beispiele: int a, b; double x, y; I Durch Deklaration einer Variablen wird lediglich (uninitialisierter) Speicherbereich zugewiesen. Ist noch kein konkreter Wert zugewiesen ist der Wert einer Variable zufällig. Initialisierung von Variablen I Nach Deklaration kann der Variable ein konstanter Wert zugewiesen werden: I I I int n; (Deklaration) n = 0; (Initialisierung) Konstanten haben (wie Variablen auch) einen Typ. Dieser muss nicht explizit angegeben werden. Integer- und Floating-Point-Konstanten werden durch die Angabe eines Punkts voneinander unterschieden: 1 1.0 1. I /* constant expression of type int */ /* constant expression of type double */ /* constant expression of type double */ Deklaration und Initialisierung können auch in einer Zeile erfolgen: I I int n = 1; double x = 1.; Platzierung von Definition I Jede Variable muss vor Ihrer ersten Verwendung definiert werden. Andernfalls bricht der Übersetzungsvorgang mit einem Fehler ab. I Ältere C-Standards (bis C99) schreiben vor, dass sämtliche Variablendefinitionen am Anfang von main (eigentlich: ihres Gültigkeitsbereichs) stehen müssen: int main(void) { /* list of all definitions */ ... /* subsequent statements */ ... } Moderne Compiler erlauben die Vermischung von Definitionen und Anweisungen. Formatierte Ausgabe mit printf Der Funktion printf kann eine beliebige Zahl von Variablen und konstanten Ausdrücken übergeben werden: int n = 1; double pi = 3.14159265359; printf("n = %d \n", n); printf("pi = %lf \n", pi); printf("n = %d, pi = %lf \n", n, pi); printf("n = %d, pi = %lf \n", 1, 3.1415); I Neben wörtlich wiederzugebendem Text enthält der Format-String (von doppelten Anführungszeichen eingeschlossen) Formatelemente (engl. conversion specifications). Formatierte Ausgabe mit printf I Formatelemente sind Platzhalter für Variablen oder Konstanten verschiedene Typs: %d /* conversion specification for type int */ %lf /* conversion specification for type double */ I Bei der Ausgabe von Gleitpunktzahlen kann man die Anzahl der Nachkommastellen festlegen. Der Code double pi = 3.14159265359; printf("%lf\n", pi); printf("%.3lf\n", pi); printf("%.12lf\n", pi); produziert die Ausgabe: 3.141593 3.142 3.141592653590 Formatierte Eingabe mit scanf Variableninhalte können zur Laufzeit des Programms von der Konsole eingelesen werden. Dazu verwenden wir die Funktion scanf: int a; double scanf( scanf( scanf( x; "%d", &a ); "%lf", &x ); "%d %lf", &a, &x ); Der Funktion können eine oder mehrere Variablen als Parameter übergegeben werden. Ihre Anzahl und Typen müssen den Formatelemente entsprechen: %d /* conversion specification for type int */ %lf /* conversion specification for type double */ Vor jedem Element der Parameterliste muss ein "&" stehen. Seine Bedeutung werden wir erst später klären. Arithmetische Operationen I I Bedeutung eines Operators kann vom Datentyp abhängen. Operatoren auf Gleitpunktzahlen: (Typ double) I I I I Operatoren auf Ganzzahlen: (Typ int) I I I I x=y; (Zuweisung) x+y, x-y, x*y (Addition, Subtraktion, Multiplikation) x/y, (Division) n=m; (Zuweisung) n+m, n-m, n*m (Addition, Subtraktion, Multiplikation) n/m, n%m (Division ohne Rest, Divisionsrest) Die Konstanten oder Variablen, die ein Operator verknüpft werden als Operanden bezeichnet, bspw. hat x+y die Operanden x und y. Ganzzahlige Division mit Rest I Für Integer-Werte entspricht das Operatorpaar /,% der ganzzahligen Division mit Rest: int quotient = 1/2; int remainder = 1%2; I /* quotient = 0 */ /* remainder = 1 */ Das Teilen durch die Null führt nicht etwa automatisch zu einem Laufzeitfehler, das Programm bricht nicht ab. Der Ausdruck hat den ausgezeichneten Wert inf: printf("%lf\n", 1./0.); I /* Output: inf */ Der Wert nan (not a number) dient als Hinweis für ”dubiose” Rechen-Operationen wie 0/0 oder inf/inf: printf("%lf\n", 0./0.); /* Output: -nan */ Implizite Typkonversion Operatoren können Variablen verschiedener Datentypen verbinden. int x = 1; double y = 2.5; double sumDouble = x + y; int sumInt = x + y; printf("sumInt = %d\n", sumInt); /* Output: sumInt = 3 */ printf("sumDouble = %lf\n", sumDouble); /* Output: sumDouble = 3.500000 */ I x + y hat den Datentyp double, bei der Addition wird x in ein double konvertiert. I sumInt hat den Typ int, d.h. x+y wird in ein int konvertiert. Implizite Typkonversion I Haben die Operanden eines arithmetischen Operators den gleichen Typ, so ist auch das Ergebnis von diesem Datentypen. I Sind die Operanden von unterschiedlichem Typ, so wird der Operand mit ”ungenauerem” Typ, zuerst zum ”genaueren” Typ konvertiert, bspw. ist 1 / 5. eine Konstante vom Typ double. I Konvertierung von double nach int erfolgt durch Abschneiden, nicht durch Rundung! int n = 3.7; printf("n = %d\n", n); /* Output: n = 3 */ Implizite Typkonversion I Klammern entscheiden die Reihenfolge in der die Operatoren angewendet werden. double double double double x_1 x_2 x_3 x_4 = = = = printf("x_1 printf("x_2 printf("x_3 printf("x_4 = = = = 2 / 2 / 10. 10. 4; 4.; * 2 / 4; * (2 / 4); %lf\n", %lf\n", %lf\n", %lf\n", x_1); x_2); x_3); x_4); Implizite Typkonversion I Klammern entscheiden die Reihenfolge in der die Operatoren angewendet werden. double double double double x_1 x_2 x_3 x_4 = = = = printf("x_1 printf("x_2 printf("x_3 printf("x_4 I = = = = 2 / 2 / 10. 10. 4; 4.; * 2 / 4; * (2 / 4); %lf\n", %lf\n", %lf\n", %lf\n", x_1); x_2); x_3); x_4); Der Code produziert den Output: x_1 x_2 x_3 x_4 = = = = 0.000000 0.500000 5.000000 0.000000 Explizite Typkonversion I Man kann dem Compiler mitteilen, in welcher Form eine Variable interpretiert werden muss. I Man stellt dazu den Ziel-Typ in Klammern voran double x_1 = (double) (2 / 4); double x_2 = (double) 2 / 4; double x_3 = 2 / (double) 4; Explizite Typkonversion I Man kann dem Compiler mitteilen, in welcher Form eine Variable interpretiert werden muss. I Man stellt dazu den Ziel-Typ in Klammern voran double x_1 = (double) (2 / 4); double x_2 = (double) 2 / 4; double x_3 = 2 / (double) 4; I Die Variablen haben den Wert: x_1 = 0.000000 x_2 = 0.500000 x_3 = 0.500000 Vordefinierte Funktionen aus math.h Die Datei math.h gehört zur C-Standardbibliothek. Mit #include <math.h> wird sie einem Programm hinzugefügt. Die Datei enthält eine Reihe von wichtigen mathematischen Funktionen, z. B. double x, base, exponent; fabs(x) /* absolute value of x */ sqrt(x) /* square root of x */ sin(x), cos(x) /* sine, cosine of x */ exp(x) /* exponential function of x */ pow(base,exponent)/* base raised to the power exponent*/ Die genannten Beispiele erwarten als Parameter Gleitpunktzahlen und geben auch einen double-Wert zurück: double x = fabs(-2.); double y = pow(4.,2.); /* x = 2. */ /* x = 16. */ Explizites Linken der Mathematik-Bibliothek Allein das Hinzufügen von math.h durch die #include-Direktive reicht in der Regel nicht aus, damit ein Programm kompiliert wird. Fehlermeldungen der Form $ gcc -o math math.c math.c:(.text+0x6b): undefined reference to ‘sqrt’ werden beim Linken (letzter Schritt im Übersetzungsvorgang) verursacht. Anders als etwa die Bibliotheksdatei stdio.h muss die Mathematik-Bibliothek im Compileraufruf explizit gelinkt werden: $ gcc -o math math.c -lm Berechnung der Quadratwurzel Quelltext (Quadratwurzel) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <math.h> #include <stdio.h> int main(void) { /* declare floating point variable */ double x; /* read from user input */ printf ("x = "); scanf("%lf", &x); /* print value of x and square root */ printf ("x = %f\n", x); printf ("sqrt(x) = %f\n", sqrt(x)); return 0; } Gliederung Rechnen mit Variablen Darstellung von Integer-Datentypen Gleitpunktzahlen Ausdrücke Bedingte Anweisungen Einfache Verzweigung Binärsystem Das Binärsystem ist ein Zahlensystem, das zur Darstellung von Zahlen nur die Ziffern 0 und 1 benutzt: Sei n ∈ N, 0 ≤ n < 2m , dann ist die Darstellung n= m−1 X xi 2i (xi ∈ {0, 1}) i=0 eindeutig. Im Binärsystem hat n die Darstellung xm−1 xm−2 . . . x1 x0 |2 Beispiel: Es gilt 378|10 = 1 · 28 + 0 · 27 + 1 · 26 + 1 · 25 + 1 · 24 + 1 · 23 + 0 · 22 + 1 · 21 + 0 · 20 Damit ist: 378|10 = 101111010|2 . Bits und Bytes I Unter einem Bit stellen wir uns eine Binärziffer x ∈ {0, 1} vor (d. h. x nimmt nur die Werte 0 oder 1 an). I Ein Byte ist eine zusammenhängende Folge von Bits. Alle Objekte in C werden in einer zusammenhängenden Folge von Bytes gespeichert. I Der C-Standard schreibt eine Länge von mindestens 8 Bit für ein Byte vor. Die Größ eines Zeichens vom Typ char entspricht einem Byte. Endlicher Wertebereich Ein Objekt vom Typ int belegt eine endliche Zahl an Byte im Speicher. Damit lassen sich nicht beliebig große Zahlen in einem Integer-Datentyp darstellen. Angenommen, eine vorzeichenlose Integer-Größe belegt M Bit. Die größte darstellbare Zahl lässt sich sofort angeben: . . 111} |2 , |111 .{z M das entspricht der Zahl M−1 X 2i = 2M − 1. i=0 Sollen positive wie negative Zahlen dargestellt werden, muss ein Bit für das Vorzeichen reserviert werden. Integer-Datentypen Die Integer-Datentypen zerfallen in zwei Klassen: solche, die negative wie positive Werte darstellen können (signed), und solche, die ausschließlich positive Werte annehmen (unsigned). Sämtliche Integer-Datentypen in C sind: /* signed integer types */ /* unsigned integer types */ signed char unsigned char short int unsigned short int int unsigned int long int unsigned long int long long int unsigned long long int Die verschiedenen Datentypen haben zum Teil eine unterschiedliche Speichergröße und damit auch unterschiedlich große Wertebereiche. Speichergrößen ermitteln Die Speichergröße in Byte eines Datentyps, einer Variablen oder Konstanten lässt sich mit dem sizeof-Operator ermitteln, z. B. printf("sizeof(int) = %zu Byte\n", sizeof(int)); printf("sizeof(double) = %zu Byte\n", sizeof(double)); Der Standard macht nur „weiche“ Vorgaben zu den Größen der einzelnen Datentypen. Je nach System können diese unterschiedlich ausfallen: Datentyp char short int int long int long long int Größe (PC, 32 Bit) Größe (PC, 64 Bit) 1 Byte 2 Byte 4 Byte 4 Byte 8 Byte 1 Byte 2 Byte 4 Byte 8 Byte 8 Byte Tabelle : Größe in Byte der Integer-Datentypen Ganzzahlüberlauf Lässt sich eine Zahl nicht mehr darstellen, spricht man von einem Ganzzahlüberlauf. Wir können diesen Fall künstlich herbeiführen: int n = (int)(pow(2.,31.)-1.); /* n = 2^31 - 1 printf("%d\n", n ); /* Output: 2147483647 printf("%d\n", n+1); /* Output: -2147483648 */ */ */ Ganzzahlüberlaufe können insbesondere bei Schleifen-Anweisungen mit grossen Datenmengen auftreten. Gliederung Rechnen mit Variablen Darstellung von Integer-Datentypen Gleitpunktzahlen Ausdrücke Bedingte Anweisungen Einfache Verzweigung Normalisierte Gleitpunktzahlen zu x ∈ R existiert die Darstellung: ∞ X x = (−1) ( xk 2−k )2e s k =1 mit s ∈ {0, 1}, Ziffern xk ∈ {0, 1} und Exponent e ∈ Z. normierte Gleitpunktzahlen eine Teilmenge von R für die die Darstellung: P X x = (−1)s ( xk 2−k )2e k =1 existiert, mit P ∈ N, s ∈ {0, 1}, x0 = 1 und x1 , . . . , xP ∈ {0, 1} und Exponent e ∈ [−emin , emax ]. Normalisierte Gleitpunktzahlen I Die Zahl m = x1 .x2 . . . xP heißt Mantisse von x. Die festgewählte Zahl P nennt man Mantissenlänge. I Eine Gleitpunktzahl x ist festgelegt durch Vorzeichen, Mantisse und Exponent: x = (s, m, e). Normalisierte Gleitpunktzahlen Anweisung: double x; I Bedeutet Deklaration einer Variablen, bedeuted Angabe eines Datentyps und eines Bezeichners: I Bei Deklaration wird dem Datentyp (double = 64 Bit) entsprechend Speicher reserviert: e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 e10 e11 e12 e13 e14 e15 e16 e17 e18 e19 e20 e21 e22 e23 e24 e25 e26 e27 e28 e29 e30 e31 e32 e33 e34 e35 e36 e37 e38 e39 e40 e41 e42 e43 e44 e45 e46 e47 e48 e49 e50 e51 e52 e53 e54 e55 e56 e57 e58 e59 e60 e61 e62 e63 I mit ei ∈ {0, 1} zufällige (uninitialisierte) Werte. Normalisierte Gleitpunktzahlen Anweisung: double x = constValue; constValue wird gerundet auf die nächstegelegene Gleitpunktzahl: x = (−1)s ( 52 X xk 2−k )2e , mit e ∈ [−1022, 1023] k =1 s n0 n1 n2 n3 n4 n5 n6 n7 n8 n9 n10 x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11 x12 x13 x14 x15 x16 x17 x18 x19 x20 x21 x22 x23 x24 x25 x26 x27 x28 x29 x30 x31 x32 x33 x34 x35 x36 x37 x38 x39 x40 x41 x42 x43 x44 x45 x46 x47 x48 x49 x50 x51 x52 I Vorzeichen (1 Bit), s ∈ {0, 1} ⇒ (−1)s = ±1 I Exponent (11 Bit), n0 , . . . , n10 ∈ {0, 1} entspricht Binärdarstellung von e. I Mantisse (52 Bit), x1 , . . . , x52 ∈ {0, 1} Arithmetik von Gleitpunkt-Zahlen Aufgrund ihrer Darstellung kommt es bei Gleitpunkt-Zahlen zu einer Reihe unintuitiver Phänomene, die das Ergebnis einer Berechnung beeinflussen. Die Addition von Gleitpunkt-Zahlen ist nicht assoziativ, d. h. im Allgemeinen gilt nicht: (a + b) + c = a + (b + c). 1 #include <stdio.h> 2 3 4 5 int main(void) { double a = 0.1, b = 0.2, c = 0.3; 6 /* print up to 20 decimal places */ printf("(a+b)+c = %1.20lf\n", (a+b)+c); printf("a+(b+c) = %1.20lf\n", a+(b+c)); 7 8 9 10 return 0; 11 12 } Gliederung Rechnen mit Variablen Darstellung von Integer-Datentypen Gleitpunktzahlen Ausdrücke Bedingte Anweisungen Einfache Verzweigung Ausdrücke Ein Ausdruck ist eine Kombination von Variablen, Konstanten und Operatoren: I Beispiel: x = 3 + y; I Durch Operatoren werden bestehende Ausdrücke (Argumente) zu komplexeren Ausdrücken verknüpft. Die Auswertung eines Ausdrucks liefert immer I einen Typ I und einen Wert. Wir müssen in der Lage sein, zusammengesetzte Ausdrücke zu zerlegen und ihren Typ und Wert zu bestimmen. Elementare Ausdrücke – Konstanten Bei Angaben von Werten, z. B. 0 1 3.14159265358979 /* integer type constant */ /* integer type constant */ /* floating point type constant */ handelt es sich um konstante (numerische) Ausdrücke. Konstanten haben einen Typ, der sich aus ihrem Wert ableiten lässt. Typ und Wert des Ausdrucks stimmen mit denen der Konstante überein. Durch Angabe zusätzlicher Attribute kann ein Integer-Typ näher spezifiziert werden: 0u 1l /* constant of type unsigned int */ /* constant of type long int */ Elementare Ausdrücke – Variablen Variablen haben vom Moment ihrer Definition an einen Typ und einen Wert: int a = 1; double x; /* value of x is unspecified */ Namen von Variablen a x /* expression of type int, value 1 */ /* expression of type double and random value */ entsprechen Ausdrücken, die in Typ und Wert mit denen der Variable übereinstimmen. Elementare Ausdrücke – Funktionsaufrufe Funktionen in C haben immer einen Rückgabetyp. Im Moment können wir auf den Rückgabetyp nur schließen: I Rückgabetyp double: ceil, fabs, floor, sqrt, ... I Rückgabetyp int: abs, printf, ... Funktionsaufrufe sind Ausdrücke der Form sqrt(2.) abs(-1) die in Typ und Wert stimmt mit Rückgabetyp und -wert (falls vorhanden) der Funktion übereinstimmen. Unäre, binäre und ternäre Operatoren Durch Operatoren werden Ausdrücke (Argumente, Operanden) verknüpft und zu zusammengesetzten Ausdrücken. Operatoren können ein, zwei oder drei Argumente haben. Ein Operator heißt. . . unär, falls der Operator auf nur einen Operanden wirkt. Beispiel: +, - (Vorzeichenoperatoren), binär, falls der Operator auf zwei Operanden wirkt. Beispiel: +, -, *, / % (arithmetische Operatoren), ternär, falls der Operator auf drei Operanden wirkt. Beispiel: ?: (Konditionaloperator). Beispiele zusammengesetzter Ausdrücke Die arithmetischen Operatoren für Addition, Subtraktion, Multiplikation und Division sind binäre Operatoren: 1+2 1./2. Ist x eine Gleitpunktzahl-Variable, so ist auch der folgende Ausdruck gültig: x = -sqrt(2) Der Zuweisungsoperator = ist ebenfalls binär. Auch komplexe arithmetische Ausdrücke werden korrekt ausgewertet (Punkt-vor-Strich-Rechnung, Klammern werden berücksichtigt): 1+(2+4)*8 /* result is 49 */ Priorität und Assoziativität von Operatoren Typ und Wert eines zusammengesetzten Ausdrücks müssen eindeutig bestimmt sein. Ihre Auswertung erfolgt unter Berücksichtigung von. . . Priorität Rangfolge, die festlegt, in welcher Reihenfolge Operatoren innerhalb eines Ausdrucks ausgewertet werden Assoziativität gibt an, in welcher Richtung Operatoren und Operanden zusammengefasst werden Beispiel (Assoziativität von rechts nach links): int a, b, c, d, e; e = 2; a = b = c = d = e; /* a = 2, b = 2, c = 2, d = 2 */ Klammer-Operator Der Klammer-Operator gehört zu einer Gruppen von Operatoren der höchsten Priorität (s. Appendix). So wird sichergestellt, dass Ausdrücke innerhalb der Klammern zuerst ausgewertet werden. Syntax: (expr) Hier und im folgenden steht expr für einen gültigen Ausdruck. Beispiel: (2+4)*8 /* evaluates to 6*8, result is 48 */ Zuweisungen In Zuweisung werden Werte in Variablen gespeichert. Entscheidend ist, dass auf der linken Seite einer Zuweisung nur Variablen (allgemeiner sog. lvalues) zugelassen sind: x = 1. /* Okay. */ 1. = 1. /* Error! */ a*x = 1. /* Error! */ Zuweisungen sind nicht kommutativ! Die Variable, die mit einem Wert belegt werden soll, steht links: a = b b = a /* assign value of b to a */ /* assign value of a to b */ Zuweisungsoperator Der Zuweisungsoperator = ist binär und hat einen linken und einen recheten Operanden. Nach der Zuweisung, z. B. x = 2 hat der Ausdruck den Wert und Typ des linken Operanden. Zuweisungsoperatoren haben mit die geringste Priorität aller Operatoren. So wird sichergestellt, dass zuerst der Ausdruck auf der rechten Seite der Zuweisung ausgewertet wird. Weitere Zuweisungsoperatoren Neben dem einfachen Zuweisungsoperator = gibt es weitere Zuweisungsoperatoren, die wie Kurzschreibweisen für häufig verwendete Operationen funktionieren: x x x x x += -= *= /= %= y y y y y /* /* /* /* /* equals: x = x x = x * x = x / x = x % x = x + y */ y */ y */ y */ y, for integers x, y only */ Priorität und Assoziativität sind genau wie bei =. Gliederung Rechnen mit Variablen Darstellung von Integer-Datentypen Gleitpunktzahlen Ausdrücke Bedingte Anweisungen Einfache Verzweigung Vergleichsoperatoren Objekte von numerischen Datentypen können miteinander verglichen werden. Hierzu dienen die binären Operatoren: expr1 expr1 expr1 expr1 expr1 expr1 < expr2 <= expr2 > expr2 >= expr2 == expr2 != expr2 /* /* /* /* /* /* less than */ less than or greater than greater than equal to */ not equal to equal to */ */ or equal to */ */ Beispiel: double x = 1., y = 2.; x < y; x >= y; x == y; x != y; Frage: Welchen Typ und Wert haben die obigen Ausdrücke? Logische Ausdrücke Vergleichsoperatoren liefern einen logischen Ausdruck. Ihre Auswertung resultiert in genau einem von zwei möglichen Werten: I Der Rückgabewert ist 0, falls der Ausdruck unwahr ist, I Der Rückgabewert ist 1, falls der Ausdruck wahr ist. Logische Ausdrücke sind damit vom Typ int. Umgekehrt gilt jeder von von Null verschiedene Ausdruck als „wahre“ Aussage. Jeder Ausdruck mit Wert Null wird als „unwahr“ interpretiert. Andere Programmiersprachen (z. B. C++, Java) reservieren einen eigenen Datentyp für Wahrheitswerte. Logische Operatoren In C gibt es die folgenden Operatoren zur Verknüpfung logischer Ausdrücke: !expr expr && expr expr || expr /* logical negation */ /* logical conjunction */ /* logical disjunction */ Ihr Wert ist jeweils vom Typ int und entweder 0 (falls die Aussage unwahr ist) oder 1 (sonst). Sogenannten Wahrheitstafeln kann man den Wert einer logischen Verknüpfung entnehmen: x y | !x | x && y | x || y ------------+-------+---------+-------true true | false | true | true true false | false | false | true false true | true | false | true false false | true | false | false ------------+-------+---------+-------- Mehrfache Vergleiche Mehrfache Vergleiche sind in C nicht sinnvoll. Wir geben ein einfaches Beispiel. Es gilt 1 1 1 < < . 8 4 2 Der folgende Ausdruck hat aber den Wert 0 (die Aussage ist „unwahr“): 0.125 < 0.25 < 0.5 denn die Vergleichsoperatoren werden von links nach rechts abgearbeitet. Gliederung Rechnen mit Variablen Darstellung von Integer-Datentypen Gleitpunktzahlen Ausdrücke Bedingte Anweisungen Einfache Verzweigung if-Anweisung Mit der if-Anweisung kann der Ablauf eines Programms von Bedingungen abhängig gemacht werden. Syntax: if(expr) statement Die Semantik der Anweisung ist naheliegend: I Der Ausdruck expr wird ausgewertet. Der Ausdruck heißt „wahr“, falls er ungleich Null ist. I Falls expr wahr ist, wird die folgende Anweisung statement ausgeführt. Der Ausdruck expr heißt Kontrollausdruck. Zusammenfassen von Anweisungen, Blöcke Sollen mehrere Anweisungen abhängig vom Kontrollausdruck ausgeführt werden, müssen sie in einem Block zusammengefasst werden: Syntax: if(expr) { statements ... } Beispiel: int a, b, c, d; ... if(a > b) { c = a - b; d = sqrt(c); return d; } Programmbeispiel Quelltext (Wurzelberechnung mit Fehlerbehandlung) 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 <math.h> #include <stdio.h> int main(void) { /* declare floating point variable */ double x; /* read from user input */ printf ("x = "); scanf("%lf", &x); /* check if value greater than 0 */ if( x < 0 ) { printf("value x is less than zero\n"); return 0; } /* print value of x and square root */ printf ("x = %f\n", x); printf ("sqrt(x) = %f\n", sqrt(x)); return 0; } if-else-Anweisung Optional kann die if-Anweisung um den sog. else-Zweig erweitert werden. if(expr) statement else statement Ob die Anweisungen des if- oder des else-Zweigs bearbeitet werden, hängt vom Kontrollausdruck ab: I Ist der Kontrollausdruck wahr, so wird der if-Zweig bearbeitet, I sonst werden die Anweisungen des else-Zweigs bearbeitet. Sowohl im if- als auch im else-Zweig können mehrere Anweisungen zu jeweils einem Block zusammengefasst werden. Programmbeispiel Quelltext (Wurzelberechnung mit Fehlerbehandlung) 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 <math.h> #include <stdio.h> int main(void) { /* declare floating point variable */ double x; /* read from user input */ printf ("x = "); scanf("%lf", &x); if( x < 0 ) { printf("value x is less than zero\n"); } else { printf ("x = %f\n", x); printf ("sqrt(x) = %f\n", sqrt(x)); } return 0; }