int i = 0;
while (i < lengte && gezocht != gegevens[i]){
i++;
}
if (i == lengte) // niet aanwezig
else // aanwezig
int i = 0;
while (i < lengte && gezocht < gegevens[i]){
i++;
}
if (i < lengte && gezocht == gegevens[i]) // aanwezig
else // niet aanwezig
int l=0, r=lengte_tab;
int m;
while(r>l){
m=l+(r-l)/2;
if (tab[m]<gezocht) l=m+1;
else if(tab[m]>gezocht) r=m;
else r=l;
}
if(tab[m]==gezocht) // gevonden
else // niet gevonden
keuze = keuze_uit_menu();
while (keuze != stopwaarde){
if (keuze == eerstewaarde) // eerste geval
else if (keuze == tweedewaarde) // tweede geval
...
else if (keuze == laatstewaarde) // laatste geval
else // foutmelding
keuze = keuze_uit_menu();
}
Merk op: indien de gebruiker een ongeldige keuze maakt, krijgt hij enkel een foutmelding, automatisch gevolgd door het opnieuw aangeboden keuzemenu.
Het afzonderen van de cijfers uit een gegeven geheel getal kan zeer eenvoudig, als we enkel de bewerkingen "/" (gehele deling) en "%" (gehele rest) gebruiken. Andere bewerkingen (-,*,+) komen er niet aan te pas! Als voorbeeld tellen we het aantal cijfers in een (strikt positief) getal, en berekenen we tegelijkertijd de som van die cijfers.
int aantal = 0;
int som = 0;
while (getal != 0){
aantal++;
som += getal % 10;
getal /= 10;
}
De regel van Horner wordt bij de meeste (ex-)scholieren direct gelinkt met het zoeken van nulpunten. Of toch minstens met "dat ding met die lijnen". Nochtans is de regel van Horner breder inzetbaar. Ga maar na: willen we weten of
t nulpunt is van de derdegraads veelterm ax3+bx2+cx+d, dan rekenen we met het schema van Horner uit:
| a b c d t | at (at+b)t ((at+b)t+c)t ___|_______________________________________________ | a at+b (at+b)t+c ((at+b)t+c)t+dWe hopen dan rechtsonder in het schema een nul tegen te komen - in dat geval is
t inderdaad nulpunt van ax3+bx2+cx+d. Maar wat heb je eigenlijk uitgerekend in dat rechteronderhoekje? Werk je de haakjes uit, dan staat er - uiteraard - at3+bt2+ct+d. En inderdaad, als dat gelijk is aan nul, is t een nulpunt van de gegeven veelterm.
Wat is dan het nut van de regel van Horner?
ax3+bx2+cx+d in (C++) programma code omzetten, dan zou je misschien geneigd zijn het volgende te schrijven:
a*x*x*x + b*x*x + c*x + d. Je gebruikte zes vermenigvuldigingen en drie optellingen. (Suggereer je a*pow(x,3)+...? Lees volgende paragraaf!)
* + * + * +. Dus slechts drie vermenigvuldigingen (aantal optellingen blijft gelijk). Voor veeltermen van hogere graad gaat dat verschil nog groter zijn.
In telegramstijl (makkelijk om te onthouden) komt er:
eerste tussenresultaat = 0 (of coëfficiënt van hoogstegraadsterm) volgend tussenresultaat = ( vorig tussenresultaat * x ) + volgende coëfficiënt
Slaan we de coëfficiënten van de veelterm op in een tabel (hoogstegraadscoëfficiiënt eerst), dan komt er:
double resultaat = 0;
for (int i=0; i<graad_van_veelterm+1; i++){
resultaat = resultaat * x + coeff[i];
}
x*x*x NIET gelijk aan pow(x,3)?
Omdat je voor x*x*x amper 2 bewerkingen nodig hebt. En voor pow(x,3) heb je een functieaanroep (onder andere doorgeven van parameters), en een reeksontwikkeling. Dit is een benaderende berekening (stopt als 'verbeterde' uitkomst niet meer verschilt van vorige benadering). Dus veel te veel bewerkingen met bovendien kans op afrondingsfouten. De functie pow(grondtal,exponent) is bedoeld voor niet-gehele exponenten. Als de exponent een negatief geheel getal is, dan is pow(x,t)=xt=1 / x-t; ook hier doe je geen beroep op de functie pow(). Bovendien gebeurt het in berekeningen dikwijls dat je xt wil uitwerken, maar xt-1 ondertussen al kent. Dan gebruik je uiteraard de regel van Horner: xt = x*xt-1 in plaats van x*x*...*x.
do...while, een for-lus met dubbele stopvoorwaarden; break? We hebben goede redenen om jullie slechts volgende structuren te leren:
for met break in plaats van een correcte while-lus), dan heb je ook meer kans om verloren te lopen. En vooral: je geeft je collega-programmeur die je code diagonaal doorleest het verkeerde signaal (de break zal een kantlijn te ver genest zijn om goed op te vallen).
Meer uitleg vind je in de bundel Programmeren met stijl. Deze tekst heeft alles weg van een mooi stuk antiek - zo eentje waar ze op 'n veiling voor vechten. Hoewel oorspronkelijk geschreven zonder de achtergrond van het objectgerichte programmeren (dat wordt in onze academische-bacheloropleiding pas geïntroduceerd als er een stevige basis ligt), hebben de aanbevelingen niet aan waarde ingeboet. Integendeel. Omdat de beschreven tips verzameld werden na lange les- en werkervaring, staan ze garant voor tijds- en moeitebesparing. Veel leesgenot.
Een lokale variabele is weer verdwenen (= geheugenplaats weer vrijgegeven) zodra het stukje code waarin hij gedeclareerd werd, afgerond is. Plastisch uitgedrukt: als je de lokale variabele declareert "aan de vierde kantlijn", dan is hij weer verdwenen zodra de code een kantlijn meer naar links inspringt (dus aan "de derde kantlijn").
Een dynamisch gecreëerde variabele wordt slechts vrijgegeven aan het geheugen, als je er expliciet om vraagt met het codewoord 'delete'. Doe je dit niet, dan blijft de variabele in het geheugen plaats innemen.
In schemavorm:
| enkelvoudige variabele | tabelvariabele | pointervariabele | |
| lokaal | int x; |
int tab[20]; |
int * xp; |
| dynamisch | int * xp; xp = new int; delete xp; |
int * tab; tab = new int[20]; delete [] tab; |
int ** xpp; xpp = new int *; delete xpp; |
Hetzelfde geldt voor variabelen (objecten) van een zelfgedefinieerde klasse:
| enkelvoudige variabele | tabelvariabele | pointervariabele | |
| lokaal | ding z; |
ding tab[20]; |
ding * zp; |
| dynamisch | ding * zp; zp = new ding; delete zp; |
ding * tab; tab=new ding[20]; delete [] tab; |
ding ** zpp; zpp = new ding *; delete zpp; |
Merk op: opkuisen MOET. VOOR je een 'new' intikt, MOET je de 'delete' al hebben staan. Hiertegen zondigen leidt gegarandeerd tot memory leaks.
| enkelvoudige variabele | tabelvariabele | pointervariabele | |
| const | int x; |
const int * tab; const int tab []; |
int * p; |
| uitvoer | int & x; |
int * tab; int tab []; |
int * & p; |
Voor variabelen (objecten) van een zelfgemaakte klasse, geldt hetzelfde. Alleen geven we hier nooit een copie door, gezien een object zeer groot kan zijn.
| enkelvoudige variabele | tabelvariabele | pointervariabele | |
| const | const ding & z; |
const ding * tab; const ding tab []; |
ding * zp; |
| uitvoer | ding & z; |
ding * tab; ding tab []; |
ding * & zp; |
Bij oproep van een functie met bovenstaande parameters, moet je niet beginnen twijfelen over de variabele die je meegeeft aan de functieoproep. Zorg ervoor dat de variabele hetzelfde type heeft als de parameter. Dus toegepast op de functies van bovenstaande tabel (voor basistype uitgeschreven; voor zowel constante als uitvoerparameter):
| enkelvoudige variabele | tabelvariabele | pointervariabele | |
int t; int * tp = new int; functie(t); functie(*tp); |
int t[20];
int * tp = new int[20];
functie(t);
functie(tp);
|
int i, *ip; double d, *dp;Welke van volgende beweringen zijn (syntactisch) correct? Indien ze correct zijn, verklaar wat ze uitdrukken. Indien je niet zeker bent, probeer dan de code uit in een programma.
| i=7; | ip=&7; | &i=ip; |
| &i=*7; | ip=&i; | *ip=i; |
| *ip++; | (*ip)++; | *ip *= i; |
| *ip = *&i; | ip++; | i = ip - &i; |
| dp = &i; | dp = ip; | &dp = &&d; |
VERSIE 1
double* kwadraat (double a){
double *q= new double;
*q = a*a;
return q;
}
int main(){
double* p;
p=kwadraat(3.1);
cout<<*p;
} |
VERSIE 4
void kwadrateer
(double*& q, double a){
*q = a*a;
}
int main(){
double* p;
kwadrateer(p,3.1);
cout<<*p;
} |
VERSIE 2
double* kwadraat (double a){
double q;
q = a*a;
return &q;
}
int main(){
double *p;
p=kwadraat(3.1);
cout<<*p;
} |
VERSIE 5
void kwadrateer
(double* q, double a){
q=new double;
*q = a*a;
}
int main(){
double* p;
kwadrateer(p,3.1);
cout<<*p;
} |
VERSIE 3
void kwadrateer
(double* q, double a){
*q = a*a;
}
int main(){
double* p;
kwadrateer(p,3.1);
cout<<*p;
} |
VERSIE 6
void kwadrateer
(double*& q, double a){
q=new double;
*q = a*a;
}
int main(){
double* p;
kwadrateer(p,3.1);
cout<<*p;
} |
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
const int MAX = 25;
void lees_bestand(string bestandsnaam, string items[],int &aantal)
{
aantal = 0;
ifstream invoer(bestandsnaam.c_str());
if (invoer.is_open())
{
string lijn;
getline(invoer, lijn);
while (!invoer.fail() && aantal<MAX)
{
if (lijn.size() > 0)
{
items[aantal] = lijn;
aantal++;
}
getline(invoer, lijn);
}
if (! invoer.eof())
cout<<bestandsnaam<<" bevat fouten of is niet volledig ingelezen "<<endl;
}
invoer.close();
}
void schrijf(const string items[],int n){
for(int i=0;i<n;i++)
cout<<items[i]<<endl;
}
int main()
{
string namen[MAX];
int aantal;
lees_bestand("namen.txt", namen, aantal);
schrijf(namen,aantal);
return 0;
}
MAX, en pas de code aan zodat de tabel dynamisch wordt aangemaakt in de module en nooit groter is dan noodzakelijk.
string items[] door string *items, en doe de nodige aanpassingen zodat het programma terug werkt.
De destructor is de lidfunctie die een object netjes opruimt als de levensduur van het object afgelopen is. Deze wordt nooit expliciet aangeroepen, maar moet wel geïmplementeerd worden. Een destructor zal altijd, automatisch, zonder dat je code moet toevoegen, alle lidvelden terug vrijgeven aan het geheugen. Maar als één van die lidvelden een pointer is, zal ENKEL de pointer vernietigd worden, en NIET de variabele/het object waarnaar die pointer verwijst. Dat wil dus zeggen dat dat object voor altijd in het geheugen blijft zitten (er is nl. geen wegwijzer meer naartoe). Gevolg: memory leak.
Hoe kan je nagaan of je een memory leak hebt? Daarvoor schrijf je (in de testfase van je programma) in de destructor uit wAt je precies vernietigt.
(Let op: "delete k;" met k een nullpointer is mogelijk,
maar "cout<<"ik vernietig nu knoop met sleutel "<<k->sl<<endl;" met k
een nullpointer werkt niet. (Maak eventueel onderscheid met if/else bij het uitschrijven.)
Tel bij het runnen van je programma na: heb je 15 knopen aangemaakt, dan moet je 15 keer
de "delete"-melding krijgen op je scherm (al dan niet op het einde van je programma).
Hoe kan je een memory leak vermijden? Door consequent "delete" te gebruiken waar nodig (in de destructor dus).
typedef int T; // type van een sleutel; te vervangen indien nodig
class Lijstknoop;
class Lijst{
public:
Lijst(); // constructor: maak een nieuwe lijst aan
~Lijst(); // destructor: geef alles wat je met new aanmaakte,
// hier weer vrij aan het geheugen.
int geefaantal() const;
void voegtoe (const T &);
void verwijder (const T &);
friend ostream& operator<< (ostream& os, const Lijst& l){
l.schrijf(os);
return os;
}
protected:
Lijstknoop * eerste;
void schrijf(ostream & os) const;
};
class Lijstknoop{
friend class Lijst;
private:
T sl;
Lijstknoop* volgende;
Lijstknoop();
~Lijstknoop();
Lijstknoop(const T&);
};
Zoek eerst uit welke functies je eerst implementeert, voor je de eerste keer test. Schrijf niet meteen teveel code: zorg voor een tussenstap, die toch een controleerbaar geheel vormt. (Controleerbaar wil zeggen dat je al minstens kan uitschrijven wat er intern door het programma werd opgeslagen.)
typedef int T; // type van een sleutel; te vervangen indien nodig
class Lijstknoop;
class Lijst{
public:
Lijst(); // constructor: maak een nieuwe lijst aan
~Lijst(); // destructor: geef alles wat je met new aanmaakte,
// hier weer vrij aan het geheugen.
int geefaantal()const;
void voegtoe (const T &);
void verwijder (const T &);
friend ostream& operator<< (ostream& os, const Lijst& l){
l.schrijf(os);
return os;
}
protected:
Lijstknoop * k; // naamwijziging...
void schrijf(ostream & os) const;
};
class Lijstknoop{
friend class Lijst;
private:
T sl;
Lijst volgende; // enige punt waarop interface
// recursief/niet-recursief verschilt
Lijstknoop();
~Lijstknoop();
Lijstknoop(const T&);
};
verwijdereerste() en zoek(const T &). Deze laatste lidfunctie geeft een poitner terug als uitvoer. Gebruik deze functie ook in de verwijderfunctie die reeds geschreven was. (BASISREFLEX van de programmeur: doe nooit dubbel werk! Dupliceer nooit code!)
Let op:
class Lijst{
protected:
Lijstknoop* k;
};
class Lijstknoop{
protected:
int sl;
Lijst volgend;
};
Stel dat je ergens in je code, voor een bepaalde Lijstknoop* k, "delete k" schrijft. Wat gebeurt er dan? Het object waar k naar wijst (een Lijstknoop dus) wordt weer vrijgegeven aan het geheugen. Dus wordt de destructor opgeroepen voor die Lijstknoop met naam *k. Voor het datalid sl van *k betekent dit niets speciaals (geheugenplaats voor een int wordt gewoon vrijgegeven). Het datalid volgend van *k is echter een Lijst, dus wordt de destructor van de klasse Lijst opgeroepen. Dit impliceert: delete l (met l=k->volgend.k), dus wordt ook de volgende knoop in het lijstje vrijgegeven aan het geheugen. Vandaar: het sneeuwbaleffect bij de destructor. Opgelet, dit heeft soms ongewenste effecten. Kijk vooral na of het verwijderen van een knoop uit een lijst netjes gebeurt (delete niet vergeten, maar ook niet teveel verwijderen.)