Funktionen

Blick hinter die Black Box

Die Sommerferien sind nah und ein abenteuerliches Schuljahr liegt hinter einem. Doch bevor die sechswöchige Faulenz-Zeit beginnen kann, stehen ja noch die Zeugnisse an. Heikle Sache... Zum Glück hast du all deine Noten sorgfältig in eine Tabelle geschrieben und kannst dir schon mal ausrechnen, was am Ende auf dem Zeugnis steht. Und da du nun zusätzlich über einige Programmierfähigkeiten verfügst, kannst du diese Sache schön deinen Computer erledigen lassen. Also ran ans Programmieren:


    int MatheNote;

    int summe1=0;


    for (int i=0; i<=5; i++) {

        cin << MatheNote;

        summe1=summe1+MatheNote;

    }


    float durchschnittMathe=(float) summe1/5;


    int DeutschNote;

    int summe2=0;


    for (int i=0; i<=5; i++) {

        cin << DeutschNote;

        summe2=summe2+DeutschNote;

    }


    float durchschnittDeutsch=(float) summe2/5;


Puh, ganz schön mühselig! Für jede Durchschnittsnote musst du die im Prinzip immer gleichen Anweisungen von Neuem eintippen. Ziemlich lästig! Wie toll wäre es doch, einmal eine allgemeine Vorgehensweise beim Berechnen des Durchschnitts zu programmieren und dem Computer dann zu sagen: Mache dies jetzt für meine Mathenoten, danach für meine Deutschnoten und so weiter. Die gute Nachricht: Das ist kein Wunschdenken! Diese Programmierkonstrukte gibt’s tatsächlich und hören auf den Namen „Funktionen“. Sie sorgen nicht nur dafür, dass ähnliche Anweisungen zusammengefasst werden, sondern geben deinem Programm in erster Linie Übersicht. Statt deinem Computer also beliebig Anweisungen an den Kopf zu werfen, ist es eleganter, zunächst kleinere Programmabschnitte, Funktionen also, zu entwerfen und diese dann anschließend in Wechselwirkung miteinander zu bringen. Ähnlich ist es auch beim Bau von Autos: Es werden erst kleinere Komponenten, wie der Motor, die Reifen, die Karosserie, die Elektronik hergestellt und anschließend alles zu einem zusammengefügt – dem Auto. 


Wir sehen schon, du bist nun ganz überzeugt von Funktionen und kannst gar nicht früh genug erfahren, wie man diese Dinger denn nun programmiert. Wir wollen dich nicht weiter auf die Folter spannen. Die allgemeine Syntax beim Deklarieren einer Funktion sieht so aus:


    Rückgabedatentyp Funktionsname (Parameterliste){

        Anweisungen;

        return(Rückgabewert);

    }


Nun können wir viel Zeit damit aufbringen, die einzelnen Komponenten zu erklären, doch besser ist es fürs Erste, das Grundprinzip zu verstehen. Und da kannst du bei Funktionen zunächst mal an eine schwarze Box denken – an „Black Boxes“. Die haben die Eigenheit, das man irgendwas in sie hineinwirft und nach einiger Zeit spucken dir Black Boxes wieder etwas aus. Auf einen Input folgt also immer ein Output. Du übergibst der Black Box einige Zutaten, Kartoffeln, Tomaten, Gurken, und am Ende bekommst du ein leckeres Gericht serviert. Dies ist das Prinzip, das jeder Funktion zugrunde liegt. Eine Funktion bekommt Eingabeparameter übergeben, stellt mit diesen etwas an und spuckt einen Rückgabewert aus. Für den Anwender bleibt unergründlich, was im Inneren der Black Box vor sich geht. Er sieht nur die Eingabe und das Ergebnis. Du als Programmierer hast nun aber das Privileg, den Inhalt der Black Box selbst zu gestalten. Betrachten wir deshalb nun die konkrete Realisierung einer Funktion, die uns den Durchschnitt bestimmter Werte berechnet:


    float durchschnitt (int anzahl) {

        int summe=0;

        int note;

        for (int i=1; i<=anzahl; i++) {

            cin >> note;

            summe=summe+note;

        }

        float d=(float) summe/anzahl;

        return d;

    }


