Ausdrücke ohne Wert in C (Reine Wirkfunktionen)
Diese Lektion wird gerade überarbeitet. Daher kann es sein, daß sie im derzeitigen Zustand noch Mängel enthält.
»abort«
Die Auswertung des Ausdrucks »abort()« beendet das laufende Programm.
- abort [Dokumentation]
Synopse
#include <stdlib.h>
_Noreturn void abort( void );
Wirkung: beendet das laufende Programm
mit einer Fehlermeldung.
Während die Auswertung des Aufrufausdruck »putchar( 65 )« einen Wert und eine Wirkung hat, hat die Auswertung des Aufrufausdruck »abort()" keinen Wert, sondern nur eine Wirkung. Wir sagen dazu auch, daß »abort()« ein reiner Wirkausdruck sei.
Um dies zu kennzeichnen wird in der Synopse das Wort "void" (leer, nichtig) dort vor den Namen der Funktion geschrieben, wo sonst der Rückgabetyp steht.
Das folgende Beispiel zeigt den Aufruf dieser Standardfunktion mit Hilfe einer Auswertungsanweisung. void -Wirkfunktionen passen gut zur Auswertungsanweisung, da sie wegen der Wirkung ihrer Auswertung verwendet werden.
main.c
#include <stdlib.h> /* abort */
int main( void ){ abort(); }
(Die Prozedur "abort" soll hier aber nicht zur Benutzung empfohlen werden. Sie wird nun deshalb hier verwendet, weil sie ein einfaches Beispiel einer reinen Wirkfunktion ist.)
Der Funktionsspezifizierer »_Noreturn«
Die Synopse für »abort« wurde oben etwas verkürzt, indem das in dieser Lektion unwichtige »_Noreturn« weggelassen wurde. In diesem Abschnitt soll das »_Noreturn« nun aber doch noch kurz erklärt werden.
- abort [Dokumentation]
Synopse
#include <stdlib.h>
_Noreturn void abort( void );
Wirkung: beendet das laufende Programm
mit einer Fehlermeldung.
Der Funktionsspezifizierer »_Noreturn« (aus »<stdnoreturn.h>«) teilt uns mit, daß diese Funktion nach ihrem Aufruf die Kontrolle über den Computer nicht an den Aufrufer zurückgibt. Die Kennzeichnung mit »_Noreturn« ist für reine Wirkfunktionen nicht typisch. Die meisten reinen Wirkfunktionen geben die Kontrolle zurück. Wir werden in dieser Lektion auch noch ein Beispiel einer reinen Wirkfunktion sehen, welche zu ihrem Aufrufer zurückkehrt (das heißt: die Kontrolle an ihren Aufrufer zurückgibt).
»srand«
- srand [Dokumentation] (vereinfacht)
Synopse
#include <stdlib.h>
void srand( int w );
Wirkung: Der Zufallszahlengenerator
wird auf den Zustandswert w eingestellt.
Die Auswertung des Ausdrucks »rand()« liefert eine Zufallszahl, welche von einem Zufallszahlengenerator erzeugt wird. Diese Zufallszahl wird durch den Zustands des Zufallszahlengenerators bestimmt. Der Zustand des Zufallszahlengenerators ist eine Zahl.
main.c
#include <stdio.h>
#include <stdlib.h>int main( void )
{ srand( 2 ); printf( "%d\n", rand() );
srand( 3 ); printf( "%d\n", rand() );
srand( 2 ); printf( "%d\n", rand() );
srand( 3 ); printf( "%d\n", rand() ); }- stdout (Ausgabe ist implementationsspezifisch)
45
48
45
48
Dies bedeutet, daß der Zufallszahlengenerator im gleichen Zustand auch die gleiche Zahl liefert. Es heißt aber nicht, daß die gelieferte Zufallszahl dem Zustand gleich ist.
main.c
#include <stdio.h>
#include <stdlib.h>int main( void )
{ srand( 1 ); printf( "%d\n", rand() );
srand( 1 ); printf( "%d\n", rand() ); }- stdout (Ausgabe ist implementationsspezifisch)
41
41
Die Auswertung des Ausdrucks »rand()« verändert den Zustand nachdem die zu liefernde Zufallszahl festgelegt wurde. Dies bedeutet, daß sich beim nächsten Aufruf von »rand()« eine andere Zufallszahl ergeben kann.
.--------. .-------. .-------. .--------. .--------.
( Argument )---->| srand |---->( Zustand )<---->| rand() |---->( Ergebnis )
'--------' '-------' '-------' '--------' '--------'main.c
#include <stdio.h>
#include <stdlib.h>int main( void )
{ srand( 2 ); printf( "%d ", rand() ); printf( "%d\n", rand() );
srand( 3 ); printf( "%d ", rand() ); printf( "%d\n", rand() );
srand( 2 ); printf( "%d ", rand() ); printf( "%d\n", rand() );
srand( 3 ); printf( "%d ", rand() ); printf( "%d\n", rand() ); }- stdout (Ausgabe ist implementationsspezifisch)
45 29216
48 7196
45 29216
48 7196
Wir sehen hier zum ersten Mal das wichtige Prinzip, daß es einen von mehreren Funktionen geteilten Zustand gibt, auf den von außen nicht direkt zugegriffen werden kann (Kapselung, privater Zustand). Wir haben eine Wirkfunktion, welche die Veränderung dieses Zustandes erlaubt, und eine Wertfunktion, deren Verhalten von diesem Zustand beeinflußt wird. (Unter einer Wertfunktion verstehen wir eine Funktion, deren Aufruf einen anderen Typ als »void« hat. Hat die Auswertung des Aufrufs eine Funktion laut Dokumentation eine Wirkung, so nennen wir jene Funktion ein Wirkfunktion.)
Der Zustand kann mit »srand« festgelegt werden, aber es gibt keine Möglichkeit, ihn direkt auszulesen.
Typische Aufrufmuster
Ausdrücke ohne Wert können nicht als Argumente oder Operanden verwendet werden.
Matrix
Beispiel Wertverwendung Wirkungsrealisierung
kein Wert und keine Wirkung - - ...; (keine Wirkung)
Wirkung und kein Wert abort - ...;
Wert und keine Wirkung cos printf( ... ); ...; (keine Wirkung)
(oder als Operand oder Argument)Wert und Wirkung putchar printf( ... ); ...;
(oder als Operand oder Argument)
Übungsfragen
Übungsfragen und Übungsaufgaben
Eine Wirkfunktion ist eine Funktion, die laut Dokumentation eine Wirkung hat. (Genauer gesagt: Die Auswertung ihres Aufrufs (welcher ein Ausdruck ist), hat laut Dokumentation eine Wirkung.)
Handelt es sich bei der Funktion »srand« um eine Wirkfunktion ?
Ein Wirkausdruck ist ein Ausdruck, dessen Auswertung eine Wirkung hat.
Ein Wertausdruck ist ein Ausdruck, dessen Auswertung einen Wert ergibt.
- ? Wirk- oder Wertaufrufausdruck?
- Handelt es sich bei den folgenden Aufrufausdrücken um Wirk- oder Wertausdrücke? Welche Wirkung beziehungsweise welchen Wert haben sie gegebenenfalls?
- »rand()«
- »abort()«
? Übungsfrage
- ? »srand«
- Ergibt die Auswertung des Ausdrucks »srand( 0 )« einen Wert?
- Hat die Auswertung des Ausdrucks »srand( 0 )« eine Wirkung?
- / »srand«
- Schreiben Sie ein Programm, in dem ein Wert des Ausdrucks »rand()« ausgegeben wird. Welcher Wert wird ausgegeben, wenn das Programm dann so geändert wird, daß einmal »srand( 10 )« und einmal »srand( 1 )« vor der Ausgabe von »rand()«ausgewertet wird? (Das Programm soll in diesen beiden Fällen immer nur einen Aufruf von »srand« und dann einen Aufruf von »rand« enthalten.)
.----------------------------------------------------------------------------------------.
| Welt |
| (Speicher, Uhr) |
| |
| |
| |
'----------------------------------------------------------------------------------------'
^ ^ ^
| | |
| v v
.----------------------. .----------------------. .----------------------.
| Auswertung von | | Auswertung von | | Auswertung von |
| ::std::srand( 10 ) | | ::std::rand() | | ::std::rand() |
| | | | | |
| | | | | |
| | | | | |
'----------------------' '----------------------' '----------------------'Zeit --->
Übungsaufgaben
/ Wertfunktion time
Der Aufruf von <time.h> »time« liefert eine Zahl, die sich im Laufe der Zeit verändert, meist wird jede Sekunde um 1 hochgezählt.
time [Synopse, für den Kurs etwas vereinfacht und daher nicht ganz korrekt]
#include <time.h>
int time( int );- ? »time«
- Hat der Ausdruck »time( 0 )« einen Wert?
- Hat die Auswertung des Ausdrucks »time( 0 )« eine Wirkung?
Tip zum schnellen Lernen Versuchen Sie jetzt nicht, zu verstehen, was die »0« in »time( 0 )« bedeutet. Dies ist derzeit noch nicht wichtig! Vorläufig reicht es sich zu merken, daß bis auf weiteres als Argument immer »0« verwendet werden muß.
main.c
#include <stdio.h>
#include <time.h>
int main( void )
{ printf( "%d\n", time( 0 ));
printf( "%d\n", time( 0 )); }- / Zufallszahlen verbessern
- Kombinieren Sie den Ausdruck »time( 0 )« mit der Wirkfunktion »srand«, um zu erreichen, daß der Pseudozufallszahlengenerator durch die Uhrzeit beim Start des Programmes beeinflußt wird. Bei jedem Programmstart sollte also eine andere Funktion erscheinen.
- Geben Sie danach eine mit »rand()« ermittelte Zufallszahl aus, um den Effekt sichtbar zu machen. In »time( 0 )« sollte die »0« so belassen und nicht durch etwas anderes ersetzt werden!
- / Zufallszahlen weiter verbessern
- Nach Lösung der vorigen Aufgabe sind die ausgegebenen Zufallszahlen von zwei aufeinanderfolgenden Programmläufen unter manchen Implementationen einander ähnlich. Ändern Sie das Programm so ab, daß die ausgegebene Zufallszahl meistens nicht mehr der vom vorangehenden Programmlauf ähnelt.
Mit den folgenden Übungsaufgaben soll das selbständige Schreiben von Programmen an Hand der Dokumentation geübt werden.
Wenn es sich bei der aufzurufenden Funktion um eine Wertfunktion handelt, soll hier Wert ausgegeben werden.
Wenn die aufzurufende Funktion keinen Wert liefert, soll sie in einer Auswertungsanweisung aufgerufen werden.
- Aufruf der Funktion »terminate«
- terminate
Diese Übungsaufgabe ist noch nicht verwendbar! Sie muß erst noch überarbeitet werden.
#include <exception>
void terminate();
Abbruch eines Programms.- Die Funktion »terminate« bewirkt (wenn nichts anderes eingestellt wurde) meist den sofortigen Abbruch eines Programms. Das ist meist nicht besonders nützlich. Diese Funktion wird hier vor allem deswegen behandelt, weil ihre Verwendung besonders einfach ist! Nützlichere Funktionen werden später vorgestellt werden.
- Übungsaufgabe: Rufen Sie die Funktion »terminate« in einem Programm auf!
- Hinweis: Wenn das Programm richtig geschrieben wurde, dann sollte die C -Implementation keinen Fehler in dem Programm melden. Beim Start des Programms sollte allerdings auch nicht viel von dem Programm zu sehen sein, da es sofort abgebrochen wird. (Manchmal erscheint dabei eine kurze Meldung, die besagt, daß das Programm abgebrochen wurde.) Hinweis: Verwenden Sie das vorherige Beispielprogramm zu »getchar« als Ausgangspunkt und ändern Sie es an zwei Stellen ab.
- Aufruf der Funktion »round«
- round
#include <math.h>
double round( double x );
- Diese Funktion rundet ihren Argumentwert in Richtung der nächsten ganzen Zahl.
- Übungsaufgabe: Rufen Sie die Funktion »round« in einem Programm auf!
Reine Wirkfunktionen
Wir hatten schon gesehen, daß einige Funktionen, wie etwa »rand()« mit »int« vor ihrem Namen (z.B. »int rand«) dokumentiert sind. Das bedeutet, daß die Auswertung des Aufrufausdrucks »rand()« einen Wert vom Typ »int« ergibt. Solch eine Auswertung, die einen Wert liefert, wird hier auch als Wertauswertung bezeichnet. Entsprechend kann »rand()« als Wertaufruf, als Wertausdruck, als Wertoperation oder als Wertfunktion angesehen werden (das ist aber deswegen nicht alles dasselbe, es kommt auf den Zusammenhang an).
Wir hatten schon gesehen, daß in der Dokumentation einiger Funktionen, wie etwa in der von »abort()«, ein bestimmte Wirkung beschrieben ist, nämlich in diesem Fall den Abbruch des laufenden Programms. Eine Funktion, deren Aufruf laut Dokumentation solch eine Wirkung hat, wird hier auch Wirkfunktion genannt.
Wirkung bedeutet dabei, daß laut Dokumentation durch den Aufruf etwas verändert wird. Eine Wirkung muß nicht unbedingt sofort beobachtbar sein, sie kann sich auch versteckt im Inneren des Computers abspielen, es genügt, wenn sie in der Dokumentation beschrieben ist. Jedoch hat man die Festlegung eines Ergebnisses vom Begriff der Wirkung ausgenommen, deswegen gilt eine Funktion, die nur ein Ergebnis liefert, aber sonst laut ihrer Dokumentation nichts anderes macht (d.h., verändert) nicht als eine Wirkfunktion.
Das folgende Beispiel zeigt den Aufruf der Wertfunktion »rand()«. Der ermittelte Wert ist in diesem Fall 41.
main.c
#include <stdio.h>
int main( void ){ printf( "%d\n", rand() ); }
stdout
41
Das folgende Beispiel zeigt den Aufruf der Wirkfunktion »abort()«. Die Wirkung besteht im Abbruch des Programms, oft zusammen mit der Ausgabe einer Fehlermeldung.
Es zeigt sich aber, daß nicht alle Aufrufvorgänge einen Wert festlegen.
main.c
#include <stdio.h>
#include <stdlib.h>int main( void ){ printf( "%d\n", abort() ); }
- Konsole
- (Fehlermeldung des Compilers)
Eine solche Funktion, die keine Wertfunktion ist, wird entsprechend ihrer Dokumentation auch manchmal als void -Funktion bezeichnet.
Wir hatten schon gesehen, daß in der Dokumentation einiger Funktionen, wie etwa in der von »abort()«, ein bestimmte Wirkung beschrieben ist, nämlich in diesem Fall den Abbruch des laufenden Programms. Eine Funktion, deren Aufruf laut Dokumentation solch eine Wirkung hat, wird hier auch Wirkfunktion genannt.
Das folgende Beispiel zeigt den Aufruf der Wirkfunktion »abort()«. Die Wirkung besteht im Abbruch des Programms, oft zusammen mit der Ausgabe einer Fehlermeldung.
main.c
#include <stdlib.h>
int main( void ){ abort(); }
- Konsole
- (Abbruch der Programmausführung)
- abort
#include <stdlib.h>
void abort( void );
Abbruch eines Programms.
Das »void« in der Dokumentation von »abort« stellt klar, daß der Aufruf dieser Funktion keinen Wert hat.
- Dokumentation von »abort« (gekürzt und überarbeitet)
The abort function
Synopsis
#include <stdlib.h>
void abort( void );
causes abnormal program termination
Wir nennen den Typ vor dem Namen einer Funktion auch den Ergebnistyp dieser Funktion.
Ausnahmsweise gibt »void« aber gar nicht den Typ eines Wertes an, wie ein Typ sonst, sondern liefert vielmehr die Information, daß es gar keinen Wert gibt (und ohne Wert gibt es natürlich auch keinen Typ des Werts). Weil es keinen Wert gibt, entfällt die Typangabe (engl. “void ” = [hier] deutsch „entfällt“). Damit ist »void« kein Typ eines Wertes, wie die Bezeichnung „Typ“ nahelegt. Es ist jedoch als Ergebnistyp in der Dokumentation einer Funktion zulässig, um zu kennzeichnen, daß ein Aufruf dieser Funktion nicht dort verwendet werden kann, wo ein Wert erwartet wird. Man kann hier genauer von einem Metatyp sprechen, um auszudrücken, daß dies kein möglicher Typ eines Wertes ist, sondern die Information, daß es keinen Wert gibt.
Der Typ vor dem Namen einer Funktion ist also genau genommen der Ergebnismetatyp dieser Funktion.
Wenn für eine Funktion in ihrer Dokumentation Wirkungen beschrieben sind, die über die Festlegung eines Rückgabewertes hinausgehen, dann nennen wir diese Funktion also eine „Wirkfunktion“.
Wirkungen (engl. “effects ”) werden manchmal auch als Nebenwirkungen (engl. “side effects ”) bezeichnet, was aber eine unpassende Bezeichnung ist, da Nebenwirkungen störend oder nebensächlich sind, während Wirkungen eine meist erwünschte Hauptsache sind. Noch schlimmer ist das Wort „Seiteneffekte“, das lediglich ein Fehler bei der Übersetzung aus dem Englischen “side effects ” (dt. „Nebenwirkungen“) ist.
- Wert von »rand« [Kompaßdiagramm]
.----------------------------.
| rand() | Wert
| |------------------->
| | Pseudozufallszahl
'----------------------------'- Wirkung von »abort« [Kompaßdiagramm]
.----------------------------.
| abort() |
| |
| |
'----------------------------'
Wirkung | Programmabbruch
V
Wertwirkfunktionen
Wirkungen einer Funktion und ein Ergebniswert einer Funktion müssen nicht einander ausschließen. Wenn eine Funktion beides hat, dann kann man sie auch als Wertwirkfunktion bezeichnen (oder als „Wirkwertfunktion“), oder auch – wahlweise – nur als „Wirkfunktion“ oder nur als „Wertfunktion“, denn sie ist ja dann beides.
Aufrufe Rahmen
... ;
abort()
printf( "%s\n", ... );
rand()
printf( "%d\n", ... );
sin( 2. )
printf( "%g\n", ... );
UE A: Aufrufe den Rahmen zuordnen!
Matrix
Wertverwendung Wirkungsrealisierung
kein Wert und keine Wirkung
Wert und keine Wirkung als Argument/Operand
oder in printfkein Wert aber Wirkung vor Semikolon aufrufen
Wert und Wirkung als Argument/Operand vor Semikolon aufrufen
oder in printf
Übungsfragen und Übungsaufgaben
Handelt es sich bei der Funktion »getchar« um eine Wirkfunktion ?
- ? Wirk- oder Wertaufrufausdruck?
- Handelt es sich bei den folgenden Aufrufausdrücken um Wirk- oder Wertaufrufausdrücke?
- »rand()«
- »abort()«
- ? Wirkung von Auswertungen
- Welche Wirkung hat die Auswertung der folgenden Aufrufausdrücke jeweils (falls überhaupt)?
- »rand()«
- »abort()«
- ? Werte von Auswertungen
- Welche Werte ergibt die Auswertung der folgenden Aufrufe jeweils (falls überhaupt)?
- »rand()«
- »abort()«
- Aufruf weiterer Funktionen
- tmpfile
#include <stdio.h>
tmpfile( void );- clock()
#include <time.h>
clock( void );
Ausdrücke ohne Wert in C
abort: Ausdrücke ohne Wert, Aufruf ist nicht im Ausdruckrahmen möglich
srand( 2 );
abort();
- Aufruf der Funktion »terminate«
- terminate
#include <exception>
void terminate( void );
Abbruch eines Programms.- Die Funktion »terminate« bewirkt (wenn nichts anderes eingestellt wurde) meist den sofortigen Abbruch eines Programms. Das ist meist nicht besonders nützlich. Diese Funktion wird hier vor allem deswegen behandelt, weil ihre Verwendung besonders einfach ist! Nützlichere Funktionen werden später vorgestellt werden.
- Welche Include-Direktive wird am Anfang des Programms benötigt, wenn der Bezeichner »terminate« darin verwendet werden soll?
- Übungsaufgabe: Rufen Sie die Funktion »terminate« in einem C++ -Programm auf!
- Hinweis: Wenn das Programm richtig geschrieben wurde, dann sollte die C++ -Implementation keinen Fehler in dem Programm melden. Beim Start des Programms sollte allerdings auch nicht viel von dem Programm zu sehen sein, da es sofort abgebrochen wird. (Manchmal erscheint dabei eine kurze Meldung, die besagt, daß das Programm abgebrochen wurde.) Hinweis: Verwenden Sie das vorherige Beispielprogramm zu »getchar« als Ausgangspunkt und ändern Sie es an zwei Stellen ab.