Fakultät für Informatik Lehrstuhl 11 / Algorithm Engineering Prof. Dr. Petra Mutzel, Carsten Gutwenger Sommersemester 2009 DAP2 Praktikum Blatt 1 Ausgabe: 21. April Abgabe: 22. 24. April Kurzaufgabe 1.1 Schreibe ein Programm, das mit zwei positiven, ganzzahligen Eingabeparametern a und b aufgerufen wird und den größten gemeinsamen Teiler von a und b ausgibt. Wird das Programm nicht korrekt aufgerufen, so soll es eine Fehlermeldung mit einer Beschreibung des korrekten Aufrufs ausgeben. Benutze dazu Euklids Algorithmus, der sehr einfach rekursiv wie folgt formuliert werden kann: function Euclid(a, b) if b = 0 then return a else return Euclid(b, a mod b) end if end function Kurzaufgabe 1.2 Schreibe ein Programm, das für einen Eingabeparameter n mit Hilfe des Sieb des Eratosthenes die Primzahlen bis n berechnet. Dieser Algorithmus legt zunächst ein boolesches Array isprime an, das mit den Zahlen von 2,..., n indiziert werden kann und mit true initialisiert ist. Das bedeutet, zunächst sind alle Zahlen potentielle Primzahlen. Danach werden die Zahlen von i = 2,..., n durchgegangen. Falls isprime[i] true ist, dann ist i tatsächlich eine Primzahl und für alle Vielfachen j > i von i wird isprime[j] auf false gesetzt. Dein Programm soll als Eingabe die Obergrenze n sowie einen optionalen Parameter -o (für output) bekommen, den Algorithmus ausführen und, falls -o angegeben wurde, die Primzahlen auch ausgeben. Ein Aufruf für die Primzahlen bis 100 mit Ausgabe würde beispielsweise so aussehen: eratosthenes 100 -o Du kannst auch zusätzlich die Laufzeit für den Aufruf des Algorithmus ausgeben. (Es werden aber keine Punkte abgezogen, wenn dieser Teil fehlt.)
1.1 Parameter der main-funktion Hinweise und Tipps Für die main-funktion gibt es zwei möglich Prototypen. Die erste Variante hat keine Parameter und kann gewählt werden, wenn man das Programm keine Eingabeparameter benötigt: int main() Will man jedoch bei einem Aufruf von der Kommandozeile (oder beispielsweise auch von einem Shell-Skript aus) Parameter an das Programm übergeben, dann muss man die zweite Variante wählen: int main(int argc, char *argv[]) Dabei bezeichnet der Parameter argc die Anzahl der übergebenen Parameter und argv ist ein Array von C-Strings. Zu beachten ist, dass der erste Parameter argv[0] immer der Programmname selbst ist, die eigentlich übergebenen Parameter sind also argv[1],,argv[argc-1]. Das folgende Beispielprogramm gibt die übergebenen Parameter aus: #include <iostream> using namespace std; int main(int argc, char *argv[]) { for(int i = 0; i < argc; ++i) cout << "Parameter " << i << ": " << argv[i] << endl; return 0; } 1.2 Arbeiten mit C- und C++-Strings Die Parameter, mit denen unser Programm aufgerufen werden, erhält die main-funktion als ein Array von C-Strings, also 0-terminierte Folgen von char. Wollen wir diese C-Strings in Zahlen umwandeln, so können wir die in <cstdlib> deklarierten Funktionen benutzen: double atof(const char *str) wandelt den C-String str in einen double um int atoi(const char *str) wandelt den C-String str in einen int um long atol(const char *str) wandelt den C-String str in einen long um double strtod(const char *str, char **stop) wandelt den C-String str in einen double um und setzt stop auf das nächste Zeichen nach der Zahl double strtol(const char *str, char **stop, int base) wandelt den C-String str in einen long um und setzt stop auf das nächste Zeichen nach der Zahl; base ist dabei die Basis der Zahlendarstellung double strtoul(const char *str, char **stop, int base) wandelt den C-String str in einen unsigned long um und setzt stop auf das nächste Zeichen nach der Zahl; base ist dabei die Basis der Zahlendarstellung
Jede dieser Funktionen liest die Zahl am Anfang des C-Strings str und ignoriert weitere Zeichen, d.h. "3.14159Hier ist Schluss" wird als die Zahl 3.14159 gelesen. Bei den letzten drei Varianten wird stop auf den Beginn der ignorierten Zeichenfolge gesetzt, im Beispiel würde also *stop auf den C-String "Hier ist Schluss" zeigen. Will man zwei C-Strings miteinander vergleichen, darf man nicht einfach den Gleichheitsoperator benutzen (str1 == str2), da man dann lediglich die Zeiger miteinander vergleichen würde. Stattdessen muss man die Funktion int strcmp(const char *str1, const char *str2) aus <cstring> verwenden. Der Rückgabewert gibt die lexikographische Ordnung der beiden Strings an: kleiner 0 bedeutet str1 < lex str2, gleich 0 bedeutet beide Strings sind gleich, und größer 0 bedeutet str1 > lex str2. Alternativ können wir auch C-Strings in C++-Strings umwandeln, welche durch die Klasse string repräsentiert werden. Diese wird in <string> definiert und erlaubt ein komfortableres Arbeiten mit Strings. So ist es zum Beispiel erlaubt, string Objekte mit Hilfe des Vergleichsoperators == direkt miteinander zu vergleichen. C-Strings können dabei auch implizit in C++-Strings umgewandelt werden: string str = "Hallo"; if(str == "Hallo") Zur Umwandlung von Strings in Zahlen können wir auch C++ Stringsstreams (istringstream) verwenden. Dazu konstruiert man aus einem C- oder C++-String einen C++ Eingabestream und liest dann mit Hilfe des Eingabeoperators >> aus dem Stream. Das folgende einfache Beispiel verdeutlicht diese Vorgehensweise: #include <iostream> #include <sstream> using namespace std; int main() { const char *str = "3.14159Hier ist Schluss"; istringstream is(str); float x; is >> x; cout << "x = " << x << endl; } return 0; Zu bemerken ist, dass im Aufruf des Konstruktors von istringstream zunächst automatisch der C-String in einen C++ string umgewandelt wird. Die Deklarationen für Stringstreams sind in <sstream> enthalten.
1.3 Dynamische Arrays Wir bezeichnen Arrays, deren Größe erst zur Laufzeit bekannt ist, als dynamische Arrays. In C++ alloziert man den Speicher für ein dynamisches Array mit dem new-operator und gibt ihn mit dem delete-operator wieder frei. Vergisst man das Freigeben des Speichers, so wird dieser erst bei Beendigung des Programms wieder freigegeben. Man spricht dann auch von einem Speicherleck (memory leak), was in Programmen zu unnötig hohem Speicherverbrauch führt. Im folgenden Beispiel legen wir ein dynamisches int-feld array der Größe n an und geben anschließend den Speicher wieder frei: int *array = new int[n]; delete [] array; Man beachte die Verwendung von [] beim delete-operator. Vergisst man diese Klammern, dann wird nur der Speicher für einen int freigeben, nicht das ganze Feld! Wie bei C++ üblich ist der Indexbereich des Arrays [0..n-1]. Die Elemente des Arrays werden mit dem Default-Konstruktor des Datentyps initialisiert. Im Falle eines primitiven Datentyps (int, float, char,... ) ist der Defaultwert aber undefiniert, d.h. es können irgendwelche Werte im Array stehen und man kann nicht davon ausgehen, dass alle Werte mit 0 initialisiert wären. Einige Compiler, wie beispielsweise der gcc, erlauben auch bei statischen Arrays dynamische Feldgrößen, d.h. man kann z.b. folgendes schreiben: int array[n]; Hierbei ist allerdings Vorsicht geboten. Im Gegensatz zu dem durch den new-operator angelegten Array wird dieses, wie jedes statische Array, auf dem Programmstack und nicht auf dem Heap angelegt. Da der Programmstack aber nur eine beschränkte, zur Compilezeit festgelegte Größe hat, kann man hier keine großen Arrays anlegen, insbesondere kann man den vorhanden Hauptspeicher nicht voll ausnutzen. 1.4 Messung von Laufzeiten Um Laufzeiten in Programmen zu messen, kann man die clock-funktion benutzen, welche in <ctime> deklariert ist: clock_t clock() Die Funktion gibt die Prozessorzeit in Clockticks zurück. Dabei ist clock t ein integraler Datentyp, der in <ctime> deklariert ist. Um Clockticks in Sekunden umzuwandeln, muss man den Wert durch CLOCKS PER SEC (ebenfalls in <ctime> deklariert) dividieren. Ein Beispielcode zur Laufzeitmessung sieht dann wie folgt aus: #include <ctime> // Beginn der Messung clock_t tstart = clock(); // Hier wird der Code ausgeführt, dessen Laufzeit gemessen werden soll
// Ende der Messung clock_t tend = clock(); // Umwandlung in Sekunden double secs = double(tend - tstart) / CLOCKS_PER_SEC; Vill Glèck! das DAP2-Team