Pagina1 van 1

Arduino Programmeren voor Beginners – Deel 6: Functies

Arduino Programmeren voor Beginners – Deel 6: Functies
   23

In deel 6 van Arduino Programmeren voor Beginners, gaan we kijken naar functies (Engels: functions). We hebben al een paar functies gebruikt en gezien, maar in dit deel gaan we kijken naar functies die we zelf kunnen maken.

Functies worden vaak gebruikt om een reeks stappen, die herhaald of hergebruikt kunnen worden, samen te voegen naar een nieuwe instructie. Dit kan bijdragen aan efficiënter programmeren, maar ook aan een beter leesbaar programma.

Deze reeks, richt zich hoofdzakelijk op het Arduino Programmeren voor beginners, en dan specifiek voor m’n neefjes Bram en Max – gebrek aan kennis voor wat betreft de Engelse taal en wiskundige achtergrond hoeft waarschijnlijk geen probleem te zijn. Het gebruik van extra elektronica componenten blijft beperkt tot een minimum en bewaren we voor een volgende reeks.




Overzicht van dit Hoofdstuk

  Een volledig overzicht van de cursus vindt je hier: Cursus Overzicht.

Wat zijn Functies en Waarom hebben we ze nodig?

Als eerste hebben we in theorie geen functies nodig voor een programma, echter onze code zou dan super lang worden om maar te zwijgen over de beroerde leesbaarheid van onze code. Daarnaast besparen functies ons een hoop werk – zeker als we de functie kunnen hergebruiken.

We hebben al een aantal functies gezien en we hebben zelfs al twee verplichte functies gedefinieerd in onze Arduino programma’s: “setup()” en “loop()”.

Maar wat zijn dan die functies (functions)? En waarom willen we ze gebruiken?

Een functie (ook wel subroutine genoemd) kunnen we zien als een groep instructies, die samen een specifieke taak uitvoeren.
We maken deze functies om ons programma beter georganiseerd te houden, beter leesbaar te maken, en vaak omdat we deze “taak” elders willen hergebruiken. En dat “elders” kan zelfs in een ander programma zijn.

In bepaalde talen noemt men een functie ook wel een “subroutine”, en dat beschrijft precies wat het is: een soort klein programma in ons programma. En net als bij een programma, kan een functie ook functies gedefinieerd hebben in de functie – een functie is echter ook onderhevig aan de eerder genoemde “scope”. Dus een functie die in een functie gedefinieerd wordt is dus alleen maar bekend in de functie waar het gedefinieerd is – dus net zoals we zagen bij variabelen!

Ik kan me voorstellen dat dit net even te ver ging om te bevatten, het is iets waar we later vanzelf mee te maken gaan krijgen, houd het dus in gedachten.

We kunnen twee soorten functies maken: een functie die een waarde terug geeft, en een functie die geen waarde terug geeft.
Die laatste is wat men, in sommige programmeertalen, een “procedure” noemt. De taal C echter, noemt beiden gewoon een functie, of in het Engels: een “function”.

Een functie is een groep instructies met een bepaalde taak in gedachten.

Wanneer definiëren we een functie?

  • Wanneer code meerder keren herhaald wordt in een programma,
  • Als onze code beter leesbaar wordt door het gebruik van functies,
  • Als we onze code beter kunnen beheren met functies
  • Als we onze code willen hergebruiken, b.v. in andere programma’s.

Laten we eens een voorbeeld doorlopen zodat we het concept beter begrijpen.

Stel we hebben een hond, en we moeten die vier keer per dag uitlaten: Om 8:00, 12:00, 17:00, en om 21:00.

De taak “hond uitlaten” kan omschreven worden met de volgende instructies:
– Jas aandoen
– Hondenriem bij de hond aandoen
– Naar buiten gaan
– 15 minuten rond lopen
– Terug naar binnen gaan
– Hondenriem afdoen
– Jas uitdoen.

Dus voor een hele dag zou onze “code” er zo uitzien:

Als het 8:00 is doe dan:
– Jas aandoen
– Hondenriem bij de hond aandoen
– Naar buiten gaan
– 15 minuten rond lopen
– Terug naar binnen gaan
– Hondenriem afdoen
– Jas uitdoen.

Als het 12:00 is doe dan:
– Jas aandoen
– Hondenriem bij de hond aandoen
– Naar buiten gaan
– 15 minuten rond lopen
– Terug naar binnen gaan
– Hondenriem afdoen
– Jas uitdoen.

Als het 17:00 is doe dan:
– Jas aandoen
– Hondenriem bij de hond aandoen
– Naar buiten gaan
– 15 minuten rond lopen
– Terug naar binnen gaan
– Hondenriem afdoen
– Jas uitdoen.

Als het 21:00 is doe dan:
– Jas aandoen
– Hondenriem bij de hond aandoen
– Naar buiten gaan
– 15 minuten rond lopen
– Terug naar binnen gaan
– Hondenriem afdoen
– Jas uitdoen.

Zo wordt ons programma best lang he? En we zien aardig wat herhaling van dezelfde instructies …

We zouden echter de functie “HondUitlaten()” kunnen definiëren, bijvoorbeeld:

HondUitlaten():
– Jas aandoen
– Hondenriem bij de hond aandoen
– Naar buiten gaan
– 15 minuten rond lopen
– Terug naar binnen gaan
– Hondenriem afdoen
– Jas uitdoen.

