Funktionen

Die klassische Art ( modulares Programmieren) ein großes, komplexes Programm überschaubar zu gestalten ist die Aufteilung der Funktionalität in kleinere, eigenständige Untereinheiten für bestimmte Aufgaben, d.h. Einteilung in Funktionen.

Einfaches Beispiel – Quadrat- und Kubikfunktion:
1 Argument wird an Funktionen quadrat(..), kubik(..) übergeben, Ergebnis ( double Wert) wird zurückgegeben.


double quadrat( double x ) // Berechne Quadrat
{              
  double r = x*x;
  return r;
}
double kubik( double x ) // Berechne Kubik
{              
  double r = x * quadrat(x); // verwende quadrat funktion
  return r;
}
int main() 
{
  double a1 = 2.5;
  double a2 = quadrat(a1); // verwende Funktion
  double a3 = kubik(a1);
  // Argument kann auch komplexer Ausdruck sein ...
  double b = quadrat( kubik(a3) * sin(0.8) + 0.99 );
}


Weiteres Beispiel – Gravitationskraft:
3 Argumente werden an Funktion übergeben, Ergebnis ( double Wert) wird zurückgegeben.



double GravForce( double mass1, double mass2, double distance) //  Kopf der Funktion
{
  const double GRAV_CONST = 6.673 e-11; // Gravitationskonstante  m^3 / ( kg s^2 )
  double f = GRAV_CONST * mass1 * mass2 / (distance * distance);
  return(f);
}

int main() 
{
  double mSonne = 1.9889e30, mErde = 5.974e24, dErdeSonne = 1.49597e11, rErde = 6.378e6;
  // Aufruf von Funktion GravForce 
  double gErdeSonne = GravForce( mSonne, mErde, dErdeSonne ); // Kraft Sonne--Erde
  double gPerson = GravForce( mErde, 80., rErde ); // Gewichtskraft 80 kg auf Erde
  ...
}




Syntax von Funktionsaufrufen:
Type name( Type arg1, ...) { statements }

Image funccall

Programm–Design


Funktionen brauchen keinen Typ und auch keine Argumente ...


void newline( ) // Funktion gibt nur neue Zeile aus
{               // keine Parameter-uebergabe
  cout << endl; // keine Rueckgabe
}
int main() 
{
  newline();
}


Argumentübergabe bei Funktionsaufruf:



Hinweis: In fast allen Fällen würde ich letzteres Verfahren empfehlen, d.h. Deklaration der Variablen in Funktion als Referenz: \bgroup\color{green}\ensuremath{\Rightarrow}\egroup keine komplexe Pointer–Syntax, gute Performance. Gab's aber noch nicht in C, d.h. alte Programme nur call-by-value oder call-by-address.



#include <typeinfo>
#include <iostream>
#include <cmath>
using namespace std;
void tfunc1( double x);
void tfunc2( double *x);
void tfunc3( double & x);
int main()
{
  double a = 3.14;
  cout << "main start " << a << endl;
  tfunc1( a );
  cout << "main tfunc1 " << a << endl;
  tfunc2( &a );
  cout << "main tfunc2 " << a << endl;
  tfunc3( a );
  cout << "main tfunc3 " << a << endl;
}
        
void tfunc1( double x )
{ // x ist Kopie von a
  x *=2;
  cout << "tfunc1 " << x << endl;
}
void tfunc2( double * x )
{ // x ist pointer auf a
  *x *= 2;
  cout << "tfunc2 " << *x << endl;
}
void tfunc3( double & x )
{ // x ist identisch a
  x *= 2;
  cout << "tfunc3 " << x << endl;
}


Ergänzung: Call-by-reference

Bei call-by-reference muss man eine weitere Komplikation beachten. Der Aufruf wie oben geht nur wenn echte Variablen (=l-value) als Argument übergeben werden, aber nicht bei temporären Variablen oder Zahlen (=r-value), dafür Übergabe als const reference nötig:



#include <iostream>
using namespace std;
void tfuncr1( double & x);
void tfuncr2( const double & x);
int main()
{
  double a = 3.14;
  tfuncr1( a ); // call ok
  tfuncr1( 2.5 ); // error, cannot take number arg with ref
  tfuncr1( 6*a ); // error, cannot take temp value arg with ref
  
  tfuncr2( a ); // call ok
  tfuncr2( 2.5 ); // ok, const guarantees value is not changed
  tfuncr2( 6*a ); // ok, dto
}
        