Merke zunächst, dass solche selbstdefinierten Funktionen außerhalb des main-Programms geschrieben werden. Es ist wieder wie beim Autobau: Zunächst wird der Motor (die Funktion) hergestellt, dann erst kann man ihn ins Auto (im Programm) einbauen. 


Wenn du nun in C++ eine Funktion schreibst, musst du zunächst einmal festlegen, von welchem Datentyp dein Output, dein Rückgabewert sein soll. Beim Durchschnitt bietet sich float an, da du hier aller Wahrscheinlichkeit nach Kommazahlen als Ergebnis erhältst. Dann musst du dir einen Namen für die Funktion ausdenken, bevor du in Klammern deine Eingabeparameter, deinen Input eingibst. Dabei wird jeder Parameter mit seinem dazugehörigen Datentyp genannt. Willst du mehr als einen Parameter übergeben, musst du die einzelnen Werte mit einem Komma getrennt aufzählen. Jeder Parameter, der übergeben wird, sollte in irgendeiner Weise in den Funktionsanweisungen verwendet werden. Dann folgen die Berechnungen und Anweisungen, ehe du zum Schluss einen Wert erhältst, den deine Funktion präsentieren soll. Dieser Rückgabewert, der Output, muss hinter dem Wörtchen „return“ stehen und sollte von dem Datentyp sein, der zu Anfang deiner Funktionsdefinition steht.


Sobald der Compiler beim Ausführen des Programms auf das Wort „return“ stößt, endet die Abarbeitung der Funktion. Es ist also ziemlich sinnlos, weitere Anweisungen hinter „return“ einzutippen. „return“ ist für den Compiler das Signal, das nun hier und jetzt ein Ergebnis präsentiert werden muss. 


In gewisser Weise hast du schon seit deinen ersten Schritten mit C++ Funktionen benutzt. Glaubst du nicht? Nun, vielleicht ist dir ja aufgefallen, dass unter dem main-Block auch immer eine return-Anweisung angegeben wird. Und in der Tat ist „main()“ auch eine Funktion – eine besondere nämlich, in der die Anweisungen stehen, die der Compiler schlussendlich auszuführen hat.


Wo wir gerade bei der main-Funktion sind: Wie genau verwendet man deklarierte Funktionen nun im Hauptprogramm? Wie können wir dem Compiler mitteilen, berechne den Notendurchschnitt für diese und jene Werte? Ein Programmbeispiel soll dies verdeutlichen:


    int main() {

        int anzahlMathenoten=5;

        float durchschnittMathe=durchschnitt(anzahlMathenoten);


        int anzahlDeutschnoten=7;

        float durchschnittDeutsch=durchschnitt(anzahlDeutschnoten);


        int anzahlEnglischnoten=5;

        float durchschnittEnglisch=durchschnitt(anzahlEnglischnoten);

        return 0;

   }


Eine Funktion wird im main-Programm durch die explizite Nennung ihres Namens aufgerufen. Allein „durchschnitt“ aufzuschreiben, reicht da jedoch nicht. Hinter dem Funktionsnamen müssen in Klammern die entsprechenden Parameter stehen. Wichtig ist dabei die Reihenfolge der Parameter! Diese ist nämlich alles andere als willkürlich. Anders hingegen die Variablennamen – du musst nicht dieselben Variablennamen verwenden, die in der Funktionsdeklaration benutzt wurden. Du siehst es schon in unserem Beispiel: In der allgemeinen Deklaration haben wir den Parameter „anzahl“ genannt, beim Aufruf der Funktion kann jedoch jeder beliebige Name für die Parameter gewählt werden – Hauptsache, der Datentyp stimmt. Einen float-Wert kannst du an unsere Durchschnittsfunktion nämlich nicht übergeben. Die Deklaration ist immer nur ein Bauplan, eine Blaupause. In ihr wird beschrieben, wie die Funktion abstrakt aussieht. Beim Aufruf musst du dich an diesen Plan halten: Ein int-Parameter ist an zweiter Stelle gefordert, also muss ich im Hauptprogramm einen int-Wert an zweiter Stelle angeben. Die Funktion spuckt float-Werte aus, also kann ich die Funktion keinem Character zuweisen. 


