Reine Wirkfunktionen in C++
Wir hatten zuerst Funktionen kennengelernt, bei denen die Auswertung eines Aufrufs einen Wert ergibt (»rand«).
Dann hatten wir Funktionen kennengelernt, bei denen die Auswertung eines Aufrufs einen Wert ergibt und eine Wirkung hat (»putchar«).
Nun behandeln wir noch Funktionen, bei denen die Auswertung eines Aufrufs nur eine Wirkung hat.
Reine Wirkfunktionen
Wir hatten schon gesehen, daß einige Funktionen, wie etwa »::std::rand()« mit »int« vor ihrem Namen (z.B. »int rand«) dokumentiert sind. Das bedeutet, daß die Auswertung des Aufrufausdrucks »::std::rand()« einen Wert vom Typ »int« ergibt. Solch eine Auswertung, die einen Wert liefert, wird hier auch als Wertauswertung bezeichnet. Entsprechend kann »::std::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 »::std::putchar()«, ein bestimmte Wirkung beschrieben ist, nämlich in diesem Fall die Ausgabe eines Zeichen. 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 »::std::rand()«. Der ermittelte Wert ist in diesem Fall 41.
main.cpp
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <cstdlib> // ::std::rand
#include <string> // ""susing namespace ::std::literals;
int main()
{ ::std::cout << ::std::rand() << "\n"s; }::std::cout
41
Das folgende Beispiel zeigt den Aufruf der Wirkfunktion »::std::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.cpp
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <cstdlib> // ::std::abort
#include <string> // ""susing namespace ::std::literals;
int main()
{ ::std::cout << ::std::abort() << "\n"s; }- Konsole
Error: binary '<<' does not take a right-hand operand of type 'void'
- Aussprachehinweis
- abort əˈ bɔɚt
Eine solche Wirkfunktion, die keine Wertfunktion ist, wird entsprechend ihrer Dokumentation auch manchmal als void -Funktion bezeichnet. Wir nennen sie auch eine reine Wirkfunktion.
Das folgende Beispiel zeigt den Aufruf der Wirkfunktion »::std::abort()«. Die Wirkung besteht im Abbruch des Programms, oft zusammen mit der Ausgabe einer Fehlermeldung.
main.cpp
#include <cstdlib> /* ::std::abort */
int main(){ ::std::abort(); }
- Konsole
- (Abbruch der Programmausführung)
- ::std::abort
#include <cstdlib>
void abort();
- Bricht den Prozeß ab.
Das »void« in der Dokumentation von »::std::abort« stellt klar, daß der Aufruf dieser Funktion keinen Wert hat.
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 »::std::rand« [Kompaßdiagramm]
.----------------------------.
| ::std::rand() | Wert
| |------------------->
| | Pseudozufallszahl
'----------------------------'- Wirkung von »::std::abort« [Kompaßdiagramm]
.----------------------------.
| ::std::abort() |
| |
| |
'----------------------------'
Wirkung | Programmabbruch
V- Wert und Wirkung der Auswertung von »::std::putchar( 65 )« [Kompaßdiagramm]
.----------------------------.
| ::std::putchar( 65 ) | Wert
| |-------------------> 65
| |
'----------------------------'
Wirkung |
V
Ausgabe eines A
Aufrufe von Wirkfunktionen ohne Wert (Aufrufschema)
Eine Wirkfunktion ohne Wert sollte normalerweise (bis auf weiteres) immer innerhalb eines Wirkaufrufrahmens aufgerufen werden, wenn die dokumentierte Wirkung eintreten soll.
Das folgende Beispiel zeigt, wie man eine Wirkfunktion ohne Wert normalerweise aufrufen sollte, damit die für sie dokumentierte Wirkung eintritt.
Wir können eine Auswertungsanweisung an der Stelle der Ausgabeanweisung in den bisherigen Ausdruckrahmen einsetzen. (Im speziellen Falle des folgenden Programmes können dann noch einige include-Zeilen und eine using-Zeile entfallen.)
main.cpp
#include <cstdlib> /* ::std::abort */
int main(){ ::std::abort(); }
- Konsole
- (Abbruch der Programmausführung)
Das folgende Beispiel zeigt, daß man eine Wirkfunktion ohne Wert normalerweise nicht in einem Ausdruckrahmen aufrufen sollte, weil dieser einen Wert erwartet.
main.cpp
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <cstdlib> // ::std::abort
#include <string> // ""susing namespace ::std::literals;
int main()
{ ::std::cout << ::std::abort() << "\n"s; }- Konsole
Error: binary '<<' does not take a right-hand operand of type 'void'
Es ist mit diesen Hinweisen hoffentlich auch verständlich geworden, daß ein Programmierer immer wissen muß, ob eine Funktion einen Wert und/oder eine Wirkung hat, denn nur dann kann er sie richtig aufrufen.
Die beiden vorgestellten Aufrufschema sollte man sich gut einprägen:
Der Aufruf einer Wertfunktion steht im allgemeinen im Ausdruckrahmen, der Aufruf einer Wirkfunktion steht im allgemeinen im Aufrufrahmen. (Diese Regel gilt nur für den Anfang, später wird erklärt werden, daß es auch noch andere Möglichkeiten gibt.)
void -Wirkfunktionen passen gut zur Auswertungsanweisung, da sie wegen der Wirkung ihrer Auswertung verwendet werden.
main.cpp
#include <cstdlib> /* ::std::abort */
int main(){ ::std::abort(); }
Wertwirkfunktionen
Wirkungen einer Funktion und ein Ergebniswert einer Funktion müssen nicht einander ausschließen. Wenn eine Funktion beides hat (wie im Falle von »::std::putchar«), 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.
Matrix
Beispiel Wertverwendung Wirkungsrealisierung
kein Wert und keine Wirkung - ...;
Wert und keine Wirkung cos ::std::cout << ... << "\n"s; -
(oder als Operand oder Argument)Wert und Wirkung putchar ::std::cout << ... << "\n"s; ...;
(oder als Operand oder Argument)Wirkung und kein Wert abort - ...;
Übungsfragen und Übungsaufgaben
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?
- Und was ist gegebenenfalls die Wirkung beziehungsweise der Wert?
- »::std::rand()«
- »::std::abort()«
? Anweisungen
Welche der beiden folgenden Anweisungen sind sinnvoll (d.h.: Welche der beiden folgenden Anweisungen können ohne Fehlermeldung übersetzt werden)?
- Anweisungen
- o »::std::abort();«
o »::std::cout << ::std::abort()<< "\n"s;«
? Übungsfragen ⃗
- Was ist das Verhalten einer Auswertung des Ausdrucks »::std::abort()«?
- Was ist das Verhalten einer Auswertung des Ausdrucks »::std::putchar( 66 )«?
- Was ist das Verhalten einer Auswertung des Ausdrucks »::std::rand()«?
Übungsaufgaben
Mit den folgenden Übungsaufgaben soll das selbständige Schreiben von Programmen an Hand der Dokumentation geübt werden.
Aufruf der Funktion »::std::terminate«
Für diese und die nächste Aufgabe soll die folgende Regel beachtet werden:
- Wenn ein Aufruf einen Wert hat, soll der Wert mit einer Ausgabeanweisung ausgegeben werden, sonst soll der Ausdruck als Ausdruck einer Auswertungsanweisung (vor einem »;«) geschrieben werden.
- Dokumentation
#include <exception>
void terminate();
- Abbruch eines Programms.
Die Funktion »::std::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 »::std::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 »::std::getchar« als Ausgangspunkt und ändern Sie es an zwei Stellen ab.
Aufruf der Funktion »::std::round«
Auch hier soll wieder die folgende Regel beachtet werden:
- Wenn ein Aufruf einen Wert hat, soll der Wert mit einer Ausgabeanweisung ausgegeben werden, sonst soll der Ausdruck als Ausdruck einer Auswertungsanweisung (vor einem »;«) geschrieben werden.
- Dokumentation
#include <cmath>
double ::std::round( double x );
- Diese Funktion rundet ihren Argumentwert in Richtung der nächsten ganzen Zahl.
Übungsaufgabe: Rufen Sie die Funktion »::std::round« in einem C++ -Programm auf!
Z Ausgabe eines eingelesenen Zeichen
Schreiben Sie ein Programm, das ein mit »::std::getchar« eingelesenes Zeichen mit »::std::putchar« wieder ausgibt.
- Dokumentation
#include <cstdio>
int ::std::putchar( int c );
- Wirkung: schreibt das Zeichen mit der Kennzahl c.
- Dokumentation
#include <cstdio>
int ::std::getchar();
- Einlesen eines Zeichens von der Konsole.
Wert: Die Kennzahl des nächsten von der Konsole eingelesenen Zeichens
Diese Aufgabe sollte möglichst mit den bisher im Kurs behandelten Mitteln gelöst werden.
Während der Auswertung eines Aufrufs des Funktion »::std::getchar« wartet der Computer auf eine Eingabe in der Konsole (normalerweise ist dies der Bereich, in dem auch Ausgaben des Programms erscheinen.) Um ein bestimmtes Zeichen einzugeben, muß dann zuerst dieses Zeichen eingegeben werden (normalerweise über die Tastatur) und dann zur Bestätigung die Eingabetaste ↵ betätigt werden.
ZZ Ausgabe des Nachfolgers eines eingelesenen Zeichen
Schreiben Sie ein Programm, das den Nachfolger einer mit »::std::getchar« eingelesenen Ziffer (0-8) mit »::std::putchar« wieder ausgibt. (Der Nachfolger von »2« ist beispielsweise »3«. Die Zeichenkennzahl des Nachfolgers einer Ziffer ist um 1 größer als die Kennzahl der Ziffer).