Übung 4

ℹ️
Bearbeitungszeitraum: 25. November - 8. Dezember.
⚠️
Möglicherweise wurden zu Beginn des Bearbeitungszeitraums noch nicht alle Inhalte, die für die Bearbeitung dieser Aufgaben benötigt werden, in der Vorlesung behandelt. Wenn Sie die Übungen trotzdem bereits bearbeiten wollen, können Sie die benötigten Inhalte aus den Vorlesungs-Notizen, der Section und den Short-Videos entnehmen. Der Bearbeitungszeitraum für diese Übung beträgt zwei Wochen.
  1. Bearbeiten Sie Lautstärke.
  2. Bearbeiten Sie Filter.
  3. Bearbeiten Sie Wiederherstellung.

Warm-Up-Aufgaben

Erstellen Sie für jede Übungseinheit jeweils eine eigene Datei für die Warm-Up-Aufgaben in dem Projektverzeichnis, in dem Sie auch die Übungsaufgaben bearbeiten werden.

Weitere Informationen finden Sie in diesem FAQ (Warm-Up-Aufgaben).

Warm-Up 1

Kopieren Sie das folgende Code-Snippet und ersetzen Sie den Kommentar, sodass die Adresse von buffer ausgegeben wird. Erinnern Sie sich, dass Sie mit & auf die Adresse einer Variablen zugreifen können.

#include <cs50.h>
#include <stdio.h>

int main(void)
{
    char buffer;

    printf("An der Adresse %p liegt der Inhalt '%c'.\n", /* Adresse des Buffers */, buffer);



    return 0;
}

Führen Sie das Programm mehrfach aus. Was fällt Ihnen auf?

Wo zu finden?
Bei Problemen und Unklarheiten schauen Sie sich am besten noch einmal die Notizen zu den Programmen addresses.c und garbage.c aus der Vorlesung 4. Memory sowie das Short zu Pointern an. Weitere Erklärungen finden Sie auch in der Section 4.

Warm-Up 2

Fügen Sie zwischen printf und return folgenden Code ein:

   char *pointer = &buffer;

   *pointer = 'A';

   printf("An der Adresse %p (Ausgabe `pointer`) liegt der Inhalt '%c' (Ausgabe `buffer`) / '%c' (Ausgabe `*pointer`).\n", pointer, buffer, *pointer);

   buffer = 'B';

   printf("An der Adresse %p (Ausgabe `pointer`) liegt der Inhalt '%c' (Ausgabe `buffer`) / '%c' (Ausgabe `*pointer`).\n", pointer, buffer, *pointer);

Führen Sie das Programm aus und vollziehen Sie die Ausgabe nach. Was passiert jeweils bei den Zuweisungen?

Erinnerung: Ein Pointer ist letztlich nur eine Variable, deren Inhalt eine Adresse ist. Der Typ des Pointers bezieht sich dabei auf den Inhalt, der an dieser Adresse zu finden ist. Dadurch weiß der Compiler beim Dereferenzieren, wie viele Bytes ab dieser Adresse gelesen werden müssen, um einen Wert des entsprechenden Typs zu erhalten. Bei einem char-Pointer wäre dies z.B. 1 Byte (8 Bit), da ein char in C 1 Byte groß ist.

Visualisieren Sie sich dies am besten einmal mit einer Handzeichnung
  • Welche Werte stehen an der jeweiligen Stelle?
  • Worauf kann mit welcher Syntax zugegriffen werden? Ordnen Sie buffer, &buffer, pointer, &pointer und *pointer den entsprechenden Boxen zu.
  • Nutzen Sie Pfeile, um Verbindungen zwischen den Inhalten zu visualisieren.
  • Vollziehen Sie dann den Code (d.h. die Zuweisungen und die daraus resultierenden Veränderungen) anhand Ihrer Zeichnung nach. Adresse, Inhalt und Pointer
Wo zu finden?
Bei Problemen und Unklarheiten schauen Sie sich am besten noch einmal die Notizen der Vorlesung 4. Memory zum Speicher und Pointern sowie das Short zu Pointern an. Weitere Erklärungen finden Sie auch in der Section 4.

Warm-Up 3

Kommentieren Sie den bisherigen Code aus und kopieren Sie folgendes Code-Snippet:

