 Ja, herzlich willkommen zurück zu Programmierparadigmen. Wir sind hier in Teil 2 von 5 des Moduls zum Thema Control Abstraction. Und konkret soll es hier in diesem Teil um das Thema Parameter Passing gehen. Also der Frage, wie genau werden Parameter denn eigentlich übergeben? Und welche Semantiken haben denn da verschiedene Programmiersprachen? Wenn eine Funktion aufgerufen wird, kann man ja Parameter übergeben. Und die Frage ist, was genau bedeutet das dann eigentlich? Also irgendwie bedeutet das, dass der Wert dann schlussendlich in der aufgerufenen Funktion ist. Aber was bedeutet das zum Beispiel dafür, wie dieser Wert verändert werden kann und ob diese Veränderung dann tatsächlich auch in der aufrufenden Funktion noch sichtbar ist? Verschiedene Programmiersprachen geben da verschiedene Antworten und haben verschiedene sogenannte Parameter Passing Modes, die festlegen, was genau passiert, wenn so ein Parameter übergeben wird. Wir werden uns 4 von diesen Parameter Passing Modes anschauen. Call by value, call by reference, call by value result und call by sharing und dann sehen, wie die voneinander sich unterscheiden. Fangen wir mal mit der ersten Variante an, nämlich call by value. Was call by value bedeutet, ist, dass der aufrufende Kot eine Kopie eines Wertes an den aufgerufenen Kot übergibt. Und sobald wir dann in dem cally, also der aufgerufenen Funktion, sind, ist der formale Parameter, also das, was der cally als Argument übergeben bekommt, unabhängig von dem Argument, den der caller sieht. Das heißt, die beiden Werte sind komplett getrennt und wenn man den einen verändert, verändert das den anderen dann eben nicht. Mein konkretes Beispiel, also hier haben wir eine integer Variable x, die im caller deklariert und definiert wird und wir rufen dann irgendwann diese Prozedur namens foo hierauf, haben vorher den Wert von x auf 2 gesetzt und übergeben x dann an foo und in foo selbst heißt das Ding dann y, also der actual Parameter, das Argument ist x und der formale Parameter ist in dem Fall y. Und die Frage ist, was passiert jetzt, wenn diese aufgerufene Prozedur hier den Wert von y auf 3 setzt, wird das den Wert von x in irgendeiner Form verändern und die Antwort, wenn wir call by value in der Sparer verwenden, ist nein, dann hat nämlich in diesem Beispiel hier dieses Assignment keinen wirklichen sichtbaren Effekt. Das heißt, was hier passiert ist, dass der Code 2 mal die 2 ausgibt, einmal hier oben, weil wir hier natürlich immer noch auf diese Variable x zugreifen und der den Wert 2 hatte und dann nochmal hier unten, weil wir da eben auch genau auf dieselbe Variable x zugreifen und dieses Assignment zu y, kein Einfluss auf x hat, denn was wir hier übergeben haben war call by value, also sozusagen eine Kopie des aktuellen Wertes von x, die aber komplett getrennt ist von der Variable x selbst. Die zweite Variante ist call by reference. Hier ist alles ein bisschen anders, denn hier funktioniert es so, dass der formale Parameter, also das, was der aufgerufene Code sieht, nur ein neuer Name für den actual Parameter, also für das Argument ist, was übergeben wurde. Das bedeutet, dass wir zwar zwei Namen haben, aber dass die beide auf denselben Wert zeigen und das bedeutet, wenn ich den einen endere, ist dann auch der andere gleich geändert. Hier ist es nochmal derselbe Code von grad eben und jetzt schauen wir mal, was passieren würde, wenn die Sprache, die wir hier verwenden, call by reference verwendet. In dem Fall bezieht sich dieses y, also dieser formale Parameter, den wir hier bekommen, auf genau das selbe Stück Speicher wie das x, was wir hier übergeben haben. Also x und y sind einfach nur zwei verschiedene Namen, die aber auf das selbe Stück Speicher zeigen und als Ergebnis davon überschreiben wir hier, oder ja, doch wie überschreiben, den Wert, der aktuell an diesem Stück Speicher steht und geben ihn dann hier aus und als Ergebnis gibt sich dieser Code dann also zweimal die drei aus, einmal hier oben, weil wir ja diesen Wert über den Namen y überschrieben haben und ihn dann aber über den Wert x hier wieder lesen und dann als drei ausgeben und dann nochmal hier unten, weil wir ja eben genau diesen einen Wert, der da in dem Stück Speicher drin ist, überschrieben haben und ihn dann hier einfach auch nochmal lesen und entsprechend wieder ausgeben. Die dritte Variante ist das sogenannte Call by Value Result. Es liegt so ein bisschen zwischen den beiden Varianten, die wir gerade eben schon gesehen haben und zwar funktioniert es hier so, dass das Argument, also der Actual Parameter beim Aufruf erstmal eine Kopie ist und als Kopie an die aufgerufene Funktion übergeben wird, der Wert, den der formale Parameter dann schlussendlich aber hat, dann zurückkopiert wird in den Wert, den auch die aufrufende Funktion sieht und zwar erst, wenn die Funktion zurückkehrt. Was das Ganze für eine Bedeutung hat, können wir wieder mit dem selben Beispiel anschauen und zwar möchte ich um das ein bisschen interessanter zu machen, das jetzt mal in Form eines Quizzes machen. Also wenn wir jetzt diese Call by Value Result Semantik in der Programmiersprache hier haben, was ist denn dann die Ausgabe dieses Programmes? Also was wird hier stets ja schlussendlich ausgegeben? Ich würde Sie bitten, das einfach mal kurz selber zu überlegen, das Video hier wieder anzuhalten und Emilia es abzustimmen und anschließend dann erst die Lösungen anzuschauen. Gut, schauen wir uns die Lösung mal zusammen an. Also in dem Fall würde dieses Y hier in dem Moment, wo es geschrieben wird, erstmal eine Kopie sein, also das Y und das X zeigen in dem Moment auf verschiedene Sachen. Das heißt wir schreiben hier in die Variable Y, geben aber dann den Wert von X aus und X ist in dem Moment noch 2. Wenn wir dann aber wirklich von der Funktion zurückkehren, dann wird der Wert, den wir in diese lokale Variable Y, die ja auch der formale Parameter ist geschrieben haben, wird dann wieder an die Stelle von X zurückgeschrieben. Das heißt, wenn wir dann schlussendlich von der Funktion zurückkehren, dann geben wir hier unten 3 aus, denn wir haben diesen Wert ja hier überschrieben, das wird aber erst sichtbar in der aufrufenden Funktion, wenn die aufgerufene Funktion dann tatsächlich zurückgekehrt ist. Also die richtige Antwort hier ist, der Kot gibt erst 2 aus und dann anschließend 3. So und jetzt schauen wir uns noch die vierte Variante an, nämlich das sogenannte Call by Sharing. Call by Sharing ist relevant nur in Sprachen, die das Reference Model für Variablen verwenden. Also nochmal zur Erinnerung, das bedeutet, dass eine Variable immer nicht den eigentlichen Wert, meint ich, der in der Variable geschrieben ist, sondern eine Referenz auf diesen Wert. Und was bei Call by Sharing jetzt passiert, ist, dass die Argumente grundsätzlich als Werte übergeben werden. Also es ist ein bisschen wie Call by Value, aber diese Werte selbst sind wiederum Referenzen und damit zeigt der formale Parameter dann natürlich trotzdem wieder auf derselbe Stück Speicher, wie der actual Parameter, den der aufrufende Kot sieht. Was passiert dann in unserem Beispiel hier, also wieder dasselbe Kot, Stück Kot, was wir gerade eben schon gesehen haben und was hier passiert ist, dass zweimal die 3 ausgegeben wird, und zwar aus folgendem Grund, also wir übergeben hier das x. x wird als Kopie übergeben, das heißt, wir kopieren den Wert von x nach y, aber das ist nicht der Wert selbst, also das ist nicht diese 2, das ist die Referenz, also die Adresse, wo diese 2 gespeichert ist. Und was wir hier dann machen, ist, dass wir an dieser Adresse nun den Wert 3 schreiben und weil das eben dieselbe Adresse ist, wo x und y drauf zeigen, geben wir hier dann auch die 3 aus und anschließend natürlich auch hier, weil an dieser einen Adresse haben wir diesen Wert 3 jetzt hingeschrieben. So, jetzt haben wir diese 4 Modelle gesehen, wie Parameter genau übergeben werden können. Es gibt auch noch ein paar andere, die diese 4 sind eigentlich die wichtigsten. Und jetzt ist ja interessant zu schauen, welche von diesen Passing-Models werdet tatsächlich in populären Programmiersprachen verwendet. Fangen wir mal an mit C, also C verwendet Core by Value grundsätzlich, außer für Arrays, denn diese werden bei Referents übergeben. Das heißt, außer für Arrays werden alle Werte immer kopiert und als Kopie an die aufgerufene Funktion übergeben. Was man in 10 aber natürlich machen kann, weil es ja Pointer gibt, ist, dass man Core by Referents sozusagen emuliert, indem man einen Pointer übergibt, dann in dem Moment, wo ich einen Pointer übergebe, übergebe ich ja dann eben den Pointer, also sprich die Referenz, und bekomme dann eben genau das Verhalten von Core by Referents. Vortran macht es ein bisschen anders. Also hier werden alle Argumente grundsätzlich immer bei Referents übergeben. Das heißt, es ist immer nur die Referenz und nie eine Kopie des Wertes, was in der aufgerufenen Funktion ankommt. Und schlussendlich vielleicht noch ein drittes Beispiel, und zwar Java. Java hat interessanterweise so ein Hybrides-Modell, wo bestimmte Typen grundsätzlich bei Value übergeben werden, nämlich alle primitiven Typen, die Teil der Sprache sind, also sowas wie Integers und Booleans und Floats. Alle anderen Typen, also insbesondere alle Instanzen von Klassen, werden aber grundsätzlich bei Referents übergeben. Das heißt, je nachdem, was für eine Art Variable oder welchen Typ ich in meiner Variable habe, wird das entweder so oder so übergeben und was man als Programmierer dann natürlich auch entsprechend wissen muss. Ja, und dann sind wir auch schon wieder am Ende dieses zweiten Teils, wo es um Parameter Passing ging. Ich hoffe, Sie haben jetzt ein bisschen besseres Verständnis dafür, welche Arten von Symantiken es eigentlich gibt, dafür wie genau Parameter überhaupt eine aufgerufene Funktion übergeben werden und wie das dann tatsächlich auch in echten Programmiersprachen so passiert. Vielen Dank fürs Zuhören und bis zum nächsten Mal.