Functies

In C++ worden functies binnen classes meestal member functions genoemd. In Java wordt meestal gesproken over methods.

Waarom functies (1): ze verhogen het overzicht

Wanneer je een programma schrijft in een programmeertaal als C++, PHP of Java dan kun je alle code onder elkaar in een grote file zetten en al die code vervolgens van boven tot beneden door de computer laten uitvoeren.

Voor een klein programmaatje gaat dat vaak nog wel, maar als je meer dan een pagina code schrijft wordt het al gauw onoverzichtelijk. In dat geval loont het de moeite om gedeelten uit je code die functioneel gezien bij elkaar horen in aparte brokken te stoppen. Zo'n brok geef je een naam die aangeeft wat het doet en je hebt daarmee de eenvoudigste vorm van een functie gemaakt.

Die functie kun je dan vanuit het hoofdprogramma aanroepen, waardoor je in het hoofdprogramma duidelijk de verschillende stappen in je programma kunt onderscheiden.

Waarom functies (2): hergebruik van stukken code

Als je een programma schrijft waarin meerdere keren ongeveer hetzelfde gebeurt dan is het vaak lonend om uit te zoeken of die herhaling altijd exact hetzelfde is en zo niet, op welke punten het verschilt.
In het eerste geval kun je de code die telkens herhaald wordt in een functie opnemen zodat je het maar 1 keer hoeft te maken en je kunt die functie dan zo vaak aanroepen als je wilt.
In het tweede geval ga je op zoek naar de grootste gemene deler van het stuk code dat met kleine verschillen telkens herhaald wordt. Goede kans dat je die code in een functie kunt stoppen en bij het aanroepen aangeven wat er anders is dan de vorige keer dat je de functie aanriep. Hoe je dat dan aangeeft bespreken we later.

Waarom functies (3): gebruik door anderen

Als programmeur kun je je lekker uitleven en mooie software schrijven waar je heel erg trots op bent en die jij helemaal begrijpt. Wanneer iemand anders met jouw software moet werken of jij wilt een stuk software van iemand anders gebruiken, dan zul je zien dat het niet meevalt om software uit te wisselen. Programmeurs zijn in het algemeen vrij eigenwijs en hebben vaak een eigen stijl van programmeren. De een gebruikt HoofdLetters Voor AlleVariabelen, de ander gebruikt_liever_underscores. De plaatsing van haakjes en het gebruik van tabs en spaties is ook vaak een kwestie van voorkeur en zo zijn er allerlei redenen om de code van iemand anders als slecht leesbaar te bestempelen.
Verder heb je niet altijd de tijd om je te verdiepen in alle details van andermans software maar wil je het gewoon gebruiken zonder te weten hoe het werkt.
Hier komen functies weer om de hoek kijken. Maak je nette functies die een bepaald gedeelte van jouw programma uitvoeren dan hoef je iemand anders alleen maar te vertellen hoe hij of zij die functie moet aanroepen om jouw funktionaliteit in zijn of haar programma te gebruiken. Je geeft als het ware een stuk gereedschap en vertelt erbij hoe je het moet gebruiken maar niet hoe het werkt.

Het maken en aanroepen van een functie

In het algemeen ziet een functie er zo uit:

returntype foo(argumentenlijst)
{
    //

    // hier komt jouw code
    //
    return result;
}

Deze functie heeft de naam foo. De argumentenlijst bevat nul of meer variabelen met hun type. Je kunt de argumentenlijst leeg laten. De haakjes () moet je wel altijd gebruiken om aan te geven dat het om een functie gaat.

Het returntype is het type van het resultaat van de functie, als de functie een resultaat oplevert. Dit kan ook void zijn als de functie geen resultaat teruggeeft.

Voorbeeld 1: een eenvoudige functie

De simpelste vorm van een functie is zonder argumenten en zonder returnwaarde:
void zegEensHallo()
{
   cout <<  "Hallo" << endl;
}
Je kunt deze functie bijvoorbeeld zo gebruiken:
cout <<"Demonstratie van een simpele functie");
cout << "We roepen de functie een paar keer aan";
zegEensHallo();
zegEensHallo();
zegEensHallo();

Let erop dat de naam die je gebruikt om de functie aan te roepen exact hetzelfde is als de naam van de functie.

Voorbeeld 2: teken een rechthoek

In openFrameworks kennen we de functie ofRect() om een rechthoek te tekenen. Als wel die nou eens zelf willen maken dan kan dat als volgt:
void rechthoek()
{
  ofLine(1,1,50,1);
  ofLine(50,1,50,50);
  ofLine(50,50,1,50);
  ofLine(1,50,1,1);
}


void draw()
{
  rechthoek();
} 

