Vorlesung 12 Flashcards
Entwicklung großer Programme
- Bislang: Quellcode in einer einzigen Datei
- Vorgehensweise gut geeignet für kleine Programme, bei größeren
Programmen entstehen folgende Probleme: - Große Quelldateien sind unübersichtlich und schlecht zu warten
- Editieren und Compilieren dauert wesentlich länger
- Eine voneinander unabhängige Arbeit der Teammitglieder ist nur
schwer möglich - Abhilfe: Aufteilung des Programms/Quelltextes in mehrere Dateien/Module
- Wiederverwendbarkeit (ein Modul in mehreren Programmen)
- Aufteilung auf mehrere Personen
- Nur geänderte Module müssen übersetzt werden
Design-Kriterien für Module
- Module bestehen aus einzelnen Funktionen
- Hoher Zusammenhang: Die Funktionen eines Moduls sollten inhaltlich
zusammenhängen - Geringe Kopplung: Nur wenige Funktionen sollten zur Kommunikation des
Moduls nach außen notwendig sein - Wiederverwendbarkeit von Modulen
Kommunikation zwischen Modulen
Funktionen und Variablen eines Moduls, die von anderen Programmteilen
benutzt werden, müssen in dem importierenden Modul deklariert werden.
* Deklarationen in Header-Dateien (Dateiendung .h), die mit #include
eingebunden werden.
* Eine Header-Datei enthält:
* #define- und #include-Befehle
* globale und externe Variablen
* Prototypen von globalen und externen Funktionen
* Selbstdefinierte Datentypen
Funktionsprototypen
- Deklaration einer Funktion mit ihren typisierten Parametern und
Rückgabewert ohne Funktionsrumpf. - Syntax wie Funktionskopf, Abschluss mit Semikolon.
- Beispiele:
int quadrat(int x);
void ausgabe(); - Funktionsdefinition (Funktionsrumpf) muss später folgen.
Speicherklassen von Funktionen
- Lebensdauer: während des gesamten Programms
- Sichtbarkeit
- in allen Quelldateien (keine Angabe)
- nur in der gleichen Quelldatei (Schlüsselwort static)
- Statische Funktionen (static) sind eine Ausnahme: Bei komplexen
Programmen, die aus mehreren Dateien (Modulen) bestehen.
Hilfsfunktionen, die nur innerhalb eines Moduls benötigt wird. Vermeidung
von Namenskonflikten.
static void fehlermeldung()
{
printf(“Es ist ein Fehler aufgetreten!\n”);
}
Kommandozeilenparameter
● Kommandozeilenparameter werden beim Aufruf eines Programms als
Parameter der Funktion main übergeben.
● Vollständige Definition der Funktion main:
int main(int argc, char *argv[])
oder
int main(int argc, char **argv)
- argc: Anzahl der Argumente (argc ≥ 1)
- argv: Zeiger auf Array von Zeigern auf Zeichenketten (argv[0] ist der
Programmaufruf, argv[1] das erste Argument, …, argv[argc-1] das
letzte Argument)
Kommandozeilenparameter beispiel
int main(int argc, char *argv[])
{
int i;
printf(“Programm %s mit %d Parameter(n) gestartet\n”,
argv[0], argc);
for(i = 0; i < argc; i++) {
printf(“Parameter %d: %s\n”, i, argv[i]);
}
}
Programmaufruf (aus Konsole/Eingabeaufforderung) und Ausgabe
> cmd_par toller text
Programm cmd_par mit 3 Parametern gestartet
Parameter 0: ./cmd_par
Parameter 1: toller
Parameter 2: text
Kommandozeilenparameter weiteres
● Eingegebene Zahlen stehen nur als Zeichenkette zur Verfügung
● Umwandeln in Zahl notwendig
● Beispiel mit Bibliotheksfunktion atoi
int main(int argc, char *argv[])
{
int a, b;
if(argc != 3) {
printf(“Programm mit zwei Zahl-Parametern aufrufen!\n”);
return 1;
}
a = atoi(argv[1]);
b = atoi(argv[2]);
printf(“Summe: %d\n”, a + b);
Zeitfunktionen – Datentypen
- Die Header-Datei time.h bietet verschiedene Funktionen, um Zeit zu erfassen
und zu messen. - Wesentliche Datentypen:
- clock_t bezeichnet CPU-Zeit
- time_t Zeitstempel in Sekunden
- struct tm erfasst einen Zeitpunkt im Kalender
Zeit ermitteln
- clock_t clock ()
- liefert die Rechnerkernzeit (Takte) seit Beginn des Programms
- Konstante CLOCKS_PER_SEC liefert Takte pro Sekunde
- time_t time (time_t* timer)
- liefert aktuelle Kalenderzeit (i.d.R. als Sekunden seit dem 1.1.1970 0:00 Uhr).
- Parameter ist optional. Kann NULL sein.
- double difftime (time_t end, time_t beginning)
- Berechnet Differenz zwischen zwei Zeitwerten in Sekunden.
- time_t mktime (struct tm * timeptr);
- Wandelt einen Wert in der tm-Struktur in time_t um.
Zeitwerte konvertieren
- char* asctime (const struct tm * timeptr);
- Konvertiert tm-Strukturwert in lesbare String-Form.
- struct tm *gmtime (const time_t * timer);
- Wandelt ein time_t-Objekt in tm-Strukturwert in UTC (Weltzeit) um.
- struct tm *localtime (const time_t * timer);
- Wandelt ein time_t-Objekt in tm-Strukturwert in lokaler Zeit um.
Anwendung: Zeit ausgeben
include <stdio.h></stdio.h>
#include <time.h>
int main() {
time_t current = time(NULL);</time.h>
const char* str = asctime(localtime(¤t));
printf(“Aktuelle Zeit:\n%s”, str);
return 0;
}
Anwendung: Zeit messen
include <stdio.h></stdio.h>
#include <time.h>
void complicatedAlgorithm() {
// do something complicated
}
int main() {
time_t startTime = time(NULL);</time.h>
complicatedAlgorithm();
double diff = difftime(time(NULL), startTime);
printf(“Dauer: %.1f s\n”, diff);
return 0;
}
Anwendung: Warten
include <stdio.h></stdio.h>
#include <time.h>
void waitFor(double secs) {
// Berechne zukünftigen Zeitstempel
clock_t futureTime = clock() + secs * CLOCKS_PER_SEC;</time.h>
// Warte bis Taktzahl erreicht ist
while (clock() < futureTime) { }
}
int main() {
printf(“Erste Ausgabe.\n”);
waitFor(5);
printf(“Zweite Ausgabe nach 5 Sekunden.\n”);
return 0;
}
Aufruf externer Programme
include <stdlib.h></stdlib.h>
Syntax
#include <stdlib.h>
int system(const char *s)
● Bedeutung
* liefert Zeichenkette s an Systemumgebung zur Ausführung
* d.h. mit der Funktion system kann aus einem Programm heraus ein anderes
Programm gestartet werden</stdlib.h>
int main()
{
system(“dir”);
/* system(“ls -l”); UNIX/Linux Version */
return 0;
}
Programmabbruch
● Syntax
#include <stdlib.h>
void abort()
void exit(int status)
int atexit(void (*fcn)(void))
● Bedeutung
* abort und exit beenden das Programm. Bei exit kann ein Rückgabewert an
das Betriebssystem angegeben werden, zudem werden alle Dateien ordentlich
geschlossen und die mit atexit festgelegten Funktionen aufgerufen
* Mit atexit können Funktionen „angemeldet“ werden, die bei einem
Programmende ausgeführt werden sollen, z.B. um Aufräumarbeiten wie Datei
speichern etc. vorzunehmen.</stdlib.h>
Zufallszahlen
Zufallszahlen: Eine Folge von Zahlen mit zufälligem Wert
● Anwendungsbereiche für Zufallszahlen
* Kryptographie (Schlüsselerzeugung)
* Simulation
* Stichproben (Auswahl aus großer Datenmenge)
* Entscheidungsfindung (z.B. bei Computerspielen)
● Problem: Computer arbeiten deterministisch
* Wie können zufällige Zahlen erzeugt werden?
* Antwort: gar nicht, aber Pseudozufallszahlen
Pseudozufallszahlen
● Pseudozufallszahlen sollen
* gleichmäßig verteilt sein
* sich möglichst lange nicht wiederholen
● Ein einfaches Verfahren für Zufallszahlen 0 bis m-1: Methode der linearen
Kongruenz (Lehmer, 1951)
a[0] = seed;
for(i = 1; i < n; i++)
a[i] = (a[i-1] * b + 1) % m;
- Güte abhängig von b und m
- Für Kryptographie nicht geeignet, da mit geeigneten Analysemethoden
Parameter aus Zufallsfolge berechenbar und somit vorhersagbar
Bibliotheksfunktionen für Zufallszahlen
include <stdlib.h></stdlib.h>
- Funktionen in stdlib.h
- int rand(): liefert eine Zufallszahl zwischen 0 und der Konstanten RAND_MAX
- void srand(unsigned int seed): Initialisierung der Zufallszahlenfolge
- Die time-Funktion ist hilfreich, um bei jedem Programmaufruf einen anderen
Startwert für den Zufallsgenerator zu setzen. - Beispiel: Erzeuge ganzzahlige Zufallszahlen im Intervall [1, 6]
#include <time.h>
...
srand((unsigned) time(NULL));
...
z1 = 1 + rand() % 6;
z2 = 1 + rand() % 6;</time.h>
Funktionen mit variabler Parameterliste
Bisher: Funktionsdefinition umfasst genaue Anzahl der Parameter und
genauen Typ jedes einzelnen Parameters.
● C erlaubt aber für Funktionen wie printf auch Parameterliste zu
definieren, die beliebig viele Parameter enthalten dürfen.
● Aber Vorsicht: Compiler kann dann keine Prüfung der Parameter vornehmen
(seltsames Verhalten bei unpassenden Variablen / Datentypen bei printf).
● Syntax
– [Speicherklasse] [Typ] Funktionsname ( fester_Parameter, …)
● Beispiel
int printf(const char *format, …)
● Beachte
– am Anfang ist mindestens ein fester Parameter erforderlich
– keine Variablen innerhalb der Funktion für die Parameter vorhanden
Funktionen mit variabler Parameterliste
weiteres
include <stdio.h></stdio.h>
Auslesen der Parameter mit Makros aus stdarg.h:
* va_list
Datentyp für Parameterliste
* va_start(va_list ap, lastarg)
Initialisierung der Argumentenliste, lastarg ist letzter
benannter Parameter
* va_arg(va_list ap, typ)
nächsten Parameter aus der Liste auslesen
* va_end(va_list ap)
Ende der Parameterbehandlung
#include <stdarg.h>
/* Berechne Mittelwert beliebig vieler double-Zahlen */
double mittelwert(int anz, ...)
{
int i;
double v, summe = 0;
va_list ap;
va_start(ap, anz);
for(i = 0; i < anz; i++) {
v = va_arg(ap, double);
summe += v;
}
va_end(ap);
return summe / anz;
}</stdarg.h>
übersichtlichkeit
— Bezeichner sollten sinnvolle, erklärende Namen haben.
* Je besser die Bezeichner, desto weniger Code-Kommentare
sind erforderlich.
* Schlecht gewählte Bezeichner erhöhen die Fehlerwahrscheinlichkeit
beträchtlich.
—Variablen
* Sinnvolle Bezeichner verwenden, die den Zweck/Inhalt der
Variablen beschreiben
* Kleinschreibung mit CamelCase:
student, studentList, currentStudent, …
* 1-Buchstaben-Variablen nur für triviale Zählvariablen (i, j, …)
—Strukturtypen, Aufzählungstypen und Unions
* Sinnvolle Bezeichner verwenden, die den Zweck des Typs
beschreiben
* Großschreibung mit CamelCase:
Node, LinkedList, DoubleLinked, List.
—Präprozessor-Direktiven und Makros
Sinnvolle Bezeichner verwenden, die den Zweck/Inhalt der
Variablen beschreiben
* Jeder Buchstabe groß, Trennung durch Unterstrich (_):
LOG_EVENT, NUMBER_OF_ENTRIES, IS_IMPERIAL
* Makro-Definition sollte nur eigene Parameter verwenden
* Referenzierung lokaler oder globaler Variablen ist problematisch.
guter stil
- Bei Funktionen, die mehr als ein Ergebnis zurückliefern
- Call by reference, oder
- Strukturtyp definieren
- Kommentare erhöhen die Lesbarkeit von Code.
- … wenn Sie sinnvoll eingesetzt werden.
Gliederung eines typischen Programms
Programme sind üblicherweise etwa wie folgt gegliedert:
1. Erläuternder Kommentar
2. #include – Anweisungen
3. #define – Anweisungen
4. Typdefinitionen
5. globale Variablen
6. Definition eigener Funktionen
7. main – Funktion
haufige Syntaxfehler
Syntaxfehler
* Fehlendes Semikolon am Ende von Anweisungen
* Fehlende geschweifte Klammer am Ende einer Funktion
* Fehlende Klammern beim Funktionsaufruf
* Falscher Multiplikationsoperator (* verwenden, nicht ·)
* Falscher Zugriff auf Elemente von Strukturen über Zeiger (-> vs. . beachten)
* Adressoperator bei Arrays oder Funktionen verwendet
* Makroparameter nicht geklammert
* Verwechslung von Zuweisung (=) und Gleichheit (==)
* Groß- und Kleinschreibung bei Variablen nicht beachtet
Häufige Fehler bei arrays
- Überschreiten von Array-Indizes
- Gegeben: Array a mit n Element
- Erstes Element: a[0]
- Letztes Element: a[n-1]
- Falsche Verwendung des Adressoperators
- Array-Bezeichner ohne Index sind Zeiger
- Beispiel: int a[5]
- a ist der Zeiger auf das erste Element und entspricht &a[0]
- &a ist ein Zeiger auf den Zeiger des ersten Elements