Einleitung und Syntax
Funktionsdeklaration (Signatur)
Funktionsdefinition
Funktionsaufruf
Funktionsparameter
Funktionsrückgabewert
Rekursive Funktionen
Sonstiges zu Funktionen
Beispiel und Übung
Eine Funktion ist, vereinfacht ausgedrückt, eine Zusammenfassung von mehreren Anweisungen unter einem bestimmten Namen. Sie können sich eine Funktion als eine Art (Unter-)Programm innerhalb eines beliebig komplexen Gesamtprogramms vorstellen.
Funktionen werden hauptsächlich aus zwei Gründen eingesetzt:
Eine Funktion hat immer folgenden Aufbau:
| RETURNTYP Funktionsname ([Parameter]) { [Definition von lokalen Variablen] Anweisungen der Funktion [return RETURNWERT] } |
Die Angaben in den Klammern [...] sind optional.
|
|
Bevor Sie eine Funktion in einem Programm verwenden (aufrufen) können, muss die Funktion vorher definiert oder aber zumindest deklariert sein. Der Unterschied zwischen einer Funktionsdefinition und einer Funktionsdeklaration ist folgender:
Vielleicht fragen Sie sich nun, warum überhaupt eine Funktionsdeklaration vor einem Funktionsaufruf notwendig ist? Nun, der C++ Compiler ist ein 'sehr strenger' Compiler. Trifft er während des Übersetzungsvorgangs auf den Aufruf einer Funktion die noch nicht definiert ist, so kann er ohne die notwendige Funktionsdeklaration nicht prüfen, ob z.B. der Name der Funktion richtig geschrieben ist und auch die (optionalen) Parameter der Funktion richtig angegeben wurden. D.h. erst durch die Funktionsdeklaration kann der Compiler den Funktionsaufruf syntaktisch überprüfen und auch den notwendigen Aufruf-Code erstellen. Doch wie sieht die Deklaration einer Funktion aus? Sie hat folgende Syntax:
| RETURNTYP Funktionsname ([Parameter]); |
Wenn Sie nun die Deklaration mit dem im vorherigen Abschnitt aufgeführten Aufbau einer Funktion vergleichen, so werden feststellen, dass sie genau dem Funktionskopf entspricht, jedoch mit einem abschließenden Semikolon. Solche Funktionsdeklarationen haben Sie bisher auch schon, mehr oder weniger bewusst, angewandt. Das Einbinden von Header-Dateien mittels #include diente unter anderem genau diesem Zweck. Nachfolgend sehen Sie einige Beispiele für Funktionsdeklarationen.
|
// Funktionsdeklarationen void PrintHeader(); short MinVal(short, short); // main() Funktion int main() { ... } |
Und noch ein weiterer Begriff wird später im Zusammenhang mit Funktionen auftauchen, der Begriff Funktions-Signatur. Die Signatur einer Funktion besteht aus dem Funktionsnamen und den Parametern, also ohne den Returntyp der Funktion. Sie spielt später beim Überladen von Funktionen noch eine wichtige Rolle.
Sehen wir uns jetzt die Definition einer Funktion an. Beginnen wir mit dem Funktionsnamen. Der Name einer Funktion muss eindeutig sein, d.h. es darf keine weitere Funktion, Variable usw. mit dem gleichen Namen geben. Ausnahme: beim Überladen von Funktionen, das in einer späteren Lektion noch behandelt wird.
|
// Funktionsdeklarationen void PrintHeader(); // PrintHeader() und void Printheader(); // Printheader() sind unterschiedliche Funktionen short Min(short,short); // Variablendefinitionen short Min; // Nicht erlaubt, da Min() als Funktion deklariert |
Beachten Sie bitte, dass C++ streng zwischen Groß-/Kleinschreibung unterscheidet. So deklarieren die ersten zwei Funktionsdeklarationen im Beispiel oben zwei unterschiedliche Funktionen. Vermeiden Sie aber der besseren Lesbarkeit wegen solche Konstruktionen. Die Definition der short-Variable Min im Beispiel würde einen Übersetzungsfehler erzeugen, da bereits zuvor eine Funktion Min() deklariert wurde.
Die letztendlich von einer Funktion auszuführenden Anweisungen werden in einem Block {...} zusammengefasst. Innerhalb einer Funktion können bis auf eine Ausnahme alle C++ Anweisungen stehen. Nicht erlaubt ist es, innerhalb einer Funktion eine weitere Funktion zu definieren, so wie dies z.B. die Programmiersprache PASCAL zulässt.
|
Nicht erlaubt! void Func1() { ... void Func2() // Das geht nicht! { ... } ... } |
Außer Anweisungen im üblichen Sinne, können Sie innerhalb von Funktionen auch Variablen oder Konstanten definieren. Auch die Definition von Variablen/Konstanten ist ja letztendlich eine Anweisung. Diese Variablen/Konstanten sind dann aber nur innerhalb der Funktion gültig.
|
void Func1() { int temp; // Definition von Variablen die nur const float PI = 3.1416; // innerhalb dieser Funktion gültig sind ... } |
Doch gehen wir jetzt zur Praxis über und sehen uns den einfachsten Fall einer Funktionsdefinition an: eine Funktion die keine Parameter (Daten) benötigt und auch kein Ergebnis zurückliefern muss. Wir wollen jetzt eine einfache Funktion schreiben, die einen bestimmten Text, z.B. für die Überschrift auf einer Seite, ausgibt. Die Funktion soll den Namen PrintHeader erhalten.
Bevor im Programm PrintHeader() aufgerufen werden kann, muss die Funktion (wie erwähnt) zumindest deklariert sein. Diese Deklaration ist wie unten angegeben vorzunehmen. Das Schlüsselwort void vor dem Funktionsnamen gibt an, dass die Funktion keinen Wert zurückliefert. Da die Funktion auch keine Parameter (Daten) benötigt um den Text auszugeben, bleibt die Funktionsklammer einfach leer.
|
#include <iostream> // Funktionsdeklaration void PrintHeader(); ... // main() Funktion int main() { ... } |
Nur muss die Funktion noch definiert werden. Dazu ist zunächst der Funktionskopf einzugeben. Er entspricht der Deklaration, jedoch ohne das abschließende Semikolon. Nach dem Funktionskopf folgt der Funktionsblock {...}. Und innerhalb dieses Blocks stehen die entsprechenden Anweisungen der Funktion. In unserem Beispiel ist die Funktion relativ klein und gibt nur einen bestimmten Text aus. Beim Erreichen des Endes des Funktionsblocks wird automatisch zu der nach dem Funktionsaufruf (wird gleich behandelt) folgenden Anweisung zurückgekehrt.
|
#include <iostream> // Funktionsdeklaration void PrintHeader(); ... // main() Funktion int main() { ... } // Definition der Funktion void PrintHeader() { cout << "Tabellenüberschrift\n"; } |
Nach der Deklaration und Definition der Funktion fehlt jetzt nur noch die entsprechende Anweisung, um die Funktion auch aufzurufen, d.h. den Funktionscode auszuführen. Auch dies ist einfach. Schreiben Sie dazu an den Stellen im Programm, an denen die Funktion aufgerufen werden soll, einfach den Funktionsnamen, gefolgt von einer leeren Klammer ( ) und einem abschließenden Semikolon.
|
#include <iostream> // Funktionsdeklaration void PrintHeader(); ... // main() Funktion int main() { ... // Funktion aufrufen PrintHeader(); } // Definition der Funktion void PrintHeader() { cout << "Tabellenüberschrift\n"; } |
Nachdem die generelle Handhabung einer Funktion bekannt ist, folgt nun der nächste Schritt: die Parametrierung von Funktionen. War bisher die Klammer nach dem Funktionsnamen noch leer, so werden wir jetzt mithilfe dieser Klammer Parameter (Daten) an die Funktion übergeben.
Fangen wir auch hier wieder mit der Deklaration der Funktion an. Wenn an eine Funktion Parameter übergeben werden, so müssen bei der Deklaration der Funktion mindestens die Datentypen der Parameter angegeben sein. Damit kann der Compiler später beim Aufruf der Funktion zum einen den richtigen Aufrufcode erzeugen und zum anderen auch eine Überprüfung der beim Aufruf angegebenen Parameter vornehmen.
Zusätzlich zum Datentyp kann auch ein beschreibender Parametername bei der Funktionsdeklaration mit angegeben werden. Benötigt eine Funktion mehrere Parameter, so sind diese durch Komma voneinander zu trennen. Besonders bei mehreren Parametern hilft die Angabe eines beschreibenden Parameternamens, der die Bedeutung des Parameters widerspiegelt. Im letzten Beispiel erhält die Funktion PrintIt() im ersten Parameter wahrscheinlich die X-Position und im zweiten Parameter die Y-Position für die Ausgabe. Die Anzahl der Parameter einer Funktion ist nicht begrenzt.
|
void CalcSqrt (double); // ist gleichbedeutend mit // void CalcSqrt (double val); void PrintIt (unsigned short xPos, unsigned short yPos); |
Bei der Definition der Funktion geben Sie genauso wie bei der Deklaration die Datentypen der Parameter an, nun jedoch zwingend gefolgt von einem Parameternamen. Wenn Sie schon bei der Deklaration die Parameter mit Namen versehen haben, so brauchen Sie bei der Definition der Funktion die Deklaration im Prinzip nur kopieren und das abschließende Semikolon entfernen. In der Funktion können Sie dann über diese Parameternamen auf die an die Funktion übergebenen Werte zugreifen, d.h. die Parameternamen wirken sozusagen als Platzhalter für die im Aufruf tatsächlich angegebenen Werte. Die Parameternamen bei der Deklaration und der Definition der Funktion müssen nicht zwingend übereinstimmen, aber der besseren Lesbarkeit wegen sollten sie es. Wie Daten beim Aufruf an die Funktion übergeben werden, das kommt gleich noch. Im Beispiel sehen Sie eine Funktion zum Ausdruck einer Kopfzeile auf einer Seite. Die aktuelle Seitennummer wird dabei als Parameter an PrintHeader() übergeben.
|
// Funktionsdeklaration void PrintHeader(int pageNum); ... // main() Funktion int main () { // gleich noch Seitenüberschrift ausdrucken ... } // Definition der PrintHeader-Funktion void PrintHeader(int pageNum) { cout << "Dies ist Seite " << pageNum << endl; } |
|
|
Bei einem Parameter vom Typ called-by-value erhält die aufgerufene Funktion lediglich eine Kopie des übergebenen Wertes. Daraus folgt, dass Änderung des Parameters zwar innerhalb der Funktion erlaubt sind, diese aber auch nur auf die Kopie wirken. Wird die Funktion wieder verlassen, so hat sich am Wert des übergebenen Parameters nichts geändert. Die Definition eines called-by-value Parameters erfolgt in der Art, dass innerhalb der Parameterklammer der Funktion lediglich der Datentyp und Name des entsprechenden Parameters angegeben wird. Im Beispiel wird am Ende der Funktion der Wert des Parameters pageNum zwar immer auf 99 gesetzt, jedoch wirkt sich dies nicht weiter in main() aus.
|
// Funktionsdeklaration void PrintHeader(int pageNum); ... // main() Funktion int main () { int page = 1; // Seitennummer initialisieren PrintHeader(page); // Funktion aufrufen ... // page hat hier immer noch den Wert 1 PrintHeader(10); // Funktionsaufruf mit konstanter Seitennummer } // Definition der PrintHeader-Funktion void PrintHeader(int pageNum) { cout << "Dies ist Seite " << pageNum << endl; pageNum = 99; // Parameter verändern } |
Beim Aufruf von Funktion können Sie für einen called-by-value Parameter entweder eine Variable (erster Aufruf oben) oder aber eine Konstante (zweiter Aufruf) an die Funktion übergeben.
|
|
Referenzparameter ermöglichen Funktionen, die an sie übergebenen Parameter dauerhaft zu verändern, so dass die innerhalb der Funktion durchgeführte Änderungen an den Parametern auch nach dem Verlassen der Funktion noch gültig sind.
Eine Referenz ist letztendlich im Prinzip nichts anderes als ein Verweis auf ein bereits bestehendes Datum. Um einen Funktionsparameter als Referenzparameter zu kennzeichnen, wird bei der Deklaration und der Definition der Funktion nach dem Datentyp des Parameters der Operator & angefügt. Beim Aufruf der Funktion wird ein Referenzparameter genauso wie ein Parameter vom Typ called-by-value übergeben.
Nachfolgend sehen Sie eine Funktion Swap(...), die einfach die Werte der beiden übergebenen Parameter vertauscht.
|
// Funktionsdeklaration void Swap (short& val1, short& val2); // main() Funktion int main () { ... Swap (var1, var2); ... } // Funktionsdefinition // Vertauscht die Inhalte der übergebenen Variablen void Swap (short& val1, short& val2) { short temp = val1; val1 = val2; val2 = temp; } |
Was Sie nicht so ohne weiteres als Referenzparameter übergeben können, sind Konstanten. D.h. die obige Funktion Swap(...) kann zum Beispiel nicht mit Swap(10,var); aufgerufen werden (was hier aber auch keinen Sinn machen würde).
In manchen Fällen kann es durchaus sinnvoll sein, dass Parameter die per Referenz übergeben wurden (da kein Kopiervorgang der Daten notwendig) innerhalb einer Funktion nicht verändert werden sollen. Denken Sie an die vorherige Funktion PrintHeader(), die die aktuelle Seitennummer als Parameter erhalten hat. Niemand würde hier vermuten, dass die Seitennummer innerhalb der PrintHeader() Funktion verändert wird. Um nun Referenzparameter als nicht veränderbar innerhalb einer Funktion zu kennzeichnen, wird dem Datentyp des Referenzparameters das Schlüsselwort const vorangestellt. Jeder Versuch, einen als const definierten Referenzparameter innerhalb der Funktion zu verändern, führt zu einem Übersetzungsfehler.
|
// Funktionsdeklaration void PrintHeader(const int& pageNum); ... // main() Funktion int main () { int page = 1; // Seitennummer initialisieren PrintHeader(page); // Funktion aufrufen ... // page hat hier immer noch den Wert 1 PrintHeader(10); // Funktionsaufruf mit konstanter Seitennummer } // Definition der PrintHeader-Funktion void PrintHeader(const int& pageNum) { cout << "Dies ist Seite " << pageNum << endl; pageNum = 99; // Parameter verändern } |
Außerdem können bei einem konstanten Referenzparameter jetzt auch Konstanten übergeben werden. Dies ist im obigen Beispiel beim zweiten Aufruf der Funktion PrintHeader() dargestellt.
|
|
Oftmals ist es notwendig, Felder an Funktionen zu übergeben. Sehen wir uns zuerst wieder den einfacheren Fall an, dass ein eindimensionales Feld an eine Funktion übergeben werden soll.
Bei der Übergabe ein eindimensionales Feld an eine Funktion wird lediglich dessen Anfangsadresse übergeben. Sie wissen doch noch hoffentlich aus der Lektion über Felder, dass der Name eines eindimensionalen Feldes nichts anderes ist als der Zeiger auf den Beginn des Feldes. Und damit erhält die Funktion durch die Angabe des Feldnamens als Parameter einen entsprechenden Zeiger von Datentyp des Feldes. Doch wie wird nun innerhalb der Funktion auf die einzelnen Feldelemente zugegriffen? Nun, der erste Ansatz hierzu könnte wie folgt aussehen: man addiert zum Zeiger den Index des gewünschten Elements und dereferenziert diesen dann, so wie im Beispiel unten aufgeführt. Sie müssen dabei nur beachten, dass das erste Element den Index 0 besitzt!
|
// Funktionsdeklaration void DoSomething(const short* const); // main() Funktion int main () { // Felddefinition und Initialisierung short array[] = {10,20,30,40}; ... DoSomething(array); ... } // Funktionsdefinition, das Feld kann hier in // der Funktion nicht verändert werden void DoSomething (const short* const ptr) { // Zugriff auf 1. Element short var = *ptr; // Zugriff auf 3. Element var = *(ptr+2); } |
Und noch ein gut gemeinter Rat. Wenn Sie innerhalb der Funktion den Inhalt des Feldes nicht verändern, definieren Sie den Feldinhalt als const. Im Beispiel oben erhält die Funktion DoSomething(...) einen konstanten Zeiger auf ein konstantes Feldes. DoSomething(...) kann damit weder den Zeiger noch den Inhalt des durch den Zeiger adressierten Feldes verändern.
Der Zugriff auf die Feldelemente innerhalb der Funktion geht aber auch eleganter, wenigsten für den armen Programmierer, der mit Zeigern noch etwas auf Kriegsfuß steht. Anstelle nun irgendwelche Zeigerarithmetik durchführen zu müssen, kann der an die Funktion übergebene Zeiger auch als Feldname verwendet werden und somit indiziert auf die Feldelemente zugegriffen werden. Dass dies möglich ist rührt von der bereits erwähnten Tatsache her, dass der Name eines eindimensionalen Feldes gleichzusetzen ist mit der Anfangsadresse des Feldes. Damit kann das vorherige Beispiel auch wie angegeben umgeschrieben werden.
|
... // Funktionsdefinition, das Feld kann hier in // der Funktion nicht verändert werden void DoSomething (const short* const ptr) { // Zugriff auf 1. Element short var = ptr[0]; // 3. Element verändern ptr[2] = 10; } |
|
void DoSomething (short array[4]); |
Ein klein wenig komplizierter sieht die Sache bei der Übergabe von mehrdimensionalen Feldern aus. Hier müssen Sie dem Compiler etwas unter die Arme greifen, damit er die Feldelemente im Speicher auch findet. Bei der Deklaration und der Definition der Funktion müssen Sie die Feldgröße des übergebenen Feldes mit angegeben, denn nur so kann der Compiler innerhalb der Funktion die Position der einzelnen Elemente korrekt berechnen. Lediglich die Angabe der 'höchsten Dimension' ist optional (siehe nachfolgendes Beispiel). Die Übergabe des Feldes beim Aufruf der Funktion bleibt gegenüber eindimensionalen Feldern unverändert, d.h. in der Parameterklammer der Funktion steht auch hier lediglich der Name des zu übergebenden Feldes.
|
// Feld definieren const int ROWS=4; const int COLUMNS=3; short array[ROWS][COLUMNS]; // Funktionsdeklaration void PrintVal(short arr[][COLUMNS]); // main() Funktion int main() { // Funktion aufrufen PrintVal(array); ... } // Funktionsdefinition void PrintVal(short arr[][COLUMNS]) { // Zugriff auf Feldelement short var = arr[0][2]; } |
|
void PrintVal(short arr[ROWS][COLUMNS]); |
Außer dass Funktionen Werte als Parameter erhalten können, können sie auch einen (und nur einen) Wert zurückliefern. Dieser Wert wird auch als Returnwert bezeichnet. Dazu muss der Compiler aber wissen, welchen Datentyp der Returnwert besitzt (für eine eventuelle Typanpassung). Dieser Datentyp wird vor dem Funktionsnamen angegeben, und zwar sowohl bei der Deklaration wie auch bei der Definition der Funktion. Die Rückgabe des eigentlichen Wertes erfolgt dann mit einer return-Anweisung innerhalb der Funktion. Nach dem Schlüsselwort return folgt der zurückzugebende Wert, der natürlich mit dem Datentyp vor dem Funktionsnamen übereinstimmen muss. Innerhalb einer Funktion können, wenig notwendig, durchaus mehrere return-Anweisungen stehen.
|
// Funktionsdeklaration float Deg2Rad (float deg); // main() Funktion int main () { ... float rad = Deg2Rad(90.0f); ... } // Funktionsdefinition // Umrechnung von Grad in Bogenmass // Liefert umgerechneten Wert float Deg2Rad (float deg) { const float PI = 3.1416f; float result; result = deg/360.0f * 2.0f * PI; return result; } |
Die Funktion Deg2Rad(...) oben erhält als Parameter eine Gradzahl als float-Wert übergeben und rechnet diese dann ins Bogenmaß um (3600 = 2*Pi).
|
float Deg2Rad (const float& deg); Wichtig ist hier die Angabe von const, da Deg2Rad(...) ansonsten den übergebenen Wert 'dauerhaft' verändern könnte und Sie auch keine Konstanten an die Funktion übergeben können. |
Wollen Sie mehr als nur einen Wert aus der Funktion zurückliefern, so müssen Sie dies bis jetzt über entsprechende Referenzparameter tun.
Sehen wir uns jetzt noch einen 'Spezialfall' von Funktionen an. Da innerhalb einer Funktion (bis auf die eine erwähnte Ausnahme) alle Anweisungen erlaubt sind, können Funktionen selbstverständlich wiederum Funktionen aufrufen. Einen Sonderfall stellen hierbei solche Funktionen dar, die sich wieder selbst aufrufen. Solche Funktionen werden auch als rekursive Funktionen bezeichnet. Diese Funktionen benötigen aber immer ein Abbruchkriterium, in etwa der folgenden Form um eine Endlos-Schleife zu vermeiden:
| if (Bedingung) return Wert; |
Die Angabe von Wert bei der return-Anweisung entfällt bei Funktionen mit dem Returntyp void natürlich.
|
#include <iostream> using namespace std; // Funktionsdeklaration void PrintLine(const short); // main() Funktion int main () { // Funktionsaufruf PrintLine(4); } // Funktion PrintLine(), ruft sich selbst auf! void PrintLine(const short count) { // Sternchen und Zeilenvorschub ausgeben for (int index=0; index < count; index++) cout << " *"; cout << endl; // Falls mehr als 1 Sternchen ausgegeben wurde if (count != 1) // Funktion erneut aufrufen, jetzt // jedoch mit einem Sternchen weniger PrintLine(count-1); // Nach dem letzten Sternchen fertig! return; } |
|
* * * * |
Oben sehen Sie ein Beispiel für eine solche rekursive Funktion. Die Funktion PrintLine(...) erhält aus main() zunächst die Anzahl der in einer Reihe auszugebenden Sternchen. Nachdem die entsprechende Anzahl von Sternchen in einer for-Schleife ausgegeben wurde, wird in der darauf folgenden if-Abfrage abgeprüft, ob mehr als 1 Sternchen ausgegeben wurde. Ist dies der Fall, so ruft sich die Funktion erneut selbst auf, jetzt doch mit einem Sternchen weniger. Dieses Spiel wiederholt sich so lange, bis nur noch ein Sternchen ausgegeben wird. In diesem Fall ist die Bedingung der if-Abfrage nicht mehr erfüllt und die Funktion wird ganz normal über die return-Anweisung beendet. Die return-Anweisung könnte hier auch entfallen, da sie sowieso die letzte Anweisung der Funktion ist. Somit ergibt sich folgende Aufrufsequenz der Funktion:
| PrintLine(4); PrintLine(3); PrintLine(2); PrintLine(1); |
Beachten Sie, dass die Funktion PrintLine(...) einen const Parameter erhält. Trotzdem kann die Funktion selbstverständlich mit diesem Parameter Operationen durchführen, wie etwa beim rekursiven Aufruf der Funktion:
PrintLine(nCount-1); |
Das Einzige was die Funktion nicht tun darf ist, zu versuchen den Parameter zu verändern!
Damit sind wir wieder fast am Ende dieser Lektion angelangt. Bevor wir zum Beispiel und der anschließenden Übung kommen, noch einige weitere 'Eigenschaften' von Funktionen:
|
|
|
| double sin(double value); |
Sie berechnet aus dem übergebenen double-Wert value den Sinus und liefert diesen ebenfalls als double-Wert zurück. Die für den Aufruf der Funktion sin(...) notwendige Funktionsdeklaration ist in der Headerdatei cmath enthalten.
|
|
In der main() Funktion werden dann für drei verschiedene Wert die Sinuswerte berechnet, und zwar einmal mit der Funktion BerechneSin(...) und einmal mit der Bibliotheksfunktion sin(...). Da die berechneten Werte nicht weiter ausgewertet werden, werden die Funktionsaufruf direkt innerhalb der cout-Anweisungen durchgeführt.
|
sin(0.5) : Berechnet = 0.479426, Bibl-Funktion =
0.479426 |
|
// Beispiel zu Funktionen // Dateien einbinden #include <iostream> #include <cmath> using std::cout; using std::endl; // Funktionsdeklarationen double BerechneSin(double wert); // main() Funktion int main() { // Konstante PI definieren const double PI = 3.1416; // Berechne Sinus(0.5) mittels Reihe cout << "sin(0.5) : Berechnet = " << BerechneSin(0.5); // Und nun Sinus(0.5) mittels Bibliotheksfunktion cout << ", Bibl-Funktion = " << sin(0.5) << endl; // Das ganz nun für PI (3.1416) cout << "sin(PI) : Berechnet = " << BerechneSin(PI); cout << ", Bibl-Funktion = " << sin(PI) << endl; // und zum Schluss für PI/2 cout << "sin(PI/2): Berechnet = " << BerechneSin(PI/2.0); cout << ", Bibl-Funktion = " << sin(PI/2.0) << endl; } // Funktion: BerechneSin // Berechnet Sinus mittels Reihe // sin(x) = x - x^3/3! + x^5/5! - x^7/7! // Parameter wert: Wert aus dem Sinus berechnet werden soll double BerechneSin(double wert) { // Ergebnis mit dem Wert vorinitialisieren // 1. Term in der Reihe double erg = wert; // Divident (Nenner) mit wert^3 vorbelegen double divident = wert*wert*wert; // Divisor (Teiler) 3! vorbelegen double divisor = 1.0*2.0*3.0; // 3! // Zweiten Term vom Ergebnis subtrahieren erg -= divident/divisor; // Dritten Term zum Ergebnis addieren divident *= wert*wert; // wert^5 divisor *= 20.0; // 3! * 4 * 5 = 5! erg += divident/divisor; // Vierten Term wieder vom Ergebnis subtrahieren divident *= wert*wert; // wert^7 divisor *= 42.0; // 5! * 6 * 7 = 7! erg -= divident/divisor; // Ergebnis zurückliefern return erg; } |
Nun zur Abwechslung mal etwas aus der Physik (aber nicht gleich erschrecken).
Für einen Wurf (schräger Wurf) sind die Wurfweite und die maximale Wurfhöhe zu berechnen. Für die Wurfweite gilt folgende Formel:
Weite = (Abwurfgeschwindigkeit2 * sin(2*Abwurfwinkel)) / G
Und für die Wurfhöhe gilt:
Höhe = Abwurfgeschwindigkeit2 * sin(Abwurfwinkel)2 / (2*G)
Die Funktionsdeklaration der Funktion sin(...) ist oben beim Beispiel angegeben. Beachten Sie, dass sin(...) den Winkel in Rad und nicht in Grad erwartet! Die Abwurfgeschwindigkeit ist in beiden Formeln in m/s und G ist die Gravitationskonstante 9.81m/s2. Beide Formeln liefern das Ergebnis in m zurück.
Schreiben Sie jetzt jeweils eine Funktion zur Berechnung der Wurfweite und eine Funktion zur Berechnung der Wurfhöhe bei vorgegebener Abwurfgeschwindigkeit und Abwurfwinkel.
In der main() Funktion ist nun zuerst bei einem fest vorgegebenem Abwurfwinkel von 45 Grad(!) die jeweilige Wurfweite und -höhe zu berechnen. Die Abwurfgeschwindigkeit ist im Bereich 10....20 m/s in 2er-Schritten zu durchlaufen. Die berechneten Daten sind dann in Tabellenform (wie unten angegeben) auszugeben.
Anschließend ist das ganze Spiel mit einer festen Abwurfgeschwindigkeit von 28m/s (entspricht ca. 100km/h) zu wiederholen, wobei jetzt der Abwurfwinkel im Bereich 30...60 Grad in 5er-Schritten zu variieren ist.
Beachten Sie bitte, dass die Winkelangaben zunächst in Grad erfolgen und die Funktion sin(...) als Einheit Rad erwartet. Sie wissen doch noch aus Ihrem Mathematik-Unterricht, dass 2*PI gleich 360 Grad sind? Damit lautet die Formel für die Umrechnung von Grad in Rad:
Rad = Grad / 360.0 * 2.0 * 3.1416
So, und nun viel Glück beim Lösen der Aufgabe. Bei richtiger Lösung sollten Sie die unten dargestellten Daten erhalten.
|
Schräger Wurf mit konst. Winkel von 45 Grad Schräger Wurf mit konst. Abwurfgeschw.
28 m/s |