void tfuncr1( double & x )
{ 
  cout << "tfuncr1 " << x << endl;
}
void tfuncr2( const double & x )
{ 
  cout << "tfuncr1 " << x << endl;
}



“Strong Typing” und Funktionendeklaration

C++ ist eine sog. strong–typing Sprache, d.h.

Ansonsten bekommt man Fehlermeldungen vom Compiler.

Um Funktionen “bekanntzumachen” gibt es drei Möglichkeiten:


Weiteres zu Funktionen




// demo function overloading
// Deklaration Funktions Prototypen
void tfunc1( double x = 999); // deklariert tfunc1() und tfunc1( double )
void tfunc2( int x = -1); // deklariert tfunc2() und tfunc2( int )
void tfunc2( double x);
// void tfunc2( float x = 0.1); // illegal, tfunc2() schon deklariert
int main()
{
  int a = 5;
  int *b;
  char *c = "ABC";
  b = &a;
  tfunc1( 1. );
  tfunc1( 1 );  // int wird umgewandelt
  tfunc1( a );
  tfunc1( );    // default wird genommen
  tfunc1( b );  // illegal, mit pointern geht die Umwandlung nicht
  tfunc1( *c ); // Seltsam, aber char sind einfach Zahlen == tfunc(65)
  tfunc2( 1 );  // int version wird benutzt
  tfunc2( 1. ); // double version ""
  tfunc2( );    // default darf nur einmal deklariert sein,
                // in dem Fall also die int version.
}

// Definition/Implementierung der Funktionen
void tfunc1( double x )
{
  cout << "tfunc1 " << x << endl;
}
void tfunc2( int x )
{
  cout << "tfunc2 int " << x << endl;
}
void tfunc2( double x )
{
  cout << "tfunc2 double " << x << endl;
}




Rekursive Funktionsaufrufe (Funktion ruft sich selbst) sind möglich:


double power( double x, int n )
{
  if ( n > 1 ) {
    return( x * power(x, n-1) ); // rekursiver Aufruf
  }
  else {
    return( x );
  }
}



      double y = power( x, 4 );

Image recursiveStack


Mehrere Werte zurückgeben

Manchmal möchte man nicht nur einzelnen Wert (oder vector) zurückgeben, sondern Kombination verschiedener Werte. Mit std::tuple lässt sich das in modernem C++ relativ leicht erreichen.
Praktisches Beispiel: Nullstellen der quadratischen Gleichung – je nach Parametern gibt es 0, 1, oder 2 reele Nullstellen


#include <tuple>
#include <cmath>
#include <iostream>
using namespace std;

std::tuple<int,double,double> qg_root(double A, double B, double C)
{
  // Returns (0, 1, or 2) real roots of
  // the quadratic equation A*x*x + B*x + C = 0.

    
  std::tuple<int,double,double> rval(0,0,0);

  if ( A != 0 ) {
      double disc = B*B - 4*A*C;
      if (disc == 0) {
          get<0>(rval) = 1;
          get<1>(rval) = -B / (2*A);
      }
      else if ( disc > 0 ) {
          get<0>(rval) = 2;
          get<1>(rval) =  (-B + sqrt(disc)) / (2*A);
          get<2>(rval) =  (-B - sqrt(disc)) / (2*A);
      }
    }
   return rval;
}

int main()  // function definition 
{
  auto r = qg_root( 1, 2, -1 );
  cout << get<0>(r) << ", " << get<1>(r) << ", " <<get<2>(r) << endl;

}


lambda Funktionen (C++11)

sind "anonyme" Funktionen, sehr praktisch als Argument bei Aufruf von anderen Funktionen, z.B. STL Algorithmen die Funktion-Objekt bzw. Functor als Argument erwarten:


// lambda function example
vector <double> vec2 = { 1.2, 3.7, -2.6, 0.9, -7.1 };
// we want sort vector elements according to abs of value
// -- lambda funktion as 3. Argument
sort(vec2.begin(), vec2.end(), [](double x, double y) { return abs(x)<abs(y);} );

// equivalent with explicit function

bool chkabs(double x, double y) {
  return abs(x)<abs(y);
}
sort(vec2.begin(), vec2.end(), chkabs );