Wer sich nun mit Funktionen auskennt, gibt seinen Programmen einen besonderen Schliff. Nie wieder ellenlanger, unübersichtlicher Syntax-Brei – wenn das nicht Anreiz ist, Funktionen ausgiebig zu studieren!

Rekursion

Inc++eption

Neulich im Klassenzimmer: Peter grübelt über das Ergebnis der Aufgabe „4!“. Er zerbricht sich den Kopf darüber, doch auf ein Ergebnis kommt er nicht. Immerhin: Peter weiß, dass er 4! in „4*3!“ zerlegen kann. Doch was um alles in der Welt ergibt denn „3!“? Zum Glück sitzt Marie neben ihm, das Mathe-Ass. Doch auch ihr will partout nicht einfallen, was 3! ergeben soll. Sie weiß allerdings: Ich kann die Aufgabe als 3*2! schreiben. Verzweifelt tippt sie Michael an. „Psst“, sagt sie. „Weißt du, was 2! ergibt?“ Dasselbe Spiel von vorn. Michael kennt das Ergebnis nicht, kann den Term aber in 2*1! zerlegen. Aber was soll 1! nur sein? Zum Glück weiß es Josefine endlich: 1! ist 1. Damit können die Kinder die Aufgabe endlich lösen! 2! ist damit 2, 3! ergibt 6 und 4! ist folglich 12. 

Falls dir das nun ein wenig zu verwirrend war, haben wir die Sache mal mit einem kleinen Bild veranschaulicht:

Das, was die Kinder hier gemacht haben, wird in Informatiker-Fachkreisen als das Prinzip der Rekursion bezeichnet. In Bezug auf Funktionen meint Rekursion dabei nichts anderes als einen Selbstaufruf.

Klingt abstrakt, ist es aber nicht: Denn innerhalb einer Funktion kann wieder eine andere Funktion aufgerufen werden, im Falle der Rekursion sogar dieselbe. Vereinfacht gesagt: Eine Funktion wird abgearbeitet, der Compiler stößt auf den Befehl, die Funktion wieder aufzurufen, die Funktion wird wieder abgearbeitet, die Funktion nochmal geöffnet und nochmal und nochmal und nochmal. Man programmiert also gewissermaßen eine „Funk-ception“.

Wir können verstehen, wenn dir das zunächst etwas sinnlos anmutet. Ist es eigentlich auch – es sei denn, die Rekursion kommt zu einem Ende. Der eigentliche Zweck der Rekursion besteht nämlich darin, ein komplexes Problem wie die Fakultätsberechnung zuerst in ein einfaches Problem zu zerlegen. So wie es Peter zu Anfang gemacht hat: 4! ist nichts anderes als 4*3!. Dieser Term kann jedoch wieder vereinfacht werden, indem man nun 3! zerlegt. Dies geschieht immer weiter, bis man auf eine triviale Lösung stößt: In diesem Fall 1!=1. Hat die Rekursionsschleife einmal die Triviallösung erreicht, kann das komplexe Problem im Nu gelöst werden.

Beim Programmieren einer rekursiven Funktion sind also zwei grundlegende Anforderungen zu erfüllen:

1. Es wird eine Rekursionsformel benötigt. Im Falle der Fakultät lautet diese: function(n) = n* function(n-1). Heißt also, n! wird berechnet, indem der Term in n*(n-1)! zerlegt wird.
2. Die Rekursion muss terminieren, sprich, an ihrem Ende zu einem trivialen Ergebnis führen. function(1)=1 oder 1!=1 lautet dieses beim Beispiel der Fakultät.

Mehr braucht man bei einer rekursiven Funktion nicht zu beachten! Diese Informationen reichen aus, um dies nun in der Programmiersprache C++ zu realisieren. Gehen wir die zwei Anforderungen nochmal durch. Im Grunde genommen sagen die uns nichts anderes, als: Wenn f(n) trivial, spucke die bekannte Lösung aus. Sonst rufe dich von Neuem mit einem weniger komplizierten Term auf. Das riecht doch förmlich nach if-Struktur! Und genau auf diese Weise sieht das Grundgerüst jeder rekursiven Funktion aus. Schauen wir uns mal unsere Fakultätsfunktion an:

    int fakultaet(int n) {
        int erg;
        if (n<=1) {
            return 1;
        }
       else{
           erg = n*fakultaet(n-1);
       }
       return erg;
    }


