Übung 4
- Bearbeiten Sie Lautstärke.
- Bearbeiten Sie Filter.
- 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?
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.
Wo zu finden?
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?
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?
Was gibt
fread
zurück, wenn eine Datei vollständig eingelesen wurde?
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
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 char
s, 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?
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?
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!