Nu wordt ons programma een stuk overzichtelijker:

Als het 8:00 is dan
  HondUitlaten()

Als het 12:00 is dan
  HondUitlaten()

Als het 17:00 is dan
  HondUitlaten()

Als het 21:00 is dan
  HondUitlaten()

Zoals je ziet is onze code nu een stuk netter en beter te lezen. We hebben nu de programmeertaal uitgebreid, voor ons programma, met de functie “HondUitlaten”.

In de meeste scenario’s, vooral als een functie meerdere keren gebruikt wordt in een programma, zal dit zelfs leiden tot een kleiner programma (na compileren) voor de Arduino of Computer. Het is efficiënter.

Maar functies hebben nog een groot voordeel. Stel we moeten de instructies voor het hond uitlaten aanpassen, bijvoorbeeld door “deur van het slot halen” ene “deur weer op slot doen” toe te voegen. Bij een functie hoeven we dat dus maar op 1 plaats te doen: in de functie definitie. In tegenstelling tot het eerste voorbeeld waar we dat op 4 plaatsen moeten veranderen. Onze code wordt dus beter te beheren.

Stel we hebben een meer generieke functie gemaakt, dan zouden we deze in een zogenaamde bibliotheek (libraries) kunnen zetten zodat we deze functie in andere programma’s ook weer kunnen gebruiken – en we dus tijd besparen wanneer we weer een ander programma gaan maken. Dit noemt met “re-use” of “hergebruik” van code. We gaan nog even niet in op het gebruik van libraries, dat is voor een stadium waarin we gevorderder zijn.

Vergeet niet:

 

  • Een functie is net als een klein programma … binnen een programma
  • Een functie kan zelf ook functies bevatten …
  • Een functie heeft een scope” – zoals we zagen bij variabelen.

Zelf Functie(s) maken

In de programmeertaal C, zoals we die voor  Arduino programmeren gebruiken, is het gelukkig redelijk eenvoudig. De basisstructuur ziet er zo uit:


datatype FunctieNaam ( FunctieParameters ) {
  // functie code
}

Ik hoop dat je nog iets van DataTypes hebt onthouden zoals we dat in Deel 3 bespraken. Mocht dit niet zo zijn, dan kun je altijd nog even terug kijken ik zal een paar details hier weer vermelden.

Het datatype geeft aan wat voor antwoord we terug mogen verwachten van de functie. Echter … niet alle datatypen zijn bruikbaar hiervoor, en dan moeten we vooral denken aan arrays!

De meest gebruikte datatypes voor functies zijn boolean, int, char, float en void (andere datatypen zoals double, long en de “unsigned” types werken ook).

Uiteraard moet onze functie ook een naam krijgen, de FunctieNaam, waarvoor welke we dezelfde regels moeten volgens als bij namen voor variabelen en constanten:

Functie naam:

  • Moeten beginnen met een letter (a, b, …, z, A, B, … , Z) of een underscore ( _ )
  • Mag letters bevatten 
  • Mag underscore(s) bevatten
  • Mag nummers bevatten
  • MAG GEEN speciale tekens, symbolen of spaties bevatten
  • Is hoofdletter gevoelig!

Een functie kan nul of meer Functie Parameters accepteren. Het aantal parameters is echter vast en voorgedefinieerd voor onze functie!

In een aantal C programmeertaal dialecten, moeten we een functie declareren (aankondigen) voor we ze kunnen gebruiken. In C voor Arduino Programmeren is dit echter niet per se noodzakelijk en daarom gaan we daar hier niet op in.

We hebben overigens ook al een aantal functies gebruikt en aangeroepen, ook al waren we ons daar misschien niet van bewust, waarbij we parameters naar de functie sturen door ze tussen ronde haakje te zetten. Een voorbeeld: Serial.print("Hallo");

Hier roepen we de functie “print()” aan van object “Serial” en geven het de parameter “Hallo” (een string). Meer over objecten later.

Het codeblok, tussen accolades en na de functie naam en parameters, bevat de instructies voor onze functie – net zoals we dat ook bij “if” en de verschillende loops zagen (“for”, “while” en “do … while …”).

Een simpele (en niet zo zinvolle) functie definitie zou kunnen zijn:


1
2
3
void ZegHallo() {
  Serial.println("Hallo");
}

Deze functie, “ZegHallo()”, gebruikt geen parameters, omdat er niks tussen de ronde haakjes () staat.
Het stuurt ook geen antwoord terug omdat we het datatype “void” gebruiken – wat zoveel betekent als “niks, nada, noppes”.

De “body” van de functie (het codeblok), bevat de instructies van de functie, en in dit geval voert het alleen maar de tekst “Hallo” uit.

Een voorbeeld hoe we dit kunnen gebruiken:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void setup() {
  // set the speed for the serial monitor:
  Serial.begin(9600);
 
  for(int A=1; A<=5; A++) {
    ZegHallo();
  }
}

void loop() {
  // leave empty for now
}

void ZegHallo() {
  Serial.println("Hallo");
}

Dit zou er een beetje vertrouwd uit moeten zien.

Aan het einde definiëren we de functie “ZegHallo()”.
In de “for”-loop roepen we de functie 5 keer aan, wat zoiets als dit oplevert

Hallo
Hallo
Hallo
Hallo
Hallo