Wie der Rekursionsprozess nun im Falle von n=4 aussieht, verdeutlicht diese Grafik:

Ist das Ende des Vorgangs erreicht, muss, wie bei den Kindern im Klassenzimmer, schließlich das Ausgangsproblem berechnet werden. Dies veranschaulicht das folgende Schema:

Die Fakultät ist das eingängigste Beispiel, um rekursive Funktionen zu erklären. Doch mit dem Prinzip der Rekursion lassen sich auch eine ganze Menge anderer Sachverhalte programmieren. Schon mal von den Fibonacci-Zahlen gehört? Tobe dich aus und probiere dich gleich an ein paar Aufgaben!

Referenzen

Best Refriends forever

Referenzen also. Es lohnt sich, dieses Kapitel zu lesen und – vor allem - zu verstehen. Und das nicht, weil wir übermäßig begeistert von Referenzen sind. Klar, die sind nützlich. Aber der Hauptgrund, sich nun damit zu beschäftigen, ist: Wer Referenzen verstanden hat, hat den schwersten Stoff gepackt! Freue dich, also am Ende sagen zu können: Das Schwerste liegt hinter einem! 


Lass uns unsere Ausführungen mal mit einer kleinen Funktion beginnen:


    void quadratzahl(int x) { 

        x= x*x;

    }


Diese Funktion bekommt einen Integer-Wert x übergeben und soll diesen in seine zugehörige Quadratzahl verwandeln. Alles schick! Rufen wir die Funktion mal im Hauptprogramm auf:


    int main() {

        int zahl;

        cin >> zahl;

        quadratzahl(zahl);

        cout << zahl;

    }


Wer dieses kleine Programm nun auf seinem Rechner laufen lässt, wird (hoffentlich) blöd aus der Wäsche gucken. Denn egal, welchen Integer wir eintippen, die Funktion will diesen einfach nicht in ihr Quadrat verwandeln. Eine Zuweisung wie „zahl=quadratzahl(zahl)“ wäre übrigens nicht möglich, da die Funktion den Rückgabewert void besitzt und dieser nicht einfach so einem Integer (in diesem Fall „zahl“) zugeschrieben werden kann. Klar könnten wir den Rückgabetyp der Funktion in „int“ ändern und das Problem wäre gelöst – wenn es da nicht noch eine pfiffige andere Variante gibt. 


Doch zuerst wollen wir verstehen, warum diese Funktion aus unseren Integern keine Quadratzahlen macht. Wenn eine Funktion einen Eingabeparamter übergeben bekommt, arbeitet die Funktion nicht auf den Parametern selbst – sondern auf Kopien dieser Parameter. Erhält unsere Quadratzahl-Funktion also den Parameter „zahl“ als Input, legt sie eine Kopie der Variablen „zahl“ an. Das führt zwangsläufig dazu, dass Veränderungen auf der Kopie keine Auswirkungen auf die Ursprungsvariable aus dem Hauptprogramm haben. Zwischen Kopie und der Hauptprogramm-Variablen besteht schließlich keine Verbindung – es sind nur zwei zufällig gleich aussehende Objekte. Ist die Funktion abgearbeitet, löscht sie zusätzlich alle lokalen Variablen, die für Berechnungen innerhalb der Funktion angelegt wurden. So gesehen verwischt die Funktion ihre Spuren – das einzige, was von ihr bleibt, ist der Rückgabewert. Da wir hier jedoch eine void-Funktion angelegt haben, bleibt wirklich gar nichts von ihr übrig. Die Funktion berechnet zwar das Quadrat, löscht das Ergebnis aber sodann aus dem Speicher. Beim Befehl „cout << zahl“ wurde also nicht das Ergebnis der Funktion ausgespuckt, sondern die unberührte Variable „zahl“, die man eben erst eingegeben hat. Da mit der Kopie von „zahl“ gearbeitet wurde, blieb die Variable in Wirklichkeit die ganze Zeit unangetastet. 


