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


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 letzte Tag des C-Kurs befasste sich im Theorieteil mit den folgenden Themen:

  • Zeigern
  • Schreiben und Lesen aus und in Datien
  • Definieren von eigenen Datentypen
  • Die Aufgaben der anderen Tage finden sich hier:

Die Kursunterlagen zum Theorieteil (Präsentationsfolien) finden sich online. Mittlerweile sind die Folien zu allen Tagen online, auch die Übungszettel finden sich dort.

  1. Zeiger I
    Schreiben Sie ein Programm, das sowohl den Variablenwert als auch die genaue Speicheradresse einer Variablen ausgibt. Die Ausgabe soll einmal über den direkten Zugriff und einmal über den indirekten Zugriff erfolgen.

    Meine Lösung:

    /* aufg-1.c */
    
    #include <stdio.h>
    
    int main(){
       int i = 5;
    
       printf("Die Adresse der Variable lautet: %pn", &i);
       printf("Der direkte Zugriff auf den Variablenwert: %d n", i);
       printf("Der indirekte Zugriff auf den Variablenwert: %d n", *(&i));
    }

    Anmerkung:
    In der Vorlesung haben wir andauernd den vorzeichenbehafteten Integer-Datentyp als Ausgabeformatierung benutzt, um die Adresse der Variablen anzugeben. Der Integer-Wert wird als Zweierkomplement interpretiert.
    Daher kommen unter anderem auch negative Zahlenwerte bei der Ausführung heraus:

    Die Adresse der Variable lautet: -1073744356

    Der Speicher wird jedoch, wie alles andere auf der Hardware-Ebene auch, über Binär-Muster angesprochen, also über Bits, die entweder gesetzt (d.h. 1) oder nicht gesetzt (d.h. 0) sind. Man kann diese Binärmuster als Zahlen Interpretieren, mit welchen sich die Speicherzellen Byteweise von 0 bis 4 Milliarden eindeutig “nummerieren” lassen (also max. 4 GB, wenn man mit jeder Adresse ein Byte als kleinste Adressierungseinheit nimmt). Das setzt sich dadurch zusammen, dass ein 32-bit System Adressen in der Länge von 32 bit verarbeiten kann (analog auf 64-bit Systemen 64 bit). Daraus resultiert dann: 232 = 4.294.967.296. Geteilt durch 1024 → 4 Millionen Kilobytes, geteilt durch 1024 → 4 Tausend Megabytes, geteilt durch 1024 → 4 Gigabytes.

    Dafür eignet sich die Zweierkomplementdarstellung wie sie bei vorzeichenbehaftete Integer-Werten genutzt wird, nicht – da sie auf 32-bit Maschinen nur 32 bit lang ist, und davon die hälfte für negative Zahlen benötigt wird. Genau so breit ist aber auch die Adresse für eine Speicherzelle. Und so wird der Integer-Wert von 0 bis 2.147.483.647 ganz normal dargestellt. Die 2.147.483.648te Speicherzelle lässt sich nicht mehr darstellen, hier findet ein Speicherüberlauf statt (die Zahl die gespeichert werden soll ist größer, als die Variable, in der sie gespeichert wird), das Binärmuster für die Zahl 2.147.483.684 wird bei vorzeichenbehafteten Integer-Wertn als -2.147.483.648 interpretiert.

    Besser wäre also, die Unsigned-Variante zu nutzen. Damit lassen sich alle Speicherzellen auch als positive Zahl darstellen:

    /* aufg-1.c */
    
    #include <stdio.h>
    
    int main(){
       int i = 5;
    
       printf("Die Adresse der Variable lautet: %un", &i);
       printf("Der direkte Zugriff auf den Variablenwert: %d n", i);
       printf("Der indirekte Zugriff auf den Variablenwert: %d n", *(&i));
    }

    Die Adresse der Variable lautet: 3221222940

    Das Interessante hieran ist, dass das Binärmuster gleich bleibt – also ist auch die Adresse die selbe. Es wird nur diesmal nicht als eine negative Zahl interpretiert, sondern als eine Zahl die größer ist als 2.147.483.648.

    Die beste Variante jedoch ist die Darstellung als Hexadezimalzahl. Es hat sich eingebürgert, bei Bit-Relevanten Darstellungen (wie z.B. die Speicheradressierung) die Hexadezimale Darstellung zu nutzen, da man mit dieser 8 bits zu zwei Zeichen zusammenfassen kann. 8 bit wiederum entsprechen einem Byte. Man kann also 1011 1001 Hexadezimal darstellen als: B9. Beide Darstellung entsprechen der 185. Ergebnis ist also: Mit der Hexadezimalschreibweise lassen sich Zahlen viel kürzer darstellen, als Binär oder Dezimal. Außerdem ist die Konvertierung einfach, da jedes Zeichen ein halbes Byte ist.
    Das Formatierungszeichen %p steht dabei wohl sogar für “pointer”, also ist speziell für Adressen angelegt. Alternativ gibt es auch %x und %X um Zahlen als Hexadezimalwerte darzustellen.

    /* aufg-1.c */
    
    #include <stdio.h>
    
    int main(){
       int i = 5;
    
       printf("Die Adresse der Variable lautet: %pn", &i);
       printf("Der direkte Zugriff auf den Variablenwert: %d n", i);
       printf("Der indirekte Zugriff auf den Variablenwert: %d n", *(&i));
    }

    Die Adresse der Variable lautet: 0xbffff61c

    Diese Lösung ist also um einiges besser und sinnvoller als die im Kurs vorgestellte Lösung mit dem Formatierung als

  2. Zeiger II
    Schreiben Sie ein Programm, das 10 Integer-Werte von der Tastatur einliest und diese in einem Feld abspeichert. Alle Werte des Feldes sollen addiert werden und die Summe anschließend wieder ausgegeben werden.

    1. Die Summenbildung soll im Hauptprogramm erfolgen.

      Meine Lösung:

      /* aufg-2a */
      #include <stdio.h>
      
      int main(){
         int werte[10], i, summe;
         i = 0;
         summe = 0;
      
         for(i;i<10;i++){
            printf("Bitte geben sie eine Zahl ein: ");
            scanf("%d",(werte+i));
         }
      
         for(i=0;i<10;i++)
            summe = summe + *(werte+i);
      
         printf("Die Summe der eingegebenen Werte lautet: %dn",summe);
         return 0;
      }
    2. Die Summenbildung soll in einer Funktion erfolgen, d.h. hier soll das Feld an eine Funktion übergeben werden.

      Meine Lösung:

      /* aufg-2b */
      #include <stdio.h>
      
      // Funktionsprototyp
      int arraysumme(int *werte);
      
      // main Funtkion
      int main(){
         int werte[10];
         int i = 0;
      
         for(i;i<10;i++){
            printf("Bitte geben sie eine Zahl ein: ");
            scanf("%d",werte + i);
         }
         printf("Die Summe der eingegebenen Werte lautet: %dn", arraysumme(werte));
         return 0;
      }
      
      // Summe eines 10-elementigen Arrays
      int arraysumme(int *werte){
         int summe = 0;
         int i = 0;
      
         for(i=0;i<10;i++)
            summe = summe + *(werte + i);
         return summe;
      }

    Anmerkung:
    Da es in dieser Aufgabe um Zeiger geht, habe ich überall die Werte über ihre Adressen und nicht ihre Variable. Natürlich wäre, gerade Aufgabe 2a die Referenzierung über werte[i] einfacher und verständlicher. Das würde man auch machen. Zeiger machen bei Arrays (nach Aussage des Kursleiters) nur in dem Falle Sinn, wenn das Array an eine Funktion übergeben wird, weil die direkte Übergabe eines Arrays an eine Funktion nicht möglich sei. Jedoch habe ich am zweiten Tag eine eben solche Funktion geschrieben (siehe Tag 2, Aufgabe 7).
    In dem Buch Jürgen Wolf: C von A bis Z. 3. Auflage wird erklärt, dass ein Array und ein Zeiger zwar nicht das gleiche sind, ein Array aber in ein Zeiger “zerfällt” wenn er als Parameter übergeben wird. Interessanterweise funktioniert aber auch folgendes:

    /* aufg-2b-alt.c */
    #include <stdio.h>
    
    // Funktionsprototyp
    int arraysumme(int *werte);
    
    // main Funtkion
    int main(){
       int werte[10];
       int i = 0;
    
       for(i;i<10;i++){
          printf("Bitte geben sie eine Zahl ein: ");
          scanf("%d",werte + i);
       }
       printf("Die Summe der eingegebenen Werte lautet: %dn", arraysumme(werte));
       return 0;
    }
    
    // Summe eines 10-elementigen Arrays
    int arraysumme(int *werte){
       int summe = 0;
       int i = 0;
    
       for(i=0;i<10;i++)
          summe = summe + werte[i];
       return summe;
    }

    Folglich findet also eine automatische Konvertierung in beide Richtungen zwischen Zeigern und Arrays statt. Das macht die Unterscheidung zwischen Array und Pointer natürlich ein wenig schwieriger. In der Literatur wird aber regelmäßig hervorgehoben, dass es hier wichtige Unterschiede gibt.

    Ein paar Erklärungsversuche gibt es auch in den FAQ der Newsgroup de.comp.lang.c. Die Universität Bayreuth hat auch einen Text veröffentlicht, der die Äquivalenzen zwischen Feldern und Zeigern darstellt.

  3. Zeiger III
    Schreiben Sie ein Programm mit einer Funktion names addarray. Diese Funktion soll zwei Felder der gleichen Größe übernehmen und die Elemente mit gleichem Index in eiden Feldern addieren und die Ergebnisse in einem dritten Feld ablegen. Aus den Elementen des dritten Feldes soll anschließend der Mittelwert gebildet werden, der dann wieder an das Hauptprogramm zurück gegeben wird.

    Meine Lösung:

    /* aufg-3.c */
    #include <stdio.h>
    
    // Prototyp
    int addarray(int *x, int *y);
    
    // main
    int main(){
       int array1[10],array2[10],i;
       
       for(i=0;i<10;i++){
          *(array1 + i) = i;
          *(array2 + i) = 10;
       }
       printf("Der Mittelwert der Summe der zwei Arrays lautet: %dn",addarray(array1, array2));
       return 0;
    }
    
    // funktion
    int addarray(int *x, int *y) {
       int i, erg[10];
       int summe = 0;
    
       for(i=0;i<10;i++){
          *(erg+i) = *(x + i) + *(y + i);
          summe+= erg[i];
       }
       printf("Die Summe lautet: %dn", summe);   
       return (summe / 10);
    }

    Anmerkung:
    Auch hier gilt natürlich wieder das gleiche wie für die Aufgabe drüber; die eher ungewöhnliche Schreibweise über Zeiger dient zur Einübung mit dem Umgang mit Zeigern.

  4. Zeiger IV
    Schreiben Sie ein Programm , das die Zahlenwerte zweier eingegebenen Variablen vertauscht. Dabei soll die Vertauschung in einer Funktion mit dem Namen swap durchgeführt werden.

    Meine Lösung:

    /* aufg-4 */
    #include <stdio.h>
    
    // Prototypen
    void swap(int *x, int *y);
    
    // main
    int main(){
       int x = 1;
       int y = 100;
       
       printf("Unvertauscht: x = %d, y = %dn",x,y);
       swap(&x, &y);
       printf("Vertauscht: x = %d, y = %dn",x,y);
    }
    
    // swap
    void swap(int *x, int *y){
       int z = *<span class="hiddenGrammarError" pre=""><span class="hiddenGrammarError" pre="x ">x;
       *x</span></span> = *<span class="hiddenGrammarError" pre=""><span class="hiddenGrammarError" pre="x "><span class="hiddenGrammarError" pre="y ">y;
       *y</span></span></span> = z;
    }
  5. in Dateien schreiben
    Schreiben Sie ein Programm , das die Zahlen von 1 bis 100 in eine Datei mit dem Namen datei1.txt schreibt.

    Meine Lösung:

    /* aufg-5.c */
    #include <stdio.h>
    
    int main(){
       // Variablen Deklaration & Initiation
       int i = 1;
       FILE *datei;
       datei=fopen("datei1.txt","w");
    
       for(i;i<101;i++)
          fprintf(datei, "%in",i);
       fclose(datei);
       printf("Feddisch!n");
    }
  6. von Dateien einlesen
    Schreiben Sie nun ein Programm, das den Inhalt der Datei datei1.txt einliest und die Zahlen in umgekehrter Reihenfolge wieder ausgibt. Diese Ausgabe soll sowohl auf dem Bildschirm als auch in die Datei ausgabe.out geschrieben werden.

    Meine Lösung:

    /* aufg-6.c */
    #include <stdio.h>
    
    int main(){
       // Variablen 
       FILE *in, *out;
       in=fopen("datei1.txt","r");
       out=fopen("datei2.txt","w");
       int temp[100], i;
    
       for(i=99;i>-1;i--){
          fscanf(in,"%d",&temp[i]);
          printf("Eingelesen: %dn",temp[i]);
       }
    
       for(i=0;i<100;i++)
          fprintf(out,"%dn",temp[i]);
       
       fclose(in);
       fclose(out);
       printf("Feddischn");
    
       return 0;
    }
  7. Strukturen I
    Definieren Sie in einem Programm eine Struktur, die die Koordinaten (x und y) zweier Punkte von der Tastatur einliest und in den Instanzen punkt1 und punkt2 abspeichert.
    Addieren Sie die x- und y-Werte dieser beiden Punkte und speichern Sie dieses Ergebnis in einer dritten Instanz mit dem Namen punkt3 ab. Addieren Sie nun alle x- und y-Koordinatenwerte und speichern Sie dieses Ergebnis in der Variablen summe.

    Meine Lösung:

    * aufg-7.c */
    #include <stdio.h>
    
    int main(){
       // definieren der Struktur und Initialisieren und Deklarieren von Variablen
      struct coord {
         int x;
         int y;
      } punkt1, punkt2, punkt3;
      int summe = 0;
    
      // Eingabe der Daten 
      printf("Bitte geben Sie die x-Koordinate für Punkt 1 ein: ");
      scanf("%d",&punkt1.x);
      printf("Bitte geben Sie die y-Koordinate für Punkt 1 ein: ");
      scanf("%d",&punkt1.y);
      printf("Bitte geben Sie die x-Koordinate für Punkt 2 ein: ");
      scanf("%d",&punkt2.x);
      printf("Bitte geben Sie die y-Koordinate für Punkt 2 ein: ");
      scanf("%d",&punkt2.y);
    
      // Berechnungen
      punkt3.x = punkt1.x + punkt2.x;
      punkt3.y = punkt1.y + punkt2.y;
      summe = punkt1.x + punkt2.x + punkt3.x + punkt1.y + punkt2.y + punkt3.y;
    
      // Ausgabe
      printf("Punkt 3: (%d, %d)n",punkt3.x,punkt3.y);
      printf("Summe aller Koordinaten: %dn",summe);
    
      return 0;
    }
  8. Strukturen II
    Schreiben Sie ein Programm mit einer Struktur rechteck, das die Fläche eines Rechtecks berechnet. Die erforderlichen zwei Koordinatenpunkte sollen über die Tastatur eingelesen werden.

    Meine Lösung:

    /* aufg-8.c */
    #include <stdio.h>
    
    // Strukturdeklaration
    struct coord {
       int x;
       int y;
    };
       
    struct rectangle {
       struct coord a;
       struct coord c;
    };
    
    // Funktionsdeklaration
    int area(struct rectangle rechteck);
    
    int main(){
    
       // Variablendeklaration;
       struct rectangle rechteck;
      
      // Einlesen der Daten
      printf("Bitte geben Sie die x-Koordinate für Punkt 1 ein: ");
      scanf("%d",&rechteck.a.x);
      printf("Bitte geben Sie die y-Koordinate für Punkt 1 ein: ");
      scanf("%d",&rechteck.a.y);
      printf("Bitte geben Sie die x-Koordinate für Punkt 2 ein: ");
      scanf("%d",&rechteck.c.x);
      printf("Bitte geben Sie die y-Koordinate für Punkt 2 ein: ");
      scanf("%d",&rechteck.c.y);
    
      // Ausgabe des Ergebnis
      printf("Die Fläche des Rechtecks beträgt: %dn",area(rechteck));
      return 0;
    }
    
    // Berechnung des Flächenihnalts eines Rechtecks
    int area(struct rectangle rechteck) {
       return ((rechteck.a.x - rechteck.c.x) * (rechteck.a.y - rechteck.c.y));
    }
    

    Anmerkung:
    Diese Aufgabe hat mir zunächst einige Kopfschmerzen bereitet. Ich habe nämlich immerzu Fehler wie diesen hier bekommen:

    aufg-8.c: In function ‘main’:
    aufg-8.c:31: error: type of formal parameter 1 is incomplete

    Irgendwann habe ich dann auch die Lösung für das Problem gefunden. Anders als die Reihenfolge von Funktionsdeklarationen und -definitionen ist die Reihenfolge bei Strukturen relevant.

    So hatte ich ursprünglich zunächst die Struktur “rectangle” definiert, und erst dann die Struktur coord. Das gab ein Problem. Ein weiteres Problem war die Funktionsdeklaration. Diese darf erst nach der Deklaration der Struktur erfolgen, wenn sie Daten von dieser Struktur verarbeitet oder als Rückgabewert hat. Diese drei Fehler werfen jeweils den Fehlertext, dass etwas “incomplete” ist.