#include <cs50.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    string hello = "Hello World!\n";

    char buffer;

    string file_name = "hello.txt";

    // File-Pointer `hello_file` mit `fopen` mit Dateinamen `file_name` im Modus "w";
    // EINFÜGEN (1)

    // Wichtig: Immer prüfen, ob `fopen` erfolgreich einen entsprechenden File-Pointer erstellen konnte;
    if (hello_file == NULL)
    {
        printf("ERROR accessing file: %s\n", file_name);
        return 1;
    }


        // Inhalt von `hello` mit `fwrite` in `hello_file` schreiben;
        // EINFÜGEN (3)


    // Datei schließen mit `fclose`;
    // EINFÜGEN (2)


    return 0;
}

EINFÜGEN (1): Erstellen Sie einen File-Pointer hello_file mit fopen im Modus "w" (Schreiben). In diesem Modus wird eine noch nicht existierende Datei automatisch erstellt. Eine bereits existierende Datei mit diesem Namen wird überschrieben. Den Dateinamen hello.txt können Sie über die bereits initialisierte Variable file_name übergeben.

EINFÜGEN (2): Am Ende des Programms muss eine geöffnete Datei immer mit fclose geschlossen werden.

EINFÜGEN (3): Nutzen Sie fwrite, um den in hello initialisierten String in die Datei zu schreiben. Lesen Sie ggf. noch einmal die erwarteten Argumente für fwrite im Handbuch nach. Die Anzahl der zu schreibenden Elemente der Größe char kennen wir in diesem Fall - diese erhalten wir mit strlen(hello). Erinnern Sie sich daran, dass ein String letztlich ein char-Pointer ist und daher bereits eine Adresse repräsentiert.

Nach dem Ausführen des Programms sollten Sie eine Datei hello.txt mit dem Inhalt Hello World! im Dateibrowser finden.

Wo zu finden?
Bei Problemen und Unklarheiten schauen Sie sich am besten noch einmal die Notizen der Vorlesung 4. Memory zur File I/O sowie das Short zu File Pointern an. Weitere Erklärungen finden Sie auch in der Section 4.

Warm-Up 4

Nachdem wir nun bereits eine Datei erstellt haben, können wir auch aus dieser Datei lesen.

Ändern Sie in Zeile 14 den Modus von fopen für den File-Pointer für hello_file, indem Sie statt "w" nun "r" (Lesen) übergeben, da wir nun mit dem Programm aus der Datei “hello.txt” lesen wollen. Kommentieren Sie den Aufruf von fwrite aus Warm-Up 3 aus. Ansonsten können wir den bestehenden Code direkt weiterverwenden.

Wissen Sie noch, was fread zurückgibt? Ansonsten können Sie auch einmal einen Blick in den Abschnitt des Handbuchs werfen, der sich mit dem Rückgabewert von fread beschäftigt.

Was gibt fread zurück?
Erster Teil des Handbucheintrags zum return-Wert von fread
Was gibt fread zurück, wenn eine Datei vollständig eingelesen wurde?
Zweiter Teil des Handbucheintrags zum return-Wert von fread

Außerdem ist dort zu lesen, dass sich die mit fopen geöffnete Datei die Anzahl der erfolgreich gelesenen Bytes merkt. Bei weiteren Aufrufen der Funktion fread auf diese Datei werden daher jeweils die Bytes nach den bereits gelesenen Bytes eingelesen.

Was geschieht, wenn fread direkt in der Bedingung einer while-Schleife aufgerufen wird, z.B. so:

    while (fread(&buffer, sizeof(char), 1, hello_file))
    {

    }
Erklärung
Mit dieser while-Schleife lesen wir char für char, also jeweils ein Byte, aus der Datei hello_file aus. Der char wird jeweils in der Variable buffer zwischengespeichert. Daher hat in diesem Fall unsere Puffer-Variable auch die Größe von einem char, d.h. einem Byte. Wollten wir etwa den Umfang von 8 Bytes, d.h. 8 chars, auf einmal “puffern”, würden wir demnach etwa ein char-Array der Größe 8, also char buffer[8], benötigen. Natürlich müssten wir dann zusätzlich entweder die Größe (size) der gelesenen Daten oder die Anzahl der auf einmal zu lesenden Elemente (nmemb) entsprechend anpassen (siehe Handbuch). Sind alle Bytes gelesen, gibt fread schließlich 0 zurück, da 0 Elemente der Größe “1 Byte” gelesen wurden. Der zurückgegebene Wert 0 wird in der Bedingung als false interpretiert, weshalb die Schleife dann beendet wird.

Geben Sie innerhalb der Schleife den Wert von buffer jeweils mit

printf("%c", buffer);

aus. Es sollte also

Hello World!

