Programmeertalen en programmeerparadigma’s
BeginnerLes 5 van 7 in Cursus Informatica: hoe computers, data, algoritmen en internet werken
Wat je leert
- Volg een programma van de code die iemand schrijft tot de binaire instructies die een processor uitvoert. Vergelijk de manieren waarop die vertaling kan plaatsvinden: vooraf, tijdens de uitvoering of als combinatie van beide.
- Vergelijk machinecode, assembleertaal en hogere programmeertalenBeschrijf de drie niveaus waarop programma’s kunnen worden geschreven, laat zien hoe één regel code in een hogere programmeertaal overeenkomt met meerdere machine-instructies en leg uit waarom de meeste software op het hoogste niveau wordt geschreven.
- Leg uit wat een compiler doetBeschrijf hoe een compiler een heel programma vooraf vertaalt, benoem de belangrijkste fasen en leg uit wat er gebeurt met fouten en met het voltooide uitvoerbare bestand.
- Leg interpreters, bytecode en just-in-timecompilatie uitBeschrijf hoe een interpreter een programma uitvoert, hoe talen zoals Java en Python compileren en interpreteren combineren en hoe je de afwegingen tussen deze aanpakken maakt.
- Vergelijk machinecode, assembleertaal en hogere programmeertalen
- Maak onderscheid tussen de regels voor hoe code moet worden geschreven en de regels voor wat code doet. Herken de bouwstenen die de meeste hogere programmeertalen delen en doorloop korte programma’s om hun resultaten te voorspellen.
- Maak onderscheid tussen syntaxis en semantiekLeg het verschil uit tussen de vorm van een programma en de betekenis ervan, maak onderscheid tussen syntaxisfouten, uitvoeringsfouten en logische fouten en herken waar talen aan code die er hetzelfde uitziet een andere betekenis geven.
- Doorloop de gemeenschappelijke bouwstenen van programma’s in hogere programmeertalenHerken de constructies die in bijna elke hogere programmeertaal voorkomen (variabelen, expressies, besturingsverloop en functies met hun bereik) en volg een kort programma stap voor stap om de uitvoer te voorspellen.
- Maak onderscheid tussen syntaxis en semantiek
- Beschrijf hoe talen waarden naar type indelen en controleren, waar een programma tijdens de uitvoering zijn gegevens bewaart en hoe geheugen door de programmeur of automatisch wordt gereserveerd en vrijgegeven, met de afwegingen voor veiligheid en snelheid die elke keuze met zich meebrengt.
- Vergelijk hoe programmeertalen gegevenstypen gebruiken en controlerenLeg uit wat een gegevenstype is, vergelijk statische en dynamische typering en strikte en soepele conversieregels, en voorspel hoe veelgebruikte talen omgaan met gemengde typen en getalgrenzen.
- Beschrijf waar een programma tijdens de uitvoering zijn gegevens bewaartLeg uit dat variabelen benoemde geheugenlocaties zijn, wat de rol is van de aanroepstapel en de heap en wat het verschil is tussen een waarde kopiëren en een referentie delen.
- Vergelijk handmatig en automatisch geheugenbeheerLeg uit hoe geheugen wordt vrijgegeven in talen waarin de programmeur het beheert en in talen met automatische opruiming van onbereikbare objecten of eigenaarschapsregels. Breng geheugenfouten in verband met het vastlopen van programma’s en beveiligingslekken.
- Vergelijk hoe programmeertalen gegevenstypen gebruiken en controleren
- Beschrijf programma’s eerst als reeksen opdrachten die een opgeslagen toestand veranderen en daarna als verzamelingen objecten die gegevens met gedrag bundelen. Gebruik klassen, inkapseling, overerving en polymorfisme om code te organiseren.
- Beschrijf de imperatieve en procedurele programmeerstijlLeg imperatief programmeren uit als stapsgewijze opdrachten die de toestand van een programma veranderen, laat zien hoe procedures en een gestructureerd besturingsverloop die opdrachten organiseren en herken de risico’s van een gedeelde veranderlijke toestand.
- Modelleer gegevens en gedrag met klassen en objectenLeg uit hoe objectgeoriënteerd programmeren gegevens en de bewerkingen daarop in objecten groepeert, lees en ontwerp een eenvoudige klasse en gebruik inkapseling om de gegevens van een object te beschermen.
- Hergebruik en breid klassen uit met overerving, polymorfisme en compositieGebruik overerving om gespecialiseerde klassen te bouwen, leg uit hoe polymorfisme één aanroep verschillend laat werken voor verschillende objecten en bepaal wanneer je objecten combineert in plaats van overerving te gebruiken.
- Beschrijf de imperatieve en procedurele programmeerstijl
- Bouw programma’s met functies die nieuwe waarden teruggeven in plaats van oude te veranderen, en schrijf programma’s die het gewenste resultaat beschrijven terwijl een taalengine bepaalt hoe dat wordt berekend.
- Bouw programma’s met zuivere functies en onveranderlijke gegevensLeg zuivere functies, onveranderlijkheid, eersteklasfuncties, functies van hogere orde en recursie uit en herschrijf een lus als een verwerkingsketen van
map,filterenreduce. - Beschrijf het gewenste resultaat met declaratieve talenLeg declaratief programmeren uit als het beschrijven van het gewenste resultaat in plaats van de manier om dat te bereiken, en lees voorbeelden in een databasequerytaal, een logische taal en andere declaratieve notaties.
- Bouw programma’s met zuivere functies en onveranderlijke gegevens
- Los hetzelfde probleem op in verschillende stijlen, herken dat de meeste moderne talen paradigma’s mengen en weeg de technische en praktische factoren af die bepalen welke taal bij een project past.
- Los hetzelfde probleem op volgens verschillende programmeerparadigma’sSchrijf dezelfde kleine taak in imperatieve, objectgeoriënteerde, functionele en declaratieve stijl, vergelijk wat elke versie gemakkelijk maakt en herken multiparadigmatalen.
- Kies een programmeertaal voor een taakWeeg de factoren af die bepalen welke taal bij een project past (platform, domein, prestaties, veiligheid, ecosysteem en mensen) en onderbouw een keuze voor een concreet scenario.
- Los hetzelfde probleem op volgens verschillende programmeerparadigma’s
Programmeertalen: uitleg
1Van broncode naar uitvoerbare instructies
Een processor voert kleine binaire instructies uit, terwijl mensen programma’s meestal schrijven in talen die zijn opgebouwd rond het probleem dat ze willen oplossen. Vertaalprogramma’s verbinden deze twee niveaus. Volg dezelfde berekening door die lagen en vergelijk vervolgens het bouwen van een programma vóór de uitvoering met interpreteren of delen compileren tijdens de uitvoering. De route beïnvloedt de overdraagbaarheid, opstarttijd, snelheid en het moment waarop een fout kan verschijnen.
1.1 Drie niveaus van programmacode
Een regel zoals x = a + b beschrijft een berekening zonder een processorregister te noemen. Abstractie maakt dit mogelijk: een taal verbergt details van lagere niveaus, zodat een programmeur zich kan richten op het werk dat het programma moet doen.
Wat de processor uitvoert
Machinecode bestaat uit instructies die als binaire getallen zijn gecodeerd. De instructiesetarchitectuur (ISA) van een processor bepaalt de instructies en hun coderingen. x86-64 komt veel voor in pc’s en servers; ARM komt veel voor in telefoons, tablets en veel laptops. Code die voor één ISA is gebouwd, moet worden vertaald of geëmuleerd om op een andere te werken.
Met hexadecimale notatie kun je instructiebytes korter weergeven. Op x86-64 zet B8 01 00 00 00 de waarde 1 in register EAX. De bytes 01 D8 tellen de waarde in EBX op bij EAX. Registers zijn kleine opslaglocaties binnen de processor. Elke instructie voert een kleine handeling uit, zoals gegevens verplaatsen, optellen, vergelijken of springen. De instructiecyclus voert deze instructies uit.
Namen voor dezelfde kleine handelingen
Assembleertaal vervangt instructiebytes door leesbare namen, mnemonics genoemd. Je kunt die twee x86-64-instructies zo schrijven:
mov eax, 1
add eax, ebx
Een assembler zet assembleertaal om in machinecode. Een gewone instructieregel levert normaal gesproken één machine-instructie op. Registernamen en instructies horen nog steeds bij een bepaalde ISA, dus woorden gebruiken in plaats van bytes maakt assembleertaal niet overdraagbaar.
Beschrijf een probleem in minder regels
Een hogere programmeertaal biedt variabelen, expressies, voorwaarden, lussen en functies. Python, C, Java en JavaScript laten de programmeur bewerkingen beschrijven zonder registers te kiezen. Eén instructie vereist vaak meerdere machine-instructies. Een vereenvoudigde vertaling van x = a + b zou:
- De waarde van
ain een register laden. - De waarde van
bin een ander register laden. - De registerwaarden optellen.
- Het resultaat voor
xopslaan.
Een echte compiler kan die stappen combineren of anders ordenen met behoud van het vereiste gedrag.
| Niveau | Wat een programmeur schrijft | Wie verzorgt de hardwaredetails? |
|---|---|---|
| Machinecode | Gecodeerde processorinstructies | De programmeur of codegenerator |
| Assembleertaal | Mnemonics en registers | De programmeur, met een assembler die instructies codeert |
| Code in een hogere programmeertaal | Variabelen en bewerkingen op de gegevens van het probleem | Een compiler, interpreter of een combinatie |
Broncode in een hogere programmeertaal is gemakkelijker te lezen, te veranderen en te controleren. Overdraagbare C-broncode kan eenmaal voor een x86-64-systeem en opnieuw voor een ARM-systeem worden gecompileerd. Elke build heeft nog steeds de juiste ondersteuning van het besturingssysteem en de juiste bibliotheken nodig. Overdraagbare broncode maakt een native uitvoerbaar bestand niet universeel.
Waarom de lagere niveaus nog steeds van belang zijn
Assembleertaal blijft nuttig in opstartcode, sommige routines van besturingssystemen en stuurprogramma’s, kleine ingebedde systemen en beveiligingsanalyse van programma’s zonder beschikbare broncode. Voor gewone toepassingscode produceren moderne compilers meestal machinecode met prestaties die vergelijkbaar zijn met die van met de hand geschreven assembleertaal.
De verschuiving naar hogere niveaus vond geleidelijk plaats. Assembleertaal verspreidde zich eind jaren 1940 en begin jaren 1950. Het A-0-systeem van Grace Hopper hielp in 1952 de automatische vertaling vooruit. IBM bracht FORTRAN uit in 1957; COBOL volgde in 1959 en Lisp ontstond rond 1958–1960. “Hoger” duidt de afstand tot de hardwaredetails aan, niet hoe moeilijk de taal te leren is.
Belangrijkste punten
- Een processor voert machine-instructies uit die door zijn ISA zijn vastgelegd.
- Een assembler codeert instructies in assembleertaal; assembleertaal blijft aan een ISA gebonden.
- Instructies in een hogere programmeertaal verbergen hardwarestappen en worden vaak meerdere instructies.
- Overdraagbare broncode kan voor verschillende doelen worden vertaald; native machinecode is doelspecifiek.
- Code op een laag niveau blijft nuttig waar toegang tot of analyse van hardware dat vereist.
1.2 Hoe een compiler een programma bouwt
Een compiler vertaalt broncode naar doelcode. De doelcode kan native machinecode zijn of een tussenvorm zoals bytecode. Compilatie vooraf (ahead-of-time compilation, AOT) vindt plaats voordat het programma draait; just-in-timecompilatie vindt plaats tijdens de uitvoering.
Van tekst naar instructies
Bij een build vooraf zetten verschillende fasen een bronbestand om in instructies:
- Lexicale analyse splitst tekst op in tokens: betekenisvolle stukken zoals namen, operatoren en getallen.
- Syntactische analyse controleert de grammatica en bouwt een structuur die laat zien hoe de stukken in elkaar passen.
- Semantische analyse controleert betekenisregels, waaronder declaraties en toegestane combinaties van typen.
- Optimalisatie zoekt een efficiëntere manier om het vereiste werk uit te voeren.
- Codegeneratie produceert de doelinstructies.
Neem de C-instructie total = price * 3;. De zes tokens zijn total, =, price, *, 3 en ;. De namen zijn identifiers, 3 is een letterlijke waarde van een geheel getal en de puntkomma beëindigt de instructie. De syntactische analyse legt vast dat de vermenigvuldiging binnen de toekenning hoort: bereken price * 3 en sla de waarde daarna op in total.
Deze fasen hebben verschillende taken. Een correct gespelde reeks tokens kan nog steeds de grammatica schenden. Een grammaticaal correcte instructie kan nog steeds een type verkeerd gebruiken. Tokens vinden is niet genoeg om vast te stellen dat een programma geldig is.
Doe hetzelfde werk met minder middelen
Stel dat broncode seconds_per_day = 60 * 60 * 24 bevat. De factoren zijn constanten, dus de compiler kan tijdens de build 60 met 60 vermenigvuldigen om 3600 te krijgen en dat vervolgens met 24 vermenigvuldigen om 86400 te krijgen. Hij kan de eindwaarde in de uitvoer plaatsen in plaats van de vermenigvuldiging tijdens de uitvoering te herhalen. Dit is constantevaluatie tijdens compilatie.
Een optimalisatie moet het gedrag behouden dat de taal vereist. Ze mag een berekening niet vervangen door een snellere die andere vereiste resultaten geeft. Het doel is om werk, tijd of geheugen te besparen met behoud van de betekenis van het programma.
Voeg de onderdelen samen
Grote programma’s worden meestal uit veel bestanden opgebouwd. Een compiler kan voor elk bronbestand een objectbestand maken. Een objectbestand bevat vertaalde code die mogelijk nog verbindingen met andere code nodig heeft. Een linker voegt de onderdelen samen en lost aanroepen naar bibliotheekroutines op, zoals afdruk- of wiskundige functies.
| Linkmethode | Waar de bibliotheekcode tijdens de uitvoering vandaan komt |
|---|---|
| Statisch linken | Code die tijdens de build in het uitvoerbare bestand is opgenomen |
| Dynamisch linken | Een compatibele gedeelde bibliotheek die bij het opstarten of tijdens de uitvoering wordt geladen |
Het resulterende uitvoerbare bestand is gebouwd voor een doelplatform, inclusief een ISA en conventies van het besturingssysteem. De gebruiker heeft de compiler voor compilatie vooraf niet nodig om het uit te voeren. Gedeelde bibliotheken of uitvoeringscomponenten kunnen nog wel nodig zijn.
Begrijp wat een geslaagde build bewijst
In Java doorstaat int count = "five"; vóór de uitvoering een typecontrole niet. Tekst kan die variabele voor gehele getallen niet initialiseren. Het programma moet de vereiste controles tijdens de compilatie doorstaan voordat een uitvoerbare build beschikbaar is.
Andere fouten overleven de compilatie. Een deling door een door de gebruiker ingevoerde nul hangt af van gegevens tijdens de uitvoering. Een programma dat moet aftrekken maar in plaats daarvan optelt, kan draaien en de verkeerde waarde teruggeven. Compilatie controleert taalregels; tests moeten ook controleren of het resultaat bij de bedoelde taak past.
Belangrijkste punten
- Compilatie vertaalt broncode naar doelcode, die geen machinecode hoeft te zijn.
- Compilerfasen controleren tokens, structuur en betekenis voordat ze doelinstructies produceren.
- Optimalisatie behoudt het vereiste gedrag; constantevaluatie berekent constanten tijdens de build.
- Een linker verbindt objectbestanden en bibliotheekcode.
- Een geslaagde build kan nog steeds uitvoeringsfouten of logische fouten bevatten.
1.3 Interpretatie, bytecode en JIT-compilatie
Een programma hoeft geen native uitvoerbaar bestand te worden voordat een gebruiker het start. Een interpreter voert het programma uit via een ander draaiend programma. Veel implementaties combineren interpretatie met compilatie, dus de nuttige vraag is hoe dit specifieke systeem code uitvoert.
Uitvoeren met een interpreter
Een interpreter moet beschikbaar zijn telkens wanneer die het programma uitvoert. Een interactieve REPL herhaalt vier handelingen: invoer lezen, evalueren, een resultaat afdrukken en terugkeren voor de volgende invoer. Dit geeft snel feedback wanneer je kleine expressies uitprobeert.
Interpreteren betekent niet noodzakelijk dat de ruwe broncode regel voor regel wordt gelezen. CPython, de standaardimplementatie van Python, compileert een bestand eerst naar bytecode. Een virtuele machine in software voert vervolgens die interne instructies uit. Bytecode hoort bij die uitvoeringsomgeving; het is niet de native instructieset van de processor.
Waarom sommige fouten eerder verschijnen
Vergelijk twee Python-bestanden. Dit bestand heeft geldige syntaxis:
print("start")
x = 10
print(x + "5")
Het drukt “start” af, koppelt x aan 10 en veroorzaakt vervolgens een TypeError. Het optellen van dat gehele getal bij een tekenreeks mislukt pas wanneer de expressie wordt uitgevoerd. Een vergelijkbare bewerking in een tak die niet wordt bereikt, kan onopgemerkt blijven totdat die tak wordt uitgevoerd.
Verwijder nu het sluithaakje uit de laatste regel. CPython kan het bestand niet compileren omdat de syntaxis onvolledig is. Het meldt een syntaxisfout voordat een instructie in dat bestand wordt uitgevoerd, dus “start” wordt niet afgedrukt. De syntaxiscontrole van het hele bestand en de controle tijdens de uitvoering van een bewerking vinden op verschillende momenten plaats.
Deel bytecode en specialiseer tijdens de uitvoering
Java laat een veelgebruikte hybride route zien. javac Hello.java produceert Hello.class, dat bytecode bevat. java Hello start een compatibele Java Virtual Machine (JVM). Verschillende JVM-implementaties laten die bytecode op verschillende processorfamilies draaien. De bytecode is overdraagbaar tussen compatibele uitvoeringsomgevingen; elke uitvoeringsomgeving moet zelf op het betreffende platform werken. C# richt zich op vergelijkbare wijze op een tussentaal die door .NET wordt gebruikt.
Een just-in-timecompiler (JIT-compiler) genereert machinecode terwijl het programma draait. De HotSpot-uitvoeringsomgeving van Java begint gewoonlijk met interpretatie en compileert veelgebruikte methoden. JavaScript-engines in browsers combineren ook uitvoeringstechnieken, waaronder JIT-compilatie. Veelgebruikte delen compileren kan de snelheid bij langdurige uitvoering verbeteren, maar kost tijd en geheugen tijdens de uitvoering.
| Route | Nuttig voordeel | Kosten om rekening mee te houden |
|---|---|---|
| Vooraf gebouwde native versie | Werk is vóór de start vertaald; de gebruiker heeft geen compiler nodig | Aparte compatibele builds voor doelplatforms |
| Interpretatie | Snelle feedback bij aanpassen en uitvoeren met een geïnstalleerde interpreter | Herhaald werk tijdens de uitvoering kan rekenintensieve code vertragen |
| Bytecode met een VM en JIT | Overdraagbare tussencode met optimalisatie tijdens de uitvoering | Installatie van de uitvoeringsomgeving, opwarmtijd en extra geheugengebruik |
Een lus die één miljard waarden optelt, draait doorgaans veel sneller in gecompileerde C dan in CPython: CPython voert bij veel bewerkingen interpreterwerk en typecontroles uit. Voor een script dat 20 bestanden hernoemt, kan de ontwikkelinspanning veel belangrijker zijn dan dat verschil. Meet de werklast voordat je een taallabel als voorspelling van de snelheid gebruikt.
Gecompileerd en geïnterpreteerd beschrijven keuzes in de implementatie. CPython gebruikt bytecode-interpretatie; PyPy gebruikt JIT-technieken. Een taal kan meer dan één implementatie hebben en één implementatie kan verschillende uitvoeringsmethoden gebruiken.
Belangrijkste punten
- Een interpreter voert een programma uit en moet aanwezig zijn om het te draaien.
- CPython compileert een bestand naar bytecode voordat het die uitvoert.
- Een syntaxisfout kan voorkomen dat een bestand start; een uitvoeringsfout verschijnt wanneer de betreffende bewerking wordt uitgevoerd.
- Een JIT-compiler maakt tijdens de uitvoering native code, vaak voor veelgebruikte delen.
- Uitvoeringsmethoden horen bij implementaties en de prestaties hangen af van de werklast.
2Lees wat korte programma’s doen
Bij het lezen van code spelen twee vragen: volgt de vorm de regels van de taal en wat betekenen de bewerkingen? Een expressie die er hetzelfde uitziet, kan zich in twee talen anders gedragen. Leer fouten indelen en doorloop daarna toekenningen, beslissingen, lussen en functieaanroepen in de volgorde van uitvoering. Een kleine tabel met veranderende waarden maakt het verschil zichtbaar tussen wat een programmeur bedoelde en wat het programma daadwerkelijk doet.
2.1 Syntaxis, betekenis en drie soorten fouten
Syntaxis stelt de regels voor geldige programmatekst vast. Semantiek geeft die tekst betekenis. Een programma kan elke grammaticaregel volgen en toch het verkeerde resultaat berekenen, dus het controleren van de vorm is slechts het eerste deel van het lezen.
Herken de onderdelen en de grammatica
Tokens zijn onder meer sleutelwoorden zoals if, identifiers zoals total, letterlijke waarden zoals 3 of "hello", operatoren zoals + en ==, en leestekens. Een grammatica beschrijft hoe deze onderdelen expressies en instructies kunnen vormen.
Python vereist een dubbele punt na if x > 3: en gebruikt inspringing om de instructies te markeren die bij die voorwaarde horen. C, Java en JavaScript gebruiken accolades voor blokken; C en Java vereisen ook puntkomma’s na veel instructies. In die talen helpt inspringing de menselijke lezer, maar bepaalt die niet tot welk blok code behoort.
Verschillende syntaxis kan een vergelijkbare handeling uitdrukken. Deze instructies drukken een begroeting af:
print("hello")
printf("hello\n");
System.out.println("hello");
De C-escape \n voegt een nieuwe regel toe. Als je het idee van afdrukken kent, helpt dat bij het leren van de manier waarop elke taal dat uitdrukt.
Dezelfde symbolen kunnen verschillende dingen betekenen
| Expressie en taal | Resultaat | Regel |
|---|---|---|
7 / 2 in Python of JavaScript | 3.5 | Deling levert een resultaat met een breukdeel op |
7 / 2 met gehele getallen als operanden in C of Java | 3 | Gehele deling kapt af richting nul |
-7 // 2 in Python | -4 | Deling met afronding naar beneden rondt naar beneden af |
-7 / 2 met gehele getallen in C | -3 | Afkappen gaat richting nul |
"3" * 4 in Python | "3333" | Een tekenreeks wordt herhaald |
"3" * 4 in JavaScript | 12 | Tekst wordt voor de vermenigvuldiging omgezet in een getal |
Dat een symbool er vertrouwd uitziet, bepaalt niet de betekenis ervan in verschillende talen.
Ook de evaluatievolgorde is van belang. Door de voorrang vinden vermenigvuldiging en deling vóór optelling plaats. Met a = 4 en b = 6 deelt average = a + b / 2 eerst 6 door 2 en telt daarna 4 op, wat 7,0 oplevert. De haakjes in average = (a + b) / 2 zorgen dat de optelling eerst gebeurt en geven 5,0.
Maak onderscheid tussen fouten
Een syntaxisfout schendt de grammatica, zoals een ontbrekende dubbele punt in Python. Een uitvoeringsfout ontstaat tijdens een uitgevoerde bewerking, zoals een deling door nul in Python. Een logische fout laat uitvoering toe maar geeft een onbedoeld antwoord, zoals bij het onjuiste gemiddelde.
Sommige geldige expressies slaan bewust een bewerking over. In Python gebruikt x != 0 and 10 / x > 2 kortsluitevaluatie. Wanneer x nul is, is de eerste voorwaarde onwaar, zodat de rechterkant niet wordt geëvalueerd en er geen deling plaatsvindt. Als je deze voorwaarden omdraait, valt die bescherming weg. Python schrijft and, or en not; C, Java en JavaScript gebruiken &&, || en !.
Toekenning en vergelijking vormen een ander belangrijk onderscheid. = slaat een waarde op; == test gelijkheid. C staat if (x = 5) toe: het kent 5 toe en beschouwt die waarde ongelijk aan nul als waar. Python wijst if x = 5: af als ongeldige syntaxis. Dezelfde fout kan daardoor in verschillende talen in een andere fase worden opgemerkt.
Belangrijkste punten
- Syntaxis bepaalt de geldige vorm; semantiek bepaalt de betekenis.
- Operatoren kunnen in verschillende talen een andere betekenis hebben.
- Voorrang en haakjes bepalen welke bewerkingen worden gegroepeerd.
- Syntaxisfouten, uitvoeringsfouten en logische fouten vereisen verschillende soorten controles.
- Kortsluitevaluatie kan een onveilige bewerking overslaan.
- Een toekenning slaat een waarde op; een gelijkheidsvergelijking vergelijkt waarden.
2.2 Volg waarden door het besturingsverloop en functieaanroepen
Een kort programma is gemakkelijker te begrijpen wanneer je telkens één uitgevoerde instructie volgt. Houd de huidige waarden zichtbaar in plaats van van de eerste regel naar een geschat resultaat te springen.
Expressies en toekenningen
Een expressie levert een waarde op: price * 3, x > 10 en len(name) zijn voorbeelden. Een instructie voert een handeling uit, zoals een waarde toekennen of een blok aansturen. In total = price * 3 wordt de expressie rechts geëvalueerd voordat het resultaat aan total wordt toegekend.
Een variabele is een naam die naar een waarde verwijst. Een toekenning geeft de naam zijn huidige waarde; het is geen wiskundige bewering dat beide kanten altijd gelijk zijn geweest. Als count de waarde 4 bevat, leest count = count + 1 eerst 4, telt er 1 bij op en koppelt count aan 5.
Volg de volgende instructie die wordt uitgevoerd
Drie structuren organiseren het besturingsverloop:
- Een sequentie voert instructies op volgorde uit.
- Selectie gebruikt een voorwaarde om een route te kiezen, zoals bij
ifenelse. - Iteratie herhaalt een blok met een
for- ofwhile-lus.
De stelling van het gestructureerde programma, verbonden aan Böhm en Jacopini in 1966, laat zien dat deze structuren elk algoritme kunnen uitdrukken. Een for-lus doorloopt gewoonlijk de elementen van een verzameling. Een while-lus herhaalt zolang de voorwaarde waar blijft.
Bekijk deze Python-lus:
total = 0
for n in [3, 8, 5]:
total = total + n
De ingesprongen toekenning wordt eenmaal voor elk element uitgevoerd. Een tracetabel legt de toestand na elke bijwerking vast:
| Moment | Huidige n | total |
|---|---|---|
| Vóór de lus | Nog geen element | 0 |
| Eerste bijwerking | 3 | 3 |
| Tweede bijwerking | 8 | 11 |
| Derde bijwerking | 5 | 16 |
Elk nieuw totaal gebruikt het vorige. Als je total binnen de lus zou terugzetten, zou dat een andere berekening opleveren omdat eerdere optellingen verloren zouden gaan.
Ga een functie binnen en verlaat die
Een functie groepeert een taak onder een naam. Een parameter is een naam in de definitie; een argument is een waarde die een aanroep doorgeeft:
def area(width, height):
return width * height
result = area(3, 4)
De aanroep koppelt width aan 3 en height aan 4. De vermenigvuldiging levert 12 op en return stuurt die waarde naar de aanroeper. De uitvoering gaat verder bij de toekenning, zodat result de waarde 12 krijgt. De namen beschrijven één taak die met andere argumenten kan worden hergebruikt.
Een naam heeft een bereik
Het bereik bepaalt waar een naam kan worden gebruikt. Namen die binnen een functie worden aangemaakt, zijn gewoonlijk lokaal voor die aanroep. Een naam op het hoogste niveau is globaal binnen de module. Afzonderlijke aanroepen hebben afzonderlijke lokale koppelingen, ook wanneer ze dezelfde namen gebruiken.
count = 7
def reset():
count = 0
reset()
print(count)
Dit drukt 7 af. De toekenning binnen reset maakt een lokale count aan; die vervangt de globale koppeling niet. Een functie kan in plaats daarvan een resultaat teruggeven en de aanroeper dat resultaat laten toekennen.
Bereik en levensduur beantwoorden verschillende vragen. Een lokale naam is niet beschikbaar voor code daarbuiten alleen omdat het object waarnaar die verwees nog bestaat. Teruggegeven objecten kunnen blijven bestaan en een teruggegeven geneste functie kan toegang houden tot variabelen uit de omsluitende aanroep; die bewaarde omgeving maakt deel uit van een closure.
Belangrijkste punten
- Evalueer de expressie rechts voordat je de waarde van het doel van de toekenning bijwerkt.
- Doorloop sequentie, selectie en iteratie in de volgorde waarin ze daadwerkelijk worden uitgevoerd.
- Parameters zijn namen in een definitie; argumenten zijn doorgegeven waarden.
- Een retourwaarde geeft een resultaat terug aan de aanroeper.
- Lokale en globale namen kunnen afzonderlijke koppelingen hebben; bereik verschilt van levensduur.
3Typen, referenties en geheugen
Waarden hebben een type, een plaats in het geheugen en een levensduur nodig. Deze keuzes verklaren waarom tekst die op een getal lijkt rekenkundige bewerkingen kan weigeren, waarom twee namen dezelfde veranderde lijst kunnen tonen en waarom ongebruikte gegevens soms geheugen bezet blijven houden. Vergelijk de controles die talen uitvoeren, volg een functieaanroep door het geheugen en onderzoek drie manieren om geheugen vrij te geven. Elke aanpak verandert de balans tussen controle, gemak en veiligheid.
3.1 Typen bepalen welke bewerkingen zinvol zijn
Een gegevenstype bepaalt mogelijke waarden en de bewerkingen die daarop worden ondersteund. Een geheel getal ondersteunt rekenen met gehele getallen; een booleaanse waarde stelt waar of onwaar voor. Tekenreeksen, lijsten, records en objecten combineren of ordenen andere waarden. Typen sturen ook hoe waarden in het geheugen worden weergegeven, hoewel de grootte van een waarde kan afhangen van de waarde en de implementatie.
Wanneer worden typen gecontroleerd?
| Aanpak | Wanneer controles plaatsvinden | Voorbeeld |
|---|---|---|
| Statische typering | Vóór de uitvoering | Java wijst int count = "five"; af |
| Dynamische typering | Tijdens de uitvoering | Python controleert of een uitgevoerde bewerking bij de operanden past |
In Python is count = 5 gevolgd door count = "five" geldig. De naam verwijst nu naar een tekenreeks. Het evalueren van count + 1 veroorzaakt dan een TypeError. Bij dynamische typering hebben waarden een bepaald type; typen verdwijnen niet.
Statische typering vereist niet dat bij elke variabele zichtbaar een type staat. Met type-inferentie kan een compiler een type afleiden uit de omringende code. Kotlin leidt voor val n = 5 een geheel getal af en Rust kan voor let n = 5; een geheel getal afleiden. De optionele typeaanduidingen van Python zijn anders: aparte hulpmiddelen zoals mypy kunnen ze controleren, maar de gewone uitvoering van Python dwingt ze niet af.
Wanneer worden waarden geconverteerd?
Het controlemoment en de striktheid van conversie zijn aparte vragen. Python wijst "3" + 4 af; expliciete conversie in int("3") + 4 geeft 7. JavaScript past voor verschillende operatoren verschillende regels voor automatische conversie toe:
"3" + 4geeft de tekenreeks"34"."3" * 4geeft het getal12."3" - 4geeft het getal-1.
Automatische conversie heet impliciete typeconversie. De aanduidingen “sterke” en “zwakke” typering zijn informele manieren om de striktheid van conversie te bespreken, geen andere naam voor statische en dynamische typering. Python is dynamisch en strikt bij de gemengde optelling hierboven; C is statisch en staat toch veel impliciete numerieke conversies toe.
De functie input() van Python geeft een tekenreeks terug. Als iemand “30” intypt, heeft het resultaat nog steeds het type str. age + 1 mislukt, terwijl int(age) + 1 de waarde 31 geeft wanneer age die tekst bevat. Wat op het scherm verschijnt, is niet genoeg om het type te bepalen.
Getallen hebben grenzen aan hun representatie
Een geheel getal met teken van 32 bits geeft waarden van −2.147.483.648 tot en met 2.147.483.647 weer. De int van Java springt van het maximum naar het minimum wanneer er 1 bij wordt opgeteld. Overloop van gehele getallen met teken in C is ongedefinieerd gedrag, dus je mag in C niet uitgaan van dezelfde cyclische overloop. Gehele getallen in Python groeien zolang het geheugen dat toelaat: 2 ** 100 is precies 1.267.650.600.228.229.401.496.703.205.376.
Drijvendekommagetallen geven de meeste decimale breuken bij benadering binair weer. In Python en JavaScript levert 0.1 + 0.2 de waarde 0.30000000000000004 op. Een rechtstreekse vergelijking van dat resultaat met 0.3 geeft onwaar.
Als een berekening een kleine fout toelaat, vergelijk je waarden met een tolerantie die voor de taak is gekozen, bijvoorbeeld abs(a - b) < 1e-9. Zo’n kleine absolute tolerantie past slechts bij sommige grootordes. Gebruik voor exact decimaal boekhouden gehele kleinste eenheden of een geschikt decimaal type. De vergelijking veranderen kan een benaderende representatie niet exact maken.
Belangrijkste punten
- Een type bepaalt waarden en ondersteunde bewerkingen.
- Statische en dynamische typering beschrijven het controlemoment, terwijl impliciete typeconversie over conversieregels gaat.
- Type-inferentie ondersteunt statische typering; typeaanduidingen in Python hebben aparte controlehulpmiddelen nodig.
- Gehele getallen met een vaste bitbreedte kunnen overlopen, met gedrag dat afhangt van de taal.
- Afronding van drijvendekommagetallen beïnvloedt gelijkheidstests en de keuze van getalrepresentatie.
3.2 Volg namen, objecten en functieaanroepen in het geheugen
Het hoofdgeheugen slaat de gegevens op van een programma dat draait. Elke byte heeft een adres. Talen bieden namen zodat programmeurs met waarden kunnen werken zonder voor elke bewerking ruwe adressen te schrijven.
Aanroepen gebruiken frames; objecten hebben een levensduur
In C neemt een int gewoonlijk 4 bytes in en haalt &x het adres van x op. Hogere programmeertalen verbergen adressen vaak achter referenties. Het belangrijke onderscheid is dat tussen een koppeling die een object benoemt en het object zelf.
De aanroepstapel houdt actieve functieaanroepen bij. Elke aanroep heeft een stapelframe met informatie zoals parameters, lokale koppelingen en waar de uitvoering na terugkeer moet doorgaan. Als main de functie area(3, 4) aanroept, wordt een frame voor area boven het frame van de aanroeper toegevoegd. De functie berekent 12, keert terug en het frame wordt verwijderd. Bij gewone geneste aanroepen keert de nieuwste aanroep het eerst terug.
Recursie voegt een aanroep toe voor elk nog niet afgerond niveau. Als aanroepen zich blijven opstapelen, kan de beschikbare stapelruimte opraken. Python veroorzaakt normaal gesproken een RecursionError na ongeveer 1.000 geneste aanroepen, waarbij de grens afhangt van de instellingen. Een stopvoorwaarde moet worden bereikt om een eindige recursieve berekening te laten eindigen.
De heap bevat dynamisch toegewezen objecten, zoals een lijst die groeit of een object dat langer bestaat dan de functie die het heeft gemaakt. Geheugentoewijzing reserveert opslagruimte. Een teruggegeven lijst kan op de heap blijven staan nadat de aanroep die de lijst maakte, is geëindigd.
Een referentie kopiëren deelt een object
Een referentie identificeert een object. Een pointer in C of C++ stelt expliciet een adres voor dat wordt gebruikt om toegang te krijgen tot gegevens. Het kopiëren van een referentie kopieert de toegang tot het object, niet alle gegevens van het object.
a = [1, 2]
b = a
b.append(3)
Na de eerste regel is er één lijst. De tweede regel koppelt er een andere naam aan. De laatste regel verandert die lijst, zodat zowel a als b [1, 2, 3] laat zien. Dit delen heet aliasing. Met b = a.copy() zou een afzonderlijke buitenste lijst ontstaan; bij deze lijst met gehele getallen zou toevoegen aan de nieuwe lijst a onveranderd laten.
Vergelijk toekenning met mutatie:
x = 5
y = x
y = y + 1
Het gehele getal 5 is onveranderlijk. De optelling levert 6 op en alleen y wordt opnieuw gekoppeld. x verwijst nog steeds naar 5. Getallen, tekenreeksen en tuples in Python zijn onveranderlijk; lijsten en woordenboeken zijn veranderlijk. De vaste inhoud van een tuple kan een referentie naar een veranderlijke lijst bevatten, dus onveranderlijkheid van de buitenste container bevriest niet elk genest object.
Pas dezelfde regels toe op argumenten
Wanneer Python een lijst aan een functie doorgeeft, verwijst de parameter naar hetzelfde object. Als je binnen de functie items.append(4) aanroept, verandert die lijst. De toekenning items = [] koppelt alleen de lokale parameternaam aan een nieuwe lijst. De naam van de aanroeper blijft aan het oorspronkelijke object gekoppeld.
C geeft argumentwaarden op waarde door. Om een functie de variabele van een aanroeper via haar adres te laten bijwerken, geef je een pointer daarnaar door. Stel bij het doorlopen in elke taal twee aparte vragen: “Naar welk object verwijst deze naam?” en “Verandert deze bewerking dat object of koppelt ze een naam aan iets anders?”
Belangrijkste punten
- De aanroepstapel houdt onafgeronde aanroepen bij; de heap bevat dynamisch toegewezen objecten.
- Een object kan langer bestaan dan de functie die het heeft gemaakt.
- Een referentie kopiëren deelt een object; het dupliceert de gegevens niet.
- Mutatie verandert een object, terwijl opnieuw koppelen verandert waarnaar een naam verwijst.
- Bij het doorgeven van argumenten geldt hetzelfde onderscheid tussen gedeelde objecten en lokale koppelingen.
3.3 Geef geheugen vrij zonder gegevens die nog nodig zijn te verliezen
Toegewezen geheugen moet uiteindelijk opnieuw kunnen worden gebruikt. De uitdaging is gegevens na het laatste gebruik vrij te geven en tegelijk elk object te behouden dat het programma nog nodig heeft. Talen leggen deze verantwoordelijkheid bij de programmeur, een uitvoeringssysteem of door de compiler gecontroleerde eigenaarschapsregels.
Handmatig beheer en wat daarbij misgaat
In C vraagt malloc(n) om n bytes en geeft bij succes een pointer naar het blok terug. free(p) geeft een blok vrij dat door geheugentoewijzing is verkregen. De programmeur moet dat op het juiste moment en precies eenmaal doen. C++ ondersteunt ook handmatig beheer, hoewel de standaardhulpmiddelen vrijgave via de levensduur van objecten kunnen automatiseren.
Verschillende fouten hebben verschillende oorzaken:
| Fout | Wat er misging |
|---|---|
| Geheugenlek | Niet meer benodigde toegewezen opslag wordt niet vrijgegeven |
| Gebruik na vrijgave | Een pointer wordt gebruikt nadat de opslag waarnaar die wijst is vrijgegeven |
| Dubbele vrijgave | Hetzelfde toegewezen blok wordt tweemaal vrijgegeven |
| Bufferoverloop | Een schrijfbewerking gaat voorbij de grenzen van een geheugenblok |
Een pointer die naar vrijgegeven opslag blijft verwijzen, is verweesd. De opslag kan voor iets anders worden hergebruikt, dus toegang ertoe kan gegevens beschadigen of het programma laten vastlopen.
Voor een C-array int scores[10] zijn de geldige posities 0 tot en met 9. Schrijven naar scores[10] gaat voorbij de array en C biedt geen automatische grenscontrole. Python veroorzaakt een IndexError wanneer index 10 op een lijst met tien elementen wordt gebruikt, waardoor die toegang geen aangrenzend geheugen kan overschrijven.
Volg bereikbaarheid of tel referenties
Een garbage collector die bereikbaarheid volgt begint bij actieve wortels, zoals actieve referenties, volgt verbindingen tussen objecten en kan onbereikbare objecten opruimen. Java, C#, Go en JavaScript gebruiken methoden die bereikbaarheid volgen. Opruimen kost tijdens de uitvoering werk en geheugen; afhankelijk van de garbage collector kan het pauzes veroorzaken.
Referentietelling houdt referenties naar een object bij. Voor een nieuwe Python-lijst is de vereenvoudigde reeks: a = [1, 2] levert één referentie; b = a levert een tweede; del a verwijdert er één; b = None verwijdert de andere. CPython ruimt deze gewone lijst normaal gesproken op wanneer het aantal nul bereikt.
Een groep objecten kan elkaars aantallen boven nul houden, ook wanneer geen actief deel van het programma de groep kan bereiken. Dit is een referentiecyclus. CPython vult referentietelling aan met het opruimen van cycli. Referentietelling en het volgen van bereikbaarheid zijn beide technieken voor automatisch geheugenbeheer.
Controleer eigenaarschap vóór de uitvoering
Rust geeft elke waarde een eigenaar en controleert regels voor het lenen van referenties. Wanneer het eigenaarschap eindigt, geeft gegenereerde opruimcode de waarde vrij. Veilige Rust wijst gebruik af van referenties die langer zouden kunnen bestaan dan het geldige doel, zonder een garbage collector nodig te hebben die bereikbaarheid volgt. Swift gebruikt automatische referentietelling die door de compiler wordt ingevoegd.
Geheugenveiligheid is belangrijk om meer redenen dan vastlopende programma’s. Microsoft meldde in 2019 dat ongeveer 70% van de kwetsbaarheden waaraan het bedrijf jaarlijks CVE’s toekende, met geheugenveiligheid te maken had; Chromium meldde in 2020 een vergelijkbaar aandeel ernstige fouten. In december 2023 drongen CISA, NSA, FBI en internationale partners er bij softwaremakers op aan de overstap naar geheugenveilige talen te plannen.
Automatisch opruimen behoudt nog steeds bereikbare objecten. Een steeds groeiende globale lijst kan elk oud record in leven houden, ook nadat de applicatie het niet meer gebruikt. Een garbage collector kan niet bepalen dat deze bereikbare records voor de taak overbodig zijn geworden; het programma moet stoppen ze vast te houden.
Belangrijkste punten
- Handmatige geheugentoewijzing vereist vrijgave op het juiste moment.
- Geheugenlekken, gebruik na vrijgave, dubbele vrijgave en bufferoverloop hebben verschillende oorzaken.
- Het volgen van bereikbaarheid vindt onbereikbare objecten; referentietelling reageert op verdwenen referenties.
- CPython vult referentietelling aan met het opruimen van cycli.
- Eigenaarschap kan vrijgave regelen zonder een garbage collector die bereikbaarheid volgt.
- Automatisch opruimen kan geen objecten vrijmaken die het programma nog vasthoudt.
4Organiseer een veranderende toestand en samenwerkende objecten
Een programma kan worden beschreven als opdrachten die waarden bijwerken. Een andere manier groepeert die waarden met de bewerkingen die ze beheersen. Deze zienswijzen werken vaak samen: een objectmethode kan een gewone lus bevatten. Volg eerst een veranderende toestand en bouw daarna een klein rekeningmodel om te zien hoe klassen regels beschermen en hoe overerving, polymorfisme en compositie verwante objecten een interface laten delen zonder verkeerde verantwoordelijkheden op zich te nemen.
4.1 Opdrachten, procedures en gedeelde toestand
Een programmeerparadigma is een manier om code te organiseren en over berekeningen na te denken. Bij imperatief programmeren geeft een programma opdrachten die zijn toestand veranderen: de waarden die variabelen en objecten op dat moment bevatten.
Maak de toestandsveranderingen zichtbaar
Een lopend totaal is een eenvoudige imperatieve berekening:
total = 0
for n in [3, 8, 5]:
total = total + n
De begintoestand is total = 0. Door 3 op te tellen verandert die in 3. Door 8 op te tellen verandert die in 11. Door 5 op te tellen verandert die in 16. Elke toekenning gebruikt de toestand die de vorige stap achterliet, dus de volgorde maakt deel uit van de methode.
Deze stijl sluit aan bij de stapsgewijze veranderingen die de processor in registers en geheugen aanbrengt. De stijl past ook bij algoritmen die bewust een verzameling ter plekke bijwerken, zoals een sorteerroutine die elementen verwisselt.
Geef taken namen
Procedureel programmeren organiseert imperatieve code in functies of procedures. Een rapportageprogramma kan read_scores(), average(scores) en print_report(avg) aanroepen. Dankzij de namen kan een lezer de taak als geheel zien zonder meteen elke interne regel te volgen. Elke procedure kan afzonderlijk worden begrepen en getest.
C, Pascal, Fortran en COBOL zijn klassieke procedurele talen. Python ondersteunt deze stijl ook: een script kan benoemde functies gebruiken zonder klassen te definiëren.
Gestructureerd programmeren organiseert het besturingsverloop met sequentie, selectie, lussen en procedureaanroepen. Het vermijdt willekeurige sprongen die moeilijk te voorspellen maken welke instructie daarna wordt uitgevoerd. De brief “Go To Statement Considered Harmful” uit 1968 van Edsger Dijkstra hielp deze aanpak populair te maken. Gecontroleerde uitwegen zoals break, continue en return blijven nuttig: ze verlaten een bepaalde lusiteratie, lus of functie.
Effecten kunnen verder gaan dan een retourwaarde
Een neveneffect verandert iets buiten het teruggegeven resultaat van een functie. Afdrukken, een bestand opslaan, een netwerkbericht versturen en gedeelde gegevens veranderen zijn neveneffecten. Programma’s hebben zulke handelingen nodig om met de wereld te communiceren.
Problemen ontstaan wanneer niet-verwante code ongemerkt dezelfde toestand verandert. Stel dat apply_discount() een globale price met 10% verlaagt en add_shipping() er 5 bij optelt. Als je begint met 100, geeft eerst de korting toepassen 90 en daarna verzending toevoegen 95. Eerst verzending toevoegen geeft 105 en daarna de korting toepassen geeft 94,5. Beide procedures voeren hun eigen stappen correct uit, maar het gecombineerde resultaat hangt af van hun volgorde.
Door waarden door te geven en nieuwe resultaten terug te geven, kun je die volgorde zichtbaar maken:
price = add_shipping(apply_discount(100))
De binnenste aanroep wordt eerst uitgevoerd: korting, daarna verzending. Een andere nesting drukt een andere regel uit. Een duidelijke gegevensstroom helpt te zien of ook op de verzending korting moet worden gegeven.
| Organisatie | Belangrijkste vraag tijdens het lezen |
|---|---|
| Imperatieve opdrachten | Welke waarde verandert hierna? |
| Benoemde procedures | Welke taak voert deze aanroep uit? |
| Gedeelde globale bijwerkingen | Welke eerdere handeling veranderde deze waarde? |
Imperatieve code komt nog steeds veel voor binnen moderne objectmethoden en gebeurtenisafhandelaars. Een breder objectgeoriënteerd of functioneel ontwerp kiezen, verhindert niet dat een klein onderdeel directe opdrachten gebruikt waar die stappen het algoritme duidelijk maken.
Belangrijkste punten
- Imperatieve code beschrijft stappen die de opgeslagen toestand veranderen.
- Procedures geven samenhangende taken namen en grenzen.
- Een gestructureerd besturingsverloop maakt uitvoeringsroutes gemakkelijker te volgen.
- Neveneffecten omvatten veranderingen in gedeelde gegevens en interacties met de buitenwereld.
- Door gedeelde bijwerkingen kunnen resultaten afhangen van de aanroepvolgorde.
4.2 Klassen houden gegevens bij hun regels
Objectgeoriënteerd programmeren (OOP) modelleert een programma als samenwerkende objecten. Elk object combineert gegevens met bewerkingen op die gegevens. Een rekening kan de eigenaar en het saldo bevatten en tegelijk bewerkingen voor storten en opnemen aanbieden.
Maak onderscheid tussen de blauwdruk en de instanties
Een klasse definieert de attributen en methoden die haar objecten bieden. Een object, of instantie, is één specifiek exemplaar van die klasse. BankAccount is de blauwdruk; de rekening van Ana met 150 en de rekening van Ben met 40 zijn afzonderlijke instanties. Hun saldi kunnen onafhankelijk van elkaar veranderen.
Een attribuut slaat gegevens op die bij een object horen. Een methode is een bewerking die via een object wordt aangeroepen, zoals acct.deposit(50). self in een Python-instantiemethode verwijst naar het object dat de aanroep ontvangt.
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self._balance = balance
def deposit(self, amount):
if amount <= 0:
raise ValueError("amount must be positive")
self._balance = self._balance + amount
De aanroep BankAccount("Ana", 100) construeert een nieuwe instantie. De methode __init__ initialiseert de gegevens van die instantie. Een constructor is het proces dat een nieuw object klaar voor gebruik maakt; Python scheidt het maken van deze initialisatiestap. De aanroep acct.deposit(50) op die rekening verandert het saldo van 100 naar 150.
Plaats de regel waar de bijwerking gebeurt
Inkapseling brengt gegevens en de bewerkingen die de regels ervoor bewaken samen. Stel dat een rekening nooit een negatief saldo mag hebben. De opnamemethode controleert de voorgestelde verandering voordat die wordt uitgevoerd:
def withdraw(self, amount):
if amount <= 0:
raise ValueError("amount must be positive")
if amount > self._balance:
raise ValueError("insufficient funds")
self._balance = self._balance - amount
Deze methode hoort binnen BankAccount. Bij een saldo van 150 veroorzaakt withdraw(200) een fout en blijft 150 over. withdraw(30) slaagt en laat 120 over. De mislukte aanroep trekt niet eerst af om daarna het probleem te ontdekken; de controle vindt vóór de bijwerking plaats.
Andere code hoort die methoden te gebruiken in plaats van een ongecontroleerd saldo toe te kennen. Java en C# kunnen directe toegang beperken met private. De voorloopunderscore in _balance van Python geeft volgens afspraak een intern detail aan; die dwingt geen afscherming af.
Informatieverberging gaat verder: aanroepers zijn afhankelijk van de bewerkingen die een klasse aanbiedt zonder de interne representatie te hoeven kennen. Het saldo zou later anders kunnen worden opgeslagen terwijl de openbare stortings- en opnamebewerkingen hun betekenis behouden.
Laat het probleem verantwoordelijkheden suggereren
Een bibliotheeksysteem heeft boeken, leden en uitleningen. Die zelfstandige naamwoorden suggereren mogelijke klassen of attributen; handelingen zoals lenen en verlengen suggereren methoden. Kies voor elke klasse één duidelijke verantwoordelijkheid. Een rekeningklasse die ook elke e-mail verstuurt en elk rapport opmaakt, wordt moeilijker te veranderen omdat niet-verwante taken worden gemengd.
Klassen en objecten verschenen in Simula 67 in 1967; Smalltalk hielp de aanpak in de jaren 1970 te verspreiden. Java, C#, C++ en Python ondersteunen OOP, maar het gebruik van een van die talen dwingt niet elk programma in klassen. De nuttige ontwerpvraag is welke gegevens en regels bij elkaar horen.
Belangrijkste punten
- Een klasse definieert attributen en methoden; elke instantie heeft eigen instantiegegevens.
- Methoden werken op het object dat de aanroep ontvangt.
- Inkapseling brengt validatie en toestandsveranderingen samen.
- Underscores in Python duiden volgens afspraak interne attributen aan; sommige talen dwingen afgeschermde toegang af.
- Door informatieverberging kunnen aanroepers afhangen van de openbare bewerkingen van een klasse in plaats van de interne werking.
- Een klasse hoort een samenhangende verantwoordelijkheid te hebben.
4.3 Specialiseer gedrag en combineer objecten
Objecten moeten vaak bewerkingen delen terwijl ze in details verschillen. Overerving, polymorfisme en compositie lossen verschillende delen van dat ontwerpprobleem op.
Specialiseer een bestaande soort object
Overerving definieert een subklasse vanuit een superklasse. In Python geeft class SavingsAccount(BankAccount): de gespecialiseerde klasse toegang tot geërfd rekeninggedrag. Ze kan add_interest(rate) toevoegen: een saldo van 1000 wordt met 2% rente 1020.
De bedoelde relatie is is-een. Een spaarrekening is nog steeds een bankrekening en moet voldoen aan de regels die voor bankrekeningen worden verwacht. Een paar regels hergebruiken is niet genoeg reden om te stellen dat de ene soort object een andere is.
Een subklasse kan een methode overschrijven met een eigen implementatie. Stel dat een betaalrekening 1 aan kosten rekent voor elke geslaagde opname. Bij een saldo van 100 kost een opname van 30 dan 31 en blijft 69 over. Om de rekeningregel te behouden, moet de methode de opname plus de kosten controleren voordat het saldo verandert. Als die gecombineerde kosten hoger zijn dan het saldo, weigert de methode de hele bewerking.
Laat één aanroep met verschillende objecten werken
Polymorfisme laat dezelfde bewerking op verschillende objecttypen werken. Een cirkel en een rechthoek kunnen beide area() aanbieden:
for shape in shapes:
print(shape.area())
De lus heeft geen if nodig voor elke vormklasse. Elk object levert zijn eigen implementatie. Het selecteren van die implementatie tijdens de uitvoering heet dynamische methodekeuze.
Voor Circle(2) is de oppervlakte ongeveer 12,57 vierkante eenheden. Voor Rectangle(3, 4) is die 12 vierkante eenheden. Hun gezamenlijke oppervlakte is ongeveer 24,57 vierkante eenheden. Een latere Triangle kan aan de verzameling worden toegevoegd door een compatibele bewerking area() aan te bieden; de lus zelf heeft geen extra tak nodig.
Een interface geeft aan welke bewerkingen een type moet aanbieden. De Java-interface interface Shape { double area(); } vereist dat implementerende klassen de oppervlaktemethode leveren. Een abstracte klasse kan een deel van de gedeelde implementatie bieden en andere methoden overlaten aan subklassen. Python gebruikt vaak ducktyping: een object kan in de lus werken als het de vereiste bewerking biedt, ook zonder een gedeelde superklasse.
Bevat een onderdeel in plaats van het te worden
Compositie slaat andere objecten op als onderdelen. Een auto heeft een motor en wielen. De auto kan de methode start() van de motor aanroepen zonder van Engine te erven.
| Relatie | Geschikt ontwerp | Reden |
|---|---|---|
| Een spaarrekening is een rekening | Overerving | Die behoudt het verwachte gedrag van de rekening |
| Een auto heeft een motor | Compositie | De motor is een onderdeel, niet de categorie van de auto |
| Verschillende vormen bieden oppervlakte | Gedeelde bewerking of interface | De aanroeper kan de bewerking op dezelfde manier aanvragen |
Diepe overervingsketens kunnen de gevolgen van een verandering in een superklasse door veel klassen verspreiden. Een subklasse die geërfd gedrag moet uitschakelen, kan wijzen op een slechte is-eenrelatie. Compositie houdt zulke afhankelijkheden vaak kleiner en duidelijker.
De vier veelgenoemde pijlers van OOP passen hier samen: inkapseling beschermt gegevens via bewerkingen, abstractie verbergt implementatiedetails, overerving specialiseert een type en polymorfisme laat een aanroeper een gedeelde bewerking op verschillende implementaties gebruiken.
Belangrijkste punten
- Overerving modelleert een is-eenrelatie en moet het verwachte gedrag behouden.
- Methodeoverschrijving vervangt een methode-implementatie in een gespecialiseerde klasse.
- Polymorfisme laat één bewerking verschillende implementaties aanroepen.
- Een interface geeft vereiste bewerkingen aan; ducktyping vertrouwt op de bewerkingen die een object biedt.
- Compositie modelleert objecten die andere objecten als onderdelen hebben.
5Bereken nieuwe waarden en beschrijf gewenste resultaten
Een programma kan zich richten op transformaties van waarden of op het resultaat dat een engine moet produceren. Zuivere functies maken afhankelijkheden expliciet, terwijl onveranderlijke gegevens veranderingen via gedeelde referenties voorkomen. Declaratieve query’s laten een gespecialiseerde engine uitvoeringsstappen kiezen. Volg een gegevensverwerkingsketen en een databasequery om te zien wat deze aanpakken gemakkelijker maken en waar invoer, uitvoer en concrete machine-instructies nog steeds een rol spelen.
5.1 Zuivere functies en gegevensverwerkingsketens
Functioneel programmeren bouwt resultaten op door functies te combineren. De centrale werkwijze is nieuwe waarden uit invoer produceren en tegelijk gedeelde mutatie beperken. Lisp introduceerde rond 1958 veelgebruikte functionele ideeën; Haskell, Erlang, Clojure, F# en OCaml ontwikkelen die ideeën op verschillende manieren verder.
Weet wat een functie zuiver maakt
Een zuivere functie geeft hetzelfde resultaat voor dezelfde argumenten en heeft geen neveneffecten. def with_tax(price): return price * 1.2 illustreert het idee: with_tax(50) geeft 60.0. Een functie die ook iets aan een globale lijst toevoegt of een bericht afdrukt, heeft een effect naast de teruggegeven waarde. Een resultaat dat afhangt van de huidige tijd hangt ook van meer af dan de argumenten.
Een zuivere aanroep kan worden vervangen door de teruggegeven waarde zonder het gedrag van het programma te veranderen. Deze eigenschap heet referentiële transparantie. Ze maakt afzonderlijke tests eenvoudig en laat onafhankelijke aanroepen parallel draaien zonder te concurreren om een gedeelde toestand bij te werken.
Onveranderlijkheid houdt een bestaande waarde ongewijzigd. [n + 1 for n in nums] maakt een nieuwe lijst en laat nums intact. Een programma kan veranderende informatie nog steeds weergeven door nieuwe versies te maken; het hoeft de oude versie niet te overschrijven.
Geef gedrag als gegevens door
Functies zijn eersteklasfuncties wanneer ze aan namen kunnen worden toegekend, als argument kunnen worden doorgegeven en als waarden kunnen worden teruggegeven. Een functie van hogere orde ontvangt een functie of geeft er een terug. Een lambda-expressie is een naamloze functie-expressie, zoals lambda n: n * n.
Drie veelvoorkomende bewerkingen van hogere orde vormen een verwerkingsketen:
filterbehoudt elementen die aan een test voldoen.maptransformeert elk overgebleven element.reducecombineert elementen met een accumulator tot één resultaat.
Om de kwadraten van de even getallen van 1 tot en met 6 op te tellen, blijven na het filteren 2, 4 en 6 over; kwadrateren geeft 4, 16 en 36; optellen geeft 56.
sum(map(lambda n: n * n,
filter(lambda n: n % 2 == 0, range(1, 7))))
Hier controleert % 2 == 0 of een getal even is en levert range(1, 7) 1 tot en met 6. Haskell drukt dezelfde berekening uit als sum (map (^2) (filter even [1..6])).
Met reduce, geïmporteerd uit functools van Python, combineert reduce(lambda acc, n: acc + n, [3, 8, 5], 0) eerst 0 met 3 tot 3, dan met 8 tot 11 en dan met 5 tot 16. De verwerkingsketen beschrijft transformaties zonder een omringende lus die een gedeeld totaal bijwerkt.
Bewaar een omgeving of roep een kleiner probleem aan
def multiplier(k): return lambda x: k * x geeft een functie terug die toegang tot k behoudt. Daardoor geeft multiplier(3)(10) de waarde 30 terug, ook nadat de buitenste aanroep is afgerond. De teruggegeven functie en de bewaarde omgeving vormen een closure.
Bij recursie roept een functie zichzelf aan op een kleiner probleem. De faculteitsfunctie geeft 1 terug bij n == 0; anders geeft ze n * factorial(n - 1) terug. Voor 5 geven de vermenigvuldigingen 120. Een eindige berekening heeft een bereikbaar basisgeval nodig. Niet-afgeronde geneste aanroepen in Python veroorzaken uiteindelijk een RecursionError; talen met volledige ondersteuning voor staartaanroepen kunnen stapelruimte hergebruiken voor aanroepen op de laatste positie.
Alleen functies gebruiken maakt code niet functioneel: procedurele functies kunnen de toestand veranderen. Echte functionele programma’s lezen nog steeds invoer en schrijven uitvoer, waarbij ze effecten gewoonlijk aan de randen van het programma houden en de centrale berekeningen zuiver.
Belangrijkste punten
- Zuivere functies hangen alleen af van hun argumenten en hebben geen neveneffecten.
- Bijwerken met onveranderlijke gegevens maakt nieuwe waarden.
- Eersteklasfuncties kunnen worden doorgegeven en teruggegeven; functies van hogere orde ontvangen of retourneren functies.
- Filter selecteert, map transformeert en reduce combineert.
- Een closure bewaart de omgeving waarin die is aangemaakt.
- Een eindige recursieve berekening heeft een basisgeval nodig dat daadwerkelijk wordt bereikt.
5.2 Beschrijf een resultaat en laat een engine het vinden
Declaratief programmeren beschrijft het gewenste resultaat en laat de uitvoeringsstappen over aan een taalengine. Functionele code wordt gewoonlijk onder dit brede idee geschaard, naast querytalen en logisch programmeren.
Lees een query als een verzoek
Een relationele database slaat gegevens op in tabellen. Stel dat students de rijen met naam en cijfer Ana 92, Ben 75, Chen 80 en Dara 64 bevat:
SELECT name
FROM students
WHERE grade >= 80
ORDER BY name;
FROM noemt de tabel, WHERE geeft de voorwaarde, SELECT kiest het teruggegeven veld en ORDER BY vraagt om alfabetische volgorde. Ana en Chen voldoen aan de voorwaarde, ook met Chens cijfer van precies 80. Het resultaat bestaat uit hun namen in die volgorde.
Een imperatief programma zou een resultatenlijst maken, rijen doorlopen, elk cijfer testen, namen die voldoen toevoegen en de lijst sorteren. SQL geeft de database het vereiste resultaat zonder die lus uit te schrijven.
Een queryoptimizer kiest een uitvoeringsplan. Die kan de tabel doorlopen of een beschikbare index gebruiken, een opzoekstructuur die geschikte zoekopdrachten kan versnellen. Veel indexen gebruiken gesorteerde structuren; andere gebruiken een andere ordening. Een index toevoegen kan het plan veranderen zonder de query te veranderen. Meer gegevens betekenen nog steeds werk: het resultaat declareren garandeert niet dat elke query weinig werk kost.
Vraag wat uit feiten en regels volgt
Logisch programmeren beschrijft feiten en relaties en zoekt vervolgens naar waarden die aan een vraag voldoen. In Prolog:
parent(tom, bob).
parent(bob, ann).
grandparent(X, Z) :- parent(X, Y), parent(Y, Z).
De regel betekent dat X een grootouder van Z is wanneer er een Y bestaat waarvoor beide ouderrelaties gelden. De query ?- grandparent(tom, Who). vindt Who = ann: Tom is een ouder van Bob en Bob is een ouder van Ann. Namen met een hoofdletter staan voor variabelen; de namen in kleine letters duiden specifieke personen aan.
Herken declaratieve hulpmiddelen om je heen
Een spreadsheetformule =SUM(B2:B5) beschrijft een afhankelijkheid. Het spreadsheetprogramma berekent die opnieuw wanneer relevante cellen veranderen. De reguliere expressie [0-9]{3}-[0-9]{4} beschrijft drie cijfers, een koppelteken en vier cijfers, zoals in “555-0199”. Een tekstzoekengine voert de zoekopdracht uit.
CSS beschrijft het uiterlijk met een regel zoals h1 { color: navy; }; HTML markeert de structuur van een pagina. Het zijn declaratieve notaties die gewoonlijk als stijl- en opmaaktalen worden ingedeeld. Hun rol verschilt van die van een taal voor algemene doeleinden die een breed scala aan berekeningen kan uitdrukken.
| Soort taal | Gericht op | Voorbeelden |
|---|---|---|
| Domeinspecifieke taal | Een bepaalde soort taak | SQL, reguliere expressies, CSS |
| Taal voor algemene doeleinden | Veel soorten programma’s | Python, Java, Haskell |
Declaratieve code kan dicht bij een probleemstelling blijven en een engine de methode laten verbeteren. Ze geeft de programmeur ook minder directe controle over afzonderlijke stappen, wat het moeilijker kan maken de oorzaak van een traag plan vast te stellen. Daaronder worden nog steeds concrete instructies uitgevoerd: declaratief beschrijft hoe de programmeur de taak uitdrukt, niet een processor die zonder stappen werkt.
Belangrijkste punten
- Declaratieve code beschrijft een gewenst resultaat; een engine kiest de uitvoeringsstappen.
- SQL selecteert rijen en velden met voorwaarden en een gevraagde volgorde.
- Een optimizer kan een queryplan veranderen zonder de query te veranderen.
- Logisch programmeren leidt antwoorden af uit feiten en regels.
- Domeinspecifieke talen richten zich op bepaalde taken; talen voor algemene doeleinden ondersteunen veel soorten programma’s.
6Kies een stijl en een taal voor de taak
Verschillende programmeerstijlen kunnen hetzelfde antwoord opleveren terwijl ze het werk anders organiseren. Door ze bij één taak te vergelijken, worden hun afwegingen duidelijker. Bij de keuze van een taal komen vervolgens praktische randvoorwaarden: het doelplatform, beschikbare bibliotheken, geheugen- en snelheidsgrenzen, veiligheidseisen en de mensen die de code onderhouden. Een project met verschillende onderdelen kan voor elk onderdeel een andere taal en een andere combinatie van stijlen gebruiken.
6.1 Eén taak in verschillende programmeerstijlen
Een paradigma verandert hoe een oplossing wordt uitgedrukt, niet welk numeriek antwoord een correcte oplossing moet opleveren. Neem bestellingen met bedragen [120, 45, 300, 80]: bepaal het totaal van de bedragen die strikt groter zijn dan 100.
Volg veranderingen in de toestand
De imperatieve versie toont de lus en accumulator:
total = 0
for amount in amounts:
if amount > 100:
total = total + amount
Het eerste bedrag verandert total in 120. Het tweede laat de waarde onveranderd. Het derde verandert de waarde in 420 en het vierde laat die daar staan. Het programma maakt de volgorde van de test en de bijwerking zichtbaar.
Transformeer een verzameling
Een compacte Python-expressie in functionele stijl is sum(a for a in amounts if a > 100). Die selecteert 120 en 300 en combineert ze tot 420. Een andere versie is sum(filter(lambda a: a > 100, amounts)). Hier beschrijft het predicaat welke waarden erbij horen en combineert sum de geselecteerde waarden.
Een generatorexpressie of filter-aanroep gebruikt de mogelijkheden van Python voor meerdere paradigma’s; de aanwezigheid ervan maakt niet elk omringend deel van de applicatie zuiver.
Plaats regels bij objecten
In een objectgeoriënteerd ontwerp slaat een Order een amount op en biedt die is_large() aan, dat self.amount > 100 teruggeeft. Een OrderBook bevat bestelobjecten en biedt large_total() aan. Die methode kan bestellingen doorlopen, bij elk object vragen of het voldoet en de bedragen die voldoen optellen.
Het totaal blijft 420. Het verschil zit in waar de verantwoordelijkheden liggen: de regel voor een grote bestelling hoort bij Order en het werk op verzamelingsniveau hoort bij OrderBook. Andere functionaliteiten kunnen de regel hergebruiken zonder de drempeltest te kopiëren.
Vraag het de database
Als de gegevens al in een relationele database staan, kan SQL het totaal opvragen:
SELECT SUM(amount)
FROM orders
WHERE amount > 100;
De database kiest het plan voor ophalen en aggregeren. De query geeft een aanroeper geen opdracht om elke rij te bezoeken of een lokale accumulator bij te houden.
| Stijl | Wat het voorbeeld benadrukt |
|---|---|
| Imperatief | Tests en bijwerkingen van een totaal in volgorde |
| Objectgeoriënteerd | Bestellingen en verzamelingsobjecten met vastgelegde verantwoordelijkheden |
| Functioneel | Selectie en combinatie van waarden |
| Declaratieve SQL | Het vereiste resultaat over opgeslagen gegevens |
Combineer stijlen waar ze helpen
Python ondersteunt procedurele, objectgeoriënteerde en functionele technieken. JavaScript biedt objecten, eersteklasfuncties en closures. C++, C#, Kotlin, Scala, Swift en Rust mengen ook stijlen. Java wordt sterk met OOP geassocieerd maar ondersteunt sinds Java 8 in 2014 lambda-expressies; Haskell is zuiver functioneel.
Een webapplicatie kan klassen voor gebruikers en bestellingen gebruiken, functionele transformaties voor rapportgegevens, imperatieve bijwerkingen binnen methoden en SQL voor databasequery’s. Deze keuzes werken op verschillende lagen.
Gebeurtenisgestuurd programmeren voegt een ander organisatieprincipe toe: registreer een afhandelaar die wordt uitgevoerd wanneer een gebeurtenis binnenkomt. In JavaScript geeft button.addEventListener("click", showMenu) de functie showMenu als waarde door. Een latere klik roept die aan. De afhandelaar kan zelf objecten, zuivere transformaties of imperatieve stappen gebruiken. De gebeurtenisgestuurde rol zegt wanneer de afhandelaar draait, niet dat de inhoud maar één stijl mag gebruiken.
Belangrijkste punten
- Correcte oplossingen in verschillende paradigma’s kunnen hetzelfde resultaat berekenen.
- Een stijl verandert waar stappen, toestand, regels en verantwoordelijkheden voorkomen.
- Moderne talen ondersteunen gewoonlijk meerdere paradigma’s.
- Een gebeurtenisafhandelaar draait als reactie op een gebeurtenis en kan intern andere stijlen gebruiken.
6.2 Kies op basis van randvoorwaarden en afwegingen
Geen enkele programmeertaal past het best bij elk project. Talen voor algemene doeleinden zijn doorgaans turingvolledig: met onbeperkte tijd en geheugen kunnen ze dezelfde klasse berekeningen uitdrukken. Echte keuzes hangen af van de vraag of een oplossing past bij het daadwerkelijke platform, de middelen, de hulpmiddelen en de mensen.
Begin met wat moet werken
Het platform is vaak een harde randvoorwaarde. Browsers voeren JavaScript uit en kunnen gecompileerde modules via WebAssembly uitvoeren. TypeScript voegt statische controles toe en compileert naar JavaScript. Swift is een gebruikelijke keuze voor apps op Apple-platforms; Kotlin wordt aanbevolen voor Android. SQL is de gebruikelijke taal voor relationele query’s.
Kijk daarna naar het domein en het ecosysteem. Python heeft veelgebruikte bibliotheken voor gegevens en machinelearning, zoals NumPy en pandas. R richt zich op statistiek. C en C++ komen veel voor in besturingssystemen, game-engines en code voor ingebedde systemen. Java en C# komen veel voor in grote bedrijfssystemen; Bash is nuttig voor het automatiseren van taken van het besturingssysteem.
Dit zijn praktische verbanden, geen exclusieve toepassingsgebieden. Een geschikte bibliotheek kan veel meer ontwikkelwerk besparen dan een klein verschil in de syntaxis van talen.
Onderzoek de behoeften aan middelen en veiligheid
Een microcontroller met slechts enkele kilobytes RAM heeft mogelijk nauwkeurige controle over het geheugen en de overhead tijdens de uitvoering nodig. C, C++ of Rust kan geschikt zijn waar het doelplatform die talen ondersteunt. Ook voor veeleisende onderdelen van games of databases gaat de voorkeur vaak uit naar die talen, maar engines en beschikbare middelen verschillen: Unity gebruikt C# en sommige databases gebruiken Java of Go.
Zoek voordat je op snelheid kiest het knelpunt: het onderdeel dat de prestaties beperkt. Een programma dat vooral op een netwerkantwoord wacht, wordt niet veel sneller alleen omdat een kleine lokale lus efficiënter wordt gecompileerd. Meet representatief werk op het doelplatform.
Geheugenveiligheid vermindert fouten zoals ongeldige geheugentoegang. Veilige Rust controleert eigenaarschapsregels tijdens de compilatie; beheerde talen zoals Java, C#, Go en Python bieden geheugenbeheer tijdens de uitvoering. Statische typering vangt veel incompatibele bewerkingen vóór de uitvoering op, wat in een grote codebase kan helpen. Noch geheugenveiligheid noch statische typering bewijst dat de logica van het programma correct is.
Neem de kosten voor onderhoud van het resultaat mee
De kennis van het team, beschikbare ontwikkelaars, debuggers, testhulpmiddelen, bibliotheken, licenties en bestaande code beïnvloeden allemaal de beslissing. Een vertrouwde, ondersteunde taal kan de kosten voor oplevering en onderhoud verlagen. Een werkend systeem herschrijven brengt migratiewerk en risico’s mee, ook als de nieuwe taal aantrekkelijke eigenschappen heeft.
Voor een stedelijk weernetwerk hebben verschillende onderdelen verschillende behoeften:
| Onderdeel | Redelijke taalkeuze | Reden |
|---|---|---|
| Firmware voor kleine sensoren | C of Rust | Controle over geheugen en ondersteunde hardwaredoelen |
| Opgeslagen metingen | SQL | Filteren en aggregeren in de database |
| Opschonen en analyseren | Python | Bibliotheken voor gegevensverwerking |
| Interactieve weergave in de browser | JavaScript of TypeScript | Browserondersteuning en hulpmiddelen voor interfaces |
Een nuttige onderbouwing verbindt elke keuze aan een vereiste: “Het team onderhoudt de Java-service al en de bibliotheken ondersteunen de nieuwe functionaliteit.” Een plaats op een populariteitsranglijst alleen kan die redenering niet leveren. Enquêtes kunnen wijzen op een grote gemeenschap of een ruime groep beschikbare ontwikkelaars; ze stellen niet vast wat de beste taal voor een bepaalde werklast is.
Belangrijkste punten
- Controleer randvoorwaarden van het platform voordat je naar voorkeuren kijkt.
- Bibliotheken en hulpmiddelen verbinden een taal met het probleemdomein.
- Meet het knelpunt voordat je vooral op snelheid kiest.
- Geheugenveiligheid en statische controles verminderen sommige fouten, maar de logica moet nog steeds worden getest.
- Vaardigheden van het team, bestaande code en onderhoudskosten horen bij de beslissing.
- Verschillende onderdelen kunnen verschillende talen rechtvaardigen.
Programmeertalen: voorbeelden
1Volg een programma van broncode tot uitvoering
13 stappen
- 1IntroductieEén berekening doorkruist verschillende lagen
Je schrijft één berekening, maar een processor heeft kleine gecodeerde instructies nodig die machinecode worden genoemd. Een vertaalprogramma levert de ontbrekende details.
Volg
x = a + bvanaf de broncode naar lagere niveaus en volg het programma daarna door het bouwen en uitvoeren. Houd twee vragen apart: in welke vorm is de code en welke software voert die uit? Een compiler, een interpreter en een JIT-compiler beantwoorden de tweede vraag verschillend. Dat onderscheid verklaart zowel overdraagbaarheid als het moment waarop fouten optreden. - 2VoorbeeldEén berekening wordt meerdere handelingen
Stel je voor
x = a + bvier handelingen op een lager niveau voor:- Laad de waarde van
ain een register. - Laad de waarde van
bin een ander register. - Tel de twee registerwaarden op.
- Sla het resultaat voor
xop.
De instructie in een hogere programmeertaal verbergt de registerkeuze en opslagdetails. De daadwerkelijk gegenereerde instructies kunnen verschillen omdat een compiler het werk kan optimaliseren. De vereiste berekening blijft gelijk.
- Laad de waarde van
- 3UitlegLeesbare instructies richten zich nog steeds op een processor
Assembleertaal gebruikt instructienamen zoals
movenadd. Een assembler codeert die als machine-instructies.Voor x86-64 zet
mov eax, 1de waarde 1 in EAX;add eax, ebxtelt EBX daarbij op. Die registers en coderingen horen bij die instructiesetarchitectuur. Bytes vervangen door leesbare namen helpt mensen, maar verandert de instructies niet in een overdraagbare beschrijving op hoog niveau. - 4UitdagingVerplaats het programma naar een andere processor
Een team heeft overdraagbare C-broncode en een native uitvoerbaar bestand voor x86-64. Het heeft een native uitvoerbaar bestand voor ARM nodig. Welk bestand kan het team als uitgangspunt gebruiken en welk werk is nodig?
---reveal---
Gebruik de broncode en compileer die voor het ARM-doel, met de vereiste platformondersteuning. Het uitvoerbare bestand voor x86-64 bevat instructies voor een andere ISA. Overdraagbaarheid van broncode maakt een nieuwe vertaling mogelijk; ze maakt die bestaande machine-instructies geen native ARM-code.
- 5VoorbeeldHerken tokens vóór de structuur
Bekijk
total = price * 3;in C.Lexicale analyse vindt zes tokens:
total,=,price,*,3en;. Syntactische analyse groepeert vervolgensprice * 3als de expressie waarvan het resultaat aantotalwordt toegekend.Dit zijn verschillende taken. Tokenisatie herkent de onderdelen; syntactische analyse bepaalt hoe ze in elkaar passen. Semantische analyse kan dan vragen controleren zoals of de namen zijn gedeclareerd en de operandtypen compatibel zijn.
- 6VoorbeeldVerplaats werk met constanten naar de build
De bronexpressie
60 * 60 * 24gebruikt alleen constanten. Constantevaluatie tijdens compilatie kan die vóór de uitvoering berekenen: eerst 3600, dan 86400.Codegeneratie kan dat resultaat gebruiken in plaats van instructies die de vermenigvuldigingen herhalen. Dit is een optimalisatie omdat ze verandert hoe het werk wordt uitgevoerd en tegelijk het vereiste resultaat behoudt. Een waarde vervangen door slechts een handige benadering zou niet door deze regel worden gerechtvaardigd.
- 7UitlegVerbind vertaalde bestanden met bibliotheekroutines
Compilatie kan afzonderlijke objectbestanden produceren. Een linker verbindt die bestanden en lost referenties naar bibliotheekbewerkingen op.
Statisch linken neemt bibliotheekcode op in het gebouwde uitvoerbare bestand. Dynamisch linken vertrouwt op een compatibele gedeelde bibliotheek die later wordt geladen. Voor geen van beide aanpakken hoeft de machine van de gebruiker de compiler voor compilatie vooraf te hebben, maar een dynamisch gelinkt uitvoerbaar bestand heeft nog steeds zijn afhankelijkheden tijdens de uitvoering nodig.
- 8UitdagingEen geslaagde build geeft het verkeerde antwoord
Een C-programma wordt met succes gebouwd. Het rapport moet een gebruikte hoeveelheid van een beginhoeveelheid aftrekken, maar de code telt ze op. Heeft de compiler nagelaten een taalregel te controleren?
---reveal---
Nee. Optellen kan geldige syntaxis met compatibele typen zijn en toch de verkeerde taak uitdrukken. Dit is een logische fout. De compiler controleert taalregels en genereert code; een test met een bekend verwacht resultaat is nodig om het verschil tussen de berekening en het bedoelde doel bloot te leggen.
- 9UitlegEen uitvoeringsomgeving kan tusseninstructies uitvoeren
CPython compileert een geldig Python-bestand naar bytecode en voert die bytecode daarna uit in zijn virtuele machine. De processor voert de machine-instructies van de uitvoeringsomgeving uit terwijl die de Python-bewerkingen uitvoert.
Java gebruikt ook bytecode:
javacproduceert klassebestanden voor een compatibele JVM. Bytecode hoort bij een instructiemodel van een uitvoeringsomgeving en mag daarom niet worden verward met een universele processorinstructieset. - 10VoorbeeldVergelijk twee momenten waarop de uitvoering kan stoppen
In dit geldige Python-bestand wordt de eerste regel uitgevoerd:
print("start") x = 10 print(x + "5")Het drukt “start” af voordat de laatste regel een
TypeErrorveroorzaakt. Verwijder nu het laatste sluithaakje: CPython kan het bestand niet compileren, zodat geen enkele instructie in dat bestand wordt uitgevoerd. Een syntaxisfout tijdens de voorbereiding en een typefout tijdens een uitgevoerde bewerking stoppen het programma in verschillende fasen. - 11UitlegCompileer veelgebruikte code tijdens de uitvoering
Een JIT-compiler produceert machinecode terwijl het programma draait. Een uitvoeringsomgeving kan eerst interpreteren, veelgebruikte methoden observeren en die methoden compileren.
Dit verschuift de kosten: compilatie gebruikt tijdens de uitvoering tijd en geheugen, terwijl herhaalde uitvoering daarna sneller kan worden. Een langlopende service kan meer baat hebben bij die opwarmfase dan een klein eenmalig script. Het resultaat hangt af van de implementatie en de werklast.
- 12UitdagingMaak onderscheid tussen overdraagbare bytecode en native snelheid
Een Java-klassebestand draait via een compatibele JVM op twee verschillende ISA’s. Tijdens elke uitvoering compileert de uitvoeringsomgeving een veelgebruikte methode. Waarom kan één klassebestand worden gedeeld terwijl de gegenereerde machinecode verschilt?
---reveal---
Het klassebestand bevat JVM-bytecode die beide compatibele uitvoeringsomgevingen begrijpen. Elke JIT richt zich op de eigen processor. De overdraagbaarheid komt door het gedeelde tussenformaat en compatibele uitvoeringsomgevingen; de machine-instructies die tijdens de uitvoering worden geproduceerd, blijven platformspecifiek.
- 13SamenvattingHoud niveaus en uitvoeringsroutes gescheiden
Machinecode richt zich op een ISA; assembleertaal geeft de instructies ervan leesbare namen; broncode in een hogere programmeertaal verbergt meer hardwaredetails.
Een compiler vertaalt code, een interpreter voert die uit en een JIT compileert tijdens de uitvoering. Een uitvoeringsomgeving kan deze methoden combineren. Om een echt systeem uit te leggen, benoem je de bronvorm, tussenvorm en native vorm en geef je daarna aan welke fase de syntaxis controleert en welke fase de bewerking uitvoert die kan mislukken.
2Volg de betekenis door een kort programma
10 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieVorm en betekenis beantwoorden verschillende vragen
- 2VoorbeeldGeldige code kan de verkeerde berekening groeperen
- 3UitlegDe eerste voorwaarde kan de tweede beschermen
- 4UitdagingVoorspel of de deling wordt uitgevoerd
- 5UitlegEen toekenning leest vóór de bijwerking
- 6VoorbeeldVolg elke bijwerking van de accumulator
- 7VoorbeeldVolg een aanroep en de teruggegeven waarde
- 8UitdagingEen lokale bijwerking kan de aanroeper onveranderd laten
- 9UitdagingVolg grammatica en toestand samen
- 10SamenvattingLees het uitgevoerde pad
3Volg typen en gedeelde objecten
14 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieVolg waarden, objecten en opslag
- 2UitlegGetypeerde waarden kunnen tussen namen bewegen
- 3KernpuntInferentie en conversie beantwoorden verschillende vragen
- 4UitdagingLees cijfers als de waarde die werkelijk wordt teruggegeven
- 5UitlegRepresentatie beïnvloedt numerieke resultaten
- 6UitlegEen aanroepframe kan eerder eindigen dan een object
- 7VoorbeeldTwee namen verwijzen naar één lijst
- 8UitdagingEen parameter kan veranderen en daarna ergens anders naar verwijzen
- 9UitlegBepaal precies hoe opslag verkeerd wordt gebruikt
- 10VoorbeeldVolg referenties totdat de laatste verdwenen is
- 11KernpuntEen cyclus kan geen actief toegangspunt hebben
- 12UitlegEigenaarschap controleert toegang vóór de uitvoering
- 13UitdagingZoek uit waarom opruiming een lijst niet kan vrijmaken
- 14SamenvattingVolg drie verschillende beslissingen
4Bescherm regels en kies relaties tussen objecten
12 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieStappen hebben betrouwbare grenzen nodig
- 2VoorbeeldDe volgorde van bewerkingen verandert de gedeelde toestand
- 3UitlegMaak de grens van een procedure nuttig
- 4UitdagingVoorspel een bijwerking in omgekeerde volgorde
- 5UitlegGeef elke rekening een eigen toestand
- 6VoorbeeldControleer voordat je een saldo verandert
- 7UitdagingZoek een omzeilde regel
- 8VoorbeeldSpecialiseer zonder het contract te verbreken
- 9UitlegLaat het object zijn oppervlaktemethode kiezen
- 10VerbandBevat het onderdeel dat het werk doet
- 11UitdagingKies twee relaties in een nieuw ontwerp
- 12SamenvattingStem grenzen af op verantwoordelijkheden
5Transformeer een verzameling en vraag een resultaat op
13 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieBeschrijf een transformatie of een verzoek
- 2UitlegZuiverheid beschrijft meer dan het teruggegeven getal
- 3VoorbeeldSelecteer eerst de even waarden
- 4VoorbeeldTransformeer en combineer de geselecteerde waarden
- 5UitdagingPas de verwerkingsketen toe in een nieuw geval
- 6UitlegGeef gedrag terug dat zijn factor onthoudt
- 7VoorbeeldVerklein het probleem totdat een direct geval geldt
- 8VoorbeeldLees SQL als een verzoek over rijen
- 9KernpuntBehoud het resultaat terwijl het plan verandert
- 10VoorbeeldVerbind twee feiten via een regel
- 11UitdagingLees een nieuwe query inclusief de grenswaarde
- 12UitdagingCombineer een query met een zuivere transformatie
- 13SamenvattingHoud de relatie tussen invoer en resultaat zichtbaar
6Vergelijk stijlen en onderbouw een taalkeuze
10 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieHoud de taak gelijk
- 2VoorbeeldHoud de taak gelijk terwijl je de stijl verandert
- 3VoorbeeldPlaats dezelfde regel in objecten of een query
- 4UitdagingBepaal of een ontwerp maar één stijl heeft
- 5UitlegSluit eerst niet-ondersteunde doelplatforms uit
- 6VoorbeeldKies voor een volledig weersysteem
- 7TipMeet wat de tijd verbruikt
- 8UitdagingVerdedig een keuze bij concurrerende randvoorwaarden
- 9UitdagingVerbind taalkeuze met programmaorganisatie
- 10SamenvattingGeef een reden die bij de eis hoort
Programmeertalen: oefenvragen
Set 1: Pas regels voor typen, objecten en query’s toe10 Oefenvragen
Vraag 1 van 10
Toon antwoordVerberg antwoord
Vraag 2 van 10
Toon antwoordVerberg antwoord
Vraag 3 van 10
a + b / 2 en (a + b) / 2 in Python respectievelijk op bij a = 8 en b = 12?Toon antwoordVerberg antwoord
Vraag 4 van 10
Toon antwoordVerberg antwoord
Vraag 5 van 10
balance en een methode deposit. Er worden twee rekeningen met verschillende beginsaldi aangemaakt. Waar horen die verschillende saldi thuis?Toon antwoordVerberg antwoord
Vraag 6 van 10
Toon antwoordVerberg antwoord
Vraag 7 van 10
[1, 4, 7, 10] de waarden groter dan 3, trekt van elke behouden waarde 1 af en telt ze daarna op. Wat is het resultaat?Toon antwoordVerberg antwoord
Vraag 8 van 10
Toon antwoordVerberg antwoord
Vraag 9 van 10
Toon antwoordVerberg antwoord
Vraag 10 van 10
Wat zijn x en y na deze Python-code?
x = [12]
y = x
x = x + [3]
y.append(6)
Toon antwoordVerberg antwoord
[12, 3] en koppelt alleen x opnieuw; toevoegen via y verandert daarna de oorspronkelijke lijst in [12, 6]. Als je verwacht dat beide namen elk toegevoegd element tonen, behandel je het optellen van lijsten als ter plekke toevoegen en mis je de tussentijdse nieuwe koppeling.Set 2: Lees code en herken de uitvoeringsroute10 Oefenvragen
Sla deze les op om elk antwoord te zien en elke set te oefenen.
Vraag 1 van 10
mov en jmp. Op welk taalniveau is het programma nu geschreven?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 2 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 3 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 4 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 5 van 10
Welke eindwaarde slaat deze Python-code op in total?
total = 1
for n in [4, 7, 2]:
total = total + n
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 6 van 10
label = "18" gevolgd door label = 18. De naam kan van type veranderen, maar "18" + 2 veroorzaakt een fout. Hoe worden deze twee feiten beschreven?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 7 van 10
Wat bevat first nadat deze Python-code is uitgevoerd?
first = [4, 8]
second = first
second.append(6)
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 8 van 10
read_data(), calculate_total() en save_report(), die elk geordende opdrachten bevatten. Welke organisatie illustreert dit?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 9 van 10
n == 0; anders roept ze zichzelf aan met n + 1. Waarom zorgt het opgeschreven basisgeval er niet voor dat de berekening eindigt als die begint bij de positieve waarde 2?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 10 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Set 3: Volg aanroepen, effecten en objectcontracten10 Oefenvragen
Sla deze les op om elk antwoord te zien en elke set te oefenen.
Vraag 1 van 10
minutes = 60 * 12 door de vooraf berekende waarde 720. Welke optimalisatie wordt gebruikt en wat moet die behouden?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 2 van 10
Wat drukt dit Python-programma af?
score = 11
def change():
score = 4
return score + 2
print(change())
print(score)
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 3 van 10
Wat is items na de aanroep?
items = [3]
def revise(values):
values.append(8)
values = [1]
values.append(5)
revise(items)
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 4 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 5 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 6 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 7 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 8 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 9 van 10
showDetails voor een klikgebeurtenis. Waarom kan diezelfde afhandelaar nog steeds objectmethoden en imperatieve toekenningen gebruiken?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 10 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Set 4: Redeneer over grenzen van uitvoering en ontwerp heen10 Oefenvragen
Sla deze les op om elk antwoord te zien en elke set te oefenen.
Vraag 1 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 2 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 3 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 4 van 10
n = 0 de expressie n != 0 and 36 / n > 4 met 36 / n > 4 and n != 0. Wat gebeurt er?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 5 van 10
Welke eindwaarde wordt opgeslagen in total?
def bump(x):
return x + 2
total = 0
for n in [2, 5, 8]:
if n > 3:
total = total + bump(n)
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 6 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 7 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 8 van 10
item.render() aan. Een nieuwe, niet-verwante klasse levert een compatibele methode render(). De aanroeper in Python vereist geen superklasse. Waarom kan het object in de lus worden opgenomen zonder een typespecifieke tak?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 9 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 10 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Set 5: Onderzoek beperkingen en combineer de ideeën10 Oefenvragen
Sla deze les op om elk antwoord te zien en elke set te oefenen.
Vraag 1 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 2 van 10
Wat drukt dit Python-programma af?
value = 6
def step(value):
value = value + 3
return value
for n in [1, 2]:
result = step(value)
print(value, result)
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 3 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 4 van 10
double() vermenigvuldigt die met 2; add_three() telt er 3 bij op. Wat is bij dezelfde beginwaarde altijd het verschil tussen eerst verdubbelen en dan optellen en eerst optellen en dan verdubbelen?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 5 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 6 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 7 van 10
readings heeft rijen (level, active): (50, true), (65, true), (20, false), (85, true) en (90, false). Een query vraagt SUM(level) op waar active = true AND level > 50. Welk totaal is vereist nadat een geschikte index is toegevoegd en mag het uitvoeringsplan veranderen?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 8 van 10
parent(lea, noah). en parent(noah, sora). plus de regel grandparent(X, Z) :- parent(X, Y), parent(Y, Z).. Wat vindt grandparent(lea, Who)?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 9 van 10
[50, 65, 20, 85]. Wat moeten hun correcte resultaten zijn?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 10 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Kom je niet uit je eigen opgave? Huiswerkhulp
Kernbegrippen
A
aanroepstapel
area voegt een item toe aan de aanroepstapel en verwijdert dat wanneer het resultaat naar de aanroeper terugkeert.abstractie
aliasing
b = a verklaart aliasing waarom toevoegen via b ook kan veranderen wat via a zichtbaar is.assembleertaal
assembler
add eax, ebx om in de bijbehorende x86-64-instructiebytes.attribuut
automatische opruiming van onbereikbare objecten
B
basisgeval
bereik
bufferoverloop
bytecode
C
callbackfunctie
showMenu als callbackfunctie door te geven, kan een knopsysteem die functie aanroepen wanneer de gebruiker klikt.closure
compilatie vooraf
compiler
compositie
constantevaluatie tijdens compilatie
60 * 60 * 24 vervangen door 86400 voordat het programma start.D
declaratief programmeren
domeinspecifieke taal
drijvendekommagetal
0.1 + 0.2 en de opgeslagen waarde van 0.3.dubbele vrijgave
free opnieuw aanroepen op een al vrijgegeven toewijzing kan een dubbele vrijgave en onvoorspelbaar gedrag veroorzaken.ducktyping
area() biedt.dynamische methodekeuze
shape.area() naar de implementatie voor rechthoeken wanneer het ontvangende object een rechthoek is.dynamische typering
E
ecosysteem
eersteklasfunctie
map worden doorgegeven als de bewerking die op elk element van een verzameling wordt toegepast.eigenaarschap
expressie
width * height levert de waarde die een functie voor de oppervlakte van een rechthoek teruggeeft.F
functie van hogere orde
filter ontvangt een predicaat dat bepaalt welke invoerwaarden behouden blijven.functioneel programmeren
G
gebeurtenisafhandelaar
gebeurtenisgestuurd programmeren
gebruik na vrijgave
gegevenstype
geheugenlek
geheugentoewijzing
geheugenveiligheid
gestructureerd programmeren
H
heap
hogere programmeertaal
I
imperatief programmeren
impliciete typeconversie
informatieverberging
inkapseling
instructie
total = price * 3 kent het resultaat van een vermenigvuldiging toe aan een variabele.instructiesetarchitectuur
interface
area() vereisen en elke vorm die anders laten berekenen.interpreter
J
just-in-timecompilatie
K
klasse
Loan van een bibliotheek kan voor elke uitlening het lid, het boek, de inleverdatum en de verlengbewerking definiëren.knelpunt
kortsluitevaluatie
and voorkomen.L
lambda-expressie
lambda n: n * n levert een kwadrateerbewerking aan een transformatieketen.lexicale analyse
price * 3 gescheiden in een naam, een vermenigvuldigingsoperator en een getal.linker
logisch programmeren
logische fout
a + b / 2 gebruiken om het gemiddelde van twee waarden te berekenen is een logische fout omdat de deling vóór de optelling plaatsvindt.M
machinecode
methode
methodeoverschrijving
multiparadigmataal
mutatie
N
neveneffect
O
object
onveranderlijkheid
overerving
overloop
P
parameter
width ontvangt 3 wanneer de aanroeper area(3, 4) evalueert.platform
polymorfisme
area() op cirkels en rechthoeken aanroepen zonder voor elk een aparte tak.procedureel programmeren
programmeerparadigma
Q
queryoptimizer
R
recursie
referentie
referentietelling
reguliere expressie
[0-9]{3}-[0-9]{4} komt overeen met een groep van drie cijfers gevolgd door een koppelteken en vier cijfers.REPL
S
semantiek
SQL
stapelframe
statische typering
syntactische analyse
total = price * 3; binnen de toekenning hoort.syntaxis
if-voorwaarde en inspringing voor het bijbehorende blok.T
taalimplementatie
toekenning
count = count + 1 leest de oude waarde van de teller voordat die wordt vervangen door de verhoogde waarde.token
tracetabel
turingvolledigheid
type-inferentie
U
uitvoeringsfout
V
verweesde pointer
virtuele machine
Z
zuivere functie
Over deze les
Door StudeliFout melden
Maak je eigen Studiemateriaal
Maak van je aantekeningen, slides en documenten Leermodules, Oefenvragen, Flashcards en 6 andere tools.