Makkelijk toch?

Waarden doorgeven aan een Functie

Dat laatste voorbeeld was natuurlijk bijzonder simpel. Laten we eens kijken hoe we de functie kunnen gebruiken in ons lampen-voorbeeld door 5 lampen aan te zetten met behulp van een “for”-loop.

Hiervoor maken we de functie “ZetLampAan”, die we een lamp nummer gaan geven voor de lamp die aangezet moet worden.

We weten eigenlijk wel dat de lamp-nummers van het datatype “int” gaat worden (zie ook de definitie van “A” in de “for”-loop).

We weten ook dat de functie niets terug hoeft te geven.

Al dat gecombineerd:


void ZetLampAan(int LampNummer) {
  Serial.print("Zet de volgende lamp AAN: ");
  Serial.println(LampNummer);
}

De parameter “LampNummer” is dus gedefineerd als een “int”. Je ziet dat we de variabele “LampNummer” in onze functie definiëren. Het is belangrijk om te weten dat deze variable de waarde aan gaat nemen van de parameter die we aan de functie doorgeven als we de functie aanroepen. Deze waarde wordt gekopieerd – dus als we de waarde van LampNummer veranderen, dan wordt de originele parameter (wat ook een variabele kan zijn) daardoor niet veranderd!

Om nog eens terug te komen op “scope”: de variabele “LampNummer” is natuurlijk niet bekend buiten de functie “ZetLampAan”.

Als we in regel 6, de functie aanroepen, geven we het de waarde van de variabele “A” mee. Deze waarde wordt gekopieerd in de variabele “LampNummer”.

Alles even bij elkaar gezet:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void setup() {
  // set the speed for the serial monitor:
  Serial.begin(9600);
 
  for(int A=1; A<=5; A++) {
    ZetLampAan(A);
  }
}

void loop() {
  // leave empty for now
}

void ZetLampAan(int LampNummer) {
  Serial.print("Zet de volgende lamp AAN: ");
  Serial.println(LampNummer);
}

Zie je hoe de variabele “LampNummer” wordt gebruikt in de functie?

Dit is natuurlijk maar een eenvoudig voorbeeld. Wat gebeurt er nu als we meerdere waarden aan de functie door willen geven. Bijvoorbeeld door een boolean toe te voegen welke aangeeft of de lamp nu aan (true) of uit (false) gezet dient te worden?

Als we meerder parameters willen gebruiken, dan moeten we ze scheiden met komma’s ( , ).

Parameters in een functie worden gescheiden gehouden door een komma,
zowel bij functie-definitie als bij functie aanroep.

Zoals met alle waarden die we door willen geven bij de parameters, moeten we ook die extra parameters voorzien van een naam (LichtAan) en een data type (boolean) zodat de functie weet wat voor waarde het kan verwachten.

Uiteraard moeten we even een “if” statement in de functie hangen, zodat afhankelijk van de boolean waarde, de lamp AAN of UIT gezet gaat worden. Dit laat zien dat we functies zodanig kunnen schrijven dat ze voor meerdere situaties inzetbaar kunnen zijn.

Het resultaat:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void setup() {
  // set the speed for the serial monitor:
  Serial.begin(9600);
 
  for(int A=1; A<=5; A++) {
    ZetLampAan(A, true);
  }
 
  for(int A=1; A<=5; A++) {
    ZetLampAan(A, false);
  }
}

void loop() {
  // leave empty for now
}

void ZetLampAan(int LampNummer, boolean LampAan) {
  if(LampAan) {
    Serial.print("Zet de volgende lamp AAN: ");
  }
  else
  {
    Serial.print("Zet de volgende lamp UIT: ");
  }
 
  Serial.println(LampNummer);
}

Zoals je ziet de “for”-loop, gaat door 5 iteraties, waarbij 5 lampen AAN gezet gaan worden.
De doorgegeven waarden zijn dus ook weer apart gehouden door een komma te plaatsen!
In de volgende “for”-loop, weer 5 iteraties, waar we de lampen UIT zetten.

De output ziet er ongeveer zo uit:

Zet de volgende lamp AAN: 1
Zet de volgende lamp AAN: 2
Zet de volgende lamp AAN: 3
Zet de volgende lamp AAN: 4
Zet de volgende lamp AAN: 5
Zet de volgende lamp UIT: 1
Zet de volgende lamp UIT: 2
Zet de volgende lamp UIT: 3
Zet de volgende lamp UIT: 4
Zet de volgende lamp UIT: 5

Bedenk wel dat deze voorbeelden wel een beetje simpel zijn, maar zodra je jouw eigen programma’s gaat schrijven, die wat groter zijn, dan zul je snel leren hoe handig functies kunnen zijn.

Antwoord van een Functie krijgen

We weten nu dus dat we een functiewaarden kunnen meegeven door middel van parameters. Maar hoe krijgen we een antwoord terug van een functie – bijvoorbeeld voor een complexe berekening?

Herinner je de “void” in onze voorgaande voorbeelden? Dat is waar we definiëren wat voor datatype we als antwoord mogen verwachten.

In de functie zelf moeten we echter duidelijk aangeven wat de “return” waarde is. De term “return” is Engels voor “terug”, dus de waarde die we “terug” sturen.

Als we een functie definiëren die een waarde terug gaat geven (dus niet “void”!) dan moeten we de instructie “return” gebruiken om een antwoord terug te sturen. Dat antwoord moet van hetzelfde datatype zijn als in de functie definitie!

