Lösungen zum C-Kurs des Rechenzentrums, Tag 2


Dieser Eintrag gehört zu einer Reihe von drei Einträgen zu den Aufgaben des Rechenzentrums der Universität Kiel zum C-Kurs. Er beinhaltet die Lösungen der letzten Aufgaben des C-Kurses.

Der zweite Tag des C-Kurs befasste sich im Theorieteil mit den folgenden Themen:

  • Arrays
  • Strings
  • Funktionen
  • Globale und lokale Variablen
  • rekursive Funktionen

Die Aufgaben der anderen Tage finden sich hier:

Die Kursunterlagen zum Theorieteil (Präsentationsfolien) finden sich online (bisher nur zum ersten Tag, die nächsten sollten aber demnächst folgen).

  1. Felder I
    Schreiben Sie ein Programm das ein Feld mit 10 Elementen initialisiert. Jedes Element soll den Wert seines Index enthalten. Geben Sie anschließend die 10 Elemente auf dem Bildschirm aus.

    Meine Lösung:

    /* aufg-1.c */
    #include <stdio.h>
    
    int main(){
       int feld[10], i;
    
       for(i=0; i<10; i++)
          feld[i] = i;
    
       for(i=0; i<10; i++)
          printf("Inhalt aus Feld[%d] ist %dn",i,feld[i]);
    
       return 0;
    }
  2. Felder II
    Ändern Sie das obige Programm ab. Nach der Ausgabe der initialiserten Werte soll das Programm die Werte in ein neues Feld kopieren und zu jedem Wert 10 addieren. Anschließend sollen die neuen Werte wieder auf dem Bildschirm ausgegeben werden.

    Meine Lösung:

    /* aufg-2.c */
    #include <stdio.h>
    
    int main(){
       int feld1[10], feld2[10], i;
    
       for(i=0; i<10; i++)
          feld1[i] = i;
    
       for(i=0; i<10; i++)
          feld2[i] = feld1[i] + 10;
    
       for(i=0; i<10; i++)
          printf("Inhalt aus Feld2[%d] ist %dn",i,feld2[i]);
    
       return 0;
    }

    Anmerkung:

    Natürlich könnte man das alles auch in einer Schleife lösen. Gleiches gilt auch für die Aufgabe 1. Die Aufgabe sagt aber deutlich, dass diese Schritte hintereinander passieren sollen. Schlanker, schöner und performanter wäre, die Schleife nur einmal durchlafen zu lassen:

    /* aufg-2.c */
    #include <stdio.h>
    
    int main(){
       int feld1[10], feld2[10], i;
    
       for(i=0; i<10; i++){
          feld1[i] = i;
          feld2[i] = feld1[i] + 10;
          printf("Inhalt aus Feld2[%d] ist %dn",i,feld2[i]);
       }
       return 0;
    }
  3. Felder III
    Schreiben Sie ein Programm, das zehn Integer-Zahlen von der Tastatur einliest und am Ende sowohl den kleinsten als auch den größten eingegebenen Zahlenwert wieder ausgibt mit dem Satz:
    Sie haben Zahlenwerte zwischen XXX und YYYY eingegeben.

    Meine Lösung:

    /* aufg-3.c */
    #include <stdio.h>
    
    int main(){
       int feld[10], i, min, max;
       
       // Einlesen der 10 Werte
       for(i=0; i<10; i++) {
          printf("Bitte geben Sie eine Zahl ein: ");
          scanf("%d",&feld[i]);
       }
       
       // setzte ersten Wert als Min/Max
       min=feld[0];
       max=feld[0];
       
       // Vergleiche Werte mit Min/Max, setzte ggf neues Min/Max
       for(i=1; i<10; i++) {
          if(feld[i]<min)
             min = feld[i];
          if(feld[i]>max)
             max = feld[i];
       }
    
       printf("Sie haben Zahlenwerte zwischen %d und %d eingegeben.n",min,max);
       return 0;
    }
  4. Strings
    Schreiben Sie ein Programm, das zwei Vornamen und 2 Altersangaben von der Tastatur einliest. Anschließend sollen diese Informationen wieder folgendermaßen auf dem Bildschirm ausgegeben werden:
    Die beiden Personen mit den Vornamen xxx und yyy sind zusammen zzz Jahre alt.

    Meine Lösung:

    /* aufg-4.c */
    #include <stdio.h>
    #include <string.h>
    
    int main(){
       int i, alter[2];
       char name[2][50];
    
       for(i=0; i<2; i++){
          printf("Bitte geben Sie einen Vornamen ein: ");
          scanf("%s",&name[i]);
          printf("Bitte geben Sie das dazugehörige Alter an: ");
          scanf("%d",&alter[i]);
       }
    
       printf("Die beiden Personen mit den Vornamen %s und %s sind zusammen %d Jahre alt.n", name[0], name[1], alter[0]+alter[1]);
    
       return 0;
    }
  5. Funktionen I
    Das folgende Programm soll die Fläche eines Rechtecks berechnen.
    Arbeitet das Programm korrekt?
    Welche Änderungen sind für eine einwandfreie Arbeitsweise erforderlich?

    #include <stdio.h>
    int main()
    {
       printf("%d n",Flaeche(6.5,5.5));
       return 0;
    }
    double Flaeche(double laenge, double hoehe)
    {
       double ergebnis;
       ergebnis=laenge*breite;
       return ergebnis;
    }

    Meine Lösung:

    /* aufg-5.c */
    #include <stdio.h>
    
    // Deklaration der Funktion
    double Flaeche(double laenge, double hoehe);
    
    int main(){
       printf("%lfn", Flaeche(6.5,5.5));
       return 0;
    }
    
    double Flaeche(double laenge, double hoehe) {
       return laenge * hoehe;
    }
  6. Funktionen II
    • Schreiben Sie eine Funktion, das zwei Integer-Zahlen als Argument übernimmt. Die Funktion soll die erste Zahl durch die zweite Zahl dividieren.
    • Fügen Sie diese Funktion in ein vollständiges Programm ein, d.h. erstellen Sie die main-Funktion.
      Was passiert in Ihrem Programm, wenn die zweite Zahl Null ist? Fangen Sie diese Situation mit einer if-Bedingung ab.

    Meine Lösung:

    /* aufg-6.c */
    #include <stdio.h>
    
    // Funktionsdeklaration
    float teile(int x, int y);
    
    int main(){
       int x, y;
       printf("Bitte geben Sie eine Zahl ein: ");
       scanf("%d",&x);
       printf("Bitte geben Sie eine zweite Zahl ein: ");
       scanf("%d",&y);
    
       while(y==0){
          printf("Der zweite Wert darf keine 0 sein!nBitte Eingabe wiederholen: ");
          scanf("%d",&y);
       }
    
       printf("Die Division der Zahle ergibt: %fn",teile(x,y));
       return 0;
    }
    
    float teile(int x, int y){
       return ((float) x / (float) y);
    }
  7. Funktionen III
    Schreiben Sie ein Programm, das fünf Integer-Werte vom Benutzer abfragt und daraus mit einer Funktion den Mittelwert berechnet.

    Meine Lösung:

    /* aufg-7.c */
    #include <stdio.h>
    
    // Funktionsdeklaration
    int mittelwert(int werte[5]);
    
    int main(){
       int werte[5], i;
    
       for(i=0;i<5;i++) {
          printf("Bitte eine Zahl eingeben: ");
          scanf("%d",&werte[i]);
       }
    
       printf("Der Mittelwert der eingegebenen Werte beträgt: %dn",mittelwert(werte));
       return 0;
    }
    
    int mittelwert(int werte[5]){
       int i, summe = 0;
       for(i=0;i<5;i++)
          summe+=werte[i];
       return summe/5;
    }
  8. Funktionen IV: Zusatzaufgabe
    Bilden Sie einen Taschenrechner nach:
    Es werden zwei Zahlen von der Tastatur eingelesen.
    Die Art der Berechnung (Addition, Subtraktion, Division oder Multiplikation) wird abgefragt.
    Fangen Sie in Ihrem Programm die Division durch Null ab.
    Das Ergebnis der Berechnung soll anschließend auf dem Bildschirm ausgegeben werden.
    In der Hauptfunktion wird die Eingabe-Funktion solange aufgerufen, bis der Benutzer die Abfrage durch die Eingabe eines bestimmten Wertes abbricht.

    Meine Lösung:

    /* aufg-8.c */
    #include <stdio.h>
    
    // Funktionsdeklarationen
    double addiere(double x, double y);
    double subtrahiere(double x, double y);
    double dividiere(double x, double y);
    double multipliziere(double x, double y);
    int input();
    
    // Hauptfunktion
    int main(){
       int in=1;
       while(in!=0)
          in=input();
    }
    
    // Eingabefunktion
    int input(){
       double x, y, erg;
       int wahl;
    
       printf("nBitte geben Sie eine Zahl ein: ");
       scanf("%lf",&x);
       printf("Bitte geben Sie eine zweite Zahl ein: ");
       scanf("%lf",&y);
    
       printf("nBitte wählen Sie eine Funktion:n 1) Additionn 2) Subtraktionn 3) Divisionn 4) Multiplikationn 0) Exit nn");
       printf("Ihre Wahl: ");
       scanf("%d",&wahl);
       
       switch(wahl) {
          case 0: 
             printf("nProgramm wird beendetn");
             return 0;
             break;
          case 1: 
             printf("nErgebnis: %lfn",addiere(x,y));
             break;
          case 2: 
             printf("nErgebnis: %lfn",subtrahiere(x,y));
             break;
          case 3: 
             printf("nErgebnis: %lfn",dividiere(x,y));
             break;
          case 4: 
             printf("nErgebnis: %lfn",multipliziere(x,y));
       }
       return 1;
    }
    
    double addiere(double x, double y) {
       return x+y;
    }
    
    double subtrahiere(double x, double y) {
       return x-y;
    }
    
    double dividiere(double x, double y) {
       if(y==0){
          printf("Eine Division durch 0 ist nicht definiert! Daher ist das Ergebnis 0n");
          return 0;
       } else
          return x/y;
    }
    
    double multipliziere(double x, double y) {
       return x*y;
    }
  9. rekursive Funktionen I
    Schreiben Sie ein Programm, das die Fakultät der Zahl n berechnet.
    Formel für die Berechnung: n! = 1*2*…(n-1)*n
    Beispiel: 5! = 1*2*3*4*5 = 120

    Meine Lösung:

    /* aufg-9.c */
    #include <stdio.h>
    
    // Funktionsdeklaration
    int fac(int x);
    
    // Hauptfunktion
    int main(){
       int n;
       printf("Bitte eine Zahl eingeben: ");
       scanf("%d",&n);
       printf("Die Fakultät von %d ist %d.n",n,fac(n));
       return 0;
    }
    
    // Fakultätsfunktion
    int fac(int n){
       if(n==1)
          return 1;
       else
          return n*fac(n-1);
    }
  10. rekursive Funktionen II
    Schreiben Sie ein Programm mit einer rekursiven Funktion, das die Potenz der Zahl 3 zu einem angegebenen Exponenten berechnet?
    (Gibt es einen Unterschied ob der Exponent größer oder kleiner als 19 ist?)

    Meine Lösung:

    /* aufg-10.c */
    #include <stdio.h>
    
    // Funktionsdeklaration
    int pot_drei(int n);
    
    // Hauptfunktion
    int main(){
      int n;
    
      printf("Bitte x für 3^x eingeben: ");
      scanf("%d",&n);
      printf("3^%d ergibt %dn",n,pot_drei(n));
    
      return 0;
    }
    
    // Exponent von 3
    int pot_drei(int n){
       if(n==0)
          return 1;
       else
          return 3*pot_drei(n-1);
    }

    Anmerkungen:

    Der Integer-Wert kann als Vorzeichenbehafteter Integer-Datentyps als höchste Zahl nur 231 als Wert speichern (entspricht der Zahl: 2.147.483.647). Für größere Werte ist er zu klein. Es kommt also zwischen 319 und 320 (entspricht den beiden Zahlen 1.162.261.467 und 3.486.784.401) zum Überlauf. Daher landen wir wieder im Negativen Bereich:

    pygospa@Laurelin ~/Desktop/c-kurs/tag2 % ./aufg-10
    Bitte x für 3^x eingeben: 20
    3^20 ergibt -808182895

    Mit dem vorzeichenlosen Datentyp können wir den Bereich auf 232 erweitern (4.294.967.296). Das reicht dann für 320 nicht aber für 321 (10.460.353.203), also nur ein Gewinn von einem weiteren Exponenten. Besser ist daher den Long Long Integer als Datentyp zu nutzen. Dieser hat auf 32-bit Systemen eine Breite von 64 bit. Als vorzeichenunbehafteter Datentyp kann dieser als höchste Zahl ca. 1,84×1019 aufnehmen, also bis 340 (1,21×1019).

    /* aufg-10-besser.c */
    #include <stdio.h>
    
    // Funktionsdeklaration
    unsigned long long int pot_drei(int n);
    
    // Hauptfunktion
    int main(){
      int n;
    
      printf("Bitte x für 3^x eingeben: ");
      scanf("%d",&n);
      printf("3^%d ergibt %llun",n,pot_drei(n));
    
      return 0;
    }
    
    // Exponent von 3
    unsigned long long int pot_drei(int n){
       if(n==0)
          return 1;
       else
          return 3*pot_drei(n-1);
    }

    ACHTUNG!

    Da wir hier mit einem Unsigned Datentyp und wirklich sehr großen Zahlen arbeiten, die sehr stark wachsen, wird nicht immer sofort ersichtlich, ob wir es mit einem Speicherüberlauf zu tun haben oder nicht!

    pygospa@Laurelin ~/Desktop/c-kurs/tag2 % ./aufg-10b
    Bitte x für 3^x eingeben: 40
    3^40 ergibt 12157665459056928801

    Dieser Wert ist noch richtig, denn wie wir oben verifiziert haben, passt er noch in den ausgewählten Datentyp. Wenn wir einen Schritt weiter gehen, dann erhalten wir:

    pygospa@Laurelin ~/Desktop/c-kurs/tag2 % ./aufg-10b
    Bitte x für 3^x eingeben: 41
    3^41 ergibt 18026252303461234787

    Ach dieser Wert sieht auf den ersten Blick völlig normal aus. Erst auf dem zweiten Blick fällt auf, dass wir es hier ebenfalls mit einer 20-Stelligen Zahl zu tun haben.

    1.2 multipliziert mit 3 sollte aber etwas deutlich höheres als Ergebnis haben, als 1.8 (das 1019 lasse ich jetzt mal weg, weil wir ja in der gleichen Dimension sind und bleiben) – nämlich etwas in Richtung 3.6.

    Der Taschenrechner verifiziert dies (in unserem Fall DrRacket, da es mit sehr großen Zahlen sehr genau rechnen kann):

    > (expt 3 40)
    12157665459056928801
    > (expt 3 41)
    36472996377170786403

    Besonders deutlich wird der Überlauf erst bei 342:

    pygospa@Laurelin ~/Desktop/c-kurs/tag2 % ./aufg-10b
    Bitte x für 3^x eingeben: 42
    3^42 ergibt 17185268762964601129

    Das Ergebnis ist tatsächlich kleiner, als das für 341.

    Aus diesem Grunde ist es essentiell, dass man sich vorher Gedanken über den Datentyp und die Größe der zu verarbeitenden Daten macht. Solche Fehler die nicht auffallen, sind besonders kritisch, wie der berühmte Ariane 5-Fall deutlich macht. Da solche Fehler Programmierfehler sind muss der Programmierer auch dafür sorgen, dass ein solcher Fehler vermieden wird, beispielsweise so:

    /* aufg-10nochbesser.c */
    #include <stdio.h>
    
    // Funktionsdeklaration
    unsigned long long int pot_drei(int n);
    
    // Hauptfunktion
    int main(){
      int n;
    
      printf("Bitte x für 3^x eingeben: ");
      scanf("%d",&n);
      while(n>40){
         printf("Bitte eine kleinere Zahl für x eingeben! (Max. 40): ");
         scanf("%d",&n);
      }
      printf("3^%d ergibt %llun",n,pot_drei(n));
    
      return 0;
    }
    
    // Exponent von 3
    unsigned long long int pot_drei(int n){
       if(n==0)
          return 1;
       else
          return 3*pot_drei(n-1);
    }
  11. rekursive Funktionen III (Zusatzaufgabe)
    Berechnen Sie die Fibonacci-Zahlen rekursiv.
    Die Folge ist rekursiv definiert durch:
    f0 = 0
    f1 = 1
    fn = fn-1 + fn-2
    Das bedeutet in Worten:
    Für die beiden ersten Zahlen werden die Werte Null und Eins vorgegeben. Jede weitere Zahl ergibt sich aus der Summe ihrer beiden Vorgänger.
    Daraus ergibt sich der Anfang der Folge zu
    0, 1, 1, 2, 3, 5, 8, 13, 21, 34, …
    Die Rekursionsgleichung für die Berechnung lautet:
    f(n+1)=f(n+1)+f(n)

    Meine Lösung:

    /* aufg-11.c */
    #include <stdio.h>
    
    // Funktionsdeklaration
    int fib(int n);
    
    // Hauptfunktion
    int main(){
       int x;
       
       printf("Bitte die gesuchte Fibonacci-Zahl eingeben: ");
       scanf("%d",&x);
       printf("Die gesuchte Fibonacci-Zahl lautet: %dn",fib(x));
       return 0;
    }
    
    // Fibonacci-Funktion
    int fib(int n){
       if(n==0)
          return 0;
       else if(n==1)
          return 1;
       else
          return (fib(n-1)+fib(n-2));
    }
Advertisements

Please comment. I really enjoy your thoughts!

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s