Das also waren alle Aufgaben des C-Kurses. Wenn es hierzu Fragen oder Anmerkungen gibt, oder aber bessere Lösungsvorschläge, schreibt mir bitte gerne Kommentare!

Die Lösungen aller Tage habe ich außerdem hochgeladen. Ihr findet sie unter meinem bitbucket-Account. Der Sourcecode kann über das Webinterface als komprimierte Datei, oder aber direkt mit Mercurial über:

$ hg clone https://bitbucket.org/pygospa/c-kurs

bezogen werden.

Da ich im nächsten Semester ein wenig fitter in C sein muss, lese ich zur Zeit auch die Bücher:

  • Kernighan, Ritchie: Programmieren in C. ANSI C. 2. Ausgabe
    DAS Referenzwerk, auch K&R C genannt, vom Erfinder von der Programmiersprache und von UNIX, Dennis Ritchie und dem Mitentwickler von C, Brian Wilson Kernighan. Von diesem Buch stammt übrigens das berühmte Hello World, das seit dem traditionell das erste Programm beim erlernen einer neuen Programmiersprache ist 😉
  • Jürgen Wolf: C von A bis Z. Das umfassende Handbuch. 3. Auflage
    Ich glaube es muss während meines Abiturs gewesen sein (so zwischen 2000-2003), da fand ich die Webseite www.pronix.de. Anfangs noch nur ein kleines, verrücktes Projekt, ein möglichst ausführliches C-Tutorial, wuchs es rasend schnell und erlangte hohen Bekanntheitsgrad. Mittlerweile ist es ein Buch, mit dem Titel C von A bis Z, in der 3. Auflage bei Galileo Press. Und weil es als offenes Tutorial gestartet wurde, dass viel vom Feedback der Leser profitiert hat, ist es auch jetzt noch ein Galileo <openbook>, und damit online als HTML-Seite lesbar, sowie zum herunter laden.
  • Herold, Arndt: C-Programmierung. Unter Linux, UNIX und Windows. 2. Aufl
    Zu dem Buch kann ich noch nicht viel sagen – in der Bibliothek hat es mich angelacht. Es scheint ganz gut geschrieben zu sein, hat viele Grafiken zur Veranschaulichungen, Struktogramme, Flussdiagramme, Speicherzellendiagramme, etc. Auch viele Codegegenüberstellungen und -vergleiche.

Vielleicht werde ich noch ein paar Beispiele und Aufgaben aus diesen Büchern hier vorstellen. Und falls mir dann noch Fehler oder bessere Lösungen zu diesen Aufgaben hier einfallen werden, dann werde ich die auch hier einbauen.

Bis dahin: Have fun 😉

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