We zouden bijvoorbeeld ons geld voorbeeld, dat we eerder hebben gebruikt, als voorbeeld kunnen gebruiken. Ook dit is natuurlijk geen ingewikkelde situatie, maar het dient slechts ter illustratie hoe een functie werkt.

Stel we maken een functie “TelAlMijnGeld”, die we twee parameters geven. Ik heb deze parameters expres een andere naam gegeven om aan te geven dat de waarden van de parameters gekopieerd worden. Maar … deze namen mogen gerust dezelfde zijn, want de waarde die als parameter wordt opgegeven is een ander variabele dan de variabele met dezelfde naam in de functie – denk terug aan de “scope”van variabelen.

In de functie dus 2 parameters, en als antwoord het “Totaal”. De “return” waarde wordt vervolgens toegewezen aan de variabele “AlMijnGeld”.

Probeer de volgende code maar eens.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void setup() {
  // set the speed for the serial monitor:
  Serial.begin(9600);
 
  // define our variables
  int ZakGeld;
  int SpaarGeld;
  int AlMijnGeld;

  // assign the values
  ZakGeld = 4;
  SpaarGeld = 12;
  AlMijnGeld = TelAlMijnGeld(ZakGeld, SpaarGeld);

  // print the values to the serial monitor
  Serial.print("ZakGeld = ");
  Serial.println(ZakGeld);

  Serial.print("SpaarGeld = ");
  Serial.println(SpaarGeld);

  Serial.print("AlMijnGeld = ");
  Serial.println(AlMijnGeld);
}

void loop() {
  // leave empty for now
}

int TelAlMijnGeld(int OpZak, int OpDeBank) {
  int Totaal;

  Totaal = OpZak + OpDeBank;

  return Totaal;
}

Een functie die terug komt met een waarde zouden we kunnen zien als een variabele die een waarde bevat. Uiteraard kunnen we niets toewijzen aan deze zogenaamde variabele, maar we kunnen de waarde wel “uitlezen” en gebruiken waar we vaak ook een variabele of constante kunnen gebruiken. Kijk in het volgende aangepaste voorbeeld maar eens naar regel 21 … We hebben als de “parameter” voor de functie “Serial.println() ” gewoon onze nieuwe functie gebruikt … en dat werkt prima! We hoeven dus de “return” (terug) waarde van een functie echt niet eerst in een variabele op te slaan – tenzij we deze waarde meerdere keren nodig hebben, want in zo’n geval willen we de berekening maar 1x doen, en iedere keer dat we de functie aanroepen, wordt de functie uitgevoerd.

Een functie die een antwoord (return) waarde geeft kan gebruikt worden alsof het een variabele of constante is …

Elke keer als een functie wordt aangeroepen, zullen de instructies in de functie opnieuw uitgevoerd worden. Als een antwoord dus meerdere keren gebruikt wordt, overweeg dan om deze waarde in een variabele op te slaan in plaats van herhaaldelijk aanroepen van de functie.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void setup() {
  // set the speed for the serial monitor:
  Serial.begin(9600);
 
  // define our variables
  int ZakGeld;
  int SpaarGeld;

  // assign the values
  ZakGeld = 4;
  SpaarGeld = 12;

  // print the values to the serial monitor
  Serial.print("ZakGeld = ");
  Serial.println(ZakGeld);

  Serial.print("SpaarGeld = ");
  Serial.println(SpaarGeld);

  Serial.print("AlMijnGeld = ");
  Serial.println( TelAlMijnGeld(ZakGeld, SpaarGeld) );
}

void loop() {
 // leave empty for now
}

int TelAlMijnGeld(int OpZak, int OpDeBank) {
 int Totaal;

 Totaal = OpZak + OpDeBank;

 return Totaal;
}

Ik adviseer weer om wat te gaan spelen met functies, bedenk een eigen functie, voeg wat extra parameters toe, etc.

Functies die Zichzelf aanroepen (Recursie)

  Deze paragraaf is wat lastig te bevatten en zeker niet superbelangrijk – sla dit dus gerust over als het onduidelijk wordt.

Een functie kan zichzelf aanroepen, en men noemt dit “Recursie“.

Je denkt vast dat je nu aangekomen bent bij het gekkenhuis, en ik kan me voorstellen dat dit onderwerp een beetje te complex is voor de meeste beginners en zelfs ervaren programmeurs hebben soms moeite met dit onderwerp. Je hebt recursie zelden nodig en ik vermeld het hier alleen maar om een beetje meer volledig te zijn.

Voor wie interesse heeft, lees gerust verder, als je even niet meer snapt waar dit over gaat: ga gerust naar Deel 7.

Recursie is een zeer krachtig, ook al is het soms moeilijk te bevatten, hulpmiddel voor programmeurs, waarmee je met weinig code toch iets moois voor elkaar kunt krijgen. Hieronder twee resultaten: een recursief getekende boom en de zogenaamde zeef van Sierpinski (een driehoek in een driehoek in een driehoek, etc). Afbeeldingen bron: Wikipedia – Tree door Brentsmith101, Sierpinski door Wereon.

Recursieve Boom

Recursieve Boom

Zeef van Sierpinski

Zeef of Driehoek van Sierpinski