Wie gesagt: Würden wir statt „void“ einen anderen Rückgabetyp nennen, könnte man das Problem umgehen. Das ändert jedoch weiterhin nichts daran, dass Funktionen mit Kopien arbeiten. Wir müssen also, wenn wir mit einer Funktion eine Variable ändern, den lästigen Schritt der Zuweisung „Variable=Funktion(Variable)“ stets durchführen. Unser Ziel ist es nun aber, dass die Funktion die Variablen wirklich verändert und nicht nur mit ihren Kopien arbeitet. Wie lösen wir das?


Zwischen Kopie und Variable herrscht keine Verbindung. Sie sind zwei Fremde, die nichts miteinander zu tun haben wollen. Blöd... Aber kennst du das? Einen Freund zu haben, der dir so treu ist, dass er alles macht, was du machst? Jeden Schritt, den du tust, tut auch dein Freund und umgekehrt. Nun, Variablen können auch solche Freundschaften eingehen – und zwar solch innigen, dass sie wirklich alles gemeinsam machen. Diese besten Freunde der Variablen werden Referenzen genannt. 


Informatisch gesprochen, sind Referenzen interne Zeiger auf Variablen. Klingt sehr trocken, deshalb wollen wir dir das mal an einem kleinen Beispiel vorführen:

    int number=38;

    int& refnumber=number;


Hier deklarierst du eine Variable „number“ und gibst ihr in der nächsten Zeile einen besten Freund. Diese Freundschaft zeigst du mit dem „&“-Symbol hinter dem Datentyp an. Die Freundschaft zwischen den beiden ist so eng, dass die Referenzvariable sogar fast den gleichen Namen trägt wie number, nämlich „refnumber“. Tatsächlich aber kannst du jeden beliebigen Namen wählen. Anschließend sagst du mit der Zuweisung „=number“, wessen bester Freund „refnumber“ werden soll. Dieser Schritt ist ungemein wichtig! Referenzen können nämlich nie alleine existieren – sie sind solch sozialen Wesen, dass sie nur und auch nur im Verbund mit einer Variablen existieren. Ohne die Zuweisung, ohne das endgültige Schließen der Freundschaft, kann keine Referenz deklariert werden. Und die Dinger sind treu! Wirklich treu! Haben sie einmal eine Freundschaft geknüpft, bleiben sie für alle Ewigkeiten an die Variable gebunden. Sie werden sich nie wieder eine andere Variable als Freund suchen. Will heißen: Eine nachträgliche Neuzuweisung einer Referenz an eine andere Variable ist nicht möglich.

Fassen wir diese Erkenntnisse nochmal im Informatiker-Jargon kurz zu zusammen:


1. Referenzen muss immer eine Variable zugeschrieben werden. Anders gesagt: Referenzen müssen initialisiert werden beziehungsweise auf etwas bereits Existierendes verweisen.
2. Referenzen können nach der Initialisierung nicht auf etwas anderes verweisen. Sie bleiben an ihre Variable gebunden. 


Diese kleine Grafik gibt dir einen Überblick darüber, wie man mit Referenzen in C++ richtig zu handhaben hat:

Die Variable und die Referenz können von nun an ihre Freundschaft ausleben. Sie machen wirklich alles im Gleichschritt! Sprich, wenn sich in unserem Beispiel „number“ erhöht, erhöht sich auch die Referenz. Und wenn die Referenz „refnumber“ einen neuen Wert zugewiesen bekommt, übernimmt auch „number“ diesen:


    number=number+1;

    //number und refnumber haben nun beide den Wert 39


    refnumber=20;

    //number und refnumber haben nun beide den Wert 20


Diese innige Freundschaft zwischen Variable und Referenz ist nun für Funktionen unglaublich praktisch. Denn weil eine Variable alles mitmacht, was ihre Referenz tut, ist es so möglich, Hauptspeichervariablen durch einen Funktionsaufruf nun wirklich zu verändern! Damit wären Programme wie unser obiges endlich funktionsfähig. Wenn wir in eine void-Funktion also einfach die Referenz einer Variablen übergeben und die Funktion diese verändert, übertragen sich alle Veränderungen auf die Variable im Hauptspeicher. Sprich, nach dem Aufruf der Funktion bleiben alle Veränderungen an der Variable erhalten. Dieses interessante Konstrukt wird „call-by-reference“ genannt, sein Gegenpart ist der Aufruf „call-by-value“, wie er üblicherweise bei Funktionen ohne Referenzen passiert und wie oben auf dieser Seite schon zu sehen.


