Wirkaufrufe im Ausdruckrahmen
Wir haben schon gesehen, daß die Ausführung einer Anweisung eine Wirkung haben kann. Die Ausführung einer Ausgabeanweisung führt nämlich zu einer Ausgabe.
Die Auswertung eines Ausdrucks ergibt hingegeben einen Wert. Wir werden aber nun sehen, daß auch eine Auswertung eines Ausdrucks manchmal – wie die Ausführung einer Anweisung – eine Wirkung haben kann.
Ein Ausdruck, dessen Auswertung eine Wirkung hat, nennen wir Wirkausdruck. Die Ermittlung eines Wertes für den Ausdruck gilt dabei nicht als eine Wirkung. Ansonsten ist jede dokumentierte Veränderung durch die Auswertung eine Wirkung der Auswertung. Anders gesagt:
Alles, was eine Auswertung eines Ausdrucks bewirkt ist eine Wirkung, abgesehen von der Festlegung des Wertes (Ergebnisses) der Auswertung.
Ein Ausdruck, dessen Auswertung einen Wert für den Ausdruck ergibt, nennen wir auch einen Wertausdruck.
Wertausdrücke sind normalerweise Argumente oder Operanden
Vor der Auswertung eines Aufrufs werden alle Argumente des Aufrufs ausgewertet
Vor der Auswertung eines Operatorausdrucks werden einige oder alle der Operanden ausgewertet
Wert und Wirkung der Auswertung eines Ausdrucks zusammen werden hier als das Verhalten (der Auswertung )des Ausdrucks bezeichnet.
Das folgende Beispiel zeigt den Aufruf der Funktion »::std::putchar( 86 )«.
Das folgende Programm gibt zunächst ein Schriftzeichen »V« und dann auch noch die Zahl 86 aus, das erstere aufgrund der Wirkung der Ausführung des Aufrufs »::std::putchar( 86 )« und das letztere aufgrund der Eigenschaft des Ausdruckrahmens den Wert des in ihn Eingesetzten auszugeben.
main.cpp
#include <cstdio> // ::std::putchar
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""susing namespace ::std::literals;
int main()
{ ::std::cout << ::std::putchar( 86 )<< "\n"s; }Protokoll
V86
Die Auswertung des Aufrufausdrucks »putchar( 86 )« ergibt den Wert 86, der dann vom Ausdruckrahmen ausgegeben wird.
Außerdem gibt dieses Programm ein »V« aus, was sozusagen eine „Nebenwirkung“ der Auswertung des Aufrufausdrucks »::std::putchar( 86 )« ist.
Die Wirkung einer Auswertung eines Ausdrucks ist jede direkt oder indirekt beobachtbare Veränderung, welche diese Auswertung herbeiführt (die Ermittlung des Wertes des Ausdrucks gilt nicht als Wirkung).
Die Ausgabe einer Information ist beispielsweise eine Wirkung.
Die Ausgabe des Numerales 86 in dem obigen Programm gilt nicht als Wirkung der Auswertung des Aufrufausdrucks »putchar( 86 )«, weil sie erst nach dem Abschluß dieser Auswertung durch den Ausdruckrahmen erfolgt.
Bei der Angabe einer Operation schreiben wir die Wirkung hinter den (ersten) Pfeil (während der sich ergebende Wert hinter einen zweiten Pfeil geschrieben werden kann).
- Operationssequenz
- ::std::putchar( 86 ) → Ausgabe von V → 86
- ::std::cout << 86 << "\n" → Ausgabe von 86
- Wert und Wirkung der Auswertung von »::std::putchar( 86 )« [Kompaßdiagramm]
.----------------------------.
| ::std::putchar( 86 ) | Wert
| |-------------------> 86
| |
'----------------------------'
Wirkung |
V
Ausgabe eines V
Mit „Wirkung“ meint man normalerweise eine dokumentierte Wirkung, also eine Wirkung, welche laut der Dokumentation einer Funktion bei der Auswertung eines Aufrufs dieser Funktion eintritt.
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.
- Dokumentation der Funktion »::std::putchar« (vereinfacht und übersetzt)
#include <cstdio>
int putchar( int c );- Schreibt das Zeichen mit der Kennzahl »c« und ergibt in der Regel den Argumentwert »c«.
Die Bedeutung des Wortes “schreibt ” stellt klar, daß die Auswertung eines Aufrufs dieser Funktion eine Wirkung hat und nicht nur einen Wert ermittelt.
Die Festlegung des Rückgabewerts gilt nicht als Wirkung.
- ? Ergebnistyp
- Welchen Typ hat ein Aufruf der Funktion »::std::putchar«?
- ? Parametertyp
- Welchen Typ hat der Parameter der Funktion »::std::putchar«?
- ? Wirkung
- Welche Wirkung hat die Auswertung eines Aufrufs der Funktion »::std::putchar«?
Es ist schwieriger zu beurteilen ob eine Funktion eine Wirkung hat als die Frage zu beantworten, ob sie einen Wert liefert, denn die Wirkung wird in der Dokumentation oft weniger explizit oder formal hingeschrieben. In C++ wird die Wirkung (engl. “effect ”) jedoch manchmal ausdrücklich angegeben, wie folgende Zitate aus N3290 zeigen:
- Zitat aus N3290
void operator delete[](void* ptr, void*) noexcept;
Effects: Intentionally performs no action.
Hier sagt die Dokumentation ausdrücklich, daß es keine Wirkung gibt.
- Zitat aus N3290
constexpr pair();
Effects: Value-initializes first and second.
Hier sagt die Dokumentation ausdrücklich, daß es eine Wirkung gibt, nämlich die Wert-Initialisierung von “first ” und “second ” (was das bedeutet, wird in N3290 an anderer Stelle erklärt, ist hier aber nicht weiter wichtig).
An anderen Stellen der Dokumentation von C++ (etwa bei den C -Funktionen) muß man selber beurteilen, ob die Erklärung einer Funktion eine Wirkung beschreibt oder nicht, weil die Beschreibung einer Wirkung darin nicht immer ausdrücklich mit “Effects: ” gekennzeichnet ist.
Bei neueren Funktionsdokumentationen in C++ wird die Wirkung und der Wert noch klarer unterschieden:
- Dokumentation der Funktion »::std::putchar« (vereinfacht und übersetzt)
#include <cstdio>
int putchar( int c );
- Wirkung: Ausgabe des Zeichens mit der Kennzahl c.
- Wert: c
Verhalten
Wir hatten bisher behandelt, daß eine Auswertung einen Wert ergeben kann, und nun lernen wir, daß sie auch noch eine Wirkung haben kann. Beides zusammen, also Wert und Wirkung einer Auswertung gemeinsam, nennen wir das Verhalten dieser Auswertung. Um zu verstehen, was eine Auswertung bedeutet, muß man ihr Verhalten kennen.
Der Satz „Das Verhalten der Funktion ändert sich nicht.“ bedeutet beispielsweise, daß weder der Wert noch die Wirkung der Funktion sich verändert.
Auswertungen (und Funktionen) kann man charakterisieren, indem man ihren Wert und ihre Wirkung angibt.
- Merkformel
- Verhalten = Wert + Wirkung
Das Verhalten ist all das, was man normalerweise vom Ablauf einer Operation beobachten kann.
Falls eine Auswertung nur einen Wert ergibt, aber keine Wirkung hat, so hat sie trotzdem ein Verhalten; es besteht in diesem Fall dann nur aus dem Wert.
Falls eine Auswertung nur eine Wirkung ergibt, dann ist ihr Verhalten diese Wirkung.
Wenn man beispielsweise fragt „Welches Verhalten hat dieser Aufruf?“, dann will man wissen, welchen Wert und welche Wirkung er hat.
Wenn man beispielsweise sagt „Diese beiden Aufrufe haben dasselbe Verhalten“, dann sagt man damit, daß sie denselben Wert und dieselbe Wirkung haben.
Reihenfolge der Auswertung
In dem folgenden Beispiel muß zuerst »1 + ::std::putchar( 86 )« ausgewertet werden, damit dann der gesamte Aufruf im Ausdruckrahmen ausgewertet werden kann. Daher kommt es zunächst zur Ausgabe von »V«.
Der ermittelte Wert von »putchar( 86 )« ist 86, so daß sich dann für »1 + ::std::putchar( 86 )« effektiv »putchar( 1 + 86 )« ergibt, also »putchar( 87 )«, ausgewertet wird. Dies hat die Wirkung der Ausgabe von »W«.
Der sich ergebende Wert 87 wird dann schließlich noch vom Ausdruckrahmen ausgegeben. So kommt es dann insgesamt zur Ausgabe von »VW87«.
main.cpp
#include <cstdio> // ::std::putchar
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""susing namespace ::std::literals;
int main()
{ ::std::cout << ::std::putchar( 1 + ::std::putchar( 86 ))<< "\n"s; }Protokoll
VW87
- Operationen
::std::putchar( 86 )
1 + 86
::std::putchar( 87 )
::std::cout << 87
::std::cout << "\n"s
Die Bedeutung von Ausdrücken
Wir hatten früher gesagt, daß die Bedeutung eines Ausdrucks nur in seinem Wert, nicht aber in seiner speziellen Schreibweise liege. Daher kann man in einem Programm den Ausdruck »--2« durch den Ausdruck »2« ersetzen, ohne die Bedeutung des Programms zu verändern.
Sobald man aber auch noch Wirkungen berücksichtig, stimmt dies nicht mehr. Man kann beispielsweise »::std::putchar( 86 )« nicht durch den gleichwertigen Ausdruck »86« ersetzen, da dessen Auswertung eine andere Wirkung hat als die Auswertung von »::std::putchar( 86 )«.
Wenn man auch noch Wirkungen berücksichtigt, dann kann man einen Ausdruck nun nur noch dann durch einen anderen Ausdruck ersetzen, wenn dieser unter allen Umständen das gleiche Verhalten (den gleichen Wert und die gleiche Wirkung) hat.
Hinweis
Ein C++ -Programmierer muß normalerweise keine Kennzahlen, wie 86, lernen oder in Programmen verwenden!
Die Zahlen wurden in dieser Lektion nur verwendet, um zu illustrieren, daß Schriftzeichen intern durch Zahlen dargestellt werden.
Für praktische Anwendungen gibt es aber Möglichkeiten, immer die Zeichen direkt in einem Programm zu verwenden. Diese Möglichkeiten werden aber erst in späteren Lektionen vorgestellt werden.
Übungsfragen
? Ausgaben
Für die folgenden Übungsfragen wird die folgende Codierung (Zuordnung von Kennzahlen zu Zeichen) vorausgesetzt.
- Codierung
83 S
84 T
85 U86 V
87 W
88 X
89 Y
Welche Ausgabe erzeugt das folgende Programm? (Codierung: 86: »V«, 87: »W«)
main.cpp
#include <cstdio> // ::std::putchar
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""susing namespace ::std::literals;
int main()
{ ::std::cout << ::std::putchar( 86 + 1 )<< "\n"s; }
Welche Ausgabe erzeugt das folgende Programm? (Codierung: 86: »V«, 87: »W«)
main.cpp
#include <cstdio> // ::std::putchar
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""susing namespace ::std::literals;
int main()
{ ::std::cout << ::std::putchar( 86 )+ 1 << "\n"s; }
Welche Ausgabe erzeugt das folgende Programm? (Codierung: 86: »V«, 87: »W«)
main.cpp
#include <cstdio> // ::std::putchar
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""susing namespace ::std::literals;
int main()
{ ::std::cout << ::std::putchar( 86 + 1 )- 1 << "\n"s; }
Welche Ausgabe erzeugt das folgende Programm? (Codierung: 86: »V«, 87: »W«)
main.cpp
#include <cstdio> // ::std::putchar
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""susing namespace ::std::literals;
int main()
{ ::std::cout << ::std::putchar( ::std::putchar( 86 ))<< "\n"s; }
Welche Ausgabe erzeugt das folgende Programm? (Codierung: 86: »V«, 87: »W«)
main.cpp
#include <cstdio> // ::std::putchar
#include <iostream> // ::std::cout
#include <ostream> // <<
#include <string> // ""susing namespace ::std::literals;
int main()
{ ::std::cout << ::std::putchar( ::std::putchar( 86 + 1 )- 1 )<< "\n"s; }
Wert von »::std::putchar« ⃗
Was ist der Wert der Funktion »::std::putchar«?
Genauer: Was ist der Wert eines Aufrufs der Funktion »::std::putchar«?
Noch genauer: Welchen Wert ergibt die Auswertung eines Aufrufs der Funktion »::std::putchar«?
Wirkung von »::std::putchar« ⃗
Was ist die Wirkung der Funktion »::std::putchar«?
Genauer: Was ist die Wirkung eines Aufrufs der Funktion »::std::putchar«?
Noch genauer: Welche Wirkung hat die Auswertung eines Aufrufs der Funktion »::std::putchar«?
Verhalten von »::std::putchar« ⃗
Beschreiben Sie das Verhalten der Funktion »::std::putchar«.
Genauer: Beschreiben Sie das Verhalten eines Aufrufs der Funktion »::std::putchar«?
Noch genauer: Beschreiben Sie das Verhalten einer Auswertung eines Aufrufs der Funktion »::std::putchar«?
Wert von »::std::rand« ⃗
Was ist der Wert der Funktion »::std::rand«?
Genauer: Was ist der Wert eines Aufrufs der Funktion »::std::rand«?
Noch genauer: Welchen Wert ergibt die Auswertung eines Aufrufs der Funktion »::std::rand«?
Wirkung von »::std::rand« ⃗
Was ist die Wirkung der Funktion »::std::rand«?
Genauer: Was ist die Wirkung eines Aufrufs der Funktion »::std::rand«?
Noch genauer: Welche Wirkung hat die Auswertung eines Aufrufs der Funktion »::std::rand«?
Verhalten von »::std::rand« ⃗
Beschreiben Sie das Verhalten der Funktion »::std::rand«.
Genauer: Beschreiben Sie das Verhalten eines Aufrufs der Funktion »::std::rand«.
Noch genauer: Beschreiben Sie das Verhalten einer Auswertung eines Aufrufs der Funktion »::std::rand«.