ausgegeben werden.

Wo zu finden?
Bei Problemen und Unklarheiten schauen Sie sich am besten noch einmal die Notizen der Vorlesung 4. Memory zur File I/O sowie das Short zu File Pointern an. Weitere Erklärungen finden Sie auch in der Section 4.

Warm-Up 5

Anstatt die zwischengespeicherten Werte auf der Konsole auszugeben, können wir diese auch in eine neue Datei schreiben - also eine Kopie der gelesenen Datei erstellen.

Erstellen Sie einen zweiten File-Pointer hello_file_copy, der eine Datei mittels fopen im Modus w (Schreiben) erstellt. Übergeben Sie als Dateinamen "hello-copy.txt".

Vergessen Sie nicht zu prüfen, ob der return-Wert von fopen, also hello_file_copy, gleich NULL ist. Beenden Sie in diesem Fall das Programm. Fügen Sie am Ende zudem den Aufruf von fclose zum Schließen der Datei ein.

Schreiben Sie in der Schleife jeweils den Inhalt von buffer mit fwrite in die Datei hello_file_copy.

In der neu erstellten Datei “hello-copy.txt” sollte nach dem Ausführen nun auch Hello World! stehen. Ergänzen Sie den Inhalt von “hello.txt” und führen Sie das Programm erneut aus. Der zusätzliche Text sollte auch in “hello-copy.txt” zu sehen sein.

In welchem Fall ist also der Ansatz mit der while-Schleife vorteilhaft?

Wo zu finden?
Bei Problemen und Unklarheiten schauen Sie sich am besten noch einmal die Notizen der Vorlesung 4. Memory zur File I/O sowie das Short zu File Pointern an. Weitere Erklärungen finden Sie auch in der Section 4.

Warm-Up 6

Wir lesen also letztlich Zeichen für Zeichen aus der Datei aus und schreiben wieder Zeichen für Zeichen in die neue Datei. Vielleicht hat es in Ihrem Kopf bereits zu rotieren begonnen: Ja, so etwas ähnliches hatten wir bereits in einer der Aufgaben! In Cäsar haben wir Zeichen für Zeichen über einen String iteriert und jeweils Zeichen für Zeichen auf der Konsole ausgegeben. Mit einem Unterschied: Zwischen diesen Schritten haben wir das Zeichen verändert.

Natürlich können wir auch unsere Puffer-Variable buffer verändern, bevor wir sie in die Datei schreiben.

Kopieren Sie sich die rotate() Funktion aus Cäsar in Ihre Datei und rufen Sie

buffer = rotate(buffer, 1);

auf, bevor Sie den Wert von buffer in die Datei schreiben. Fügen Sie außerdem noch fehlende Header-Dateien und Konstanten zur Verwendung von rotate() ein.

Sie haben keine rotate-Funktion aus Cäsar?

Lagern Sie zur Übung den entsprechenden Code aus Ihrer Cäsar-Lösung in eine entsprechende Funktion mit der Signatur char rotate(char letter, int key) aus. Diese Funktion nimmt einen Buchstaben und einen Schlüssel, d.h. die Zahl der Verschiebung, entgegen und gibt den verschobenen Buchstaben zurück, wobei Groß- und Kleinschreibung berücksichtigt wird und alle nicht-alphabetischen Zeichen unverändert bleiben (siehe Cäsar).

Alternativ hier kopieren bzw. vergleichen
// Rotates a character in the alphabet n times
char rotate(char letter, int n)
{
    // Check if the character is an uppercase or lowercase letter
    if (isupper(letter))
    {
        // Rotate the character within the uppercase alphabet range
        return 'A' + (letter - 'A' + n) % ALPHASIZE;
    }
    else if (islower(letter))
    {
        // Rotate the character within the lowercase alphabet range
        return 'a' + (letter - 'a' + n) % ALPHASIZE;
    }

    // If the character is not a letter, return it unchanged
    return letter;
}

In “hello-copy.txt” sollte nach Ausführung des Programms statt Hello World! nun Ifmmp Xpsme! stehen.

Damit haben wir jetzt bereits ein kleines, minimalistisches Programm erstellt, um einfache Textdateien mit der Cäsar-Methode zu verschlüsseln. In den Übungsaufgaben brauchen Sie nun etwas Einarbeitungszeit - lassen Sie sich davon nicht abschrecken! -, aber dafür können Sie dann sogar Audio- und Bild-Dateien manipulieren - und schließlich sogar Daten von einem Speicherkartenabbild wiederherstellen!