OK, laten we een makkelijker voorbeeld bedenken om recursie uit te leggen.

Stel we hebben de nummers 1 tot en met 5 en we willen ze allemaal optellen, dus 1+2+3+4+5 (= 15).
We kunnen dit heel simpel doen natuurlijk, met een eenvoudig “for”-loop, maar vandaag gaan we lekker moeilijk doen en gaan we recursie gebruiken.

We gaan een functie maken die we het nummer “5” geven, die daarbij “4” bij op gaat tellen. Daarna geven we dezelfde functie het nummer “4” en tellen daar “3” bij op, etc.


1
2
3
4
5
6
int VoegVorigeNummerToe(int Nummer){
    if(Nummer==0)
       return Nummer;
    else
       return Nummer+VoegVorigeNummerToe(Nummer-1);
}

Wat deze functie doet is de waarde van “Nummer” pakken en kijken of dit nul is. Als het nul is, stuur dan nul terug (return). Als dit niet nul is, roep dan de functie “VoegVorigNummerToe” aan met “Nummer – 1”.

Maf he?

In andere woorden: deze functie begint met een nummer, en blijft er steeds het voorgaande nummer aan toevoegen (min 1) tot het nul heeft bereikt.

Laten we dit eens van dichtbij gaan bekijken met een voorbeeld:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void setup() {
  // set the speed for the serial monitor:
  Serial.begin(9600);
 
  Serial.println(VoegVorigeNummerToe(5));
}

void loop() {
  // leave empty for now
}

int VoegVorigeNummerToe(int Nummer){
    if(Nummer==0)
       return Nummer;
    else
       return Nummer+VoegVorigeNummerToe(Nummer-1);
}

We starten dus met VoegVorigeNummerToe(5).

De waarde van “Nummer” is niet nul, dus we doen 5 + “VoegVorigeNummerToe(5-1)”. We hebben nog geen return waarde of antwoord omdat eerst de aanroep van VoegVorigeNummerToe(5-1) afgewerkt moet worden.
In deze tweede aanroep van “VoegVorigeNummerToe” is “Nummer” gelijk aan 4, en dus nog steeds niet nul. We doen dus 4 + “VoegVorigeNummerToe(4-1)” en weer moeten we de aanroep van “VoegVorigeNummerToe(4-1)” eerst afwerken voor we een antwoord terug kunnen sturen.
In de derde aanroep is “Nummer” gelijk aan 3 en dus weer niet nul, en we doen dus weer 3 + “VoegVorigeNummerToe(3-1)” – aan.
In de vierde aanroep is “Nummer” gelijk aan 2 en dus weer niet nul, en we doen dus weer 2 + “VoegVorigeNummerToe(2-1)” – aan.
In de vijfde aanroep is “Nummer” gelijk aan 1 en dus weer niet nul, en we doen dus weer 1 + “VoegVorigeNummerToe(1-1)” – aan.
Bij de zesde aanroep echter is “Nummer” wel gelijk aan nul en we sturen dus nul als antwoord terug.

Omdat we nu in de scope van de vijfde aanroep terug komen, en we dus een return waarde kunnen bepalen, gaan we weer een stap terug met het antwoord (1+0 = 1).
Vervolgens komen we terug in de scope van de vierde aanroep, met als resultaat 2+1 = 3.
Dit herhaalt zich voor de scope van de derde functie aanroep, met als resultaat 3+3 = 6,
en de scope van de tweede aanroep met als resultaat 4+6 = 10,
en de scope van de eerste aanroep wat uiteindelijk het antwoord 5+10 = 15 levert.

Lekker verwarrend he?

Misschien helpt het als ik het zo laat zien:


1
2
3
4
5
6
7
8
9
10
11
12
13
1ste aanroep:     VoegVorigeNummerToe(5)    // in de setup() functie
2de aanroep:     VoegVorigeNummerToe(4)    // in de VoegVorigeNummerToe(5) functie aanroep
3de aanroep:     VoegVorigeNummerToe(3)    // in de VoegVorigeNummerToe(4) functie aanroep
4de aanroep:     VoegVorigeNummerToe(2)    // in de VoegVorigeNummerToe(3) functie aanroep
5de aanroep:     VoegVorigeNummerToe(1)    // in de VoegVorigeNummerToe(2) functie aanroep
6de aanroep:     VoegVorigeNummerToe(0)    // in de VoegVorigeNummerToe(1) functie aanroep, Nummer is nu NUL!
terug naar de 5de aanroep:     Return = Nummer + resultaat van de 6de aanroep = 1 + 0 = 1
terug naar de 4de aanroep:     Return = Nummer + resultaat van de 5de aanroep = 2 + 1 = 3
terug naar de 3de aanroep:     Return = Nummer + resultaat van de 4de aanroep = 3 + 3 = 6
terug naar de 2de aanroep:     Return = Nummer + resultaat van de 3de aanroep = 4 + 6 = 10
terug naar de 1ste aanroep:     Return = Nummer + resultaat van de 2de aanroep = 5 + 10 = 15

Eindresultaat: = 15

Ik weet dat dit concept moeilijk te bevatten is, zelfs voor ervaren programmeurs, omdat dit niet de gebruikelijk manier is zoals onze grijze cellen hier mee omgaan. Je hoeft alleen maar te onthouden dat de functie zichzelf kan aanroepen en zichzelf daarbij niet in de weg zit, maar dat iedere aanroep een eigen “scope” heeft.