Dit was nogal beperkt want het kan alleen maar een rechthoek op een bepaalde positie en met een bepaalde grootte tekenen. En het tekent eigenlijk alleen vierkanten.

Het volgende voorbeeld maakt het al iets flexibeler:

void rechthoek(int x, int y)
{
  ofLine(x,y,x+50,y);
  ofLine(x+50,y,x+50,y+50);
  ofLine(x+50,y+50,x,y+50);
  ofLine(x,y+50,x,y);
}


void draw()
{
  rechthoek(0,0);
  rechthoek(10,10);
  rechthoek(30,30);
}
Maak zelf eens een functie die niet alleen vierkanten tekent maar ook niet-vierkante rechthoeken.

Voorbeeld 3: Uitrekenen van een afstand

Om de afstand tussen twee punten uit te rekenen moeten we de wortel nemen uit de som van de kwadraten van de verschillen tussen de coordinaten. Dat is een hele mond vol. Een voorbeeldje maakt het hopelijk wat duidelijker:

float distance(int x1,int y1,int x2,int y2)
{
  return sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) );
}


if(distance(xPos[0],yPos[0],xPos[1],yPos[1]) < 2*ballRadius)
{
  XSpeed[0] = -XSpeed[0];
  YSpeed[0] = -YSpeed[0];
  XSpeed[1] = -XSpeed[1];
  YSpeed[1] = -YSpeed[1];
}

Voorbeeld 4: Uitrekenen van een oppervlakte

Uitrekenen van de oppervlakte van een rechthoek. Zoals je weet kun je de oppervlakte berekenen door de breedte en hoogte met elkaar te vermenigvuldigen. Dat gaan we nu eens met een functie doen. Eerst zie je de functie calcSurface(), daarna een paar verschillende manieren om de functie te gebruiken.

De functie calcSurface() heeft twee argumenten en een return-waarde. Met een argument wordt hier bedoeld een variabele die de functie in gaat. Er wordt ook wel gezegd dat je die variabelen "aan de functie meegeeft". In deze functie zijn dat de breedte en hoogte (Eng: width en height).

De return-waarde is een variabele die uit de functie komt. Er wordt ook wel gezegd dat de functie die waarde teruggeeft.


  // Hier wordt de functie calcSurface() gemaakt
  int calcSurface(int width, int height)
  {
    int surface = width * height;
    return surface;
  }

  
  /*
   * Hieronder wordt de functie calcSurface() op
   * een aantal verschillende manieren gebruikt
   */

  // Aanroep met getallen
  cout << "Breedte: 4";
  cout << "Hoogte: 5";

  int surface = calcSurface(4,5);
  cout << "Oppervlakte: " + surface);


  // Aanroep met variabelen
  int width=3;
  int height=5;

  cout << "Breedte: " + width;
  cout << "Hoogte: " + height;
  cout << "Oppervlakte: " + calcSurface(width,height);


  // Aanroep vanuit een loopje
  for(width=1; width <= 4; width++) {
    for(height=1; height <= 4; height++) {
      cout << "Breedte: " + width;
      cout << "Hoogte: " + height;
      cout << "Oppervlakte: " + calcSurface(width,height);
    } // for height
  } // for width

Verder werken met het resultaat

In plaats van het resultaat direct met print() of println() te laten zien kun je het ook gebruiken voor bijvoorbeelde verdere berekeningen.

Stel dat je de oppervlakten van twee rechthoeken bij elkaar wilt optellen en alleen het totaal laten zien, dan doe je dat zo:


  // Hier wordt de functie calcSurface() gemaakt
  int calcSurface(int width, int height)
  {
    int surface = width * height;
    return surface;
  }

  int totalSurface = calcSurface(4,5) + calcSurface(7,7);
  cout << "Totale oppervlakte: " + totalSurface;

Inline functies

Voor optimalisatie van de snelheid kun je inline functies gebruiken. Dit heb je in de meeste gevallen niet nodig, maar mocht je merken dat je programma te langzaam draait voor bijvoorbeeld real time werk op een embedded systeem dan kan het van pas komen.

Een functie is een inline functie als je het keyword inline ervoor zet, of wanneer je de body van een member functie al in de class uitschrijft, dus niet met class::function() {....}

Hiermee verzoek je de compiler om een functie bij elke aanroep uit te rollen: ter plaatse van de aanroep een kopie van de functie-body te maken. Dat maakt je executable groter maar mogelijk ook sneller omdat je geen return-adres en variabelen op de stack moet zetten. De compiler kan besluiten om dat niet te honoreren als bv. de functie zo groot is of vanuit zo veel plaatsen wordt aangeroepen dat de executable onhandig groot wordt, of andere redenen.

Maar nogmaals: meestal heb je dit niet nodig, tenzij je echt alle processor cycles wilt benutten op een embedded systeem.