Mit diesem Wissen können wir unser Programm nun anpassen und Integer endlich in ihre Quadratzahlen verwandeln:


    void quadratzahl(int& x){

        x=x*x;

    }


    int main(){

        int zahl;

        cin >> zahl;

        quadratzahl(zahl);

        cout << zahl;

    }


Das einzige, was du also wirklich ändern brauchtest, ist das „&“-Symbol in der Parameterliste. 


Referenzen sind also nicht nur ziemlich praktisch, sondern zeigen uns auch den wahren Wert von Freundschaft – und mit diesen kitschig-pathetischen Worten schließen wir dieses große Kapitel über Funktionen. Jetzt heißt es: Ran ans Üben!

Aufgaben: Funktionen

A1: Quiz dich schlau!

Created with Sketch.

Nachfolgend sind einige Fragen aufgeführt, die du nach diesem Abschnitt beantworten können solltest:

(1) Welche Vorteile hat die Verwendung von Funktionen?

(2) Durch welche Angaben werden bei der Definition einer Funktion die Eingabeparameter beschrieben? Und was lässt sich über diese Eingabeparameter im Hinblick zur Funktionsdefinition und des Funktionsaufrufs in main sagen?

(3) Was bewirkt der Befehl return in einer Funktion?

(4) Was ist beim Datentyp der Werte zu beachten, die in der return-Anweisung stehen? Womit muss dieser Datentyp übereinstimmen oder kompatibel sein?

(5) Was ist der Unterschied zwischen call-by-value und call-by-reference? Und was deren Vor- und Nachteile?

(6) Du willst aus einer Funktion 3 Werte an das aufrufende Programm zurückgeben, wie kann man das umsetzen?

(7) Was ist ein Stack? Wozu wird er eingesetzt?

(8) Welche Bedingungen müssen erfüllt sein, damit rekursive Funktionen terminieren (also beendet werden)?

A2: Einmal Pizza mit allem, bitte!

Created with Sketch.

Siggi möchte mit ihren Freunden einen entspannten Abend mit Film und Pizza machen. Doch die Bestellung stellt alle vor eine kleine Herausforderung. Schreibe daher ein Programm mit folgenden (globalen) Funktionen:
- preisLeist(): Berechnet das Preis-Leistungs-Verhältnis einer Pizza aus dem Flächeninhalt und dem Preis. Nutze hierfür die üblichen Angaben Durchmesser und Preis als Input!
- warenkorb(): Ermittelt die Summe aus den Preisen der bestellten Pizzen sowie deren Anzahl im Warenkorb. Nutze für die Summe call-by-reference, sodass warenkorb() nur die Anzahl der bestellten Pizzen zurückgibt!

Der User soll solange innerhalb von main() nach weiteren Pizzen mit den entsprechenden Maßen und Preisen gefragt werden, bis dieser die Zahl 0 eingibt.

Ist es bezüglich des Preis-Leistungs-Verhältnisses günstiger eine im Durchmesser 25cm große Pizza für 8,49€ oder eine 32cm große Pizza für 12,99€ zu kaufen?

A3: Schnipp schnapp - Haare ab

Created with Sketch.

Sebastian sitzt beim Friseur und lässt sich die Haare bis auf 2,1 cm abschneiden. Er weiß, dass Haare durchschnittlich um 0,5 mm pro Tag wachsen. Wie lang sind diese nach 40 Tagen?
Schreibe hierfür ein C++-Programm, welches eine rekursive Funktion haarlaenge() beinhaltet. 
Was ist die Abbruch- bzw. Anfangsbedingung?

Musterlösungen

L1: Quiz dich schlau!

Created with Sketch.

(1) Übersichtlichkeit des Codes, Reduzierung redundanter Algorithmen (Wiederverwendung), Fehlervermeidung

(2) Eingabeparameter: Datentyp Variablenname
Dabei muss der Variablenname beim Funktionsaufruf (in main) nicht derselbe wie in der Funktionsdefinition sein. Lediglich die Position und der Datentyp muss an beiden Stellen gleich sein.