Als vuistregel voor recursie: zorg er altijd voor dat de functie een “uitgang” heeft, anders blijft de functie eindeloos zichzelf aanroepen wat als gevolg heeft dat de computer of de Arduino geen geheugen meer heeft en vast loopt. Iedere scope wordt namelijk opgeslagen voor wanneer de functie weer “terug” komt …

Bij Recursief gebruik van functies:

ALTIJD zorgen dat er een “uitgang” is voor de functie!

 

Als je vragen hebt: stel ze dan hieronder, en bedenk dat er geen domme vragen zijn, behalve dan natuurlijk de vraag die niet gesteld is. We zijn allemaal een keer bij nul begonnen!

Volgende hoofdstuk: Arduino Programmeren voor Beginners – Deel 7: Strings

Ondersteun ons ...


Jouw ondersteuning wordt zeer gewaardeerd, en hoeft zelfs niets te kosten. Bijvoorbeeld door links naar ons te delen op social media, of andere websites.

Andere vormen kunnen ook gratis zijn (b.v. shoppen op Amazon).
Alle opbrengsten worden gebruikt voor web-hosting kosten, project hardware en software, koffie, etc.

Hartelijk dank voor wie al heeft bijgedragen!
Het is altijd geweldig om te zien hoe men mijn artikeltjes en applicaties weet te waarderen.

Merk op dat het klikken op affiliate links een kleine commissie voor ons kunnen genereren - dit wordt zeer gewaardeerd.

Reacties