(3) return ist ein Befehl zum Beenden der aktuellen und Rückkehr zur aufrufenden Funktion (meist main) und zur Rückgabe des in den Klammern stehenden Wertes an diese aufrufenden Funktion.


(4) Der Datentyp in der return-Anweisung muss derselbe sein wie der Datentyp der Funktion ab sich.

(5) call-by-value beschreibt ein Verfahren, welches als Eingabeparameter einer aufzurufenden Funktion eine Kopie einer bestehenden Variable aus der aufrufenden Funktion (meist main) erstellt.
:)  + Seiteneffekte werden vermieden.

:(  - Änderungen erfolgen innerhalb der aufgerufenen Funktion nur auf der Kopie der Werte.

call-by-reference ist eine Referenz bzw. ein Alias-Name auf eine bestehende Variable aus der aufrufenden Funktion (meist main). Dabei wird sich stets auf die originale Adresse im Speicher bezogen.
:)  + Gleichzeitige Wertänderung der Variable in aufrufender und aufgerufener Funktion. Dadurch Rückgabe einer Funktion von mehreren Werten möglich


:(  - schwer nachvollziehbare Seiteneffekte

(6) Umsetzbar ist diese Situation u. a. mithilfe des call-by-reference Verfahrens:
Beispiel:
void function (int &zahl, char &letter, bool &choice){ .... }

(7) Ein Stack ist ein statischer Speicherbereich, in dem Werte, die an Funktionen übergeben werden, temporär zwischengespeichert sind.
 
(8) Eine Abbruchbedingung muss definiert sein.

L2: Einmal Pizza mit allem, bitte!

Created with Sketch.

#include <iostream>
using namespace std;

const float pi = 3.141;
// globale Konstanten sind hilfreich, falls diese in mehreren Funktionen verwendet werden.

void preisLeist(int diameter, float price){   
 // void-Datentyp, da diese Funktion schon gewünschte Ausgabe beinhaltet
    cout << "Das Preis-Leistungs-Verhaeltnis dieser Pizza betraegt: ";
    cout << price / (pi*diameter*diameter/4.0) << " EUR je cm^2" << endl;
   
    return;
}


int warenkorb( float price, float &sum, int count){   
// Bezeichnungen der Variablen müssen in main und in den Funktionen nicht unbedingt übereinstimmen
    sum = sum + price;
    count = count++;
   
    return(count);
}


int main()
{
    int durchmesser;
    float preis;
    float summe = 0;
    int anzahl = 0;
    int menu;
   
   
    do{
// kopfgesteuerte Schleife funktioniert auch
   
    cout << "Durchmesser in cm: ";
    cin >> durchmesser;
    cout << "Preis in EUR: ";
    cin >> preis;
   
    preisLeist(durchmesser, preis);
    anzahl = warenkorb(preis, summe, anzahl);
   
    cout << "Sie haben bisher " << anzahl << " Pizzen fuer insgesamt " << summe << " EUR bestellt.";
    cout << endl;
    cout << "(0) Einkauf abschliessen \t (1) Einkauf fortsetzen" << endl;
    cout << "Eingabe: ";
    cin >> menu;
    cout << endl;
   
    }while(menu != 0);

    return 0;
}

Es ist in der Tat günstiger eine 32cm Pizza für 12,99€ (1,6 Cent / cm^2) zu kaufen, als die 25cm Pizza für 8,49€ (1,7 Cent / cm^2) .

L3: Schnipp schnapp - Haare ab 

Created with Sketch.

#include <iostream>
using namespace std;

float haarlaenge(int days){
    if (days == 0){
// Abbruchbedingung
    return(21.0);
    }
    else{
        return(haarlaenge(days-1) + 0.5);
    }
}


int main()
{
    float haar;
    int tage;
   
    cout << "Tage: ";
    cin >> tage;
   
    haar = haarlaenge(tage);
   
    cout << "Nach " << tage << " Tag/en hat das Haar eine Laenge von " << haar;
    cout << " mm";

    return 0;
}

Haarlänge nach 40 Tagen: 41 mm

Fragen, Anregungen?

Ist etwas unverständlich oder du hast Fragen zu einem Thema, Aufgabe oder einfach zur C++-Welt? 
Dann schreibe uns einfach eine Mail!