Er zijn 23 reacties welke je hieronder kunt lezen.
Je kunt jouw eigen opmerkingen plaatsen m.b.v. dit formulier, of een reactie op een bestaande opmerking plaatsen door op de "Beantwoorden" knop te klikken.

  • 27 jun 2016 - 1:22 - peter bakkers Reactie Link

    Hallo mensen,

    Een hoop info opgedaan alhier, dank daarvoor!! Ik ben weer een stukje Arduino-wijzer.

    Met vr gr

    Peter Bakkers

    Beantwoorden

    peter bakkers

    • 27 jun 2016 - 10:31 - hans - Auteur: Reactie Link

      Hi Peter!

      Dank je wel dat je de moeite hebt genomen voor een bedankje! 
      Dat wordt zeer gewaardeerd.

      Mocht je vragen hebben over de onderwerpen, vraag dan gerust! 

      Beantwoorden

      hans

    • 8 nov 2017 - 1:08 - Jan Reactie Link

      Tot nu toe heb ik alles heel goed kunnen volgen, bedankt voor de heldere uitleg.

      Alleen in deel 6 loop ik een beetje vast, waarschijnlijk ga ik het begrijpen met oefenen, maar een uitleg mocht die er nog niet geweest zijn zou de tutorial misschien wat verduidelijken.

      Bij de 3e code word er een loop gemaakt, en omdat het een makkelijke code is zal alles in een seconde klaar zijn.

      Maar ik neem aan dat de code als het langer duurt (maar ook nu al) in werkelijkheid als volgt gaat:

      “hallo”

      herhaal code

      “hallo”

      herhaal code

      “hallo”

      herhaal code

      enz

      Mij is nu nog niet duidelijk waarom de print al gebeurt terwijl de loop nog bezig is en dat is in de 7e voorbeeld code nog meer van toepassing.

      Kunt u dat misschien nog extra uitleggen?

      Beantwoorden

      Jan

      • 10 nov 2017 - 16:48 - hans - Auteur: Reactie Link

        Hoi Jan,

        geen probleem, ik leg het e.e.a. graag voor je uit.
        Mij is niet helemaal duidelijk welke code je bedoelt – kun je evt. de code in de opmerkingen hier plaatsen?

        Ik weet niet zeker of ik jouw vraag goed heb begrepen, maar ik ga het een poging geven.

        Stel we hebben deze code:

        void setup() {
          // set the speed for the serial monitor:
          Serial.begin(9600);
          
          for(int A=1; A<=5; A++) {
            ZegHallo();
          }
        }
        void loop() {
          // leave empty for now
        }
        void ZegHallo() {
          Serial.println("Hallo");
        }

        In dit geval start de Arduino op en gaat de functie “setup()” starten en de stappen daarin uitvoeren.
        In dit geval stellen we de seriële poort in op 9600 baud en beginnen we een loop die tot 5 telt.
        Voor iedere stap in de loop wordt de functie “ZegHallo()” uitgevoerd.

        Als we naar de functie ZegHallo() kijken dan zien we dat het “Hallo” naar de seriële poort stuurt (debug venster in de Arduino IDE).

        Misschien helpt het om elke keer als je ZegHallo() ziet als aanroep, deze regel te vervangen door de regels die in de ZegHallo() functie staan. Zoals dit:

        void setup() {
          // set the speed for the serial monitor:
          Serial.begin(9600);
          
          for(int A=1; A<=5; A++) {
            Serial.println("Hallo");
          }
        }
        void loop() {
          // leave empty for now
        }

        of nog verder uitgebouwd:

        void setup() {
          // set the speed for the serial monitor:
          Serial.begin(9600);
          a=1;
          Serial.println("Hallo");  
          a=2;
          Serial.println("Hallo");
          a=3;
          Serial.println("Hallo");
          a=4;
          Serial.println("Hallo");
          a=5;
          Serial.println("Hallo");
          }
        }
        void loop() {
          // leave empty for now
        }

        Misschien verduidelijkt dit waarom het printen gebeurt in de loop – tenzij ik de vraag niet goed begrepen heb natuurlijk 

        Beantwoorden

        hans

        • 10 nov 2017 - 23:59 - Jan Reactie Link

          Dit is inderdaad wat ik bedoelde. Ik denk dat het laat was toen ik het de vorige keer niet zag, want nu zie ik duidelijk de functie ook in de eerdere voorbeelden.

          Bedankt voor de extra uitleg

          Beantwoorden

          Jan

  • 31 mrt 2017 - 22:26 - Nico Kwaak Reactie Link

    Ik vind het super interessant en leerzaam. Recursie is inderdaad ingewikkeld.

    Is er nog een boek over C programmeren wat je me aan kunt raden?

    En de belangrijkste vraag: komt er nog een vervolg op deze cursus?

    Beantwoorden

    Nico Kwaak

    • 1 apr 2017 - 2:25 - hans - Auteur: Reactie Link

      Hoi Nico,

      Erg leuk om te horen! 

      Nou een vervolg zou weleens kunnen gebeuren ja. Maar het e.e.a. hangt af van de tijd die ik beschikbaar heb. Deze kleine cursus schreef ik toen ik een jaartje vrij nam van werk. Niet dat het een heel jaar duurde om te schrijven haha

      Wat boek betreft kan ik eigenlijks zo 1-2-3 niets aanraden, omdat ik zelf nooit boeken gebruikt heb voor het leren programmeren. Het meeste heb ik zelf geleerd door gewoon te proberen, en (tegenwoordig) het door snuffelen van het Internet.
      Ik leer zelf altijd het snelste als ik een doel heb, daarom vindt ik het schrijven van een cursus ook best moeilijk, maar wel leuk.

      Wat recursie betreft; ik wil daar best nog weleens wat over schrijven, met name omdat het zo’n interessante techniek is die soms erg efficient kan zijn.

      Beantwoorden

      hans

  • 11 sep 2017 - 20:09 - SmlyTim Reactie Link

    Beste Hans,

    Zeer interresant dit artikel, ik ben net begonnen met arduino en dit is DE manier om te beginnen.
    Ik wou gewoon nog een kleine fout melden (denk ik) die voor andere beginners misschien verwarrend zou kunnen zijn. Er staat namelijk;

    datatype FunctieNaam ( FunctieParameters } {

      // functie code

    }

    Wat natuurlijk 2 ronde haakjes moeten zijn->      ( FunctieParameters ) {

    Bedankt voor deze cursus!

    Beantwoorden

    SmlyTim

  • 11 sep 2017 - 23:06 - bob Reactie Link

    Hoi,
    Wat een goede tutorial zeg!Ik heb wel een probleem met de functie’s: Als ik mijn code probeer te compileren krijg ik de foutmelding “function not declared in this scope”Hoe kan ik dit oplossen?

    Beantwoorden

    bob

  • 13 okt 2017 - 18:27 - Peer Strik Reactie Link

    Dag,   Alhoewel het eenvoudig zou moeten zijn, valt het me niet mee. Ik ben niet zo talig en ook hier durf ik niet zo snel de grens over om me aldaar onverstaanbaar te maken. Toch heb ik hoop. Dankzij de cursus begin ik er toch een beetje vertrouwen in te krijgen. Waarvoor mijn dank.

    Opmerking: Fouten in voorbeeld voor onderwerp recursie expres gemaakt? Bv OpZak ipv Zakgeld

    Beantwoorden

    Peer Strik

    • 15 okt 2017 - 11:13 - hans - Auteur: Reactie Link

      Hallo Peer,

      als eerste leuk om te horen dat dit het e.e.a. een beetje toegankelijk heeft gemaakt voor je, en natuurlijk hartelijk dank voor het compliment.

      Wat betreft de fout die je vond: Dit is geen fout.
      Als je in een functie een naam voor een variable definieert (zoals hieronder), dan hoef je die naam niet te hanteren als je de functie aanroept. De naam wordt namelijk alleen maar binnen de functie gebruikt om de waarde op te vangen die meegegeven werd door de aanroep van de functie.

      int TelAmMijnGeld(int OpZak, int OpDeBank) {
       int Totaal;
       Totaal = OpZak + OpDeBank;
       return Totaal;
      }

      In deze functie definieren we de variable OpZak en OpDeBank. Als de functie aangeroepen wordt, dan geven we een waarde door voor ieder van deze variabelen. Dit kan een andere variabele zijn (b.v. ZakGeld) of gewoon een waarde (b.v. het nummer “3”). Binnen de functie wordt dan de doorgegeven waarde gekopieerd in de variabelen OpZak en OpDeBank.
      Het is zelfs beter om andere variabele namen te gebruiken binnen de functie zodat je verwarring in de code op die manier minimaliseert.|Stel we zouden hier ook de variabele naam “ZakGeld” gebruiken dan kan de waarde buiten de functie (vanwaar de aanroep plaats vindt) veranderd worden, maar in de functie kan het weleens zijn dat deze niet overeenkomt met die waarde.
      Dit komt omdat iedere variabele een “scope” heeft – het gebied waarin de variabele toegankelijk is. 
      Misschien een beetje lastig om de eerste keren te bevatten en misschien is mijn uitleg niet helemaal duidelijk, dus vraag gerust! 

      Beantwoorden

      hans

  • 16 apr 2018 - 12:12 - Jan Reactie Link

    De  functie CalculateMyMoney bestaat toch niet of wordt dat automatisch vertaald naar TelAmMijnGeld?

    Vr grt Jan

    Beantwoorden

    Jan

    • 16 apr 2018 - 13:59 - hans - Auteur: Reactie Link

      Hoi Jan,

      dank je wel voor melden van de fout – dat krijg je als je een artikel in het Engels en Nederlands plaatst 
      Ik heb de fout meteen gecorrigeerd … en TelAmMijnGeld is ook niet helemaal zuiver, dus daar heb ik TelAlMijnGeld van gemaakt 

      Nogmaals dank!

      Beantwoorden

      hans

  • 19 jan 2021 - 17:14 - Jasper Basta Reactie Link

    Hey,

    Ik ben jasper en zit in het tweede jaar STEM.

    Ik heb een probleem… Ik vind programmeren heel moeilijk en dat vind ik jammer.

    Zou je mij tips kunnen geven of gewoon iets van hulp?

    Alvast bedankt, Jasper

    Beantwoorden

    Jasper Basta

    • 20 jan 2021 - 11:56 - Hans - Auteur: Reactie Link

      Hoi Jasper,

      Ik wil zo nu en dan best helpen hoor.
      Stel je vragen in het Forum, en ik zal proberen ze te beantwoorden.

      Beantwoorden

      Hans

  • 5 feb 2023 - 17:11 - kamiel Reactie Link

    hoe kan je nu van die 2 “returns” naar 1

    Beantwoorden

    kamiel

    • 6 feb 2023 - 10:09 - Hans - Auteur: Reactie Link

      Hallo Kamiel,

      Wil je graag helpen, maar moet eerlijk zeggen dat ik de vraag niet zo goed begrijp?
      (misschien heb ik nog wat meer koffie nodig haha)

      Beantwoorden

      Hans

  • 17 sep 2023 - 8:20 - Toon Reactie Link

    Goedemorgen,

    Waarom staat de functie definitie niet aan het begin van de code 

    Zoals de variabele en constanten

    Groet toon

    Beantwoorden

    Toon

    • 19 sep 2023 - 12:39 - Hans - Auteur: Reactie Link

      Hi Toon,

      Erg goede vraag … technisch gezien is dat ook correct (zogenaamde Forward Declaration), maar voor Arduino niet nodig en wordt daarom ook maar zelden gedaan.
      Bij een aantal C/C++ compilers op b.v. PC of Mac is dat ook niet meer zo gebruikelijk, maar zeker wel correct om te doen.

      Voorbeeld van forward declaration:

      ...
      #define EenVoorbeeld 1 int Andervoorbeeld; void MijnFunctie(int a); // forward declaraion
      ....
      void setup() {
        ...
        // mogelijk MijnFunctie aanroepen
      }
      void loop() {
      ...
        // mogelijk MijnFunctie aanroepen
      }
      ...
      // implementatie van de functie die bij de forward declaration hoort void MijnFunctie() {
            // iets doen
      }
      Beantwoorden

      Hans

      • 5 okt 2023 - 15:30 - Toon Reactie Link

        Ik had als absolute beginner het simpele idee dat de Arduino ,iedere keer bij het opstarten, de code leest en uitvoert in de volgorde zoals wij hem geschreven hebben. Begrijp ik het zo beter, na het compileren wordt de hele code geüpload en aan de hand van alle declaraties en definities wordt er van alles voorbereid zoals plaats en hoeveelheid vrij geheugen enzovoort. Pas dan gaat het programma lopen. De exacte plek van de bijvoorbeeld een definitie is alleen belangrijk ivm de scope. 

        Alvast dank

        Beantwoorden

        Toon

        • 6 okt 2023 - 9:49 - Hans - Auteur: Reactie Link

          Hi Toon!

          Declaratie en definities zijn eigenlijk alleen van belang voor de compiler.
          De definities worden gebruikt om betreffende uitdrukkingen in de code te veranderen.

          B.v.

          #DEFINE xyz 123 –> elk voorkomen van xyz wordt door 123 vervangen voor de compiler alles omzet naar machine code.

          int xyz; –> vanaf dit punt wordt inderdaad geheugen gereserveerd voor de variable xyz, met een omvang die groot genoeg is om een integer op te slaan.

          int eenFunctie(int abc); -> declareert voorde compiler dat de aanroep van “eenFunctie” er zo uit ziet. 

          Als de compiler daarna echt aan de slag gaat, dan worden functies en variable gerefereerd op basis van een geheugen locatie.

          Hopelijk maakt dit het e.e.a. duidelijker.

          p.s. een #include is ook een compiler instructie die zegt dat het betreffende bestand meegenomen moet worden.

          Beantwoorden

          Hans



Jouw Opmerking ...

Plaats hier geen grote bestanden (zoals source codes, log files of config files). Gebruik hiervoor het Forum.

Delen:
*
*
Laat me per email weten als er nieuwe reacties zijn.
       Je kunt jouw RSS reader gebruiken om reacties te volgen.


Tweaking4All gebruikt de gratis Gravatar dienst voor Avatar weergave.