- Leren
- Informatica & ICT
- Programmeren
- Programmeren in C: pointers, geheugenbeheer en debuggen
- Typen en operatoren in C
Basis van C: typen, operatoren en programmaverloop
printf en scanf. Bouw expressies met de rekenkundige, vergelijkings-, logische en bitsgewijze operatoren van C en voorspel conversies en operatorprioriteit. Stuur vervolgens een programma met if, switch, while, do-while en for. Leer tot slot waarom overloop bij gehele getallen optreedt, wat er dan gebeurt en hoe je code schrijft en test die dit voorkomt.BeginnerLes 1 van 8 in Cursus Programmeren in C: pointers, geheugenbeheer en debuggen
Wat je leert
- Zet C-broncode om in een werkend programma, weet wat de compiler in elke fase doet en gebruik zijn meldingen om fouten op te sporen en te herstellen.
- Schrijf, compileer en voer je eerste C-programma uitLees de onderdelen van een minimaal C-programma, compileer het met gcc of clang en voer het uitvoerbare bestand uit vanuit een terminal.
- Lees compilermeldingen en herstel foutenOnderscheid compilatiefouten, waarschuwingen, koppelingsfouten, uitvoeringsfouten en logische fouten en gebruik compilermeldingen en proefuitvoeringen om ze te herstellen.
- Schrijf, compileer en voer je eerste C-programma uit
- Declareer en initialiseer variabelen, kies tussen de geheelgetaltypen en drijvendekommatypen van C en voorspel het waardenbereik en de precisie van elk type.
- Declareer en initialiseer variabelen en geef ze een naamMaak variabelen met een type en een betekenisvolle naam, geef ze beginwaarden en gebruik constanten voor vaste waarden.
- Kies een geheelgetaltype en ken het waardenbereikKies tussen de geheelgetaltypen met en zonder teken, tekenwaarden en Booleaanse waarden van C aan de hand van hun grootten, waardenbereiken en literalvormen.
- Werk met drijvendekommagetallen en hun beperkingenSla reële getallen op in float en double, voorspel hun precisie en afrondingsfouten en converteer veilig tussen drijvendekommawaarden en gehele getallen.
- Declareer en initialiseer variabelen en geef ze een naam
- Maak getallen en tekst nauwkeurig op met printf en lees en controleer met scanf waarden die de gebruiker typt.
- Maak uitvoer op met printfGebruik conversiespecificaties, veldbreedten, precisie, vlaggen en escapesequenties om precies de bedoelde uitvoer te maken.
- Lees en controleer invoer met scanfLees getallen en tekens die de gebruiker typt, controleer of het lezen is geslaagd en herstel na ongeldige invoer.
- Maak uitvoer op met printf
- Gebruik de rekenkundige, toewijzings-, vergelijkings-, logische en bitsgewijze operatoren van C en voorspel de waarde en het type van elke expressie op basis van conversies, operatorprioriteit en associativiteit.
- Reken met rekenkundige operatoren en toewijzingsoperatorenPas +, -, *, / en %, geheeltallige deling, samengestelde toewijzing, incrementatie en decrementatie toe en vermijd de gebruikelijke valkuilen.
- Voorspel typeconversies in expressies met verschillende typenLeg uit hoe C operanden van verschillende typen converteert, gebruik expliciete typeconversies bewust en vermijd de valkuil van typen met en zonder teken.
- Combineer voorwaarden met relationele, logische en bitsgewijze operatorenSchrijf correcte vergelijkingen en samengestelde voorwaarden, gebruik kortsluitevaluatie en bewerk afzonderlijke bits.
- Pas operatorprioriteit, associativiteit en evaluatievolgorde toeVoorspel hoe C de operatoren in een expressie groepeert, evalueer expressies stap voor stap en gebruik haakjes waar de groepering onduidelijk is.
- Reken met rekenkundige operatoren en toewijzingsoperatoren
- Kies tussen alternatieven met if, else en switch en herhaal werk met while-, do-while- en for-lussen die precies volgens de bedoeling beginnen, draaien en stoppen.
- Maak keuzes met if en elseSchrijf if-, else if- en else-instructies met correcte voorwaarden en accolades en vermijd de gebruikelijke valkuilen van de syntaxis van C.
- Kies uit verschillende gevallen met switchGebruik switch met case-labels, break en default om uit vaste gehele waarden te kiezen en laat alleen bewust doorvallen.
- Herhaal zolang een voorwaarde waar is met while en do-whileSchrijf lussen die door een voorwaarde worden gestuurd, correct beginnen, voortgang maken en stoppen, waaronder invoerlussen en lussen die minstens eenmaal moeten worden uitgevoerd.
- Tel met for-lussen, nest lussen en stop voortijdigSchrijf for-lussen die precies het bedoelde aantal keren worden uitgevoerd, combineer lussen binnen lussen en gebruik break en continue bewust.
- Maak keuzes met if en else
- Leg uit wat er gebeurt als een resultaat niet in het geheelgetaltype past en schrijf en test code die overloop voorkomt of detecteert.
- Leg uit wat er gebeurt als een waarde niet in het type pastBeschrijf modulaire terugloop bij gehele getallen zonder teken, ongedefinieerde overloop bij gehele getallen met teken en conversies buiten het waardenbereik en verbind die met werkelijke fouten.
- Schrijf code die overloop voorkomt of detecteertKies typen met voldoende bereik, controleer bewerkingen voordat ze overlopen en gebruik compilerhulpmiddelen en grenswaardetests om overloop vroeg op te sporen.
- Leg uit wat er gebeurt als een waarde niet in het type past
Typen en operatoren in C: uitleg
1Bouw C-programma’s en voer ze uit
C-code wordt door een bouwproces een werkend programma. Leer de onderdelen van een volledig eerste programma, compileer het met een gekozen taalstandaard en voer het resulterende uitvoerbare bestand uit. Als een bouwproces of test misgaat, gebruik je de fase waarin de fout optreedt en de eerste compilermelding om de oorzaak te vinden. Door de cyclus van bewerken, bouwen en testen zorgvuldig te volgen, voorkom je dat een oud uitvoerbaar bestand een mislukte wijziging verhult en kun je elke reparatie gemakkelijker controleren.
1.1 Van broncode naar een werkend programma
Een C-programma begint als tekst zonder opmaak en wordt uitgevoerd als machinecode. De compiler verbindt die twee vormen. Als je een wijziging opslaat, verandert de broncode; het eerder gebouwde uitvoerbare bestand verandert niet.
Lees een volledig programma
Sla dit op als hello.c:
#include <stdio.h>
int main(void)
{
printf("Hello, world!\n");
return 0;
}
#include <stdio.h> maakt declaraties voor standaardinvoer en -uitvoer beschikbaar, waaronder die van printf. De uitvoering begint in main. In int main(void) noemt int het retourtype en betekent void dat er geen parameters zijn. Accolades omsluiten de functie-inhoud. De aanroep van printf schrijft de tekst tussen aanhalingstekens; \n gaat naar een nieuwe regel. return 0; beëindigt het programma en meldt aan het besturingssysteem dat de uitvoering is geslaagd. Een status anders dan nul kan een fout melden. Sinds C99 geeft ook het bereiken van het einde van main nul terug.
Eenvoudige instructies eindigen met een puntkomma. De include-regel niet. C maakt onderscheid tussen hoofdletters en kleine letters: Printf en printf zijn verschillende namen. Spaties tussen tokens beïnvloeden het programma meestal niet, maar consequent inspringen maakt de blokstructuur zichtbaar.
Compileer en voer uit
Open in de terminal de map met de broncode en voer in:
gcc -std=c17 -Wall -Wextra hello.c -o hello
./hello
De eerste opdracht bouwt een uitvoerbaar bestand met de naam hello; de tweede voert het uit vanuit de huidige map. De uitvoer is Hello, world! gevolgd door een nieuweregelteken. Je kunt clang gebruiken in plaats van gcc. Zonder -o hello is de gebruikelijke naam van de uitvoer op Linux of macOS a.out. Uitvoerbare bestanden op Windows gebruiken .exe. make hello van CS50 is een verkorte opdracht die het bouwhulpmiddel vraagt de compiler aan te roepen.
Nadat je de begroeting hebt gewijzigd, sla je op, compileer je opnieuw en voer je het programma opnieuw uit. Als de compilatie mislukt, kan een ouder uitvoerbaar bestand nog aanwezig zijn. Als je dat uitvoert, zie je het oude gedrag. Controleer daarom of het bouwen is geslaagd voordat je je wijziging beoordeelt.
Wat het bouwproces doet
De preprocessor verwerkt richtlijnen zoals #include en voegt headertekst in. De compiler zelf vertaalt C naar assembleertaal. De assembler maakt objectcode, die nog niet-opgeloste namen kan bevatten. De linker combineert objectcode en lost verwijzingen zoals printf op. Bibliotheekcode kan door statisch koppelen naar een uitvoerbaar bestand worden gekopieerd of worden geleverd via een gedeelde bibliotheek die wordt geladen wanneer het programma draait.
Je kunt het bouwproces in tussenfasen stoppen:
| Optie | Resultaat |
|---|---|
-E | Voorbewerkte tekst |
-S | Assembleertaal |
-c | Een objectbestand, zoals hello.o |
Een objectbestand is nog niet het voltooide uitvoerbare bestand. Een uitvoerbaar bestand kan zonder de compiler draaien, maar is gebouwd voor een bepaald besturingssysteem en een bepaalde processor. Om het programma naar een ander platform over te brengen, compileer je de broncode daar.
Commentaar en taalversies
// begint commentaar dat eindigt bij het nieuweregelteken. /* ... */ kan meerdere regels beslaan, maar deze commentaren kunnen niet worden genest: de eerste */ beëindigt het commentaar. Gebruik commentaar om een beslissing uit te leggen die de code zelf onduidelijk laat.
Kies de taalversie expliciet. C17 ondersteunt de voorbeelden hierboven, waaronder commentaar met //, long long en declaraties in de kop van for-lussen. C23 voegt voorzieningen toe zoals bool als sleutelwoord; deze versie is gepubliceerd als ISO/IEC 9899:2024. -Wall -Wextra schakelt nuttige waarschuwingen in. De standaardinstellingen van compilers verschillen, dus met een expliciete -std=c17 zijn de bedoelde taalregels duidelijk.
Belangrijkste punten
- C-broncode moet na elke wijziging opnieuw worden gecompileerd.
- De uitvoering begint in
main; nul teruggeven meldt dat de uitvoering is geslaagd. - De bouwfasen zijn voorbewerking, compilatie, assemblage en koppeling.
- Gebruik
-std=c17 -Wall -Wextraom de standaard te kiezen en waarschuwingen in te schakelen.
1.2 De eerste oorzaak van een fout vinden
Het moment waarop een probleem verschijnt, helpt je de oorzaak te vinden. Een programma kan niet worden gebouwd, tijdens de uitvoering stoppen of met het verkeerde antwoord eindigen. Die situaties vragen om verschillende controles.
Onderscheid de soorten problemen
| Soort | Wat je ziet | Waar je kijkt |
|---|---|---|
| Compilatiefout | Geen nieuw uitvoerbaar bestand | Syntaxis, declaraties en typen |
| Waarschuwing | Een mogelijk probleem; het bouwen gaat meestal door | De expressie die in de melding wordt genoemd |
| Koppelingsfout | Objectcode bestaat, maar het uitvoerbare bestand kan niet worden voltooid | Niet-opgeloste functienamen of vereiste bibliotheken |
| Uitvoeringsfout | Een probleem verschijnt tijdens de uitvoering | Bewerkingen en waarden die bij die invoer worden bereikt |
| Logische fout | Het programma draait maar geeft een onbedoeld antwoord | Voorwaarden, formules en bijwerkingen |
Een waarschuwing kan zowel ongeldige code of ongedefinieerd gedrag als een gewone fout aanwijzen. Een geslaagde compilatie garandeert geen correct programma.
Lees een diagnostische melding
Een compilermelding geeft normaal gesproken een locatie in de broncode, een ernstniveau en een beschrijving. Een melding zoals hello.c:5:16: error: expected ';' after expression wijst op een ontbrekende puntkomma rond regel 5. Soms merkt de compiler het probleem op de volgende regel op. Bekijk de gemelde regel en de regel direct ervoor.
Herstel eerst de eerste fout en compileer daarna opnieuw. Een niet-afgesloten aanhalingsteken kan ervoor zorgen dat latere regels deel van een string lijken; een niet-overeenkomende accolade kan latere declaraties in het verkeerde blok doen lijken te staan. Na één reparatie kunnen meerdere meldingen verdwijnen. Begin niet met het wijzigen van elke regel die in de uitvoer wordt genoemd.
Voor dit fragment:
int total = 6
printf("%d\n", total);
De declaratie heeft een puntkomma nodig na 6. De regel met printf kan de plaats zijn waar de compiler voor het eerst merkt dat de vorige declaratie niet is geëindigd. Controleer ook de exacte spelling, overeenkomende haakjes en accolades en de header die een bibliotheekfunctie declareert. Een aanroep van printf heeft <stdio.h> nodig; aanroepen van niet-gedeclareerde functies zijn in C99 en later niet toegestaan.
Neem waarschuwingen serieus
Bouw met -Wall -Wextra. Deze opties helpen bij het opsporen van een toewijzing in een voorwaarde, ongebruikte variabelen, vergelijkingen tussen waarden met en zonder teken en niet-overeenkomende uitvoerformaten. Zo vraagt printf("%d\n", 3.5); om een geheel getal maar geeft het een double door; het formaat converteert het argument niet voor je. Herstel het formaat naar %f of converteer de waarde bewust. Met -Werror laten waarschuwingen het bouwproces stoppen.
Begrijp ongedefinieerd gedrag
Ongedefinieerd gedrag betekent dat C geen eisen stelt aan het gedrag van het programma. Overloop bij gehele getallen met teken, geheeltallige deling door nul en sommige leesacties van niet-geïnitialiseerde variabelen kunnen dit veroorzaken. Een programma kan stoppen, een onverwachte waarde afdrukken of lijken te werken. Compilerinstellingen kunnen veranderen wat er gebeurt.
Een melding zoals “Floating point exception” kan op sommige systemen zelfs het gevolg zijn van geheeltallige deling door nul. Onderzoek de bewerking en invoer, niet alleen de formulering van een melding van het besturingssysteem. Controleer een deler voordat je de deling uitvoert.
Volg een verkeerd resultaat
Voorspel bij een logische fout een klein geval met de hand en vergelijk dat met de werkelijke uitvoer. Voeg tijdelijke printf-aanroepen toe om een lopend totaal of de waarde die een voorwaarde test te tonen. Verander steeds één oorzaak en bouw na elke wijziging opnieuw. Test gewone waarden en grenswaarden zoals nul, één, een negatieve invoer en de grootste verwachte invoer. Een uitvoeringsspoor laat zien waar het programma voor het eerst afwijkt van je voorspelling.
Belangrijkste punten
- Herstel de oorzaak van de eerste diagnostische melding en bouw opnieuw voordat je latere meldingen onderzoekt.
- Een waarschuwing kan ongedefinieerd gedrag aanwijzen; compilatie alleen bewijst niet dat een programma correct is.
- Stem formaatconversies af op de typen van de argumenten.
- Volg bij een verkeerd resultaat een kleine invoer en vergelijk tussenwaarden.
- Test zowel grenswaarden als gewone invoer.
2Sla gegevens op in variabelen van de basistypen
Een type bepaalt hoe een opgeslagen waarde wordt geïnterpreteerd en welke waarden het kan bevatten. Begin met declaraties, beginwaarden en de namen die binnen een blok beschikbaar zijn. Kies vervolgens geheelgetaltypen op basis van hun bereik en drijvendekommatypen op basis van zowel precisie als bereik. Ook tekencodes en Booleaanse waarden passen in dit geheel. Door literals te lezen en grenzen te controleren, leer je voorspellen wat een berekening kan voorstellen voordat je die schrijft.
2.1 Variabelen, namen en vaste waarden
Een variabele geeft een opgeslagen waarde een naam. Het type vertelt C hoe die opslag moet worden geïnterpreteerd. Je declareert het type voordat je de variabele gebruikt en de variabele houdt dat type gedurende de hele levensduur.
Geef elke variabele een beginwaarde
int count = 0;
double length_m = 1.8;
count = count + 1;
De eerste twee regels declareren en initialiseren variabelen. De derde wijst een nieuwe waarde toe aan een bestaande variabele: de instructie leest de huidige count, telt er één bij op en slaat het resultaat weer op. Een toewijzing is een actie, dus count = count + 1 heeft betekenis, ook al zou het geen wiskundige vergelijking zijn.
Initialisatie gebeurt in een declaratie; een toewijzing kan later gebeuren. In int a, b = 5; wordt alleen b geïnitialiseerd. Een lokale a heeft een onbepaalde waarde totdat je er een waarde in opslaat. Die lezen kan onvoorspelbaar gedrag opleveren, waaronder ongedefinieerd gedrag. Initialiseer tellers en totalen wanneer je ze declareert, bijvoorbeeld int total = 0;.
Een toewijzing kopieert de waarde op dat moment:
int width = 4;
int area = width * 3;
width = 7;
printf("%d %d\n", width, area);
Dit drukt 7 12 af. De toewijzing aan area berekende eenmaal 12. Die maakte geen formule die opnieuw rekent telkens wanneer width verandert. Om area bij te werken, wijs je er een nieuwe berekening aan toe.
Kies leesbare namen
Een identifier kan letters, cijfers en onderstrepingstekens bevatten, maar mag niet met een cijfer beginnen of een sleutelwoord zijn. height_cm en second_place zijn geldig; 2nd_place niet en my-score wordt als een aftrekking gelezen. Namen zijn hoofdlettergevoelig, dus total en Total verschillen. Namen die een betekenis of eenheid bevatten, maken formules gemakkelijker te controleren.
Houd rekening met het geldigheidsgebied van een blok
Een lokale naam is beschikbaar vanaf de declaratie tot het einde van het blok. Sinds C99 mogen declaraties tussen andere instructies staan.
int count = 2;
{
int count = 9;
printf("%d\n", count);
}
printf("%d\n", count);
De binnenste declaratie verbergt de buitenste naam zolang dat blok wordt uitgevoerd. De uitvoer is 9 en daarna 2, op afzonderlijke regels. Dit zijn twee variabelen, geen twee toewijzingen aan één variabele. Dezelfde naam tweemaal in één blok declareren is een fout. Namen opnieuw gebruiken in geneste blokken is toegestaan, maar maakt code moeilijker te volgen. Kies daarom bij voorkeur namen die elke rol duidelijk houden.
Geef vaste waarden een naam
const int SECONDS_PER_MINUTE = 60;
#define MAX_HEIGHT 8
const maakt een getypeerde variabele waaraan je via die naam geen nieuwe waarde kunt toewijzen. Een latere instructie SECONDS_PER_MINUTE = 90; vereist een diagnostische melding. De initialisator geeft de vaste waarde bij de declaratie.
#define maakt een vervanging voor de preprocessor: latere voorkomens van MAX_HEIGHT worden vóór de compilatie vervangen door het token 8. Deze richtlijn bevat geen = en geen puntkomma. Een puntkomma zou deel worden van de vervanging en een expressie zoals height > MAX_HEIGHT ongeldig kunnen maken.
Een naam zoals SECONDS_PER_MINUTE vertelt een lezer waarom 60 in een berekening voorkomt en geeft je één plaats om de waarde bij te houden. Hoofdletters zijn een naamgevingsafspraak, geen regel die een waarde constant maakt. De declaratie of richtlijn zorgt voor dat gedrag.
Belangrijkste punten
- Initialiseer lokale variabelen voordat je ze leest.
- Een toewijzing kopieert eenmaal een waarde; afhankelijke waarden worden niet vanzelf bijgewerkt.
- Een blok begrenst waar je een lokale naam kunt gebruiken.
- Namen zijn hoofdlettergevoelig en mogen niet met een cijfer beginnen.
constlevert een getypeerde alleen-lezenvariabele;#definelevert een tokenvervanging.
2.2 Geheelgetaltypen, tekentypen en Booleaanse typen kiezen
Geheelgetaltypen slaan gehele getallen exact op binnen een vast bereik. Kies een type op basis van de waarden die het programma moet verwerken, inclusief tussenresultaten, en op basis van de vraag of negatieve waarden zinvol zijn.
Grootten en waardenbereiken
De standaard garandeert minimumbreedten: minstens 8 bits voor char, 16 voor short en int, 32 voor long en 64 voor long long. Elk type in deze reeks is minstens zo breed als het vorige. De werkelijke grootten hangen af van het platform.
| Type | Gebruikelijk aantal bytes op 64-bits Linux/macOS | Verschil op 64-bits Windows |
|---|---|---|
char | 1 | Geen |
short | 2 | Geen |
int | 4 | Geen |
long | 8 | 4 bytes |
long long | 8 | Geen |
sizeof(int) geeft de grootte in bytes als een size_t; druk die af met %zu. De header <limits.h> levert namen zoals INT_MIN, INT_MAX, UINT_MAX, LLONG_MAX en CHAR_BIT, het aantal bits per byte.
Met N waardebits heeft een geheel getal zonder teken waarden van 0 tot en met 2ᴺ − 1. Een geheel getal met teken in tweecomplement met N bits loopt van −2ᴺ⁻¹ tot en met 2ᴺ⁻¹ − 1. Een 32-bits int loopt dus van −2.147.483.648 tot en met 2.147.483.647, terwijl een 32-bits unsigned int tot 4.294.967.295 gaat. C23 vereist tweecomplement; C17 stond ook andere representaties toe.
In een 8-bits patroon in tweecomplement heeft de hoogste bit het gewicht −128. 11111111 stelt −1 voor en 10000000 stelt −128 voor. Om −5 te vormen, begin je met 00000101, keer je de bits om tot 11111010 en tel je er één bij op om 11111011 te krijgen.
Tekens zijn ook gehele getallen
Een char slaat een tekencode op in één byte. Gebruik enkele aanhalingstekens voor een tekenconstante, zoals in 'A'. In ASCII heeft 'A' code 65 en '0' code 48. printf("%c %d\n", 'A', 'A'); drukt in ASCII A 65 af. Een tekenconstante in C heeft het type int, dus sizeof('A') is sizeof(int).
Gewoon char kan afhankelijk van de implementatie een type met of zonder teken zijn. Gebruik signed char of unsigned char wanneer het numerieke bereik van belang is. <stdint.h> biedt typen met exacte bitbreedte zoals int32_t en uint8_t op platforms die die exacte breedten ondersteunen; gangbare platforms doen dat.
Lees geheeltallige literals
Een decimale constante zoals 42 heeft het type int als de waarde daarin past. Achtervoegsels vragen om andere typen: 42u is zonder teken, 42L is long, 42LL is long long en 42ULL is unsigned long long. Hexadecimale notatie gebruikt 0x, zoals in 0x2A. Een voorloopnul betekent octaal: 052 betekent ook 42 en 010 betekent 8. 09 is ongeldig als octaal getal. C23 voegt binaire notatie toe, zoals 0b101010.
Waarheidswaarden en typekeuze
Neem in C17 <stdbool.h> op voor bool, true en false; C23 maakt er sleutelwoorden van. Een conversie naar bool geeft onwaar voor nul en waar voor elke waarde anders dan nul, waaronder 0.5. Druk een Booleaanse waarde af met %d om 0 of 1 te zien.
Gebruik int voor gewone aantallen en lustellers. Gebruik long long of een geschikt type met exacte bitbreedte wanneer het bereik boven ongeveer 2,1 miljard kan uitkomen. Typen zonder teken zijn geschikt voor bitpatronen, maar aftrekken tot onder nul loopt modulair terug naar een grote waarde, dus ook daarbij zijn bereikcontroles nodig.
Belangrijkste punten
- Gebruik de typegrenzen in plaats van een geschatte grootte voor
long. - Een 32-bits
intmet teken gaat tot 2.147.483.647. - Waarden zonder teken hebben geen negatief bereik en lopen bij onderloop modulair terug.
- Een voorloopnul begint een octale geheeltallige literal.
- Of gewoon
chareen teken heeft, hangt af van de implementatie. - In C17 komen Booleaanse namen uit
<stdbool.h>.
2.3 Decimale waarden en beperkte precisie
Een drijvendekommatype slaat een benadering van een reëel getal op. Het heeft een groot bereik, maar slechts een vast aantal significante cijfers. Bereik en precisie beantwoorden verschillende vragen: hoe groot een waarde kan zijn en hoe nauwkeurig dicht bij elkaar liggende waarden kunnen worden onderscheiden.
Kies een drijvendekommatype
Op gangbare IEEE 754-systemen neemt float 4 bytes in en heeft het ongeveer 6–7 significante decimale cijfers. double neemt 8 bytes in en heeft er ongeveer 15–16. Hun grootste eindige waarden zijn ongeveer 3,4 × 10³⁸ en 1,8 × 10³⁰⁸. long double is minstens zo precies als double en heeft een formaat dat afhangt van het platform. double is een nuttige standaardkeuze voor gemeten grootheden.
Constanten zoals 2.0, 3.14 en 1e-3 zijn van het type double; 1e-3 betekent 0,001. Voeg f toe voor float, zoals in 2.5f, of L voor long double. De constante 2 is een geheel getal en kan dus leiden tot geheeltallige deling waar 2.0 een drijvendekommadeling zou opleveren.
Bekijk de opgeslagen benadering
Veel decimale breuken hebben geen exacte eindige binaire vorm. Op een gangbaar systeem met dubbele precisie:
printf("%.20f\n", 0.1);
printf("%.17f\n", 0.1 + 0.2);
De uitvoer is 0.10000000000000000555 en 0.30000000000000004. De extra cijfers tonen de opgeslagen benadering. Ze verklaren waarom 0.1 + 0.2 == 0.3 onwaar kan zijn.
Gebruik een tolerantie bij het vergelijken van berekende meetwaarden:
#include <math.h>
/* inside main */
if (fabs(a - b) < 1e-9) {
printf("Close enough\n");
}
fabs geeft een absoluut verschil terug. Kies de tolerantie passend bij de schaal en precisie van de waarden; 1e-9 is een voorbeeld, geen universele instelling. Voor sommige Linux-builds is -lm aan het einde van de compileropdracht nodig om wiskundige functies te koppelen.
Precisie beïnvloedt grote gehele getallen
Een binaire double stelt elk geheel getal tot en met 2⁵³ exact voor. Een binaire float kan boven 2²⁴ = 16.777.216 niet meer elk geheel getal voorstellen: als je 16.777.217 aan zo’n float toewijst, krijg je 16.777.216. Een groter bereik betekent niet dat elke stap tussen gehele getallen beschikbaar blijft.
Bij het afdrukken wordt de opgeslagen binaire waarde afgerond. printf("%.2f\n", 2.675); drukt meestal 2.67 af, omdat de opgeslagen waarde iets kleiner is dan 2,675. Gebruik voor exacte bedragen in hele centen een geheel getal zoals long long cents = 1999; en maak de weergave aan het einde op.
Converteer en rond bewust af
Conversie naar een geheel getal kapt het breukdeel af naar nul: (int)3.99 is 3 en (int)-3.99 is −3. round uit <math.h> geeft een drijvendekommawaarde terug die is afgerond op het dichtstbijzijnde gehele getal, waarbij waarden halverwege van nul af worden afgerond: round(-2.5) geeft −3,0. Converteer dat resultaat alleen naar een geheel getal als het past. Een conversie van een drijvendekommawaarde naar een geheel getal waarvan het gehele deel buiten het bereik valt, heeft ongedefinieerd gedrag.
Bij IEEE-rekenen kunnen resultaten buiten het eindige bereik oneindig worden. Een drijvendekommawaarde anders dan nul delen door nul kan oneindig opleveren; nul gedeeld door nul levert NaN op, wat “geen getal” betekent. Een NaN is bij vergelijking zelfs ongelijk aan zichzelf. Dit zijn gedragingen van IEEE-drijvendekommagetallen; geheeltallige deling door nul heeft ongedefinieerd gedrag.
Belangrijkste punten
- Drijvendekommatypen hebben een beperkte precisie en een beperkt bereik.
- Vergelijk berekende meetwaarden met een geschikte tolerantie.
- Bij het afdrukken wordt de opgeslagen benadering afgerond.
- Conversie van een drijvendekommawaarde naar een geheel getal kapt af naar nul en vereist een representeerbaar resultaat.
3Toon uitvoer en lees invoer met printf en scanf
Een programma heeft een duidelijke uitwisseling van tekst en waarden nodig. Maak uitvoer zo op dat een lezer labels, velden en afgeronde meetwaarden kan onderscheiden en stem elke invoerconversie af op de variabele die de waarde ontvangt. Het lezen van tekst kan verschillende resultaten opleveren: alle gevraagde waarden, slechts enkele waarden of helemaal geen conversie. Door die resultaten en de ongelezen tekst te controleren, zorg je dat latere berekeningen afhangen van waarden die het programma daadwerkelijk heeft verkregen.
3.1 Waarden met het bedoelde formaat afdrukken
printf zet waarden met een formaatstring om in tekst. Gewone tekens worden afgedrukt zoals ze zijn geschreven. Een conversie die met % begint, geeft aan hoe het volgende argument moet worden weergegeven.
Stem elke conversie af op het argument
printf("%d samples, %.2f kg\n", 4, 1.5);
Dit drukt 4 samples, 1.50 kg en een nieuweregelteken af. De conversies komen op volgorde overeen met de argumenten. Een formaat converteert het type van het opgegeven argument niet naar het type dat je bedoelde.
| Waardetype of weergave | Conversie |
|---|---|
int | %d of %i |
unsigned int | %u |
long, long long | %ld, %lld |
size_t | %zu |
double, of een gepromoveerde float | %f, %e, %g |
long double | %Lf |
| Teken, stringliteral | %c, %s |
| Hexadecimaal zonder teken, octaal | %x of %X, %o |
Een float-argument wordt in printf gepromoveerd tot double, dus beide gebruiken %f; %lf is hier gelijkwaardig. Als je 3 doorgeeft aan %f, komen de typen niet overeen; het is geen verzoek om 3.0 af te drukken. Gebruik 3.0 of (double)3. Een niet-overeenkomend type of een ontbrekend argument kan ongedefinieerd gedrag veroorzaken. Bouw met waarschuwingen ingeschakeld, zodat formaatstrings die literals zijn worden gecontroleerd.
Stel breedte en precisie in
Een breedte is een minimaal aantal tekens, inclusief een teken en decimale punt. Een drijvendekomma-precisie na de punt bepaalt het aantal decimalen voor %f. In %8.2f is acht de minimale breedte en twee het aantal cijfers na de decimale punt. Een breder getal wordt volledig afgedrukt; de breedte kapt het niet af.
Met het gehele getal 42 geeft %5d de uitvoer 42, %-5d geeft 42 , %05d geeft 00042 en %+d geeft +42. Zonder precisie drukt %f zes decimalen af.
printf("%-6s%8s\n", "Item", "Price");
printf("%-6s%8.2f\n", "Tea", 2.5);
printf("%-6s%8.2f\n", "Cake", 12.75);
Elk label neemt een links uitgelijnd veld van zes tekens in, gevolgd door een rechts uitgelijnd prijsveld van acht tekens. Beide prijzen hebben twee decimalen, zodat de decimale punten onder elkaar staan. De spaties komen van de veldbreedten, niet van automatische scheiding tussen argumenten.
Wetenschappelijke en algemene weergave
%e gebruikt wetenschappelijke notatie: 12.345,678 wordt bij de standaardprecisie afgedrukt als 1.234568e+04. Voor %g telt de precisie significante cijfers, standaard zes. Het gebruikt wetenschappelijke notatie wanneer de decimale exponent kleiner is dan −4 of minstens gelijk is aan de precisie, en verwijdert nullen aan het einde. Zo drukt %g 0,0001 af als 0.0001, 0,00001 als 1e-05 en 1.000.000,0 als 1e+06. Het kiest niet simpelweg de kortst mogelijke schrijfwijze.
Druk speciale tekens af
In een C-string betekent \n een nieuweregelteken, \t een tab, \\ een backslash en \" een aanhalingsteken. Een procentteken in een formaat is %%: printf("100%%\n"); drukt 100% en een nieuweregelteken af.
printf voegt zelf geen spaties of regeleinden toe. printf("Count:"); printf("%d", 4); drukt Count:4 af. Een terminal kan tekst zonder nieuweregelteken vertraagd tonen; fflush(stdout); verzendt een wachtende prompt voordat om invoer wordt gevraagd.
Belangrijkste punten
- Stem conversies af op de werkelijke typen van de argumenten.
- De veldbreedte is een minimum; de precisie van
%ftelt decimalen. - De precisie van
%gtelt significante cijfers. - Schrijf
%%voor een letterlijk procentteken in een formaat. printfvoegt geen spaties of nieuweregelteken toe tenzij het formaat daarin voorziet.
3.2 Invoer lezen en controleren wat is gelezen
scanf leest tekst, converteert die en slaat waarden op in variabelen. Controleer het resultaat voordat je die variabelen gebruikt. Tekst die met cijfers begint, kan slechts gedeeltelijk worden geaccepteerd, dus een geslaagde conversie en een volledig geldige invoerregel zijn verschillende dingen.
Geef een bestemming van het juiste type op
int count;
double mass_kg;
if (scanf("%d %lf", &count, &mass_kg) != 2) {
return 1;
}
De & geeft het adres van elke variabele door zodat scanf daarin kan opslaan. %d vereist een int, %u een geheel getal zonder teken, %ld een long en %lld een long long. Voor drijvendekomma-invoer vereist %f een float en %lf een double. Anders dan bij printf zijn deze twee conversies niet uitwisselbaar. Een ontbrekende & of een verkeerd bestemmingstype kan ongedefinieerd gedrag veroorzaken.
%d leest decimaal. %i herkent ook hexadecimale invoer en octale invoer met een voorloopnul; met %i betekent de tekst “010” 8. Gebruik %d voor gewone decimale aantallen.
Controleer de retourwaarde
scanf geeft het aantal voltooide toewijzingen terug. Voor twee gevraagde waarden betekent succes 2. Een mislukte conversie laat haar bestemming ongewijzigd. Die kan nog niet-geïnitialiseerd zijn, dus gebruik de variabele niet tenzij het teruggegeven aantal bevestigt dat de conversie is geslaagd. EOF betekent dat de invoer eindigde of een invoerfout optrad voordat de eerste conversie was voltooid.
Bij scanf("%d:%d", &h, &m) moet de dubbele punt letterlijk voorkomen. De invoer “14:30” vult beide variabelen. De invoer “14 30” vult h, vindt daarna niet de vereiste dubbele punt en laat m ongewijzigd. De retourwaarde is 1.
Begrijp witruimte en gedeeltelijke invoer
Numerieke conversies slaan voorafgaande spaties, tabs en nieuweregeltekens over. %c leest het eerstvolgende teken, inclusief een achtergebleven nieuweregelteken. Gebruik scanf(" %c", &choice) om witruimte over te slaan voordat je een zichtbaar teken leest. Vermijd een afsluitend nieuweregelteken in het formaat: dat kan ervoor zorgen dat de aanroep wacht op een volgend teken dat geen witruimte is.
Met %d slaat “12abc” 12 op en laat “abc” ongelezen. “3.7” slaat 3 op en laat “.7” staan. “abc” slaat niets op en geeft 0 terug. Als je dezelfde conversie opnieuw aanroept zonder ongeldige invoer te verwerken, kan die eindeloos op hetzelfde teken blijven mislukken.
Herstel na een mislukte conversie
int n, result;
while ((result = scanf("%d", &n)) != 1) {
if (result == EOF) {
return 1;
}
int ch;
while ((ch = getchar()) != '\n' && ch != EOF) {
}
printf("Try again: ");
fflush(stdout);
}
getchar geeft een int terug, zodat de functie zowel een teken als EOF kan voorstellen. De binnenste lus gooit de mislukte regel weg. De buitenste lus probeert het opnieuw, maar accepteert nog steeds een numeriek begin zoals 12 in “12abc”. Als je vereist dat het volgende teken '\n' is, controleer je de strengere regel van één geheel getal dat direct door een nieuweregelteken wordt gevolgd; die regel wijst ook afsluitende spaties af. Een beleid dat spaties of een laatste regel die bij EOF eindigt toestaat, moet die gevallen expliciet afhandelen.
Controleer na een geslaagde conversie het toegestane bereik, zoals 1 tot en met 8 voor een hoogte-instelling. scanf wijst een numerieke waarde buiten het bereik niet veilig af: een waarde die te groot is voor de bestemming heeft ongedefinieerd gedrag. Gebruik testinvoer die in het gekozen type past. Om een resultaat zoals massa gedeeld door lengte in het kwadraat te berekenen, controleer je of de meetwaarden positief zijn en houd je het kwadraat gegroepeerd als height_m * height_m.
Belangrijkste punten
- Geef adressen van variabelen door en gebruik
%lfom eendoublete lezen. - Vereis dat het teruggegeven aantal overeenkomt met het aantal benodigde waarden.
- Een geslaagde conversie kan extra invoer ongelezen laten.
- Verwerk ongeldige invoer voordat je een mislukte numerieke conversie opnieuw probeert.
- Controleer na het lezen de toegestane waarden; invoerconversie heeft eigen grenzen voor het typebereik.
- Gebruik
" %c"als het lezen van een teken witruimte moet overslaan.
4Reken met operatoren en expressies
Een expressie combineert waarden, typen en operatoren. Volg eerst geheeltallige deling en bijwerkingen en daarna de conversies die bij elke bewerking plaatsvinden. Vergelijkingen en logische controles bepalen of een berekening wordt bereikt, terwijl bitsgewijze bewerkingen op afzonderlijke bits werken. Onderscheid ten slotte de groepering door operatorprioriteit van de volgorde die nodig is wanneer een expressie een variabele wijzigt. Zowel de waarde als het type van een tussenresultaat is van belang.
4.1 Rekenen, toewijzing en wijzigingen met één stap
Elke C-expressie heeft een waarde en een type. Het type bepaalt hoe een bewerking werkt. Geheeltallige deling kan bijvoorbeeld geen resultaat met een breukdeel bewaren, ook niet als je het later in een drijvendekommavariabele opslaat.
Quotiënten en resten
+, - en * tellen op, trekken af en vermenigvuldigen. / deelt en % geeft een geheeltallige rest. Vermenigvuldiging, deling en rest binden sterker dan optelling en aftrekking.
Wanneer beide operanden van een deling gehele getallen zijn, kapt C het quotiënt af naar nul. 17 / 5 is 3, -7 / 2 is −3 en 7 / -3 is −2. De rest voldoet aan de delingsidentiteit: als je het quotiënt met de deler vermenigvuldigt en de rest optelt, krijg je het deeltal. Zo is -7 % 2 gelijk aan −1 en 7 % -3 aan 1. Een rest anders dan nul heeft het teken van het deeltal.
Gebruik n % 2 == 0 om te controleren of een getal even is. Gebruik n % 2 != 0 om bij positieve of negatieve gehele getallen te controleren of ze oneven zijn; een controle op precies 1 mist negatieve oneven waarden. % vereist gehele operanden; <math.h> biedt fmod voor drijvendekommaresten. Een geheeltallige deler nul maakt zowel / als % ongedefinieerd, dus controleer de deler vóór de bewerking.
Voor 135 minuten geeft 135 / 60 twee hele uren en 135 % 60 15 resterende minuten. Voor een positief decimaal geheel getal haalt % 10 het laatste cijfer op en verwijdert / 10 dat cijfer: 4096 geeft 6 en 409.
Behoud breukdelen wanneer je ze nodig hebt
int correct = 45, total = 60;
double percent = 100.0 * correct / total;
De drijvendekommaconstante maakt de vermenigvuldiging een drijvendekommabewerking en de deling geeft 75,0. In correct / total * 100 is de eerste bewerking een geheeltallige deling die nul geeft. Door die nul te vermenigvuldigen kun je het verloren breukdeel niet terugkrijgen. Eerst geheeltallig vermenigvuldigen, correct * 100 / total, geeft hier 75, mits het product past.
Sla een nieuwe waarde op
Een toewijzing evalueert de rechterkant en slaat de geconverteerde waarde links op. De toewijzingsexpressie heeft die opgeslagen waarde, dus a = b = 0; zet beide variabelen op nul en groepeert vanaf rechts.
Samengestelde toewijzingen combineren een berekening met opslag: x += 5 telt vijf op bij x en x /= 2 deelt x door twee met het huidige type. De hele rechteroperand hoort bij de berekening. Als x gelijk is aan 4, maakt x *= 2 + 1 die waarde 12, omdat de instructie met drie vermenigvuldigt.
Prefix- en postfixwijzigingen
++ telt één op en -- trekt één af. Op een eigen regel hebben i++; en ++i; hetzelfde uiteindelijke effect. Binnen een grotere expressie levert postfix de oude waarde op; prefix levert de nieuwe waarde op.
| Begin | Instructie | Eindwaarden |
|---|---|---|
a = 5 | y = a++; | y = 5, a = 6 |
a = 5 | y = ++a; | y = 6, a = 6 |
Houd wijzigingen eenvoudig te ordenen. i = i++ + 1; en printf("%d %d\n", n, n++); bevatten tegenstrijdige, niet-geordende toepassingen van dezelfde variabele en hebben ongedefinieerd gedrag. Scheid het lezen, het bijwerken en de uitvoer in afzonderlijke instructies wanneer hun volgorde van belang is.
Belangrijkste punten
- Geheeltallige deling kapt af naar nul.
- Een geheeltallige rest anders dan nul heeft het teken van het deeltal.
- Gebruik vóór de deling een drijvendekommaoperand als je een breukdeel nodig hebt.
- Postfix levert de oude waarde op; prefix levert de bijgewerkte waarde op.
- Zet tegenstrijdige leesacties en bijwerkingen in afzonderlijke instructies.
4.2 Typen worden bepaald voordat een resultaat wordt opgeslagen
C converteert waarden telkens wanneer een operator wordt toegepast. De doelvariabele ontvangt het voltooide resultaat; die grijpt niet terug in de expressie om eerdere bewerkingen te veranderen.
Converteer vóór het delen
int sum = 7, count = 2;
double a = sum / count;
double b = (double)sum / count;
double c = (double)(sum / count);
a wordt 3,0: de twee gehele operanden leveren het gehele getal 3 op, dat vervolgens wordt geconverteerd. b wordt 3,5: de expliciete typeconversie converteert sum vóór de deling, zodat ook count naar double wordt geconverteerd. c wordt 3,0: de typeconversie staat buiten de deling en komt nadat het breukdeel is weggegooid.
Dezelfde regel geldt binnen een langere expressie. 7 / 2 * 2.0 geeft 6,0, omdat het eerste resultaat het gehele getal 3 is. 7 / 2.0 * 2 geeft 7,0. Doorloop de operatoren in de volgorde van hun groepering en noteer het type van elke tussenwaarde.
Bepaal een gemeenschappelijk rekenkundig type
Als een van de operanden een drijvendekommawaarde is, converteert een rekenkundige bewerking doorgaans naar het breedste betrokken drijvendekommatype: long double, dan double, dan float. Bij gehele getallen ondergaan kleine typen eerst integerpromotie. Op platforms met 8-bits char, 16-bits short en 32-bits int past elke waarde van de kleinere typen in int, zodat die typen worden gepromoveerd tot int.
Met unsigned char a = 200, b = 100; is a + b het gehele getal 300 op een systeem met 8-bits bytes en 32-bits int. Tijdens de optelling loopt de waarde niet modulair terug bij 255. Als je 300 opslaat in een 8-bits tekentype zonder teken, wordt de waarde verlaagd tot 44. Promotie kan ook overloop bij getallen met teken veroorzaken: twee waarden van unsigned short van 50.000 worden als int vermenigvuldigd, maar 2.500.000.000 is groter dan een 32-bits INT_MAX.
Voor typen met en zonder teken bepalen rang en representeerbaar bereik het gemeenschappelijke type. int met unsigned int wordt geconverteerd naar een type zonder teken. Een type met teken en een hogere rang kan de bewerking met teken houden wanneer het elke waarde van het type zonder teken kan voorstellen; anders kan nog steeds rekenen zonder teken ontstaan.
De valkuil van typen met en zonder teken
Bij een 32-bits geheel getal zonder teken is -1 < 0u onwaar. De −1 met teken wordt vóór de vergelijking geconverteerd naar de waarde zonder teken 4.294.967.295. Evenzo geeft 2u - 10 de waarde 4.294.967.288. Een resultaat van sizeof is ook zonder teken, wat van belang is bij vergelijking met een mogelijk negatief aantal. Schakel waarschuwingen in en houd samenhangende grootheden in compatibele typen.
Bij opslagconversies kan informatie verloren gaan
Een conversie van een drijvendekommawaarde naar een geheel getal kapt af naar nul. Het gehele deel moet passen, anders is het gedrag ongedefinieerd. Een geheel getal dat naar een N-bits type zonder teken wordt geconverteerd, wordt gereduceerd modulo 2ᴺ; −1 wordt de maximale waarde zonder teken. Onder C17 geeft een geheeltallige conversie buiten het bereik naar een type met teken een door de implementatie bepaald resultaat of wekt die een door de implementatie bepaald signaal op. Die conversie verschilt van ongedefinieerde rekenkundige overloop bij getallen met teken.
Tekenconstanten nemen ook deel aan berekeningen. '7' - '0' geeft 7 omdat C opeenvolgende codes voor decimale cijfers garandeert. In ASCII zijn ook letters opeenvolgend, maar ga niet uit van die eigenschap voor elke tekenset. In formules met gehele getallen kan vermenigvuldigen vóór het delen alleen nuttige informatie behouden als dat product in het berekeningstype past.
Belangrijkste punten
- Het doeltype verandert eerdere rekenkundige bewerkingen niet.
- Converteer een operand expliciet vóór de bewerking die een breder type of een drijvendekommatype nodig heeft.
- Kleine gehele getallen worden vóór rekenkundige bewerkingen gepromoveerd.
- Een combinatie van operanden met en zonder teken kan vergelijkingen en aftrekkingen veranderen.
- Voor conversie buiten het bereik en rekenkundige overloop bij getallen met teken gelden verschillende regels.
- Volg zowel tussentypen als eindwaarden.
4.3 Voorwaarden en bitpatronen
Een voorwaarde vraagt of iets waar is. Een bitsgewijze bewerking werkt op de afzonderlijke bits van een geheel getal. C geeft deze taken verschillende operatoren, ook wanneer hun symbolen op elkaar lijken.
Vergelijk waarden en combineer controles
De vergelijkingen <, >, <=, >=, == en != leveren een int op: 1 voor waar en 0 voor onwaar. In een voorwaarde is nul onwaar en elke waarde anders dan nul waar. Zo test if (count) of count ongelijk aan nul is. == vergelijkt twee waarden; = wijst een nieuwe toe.
&& betekent dat beide voorwaarden waar moeten zijn, || betekent dat minstens één waar moet zijn en ! keert de waarheidswaarde om. Ze geven 0 of 1 terug. Voor een bereik van 1 tot en met 8 zijn twee vergelijkingen nodig:
height >= 1 && height <= 8
1 <= height <= 8 drukt dat bereik in C niet uit. Het wordt gegroepeerd als (1 <= height) <= 8, zodat de tweede vergelijking 0 of 1 ontvangt en altijd waar is. Om buiten het bereik te testen, schrijf je height < 1 || height > 8.
Bij het ontkennen van een samengestelde voorwaarde veranderen beide delen: !(a && b) is gelijkwaardig aan !a || !b en !(a || b) aan !a && !b. Dit zijn de wetten van De Morgan. Ze verklaren waarom buiten een interval zijn een “of” vereist, terwijl erin zijn een “en” vereist.
Zet een controle vóór de risicovolle bewerking
if (d != 0 && n / d > 2) {
printf("Above two\n");
}
&& evalueert eerst de linkerkant. Als die onwaar is, wordt de rechterkant overgeslagen. || slaat de rechterkant over wanneer de linkerkant waar is. Met deze kortsluitevaluatie kan een controle een latere bewerking beschermen. Als je de twee delen hierboven omkeert, wordt gedeeld voordat de deler is gecontroleerd. Voor gehele getallen met teken bestaat een afzonderlijk bereikprobleem wanneer INT_MIN door −1 wordt gedeeld op een systeem met tweecomplement; controleer ook dat paar wanneer willekeurige waarden mogelijk zijn.
Een schrikkeljaarcontrole combineert deelbaarheid en uitzonderingen: (y % 4 == 0 && y % 100 != 0) || y % 400 == 0. Het jaar 2000 voldoet aan de controle op 400; 1900 niet, omdat de eeuwuitzondering geldt.
Werk bit voor bit
De bitsgewijze & behoudt bits die in beide operanden zijn gezet; | behoudt bits die in een van beide zijn gezet; ^ behoudt bits die verschillen; ~ keert bits om. Met 12 (1100) en 10 (1010) zijn de resultaten 8, 14 en 6 voor &, | en ^.
Bitverschuivingen verplaatsen bits. Voor niet-negatieve waarden waarbij het resultaat past, vermenigvuldigt x << k met 2ᵏ en gooit x >> k k lage bits weg, wat deelt door 2ᵏ met weggooien van de rest. Gebruik typen zonder teken voor bitsgewijze bewerkingen. Onder C17 is verschuiven met een negatief aantal of minstens de breedte van het gepromoveerde type ongedefinieerd; een negatieve waarde met teken naar links verschuiven is ongedefinieerd en die naar rechts verschuiven is door de implementatie bepaald.
Bitposities beginnen bij nul. flags | (1u << 3) zet bit 3; flags & ~(1u << 3) wist die; (flags >> 3) & 1u leest die. 1 & 2 is 0 omdat er geen gemeenschappelijke bit is, terwijl 1 && 2 gelijk is aan 1 omdat beide waarden ongelijk aan nul zijn. Bitsgewijze operatoren evalueren beide operanden en kunnen logische controles niet vervangen.
Belangrijkste punten
- Vergelijkingen en logische operatoren leveren 0 of 1 op.
- Een bereikcontrole verbindt twee vergelijkingen met
&&. - Controles met kortsluitevaluatie moeten vóór de beschermde bewerking komen.
- Gebruik logische operatoren voor waarheidswaarden en bitsgewijze operatoren zonder teken voor maskers.
- Bitposities beginnen bij nul; het aantal verschuivingen moet binnen de breedte van het gepromoveerde type vallen.
4.4 Expressies groeperen en effecten ordenen
Twee vragen helpen je een expressie te lezen: welke operanden horen bij elke operator en wanneer vinden bewerkingen met neveneffecten plaats? Operatorprioriteit en associativiteit beantwoorden de vraag over groepering. Ze leggen doorgaans niet de evaluatievolgorde van operanden vast.
Volg de operatorprioriteit
Operatoren hoger in deze tabel binden sterker. Elke rij staat lager dan de voorgaande rij; de operatoren die door “dan” worden gescheiden, hebben afzonderlijke niveaus.
| Van hoog naar laag | Operatoren |
|---|---|
| Postfix | x++, x--, aanroepen |
| Unair | ++x, --x, +, -, !, ~, expliciete typeconversies, sizeof, adresoperator & |
| Vermenigvuldiging | *, /, % |
| Optelling | +, - |
| Bitverschuiving | <<, >> |
| Relationeel | <, <=, >, >= |
| Gelijkheid | ==, != |
| Bitsgewijs | &, dan ^, dan ` |
| Logisch | &&, dan ` |
| Voorwaardelijk | ?: |
| Toewijzing | =, += en andere samengestelde toewijzingen |
| Komma | , |
Haakjes bepalen de groepering. 2 + 3 * 4 is 14; (2 + 3) * 4 is 20.
Gebruik associativiteit op gelijke niveaus
De meeste binaire rekenkundige operatoren groeperen van links naar rechts. 10 - 4 - 3 betekent (10 - 4) - 3, wat 3 is. 100 / 10 / 2 geeft 5. Toewijzing groepeert van rechts naar links, dus a = b = 0 slaat nul op in beide variabelen. Groepering beschrijft de structuur van de expressie; die legt geen algemene regel voor uitvoering van links naar rechts vast.
Volg voor int a = 3, b = 4, c = 5; de expressie a + b * c > 20 && c % 2 == 1. Vermenigvuldiging geeft 20, optelling geeft 23 en 23 > 20 geeft 1. De rest is 1 en de bijbehorende gelijkheidscontrole geeft 1. De laatste 1 && 1 is gelijk aan 1.
Bescherm bitcontroles en toewijzingen met haakjes
x & 1 == 0 betekent x & (1 == 0) en levert dus altijd nul op. Schrijf (x & 1) == 0 om de lage bit te testen. Optellen bindt ook sterker dan verschuiven: 1 << 2 + 1 is 1 << 3, oftewel 8.
Toewijzing bindt minder sterk dan vergelijking. a = b == c slaat de 0 of 1 van de vergelijking op in a. In while ((ch = getchar()) != '\n') zorgen de binnenste haakjes ervoor dat de toewijzing eerst het teken ontvangt. Zonder die haakjes zou ch een vergelijkingsresultaat ontvangen.
Kies één waarde of orden twee acties
condition ? first : second evalueert de voorwaarde en daarna alleen de gekozen waarde-expressie. max = (a > b) ? a : b; kiest een waarde; een if kan een blok met acties kiezen. De kommaoperator evalueert eerst zijn linkerexpressie en levert de waarde van zijn rechterexpressie op. Die kan twee bijwerkingen in de kop van een for-lus op elkaar afstemmen. Komma’s tussen functieargumenten zijn scheidingstekens en leggen die volgorde niet vast.
Houd neveneffecten gescheiden
C laat veel volgorden van operanden en argumenten ongespecificeerd. n++ * n++ heeft niet-geordende wijzigingen van dezelfde variabele en dus ongedefinieerd gedrag. Extra haakjes herstellen de ontbrekende ordening niet. Gebruik afzonderlijke instructies wanneer de ene actie aan de andere moet voorafgaan. &&, ||, ?: en de kommaoperator bieden specifieke regels voor de volgorde; gewone rekenkundige operatorprioriteit biedt zo’n garantie niet.
Belangrijkste punten
- Operatorprioriteit bepaalt de groepering; die bepaalt doorgaans niet de evaluatievolgorde.
- Op één rekenkundig niveau is de groepering meestal van links naar rechts.
- Zet een bitsgewijs resultaat tussen haakjes voordat je het vergelijkt.
- De voorwaardelijke operator evalueert alleen de gekozen waarde-expressie.
- De kommaoperator ordent operanden; komma’s die argumenten scheiden doen dat niet.
- Scheid tegenstrijdige bijwerkingen om ongedefinieerd gedrag te voorkomen.
5Stuur het programmaverloop met voorwaarden en lussen
Het programmaverloop bepaalt welke instructies worden uitgevoerd en hoe vaak. Gebruik voorwaarden om een tak te kiezen, exacte gehele waarden om een ingang in een switch te kiezen en lussen om een taak te herhalen totdat aan de stopregel is voldaan. Uitvoeringssporen maken de volgorde zichtbaar: beginwaarden, controles, instructies in de lusinhoud en bijwerkingen. Let vooral op grenswaarden in de invoer en op de bestemming van break of continue binnen geneste constructies, waar een kleine wijziging in de syntaxis het hele verloop kan veranderen.
5.1 De juiste tak kiezen
Een if-instructie kiest of een actie wordt uitgevoerd. De voorwaarde moet tussen haakjes staan. Nul betekent onwaar; elke waarde anders dan nul betekent waar.
Gebruik accolades om de tak te tonen
if (temperature < 0) {
printf("Freezing\n");
} else {
printf("At or above freezing\n");
}
Het eerste blok wordt uitgevoerd bij een negatieve temperatuur. Bij precies nul is de vergelijking onwaar en wordt het else-blok uitgevoerd. Lees de grens rechtstreeks af uit <, <=, > of >=; de melding moet bij die grens passen.
Zonder accolades wordt alleen de volgende instructie door de voorwaarde gestuurd:
if (temperature > 30)
printf("Hot\n");
printf("Drink water\n");
De tweede melding wordt bij elke temperatuur uitgevoerd. Inspringen helpt mensen, maar verandert de grammatica niet. Met accolades kun je een tweede instructie aan een tak toevoegen zonder die per ongeluk erbuiten te laten.
Orden een else-if-keten
Een if-else if-else-keten voert de eerste tak uit waarvan de voorwaarde waar is en slaat daarna de rest over. Begin bij scorecategorieën met de hoogste ondergrens:
if (score >= 90) {
grade = 'A';
} else if (score >= 80) {
grade = 'B';
} else if (score >= 70) {
grade = 'C';
} else {
grade = 'F';
}
Een score van 85 voldoet niet aan de eerste controle en wel aan de tweede. Een score van 90 voldoet aan de eerste. Als je eerst score >= 70 zou testen, zou 85 die tak ingaan en nooit de bedoelde B-tak bereiken. Afzonderlijke if-instructies gedragen zich anders: elke voorwaarde wordt onafhankelijk getest, zodat meerdere blokken kunnen worden uitgevoerd.
Let op twee kleine syntaxisfouten
if (x = 5) wijst 5 toe en behandelt die waarde anders dan nul vervolgens als waar. Gebruik if (x == 5) om te vergelijken. Bij if (x = 0) vindt de toewijzing nog steeds plaats, maar is de voorwaarde onwaar. Waarschuwingen helpen onbedoelde toewijzingen in voorwaarden zichtbaar te maken.
In if (x > 0); is de puntkomma een lege instructie die door de voorwaarde wordt gestuurd. Een blok dat erna staat, wordt onafhankelijk uitgevoerd, ook als x negatief is. Voeg geen puntkomma toe tussen een if-voorwaarde en het bedoelde blok.
Koppel elke else expliciet
Een else hoort bij de dichtstbijzijnde eerdere if die nog geen else heeft. Gebruik bij geneste keuzes accolades om de bedoelde koppeling duidelijk te maken. Nesten is handig wanneer controles die niet slagen verschillende meldingen vereisen: controleer eerst of een meetwaarde geldig is en controleer daarna of de waarde binnen de gewenste categorie valt. Beide combineren met && werkt wanneer maar één gezamenlijke actie nodig is.
Zet uitzonderingen vóór algemene regels
Schrikkeljaren bieden een nuttige keuze op volgorde. Controleer eerst de deelbaarheid door 400, wat een schrikkeljaar betekent. Controleer daarna de deelbaarheid door 100, wat geen schrikkeljaar betekent. Controleer vervolgens de deelbaarheid door 4; anders is het jaar geen schrikkeljaar. Voor 2000 geldt de eerste tak. Voor 1900 geldt de tweede. Als je alleen de deelbaarheid door vier zou controleren, zou je de eeuwuitzondering missen. De volgorde is van belang omdat algemene regels gevallen met een specifiekere uitzondering kunnen bevatten.
Belangrijkste punten
- Een else-if-keten voert alleen de eerste tak uit waarvan de voorwaarde waar is.
- Accolades bepalen welke instructies bij een tak horen.
==vergelijkt;=wijst toe.- Een puntkomma na een
if-voorwaarde maakt een lege tak.
5.2 Vaste gevallen kiezen met switch
Een switch kiest een startpunt met één gehele waarde. Dit is handig voor een opdrachtteken of menunummer met verschillende vaste alternatieven. De uitvoering kan daarna doorlopen naar latere gevallen, tenzij je die stopt.
Kies een geval en verlaat het
switch (choice) {
case 1:
printf("Start\n");
break;
case 2:
printf("Pause\n");
break;
default:
printf("Unknown choice\n");
break;
}
Als choice gelijk is aan 2, begint het programma bij case 2, drukt het Pause af en verlaat het de switch bij break. Als geen geval overeenkomt, begint de uitvoering bij default. Zonder default slaat een waarde zonder overeenkomst de switch over.
De sturende expressie moet een geheelgetaltype hebben, waaronder char of een enumeratie. Elk geval moet een afzonderlijke geheeltallige constante expressie zijn, zoals 3, 'q' of een geschikte #define-naam. In C17 is een const int-variabele geen geheeltallige constante expressie voor een case-label. Drijvendekommawaarden en strings kunnen geen sturende waarden van een switch zijn.
Begrijp doorvallen
Case-labels markeren ingangen, geen afzonderlijke blokken die automatisch eindigen. Verwijder de eerste break hierboven en een keuze van 1 drukt zowel Start als Pause af. Het programma test niet of choice gelijk is aan 2 voordat het door dat geval verdergaat. De uitvoering stopt pas bij een overdracht van de besturing of het einde van de switch.
Meerdere labels kunnen één actie delen:
case 'y':
case 'Y':
printf("Yes\n");
break;
Beide tekens leiden naar dezelfde actie. Wanneer een geval bewust werk uitvoert en doorgaat naar een ander geval, leg je de bedoeling uit met commentaar. C23 biedt ook [[fallthrough]];. Waarschuwingen kunnen een ontbrekende break helpen vinden, maar bij het bewust groeperen van labels hoef je de actie niet te herhalen.
Geef lokale variabelen van een geval een blok
Onder C17 kan een declaratie niet direct op een case-label volgen. Zet de instructies van dat geval tussen accolades:
case 3: {
int delay = 5;
printf("%d\n", delay);
break;
}
Het blok houdt delay ook lokaal voor dat geval. C23 versoepelt de beperking op een label vóór een declaratie, maar accolades maken het geldigheidsgebied nog steeds duidelijk.
Kies tussen switch en if
Gebruik switch voor exacte gehele alternatieven. Gebruik if-else if voor ongelijkheden, voorwaarden met meerdere variabelen of drijvendekommameetwaarden. Scorecategorieën van gelijke breedte kunnen soms door geheeltallige deling naar exacte gevallen worden omgezet. Bij scores van 0 tot en met 100 geeft score / 10 de waarde 8 voor 85; de gevallen 9 en 10 kunnen de hoogste categorie delen. Controleer dat waardenbereik, zodat negatieve of te hoge scores niet zonder melding aan een categorie worden toegewezen.
Een switch binnen een lus
Een break verlaat de dichtstbijzijnde omsluitende switch of lus. Binnen een switch die zelf in een lus staat, beëindigt de break alleen de switch. Om de menulus te beëindigen, stel je een vlag in die door de voorwaarde wordt gecontroleerd, of keer je terug uit main wanneer het programma moet eindigen. Een continue in die switch geldt voor de omsluitende lus en gaat daar naar de volgende iteratie. Ga na op welke constructie elke overdracht van de besturing betrekking heeft voordat je bepaalt of het menu stopt.
Belangrijkste punten
- Een switch kiest een beginlabel met één gehele waarde.
- De uitvoering valt door tot
break, een andere overdracht van de besturing of het einde. - Case-waarden moeten verschillende geheeltallige constante expressies zijn.
- Gebruik in C17 een blok voor een declaratie die lokaal is voor een geval.
- Een
breakin een switch beëindigt niet ook een omsluitende lus.
5.3 Herhalen totdat de gegevens aangeven dat je moet stoppen
Een while-lus test een voorwaarde vóór elke iteratie. Een do-while-lus test na de lusinhoud en wordt dus minstens eenmaal uitgevoerd. Kies de vorm die de controle plaatst waar het programma die nodig heeft.
Volg een berekening die door een voorwaarde wordt gestuurd
int n = 4096, sum = 0;
while (n > 0) {
sum += n % 10;
n /= 10;
}
n % 10 haalt een cijfer op en n /= 10 verwijdert het. De waarden na de iteraties zijn:
| Iteratie | sum | n |
|---|---|---|
| 1 | 6 | 409 |
| 2 | 15 | 40 |
| 3 | 15 | 4 |
| 4 | 19 | 0 |
De volgende voorwaarde is onwaar, dus de lus eindigt met een cijfersom van 19. Als n bij nul begint, wordt de lusinhoud nul keer uitgevoerd en blijft de som nul. Negatieve invoer slaat deze lus ook over, dus een programma dat die accepteert, heeft een vastgelegde aanpak nodig.
Een tellende lus stelt een teller in, test die en werkt die bij. Andere lussen maken voortgang door gegevens te lezen of break te bereiken. Bij herhaald geheeltallig halveren van 100 zijn de waarden 50, 25, 12, 6, 3 en 1: er zijn zes iteraties nodig om één te bereiken.
Laat invoer de herhaling sturen
int x, sum = 0, count = 0;
while (scanf("%d", &x) == 1 && x != 0) {
sum += x;
count++;
}
if (count != 0) {
printf("%.2f\n", (double)sum / count);
}
Bij invoer “5 8 3 0” beëindigt de stopwaarde nul de lus en wordt die niet opgeteld. De som is 16, het aantal is 3 en het weergegeven gemiddelde is 5.33. De controle van het teruggegeven aantal beschermt het gebruik van x; de controle van het aantal beschermt de deling als de eerste waarde nul is. Deze berekening vereist ook invoer en totalen die in int passen.
Zonder controle op de stopwaarde stopt while (scanf("%d", &x) == 1) bij een mislukte numerieke conversie of het einde van de invoer. In een terminal geeft Ctrl+D normaal gesproken het einde van de invoer aan op Linux en macOS; op Windows gebruik je Ctrl+Z gevolgd door Enter.
Voer eenmaal uit vóór de controle
int height;
do {
printf("Height (1-8): ");
fflush(stdout);
if (scanf("%d", &height) != 1) {
return 1;
}
} while (height < 1 || height > 8);
De prompt en het lezen vinden plaats vóór de bereikcontrole. De waarden nul en negen leiden tot herhaling; de waarden één en acht worden geaccepteerd. De puntkomma na while (...) is in deze vorm vereist. De conversiecontrole beëindigt dit voorbeeld bij niet-numerieke invoer; de bereikcontrole alleen kan een mislukte conversie niet afhandelen.
Maak voortgang op elk pad dat doorgaat
break verlaat de binnenste lus onmiddellijk. continue slaat de rest van de lusinhoud over en keert in een while-lus terug naar de voorwaarde. Als een bijwerking onder een continue staat, wordt die ook overgeslagen. Een lus kan dan eindeloos dezelfde waarde blijven testen.
Bepaal voordat je een lus uitvoert wat er verandert en waarom de stopvoorwaarde uiteindelijk wordt bereikt. Een bewuste while (1) kan stoppen via break; een ontbrekende bijwerking van een teller is een andere situatie. Ctrl+C stopt een programma dat in een terminal vastzit. Test de kleinste invoer en de waarde op de stopgrens om fouten met nul iteraties en één extra iteratie op te sporen.
Belangrijkste punten
whiletest eerst;do-whiletest na minstens één iteratie.- Een stopwaarde stopt de invoer zonder deel uit te maken van de gegevens.
- Initialiseer accumulatoren buiten de lus.
- Een pad dat doorgaat, moet voortgang maken richting de stopvoorwaarde.
- Een continue in een
while-lus kan een latere bijwerking overslaan. - Controleer of er nul gegevensitems zijn voordat je een gemiddelde berekent.
5.4 Iteraties tellen en lussen combineren
Een for-lus houdt een telpatroon in één kop bij elkaar. Geneste lussen herhalen een binnenste taak voor elke buitenste iteratie. Beide zijn gemakkelijker te controleren als je de werkelijke tellerwaarden opsomt en elke uitgang aanwijst.
Volg de kop op volgorde
for (int i = 0; i < 3; i++) {
printf("%d ", i);
}
De initialisatie wordt eenmaal uitgevoerd. Daarna wordt de voorwaarde getest, de lusinhoud uitgevoerd als die waar is en de bijwerking uitgevoerd vóór de volgende controle. De uitvoer is 0 1 2 . De variabele die in de kop is gedeclareerd, is lokaal voor de lus; declareer die vóór de lus als je de eindwaarde daarna nodig hebt.
Voor een niet-negatieve n wordt i = 0; i < n; i++ n keer uitgevoerd, met waarden van nul tot en met n − 1. Met i <= n wordt de lus één keer extra uitgevoerd. Voor een positieve stapgrootte s van a tot en met b is het aantal (b - a) / s + 1 met geheeltallige deling, mits de berekening en bijwerkingen van de teller passen. Van 2 tot en met 20 in stappen van 3 zijn de waarden 2, 5, 8, 11, 14, 17, 20: zeven iteraties.
Houd de accumulator buiten de lus
int total = 0;
for (int i = 1; i <= 100; i++) {
total += i;
}
Het eindtotaal is 5050. De initialisatie moet eenmaal vóór de lus plaatsvinden. Als je int total = 0; naar de lusinhoud verplaatst, ontstaat bij elke iteratie een nieuw totaal en gaat het eerdere werk verloren.
Kies tellers die het einde kunnen bereiken
Een puntkomma direct na de kop van de for-lus maakt de lusinhoud leeg. Een daaropvolgend blok wordt dan eenmaal na de lus uitgevoerd. Gebruik accolades direct na de kop om de lusinhoud zichtbaar te maken.
Aftellen zonder teken met i >= 0 kan niet op die manier stoppen: waarden zonder teken zijn altijd niet-negatief en nul verlagen met één loopt modulair terug naar het maximum. Een drijvendekommateller die herhaaldelijk 0.1 optelt, kan de exacte waarde 1.0 overslaan, zodat x != 1.0 onbetrouwbaar is als stopregel. Tel met een geheel getal en bereken een meetwaarde zoals x = i / 10.0 binnen de lusinhoud.
Bouw rijen met geneste lussen
Voor elke buitenste iteratie begint de binnenste lus en doorloopt die haar eigen reeks. Drie buitenste iteraties en vier vaste binnenste iteraties voeren de binnenste lusinhoud twaalf keer uit. Wanneer de binnengrens afhangt van de rij, tel je elke rij afzonderlijk.
int height = 3;
for (int row = 1; row <= height; row++) {
for (int s = 0; s < height - row; s++) printf(" ");
for (int h = 0; h < row; h++) printf("#");
printf("\n");
}
De rijen bevatten twee spaties en één hekje, één spatie en twee hekjes, en daarna drie hekjes. Het nieuweregelteken hoort na de binnenste lussen, zodat elke buitenste iteratie één rij maakt.
Sla werk over of stop voortijdig
continue in een for-lus gaat naar de bijwerking en daarna naar de volgende controle. Een lus die alleen oneven waarden van 1 tot en met 9 optelt, kan even waarden overslaan en eindigen met 25. break verlaat de binnenste lus onmiddellijk. In geneste lussen stopt een break in de binnenste lus de buitenste lus niet.
Een for-lus is vaak de duidelijkste keuze voor een bekend tellerbereik; while is geschikt voor herhaling die door gegevens wordt gestuurd en do-while voor werk dat vóór de controle moet gebeuren. Als je een for-lus herschrijft als een while-lus, moet je extra op continue letten, omdat de bijwerking ook op dat pad moet plaatsvinden.
Belangrijkste punten
- Een
for-lus voert de initialisatie eenmaal uit en de bijwerking na elke iteratie die doorgaat. - Controleer de doorlopen waarden om één-ernaastfouten op te sporen.
- Gebruik gehele tellers met voldoende bereik.
- Bij geneste lussen begint de binnenste reeks voor elke buitenste iteratie opnieuw.
- Een
breakin de binnenste lus verlaat alleen die lus. - Een continue in een
for-lus voert de bijwerking in de kop nog steeds uit. - Houd de initialisatie van een lopend totaal buiten de herhaalde lusinhoud.
6Herken en voorkom overloop bij gehele getallen
Eindige bereiken van gehele getallen beïnvloeden berekeningen, ook wanneer het eindantwoord klein genoeg lijkt. Onderscheid modulaire terugloop bij getallen zonder teken, overloop bij getallen met teken en waarden die bij conversie verloren gaan. Onderzoek daarna de typen van tussenliggende bewerkingen. Preventie begint vóór een risicovolle berekening: verbreed een operand, bewijs de grenzen of gebruik een bewerking met overloopcontrole. Tellerbereiken en grenswaardetests helpen fouten te vinden die gewone kleine invoer en korte proefuitvoeringen kunnen missen.
6.1 Wat gebeurt er als een geheel getal niet past
Het bereik van een geheelgetaltype is eindig. Een wiskundig antwoord kan geldig zijn en toch te groot voor het C-type waarmee het wordt berekend. De gevolgen hangen af van de vraag of de bewerking zonder teken, met teken of een conversie is.
Rekenen zonder teken loopt modulair terug
Een N-bits resultaat zonder teken wordt gereduceerd modulo 2ᴺ. Met een 32-bits unsigned int geeft UINT_MAX + 1u nul, 0u - 1u geeft 4.294.967.295 en 3u - 5u geeft 4.294.967.294. Het resultaat is gedefinieerd, maar kan voor de taak verkeerd zijn: vijf items aftrekken van een voorraad van drie hoort geen miljarden items op te leveren.
Voor uint8_t b = 255; slaat b = b + 1; nul op. Op een typisch systeem met 32-bits int vindt de optelling eerst plaats als int en levert die 256 op; conversie van die waarde terug naar een 8-bits type zonder teken levert nul op. Kleine opslag zonder teken betekent niet dat elke tussenliggende bewerking dat kleine type heeft.
Rekenkundige overloop bij getallen met teken is ongedefinieerd
Bij een 32-bits int vereist één optellen bij INT_MAX de waarde 2.147.483.648. C definieert het rekenkundige resultaat met teken niet. Hardware kan modulair lijken terug te lopen naar INT_MIN, maar de compiler kan optimaliseren op basis van de aanname dat overloop bij getallen met teken nooit optreedt. Een controle zoals if (x + 1 < x) kan voor gehele getallen met teken worden verwijderd, omdat elke gedefinieerde optelling van één bij een getal met teken een grotere waarde oplevert.
Bereken niet eerst om daarna de schade te bekijken. Een ongedefinieerde bewerking heeft al plaatsgevonden voordat een latere controle wordt uitgevoerd.
Een breder doeltype kan te laat komen
int a = 50000, b = 50000;
long long product = a * b;
De vermenigvuldiging wordt uitgevoerd als int en loopt dus over op een systeem met 32-bits int. De breedte van het doeltype kan dat niet herstellen. (long long)a * b verandert het type van de bewerking vóór de vermenigvuldiging en levert 2.500.000.000 op.
Faculteiten groeien snel: 12! is 479.001.600 en past in een 32-bits geheel getal met teken; 13! is 6.227.020.800 en past niet. Bij 32-bits rekenen zonder teken loopt 13! modulair terug naar 1.932.053.504. Een 64-bits long long met teken bevat 20! = 2.432.902.008.176.640.000, maar niet 21!.
Voor conversie gelden afzonderlijke regels
Bij conversie van een geheel getal naar een type zonder teken wordt modulo gereduceerd. Onder C17 geeft conversie van een geheel getal buiten het bereik naar een type met teken een door de implementatie bepaald resultaat of signaal. Conversie van een drijvendekommawaarde waarvan het afgekaptе gehele deel niet in het gehele doeltype past, heeft ongedefinieerd gedrag. Een getal buiten het bereik lezen met een numerieke scanf-conversie is ook ongedefinieerd.
Bereikfouten kunnen grote gevolgen hebben
De eerste vlucht van Ariane 5 op 4 juni 1996 ging verloren nadat een drijvendekommawaarde niet in een 16-bits geheel getal met teken in de traagheidsreferentiesoftware paste. Dat programma was geschreven in Ada; de les over het controleren van conversiebereiken geldt ook voor C.
Een 32-bits secondeteller met teken die begint bij het Unix-tijdperk bereikt 2.147.483.647 op 19 januari 2038 om 03.14.07 uur UTC. Systemen die die tijdrepresentatie gebruiken, hebben een bredere representatie nodig. In 2015 pakte de FAA ook software voor de generatorbesturing van de Boeing 787 aan die na 248 dagen ononderbroken stroom kon overlopen. Deze fouten laten zien waarom zowel een lange bedrijfstijd als tussenwaarden in een schatting van het bereik thuishoren.
Drijvendekommagrenzen zijn anders: gangbare IEEE-systemen kunnen oneindig opleveren en een float kan precisie voor gehele getallen verliezen voordat het maximum wordt bereikt. Modulaire terugloop van gehele getallen zonder teken levert een gewone numerieke waarde op; overloop bij getallen met teken biedt helemaal geen betrouwbaar resultaat.
Belangrijkste punten
- Rekenen zonder teken gebruikt reductie modulo.
- Rekenkundige overloop bij getallen met teken heeft ongedefinieerd gedrag.
- Een breder doeltype kan een tussenliggende bewerking in een smaller type niet herstellen.
- Voor geheeltallige conversie, drijvendekommaconversie en rekenkundige overloop gelden verschillende regels.
- Schat waarden over de volledige bedrijfsduur.
6.2 Overloop voorkomen voordat die optreedt
Begin met de grootste waarde die elke berekening kan opleveren. Neem tussenproducten en bijwerkingen van tellers mee, niet alleen het eindantwoord. Kies vervolgens een type of een voorafgaande controle die elke bewerking binnen het bereik houdt.
Verbreed de berekening vroeg
Een 32-bits geheel getal met teken bevat waarden tot ongeveer 2,1 miljard, terwijl een 64-bits long long met teken tot 9.223.372.036.854.775.807 gaat. Een millisecondenteller bereikt de 32-bits grens met teken in ongeveer 24,9 dagen. Gebruik een 64-bits type wanneer een tijdsduur langer kan zijn.
Schrijf (long long)a * b om een geheeltallig product te verbreden voordat het wordt geëvalueerd. Ook constanten zijn van belang: 86400LL * days begint de vermenigvuldiging als long long. (long long)(a * b) converteert pas na het product in het smallere type en kan dus overloop van dat product niet voorkomen. Bredere typen hebben nog steeds grenzen; controleer de grootst mogelijke waarden daartegen.
Controleer optelling met aftrekking
Neem voor int a en int b <limits.h> op en test:
if ((b > 0 && a > INT_MAX - b) ||
(b < 0 && a < INT_MIN - b)) {
printf("Sum out of range\n");
} else {
int sum = a + b;
printf("%d\n", sum);
}
Elke aftrekking wordt beschermd door het teken van b, zodat de controle zelf representeerbaar blijft. Voor 2.000.000.000 plus 200.000.000 is INT_MAX - b gelijk aan 1.947.483.647, lager dan a, dus wordt de optelling geweigerd. Het geval van gelijkheid past: wanneer a gelijk is aan INT_MAX - b, is de som precies INT_MAX.
Controleer positieve vermenigvuldiging door te delen
Voor positieve int-operanden betekent a > INT_MAX / b dat het product het maximum zou overschrijden. De voorwaarde voorkomt dat het product wordt berekend en voorkomt een deler nul omdat b positief is. Bij een faculteitsberekening detecteert de controle vóór vermenigvuldiging van 12! met 13 dat 479.001.600 groter is dan 165.191.049. Stop vóór die vermenigvuldiging.
Controleer bij een voorraad zonder teken vóór het aftrekken stock >= requested. Alleen overschakelen op een type zonder teken voorkomt geen onzinnig modulair teruggelopen resultaat.
Herschrijf risicovolle tussenexpressies
Het midden (low + high) / 2 kan overlopen, ook als het antwoord past. Wanneer 0 <= low <= high en beide int zijn, gebruik je low + (high - low) / 2. Het verschil is niet-negatief en binnen bereik en het resultaat is nooit groter dan high. Deze voorwaarden zijn van belang; de formule is geen algemeen bewijs voor willekeurige eindpunten met teken.
Ook lustellers hebben bereik nodig. Een 16-bits short kan 40.000 niet bereiken, dus een lus die daarmee zover telt, kan niet volgens de bedoeling eindigen. Gebruik een tellertype dat de grens en elke laatste bijwerking kan voorstellen.
Gebruik rekenen met overloopcontrole en tests
C23 biedt <stdckdint.h> met ckd_add, ckd_sub en ckd_mul. Zo slaat ckd_add(&r, a, b) een resultaat op en geeft de functie waar terug wanneer het wiskundige antwoord niet in r past. GCC en clang bieden ook __builtin_add_overflow, __builtin_sub_overflow en __builtin_mul_overflow als compileruitbreidingen.
Waarschuwingen vangen sommige gevallen van overloop in constanten en fouten met waarden met en zonder teken op. In GCC of clang kan -fsanitize=undefined -g overloop bij getallen met teken melden die tijdens een test wordt bereikt; -ftrapv vraagt om onderbrekingen bij overloop van optelling, aftrekking en vermenigvuldiging met teken. Test nul, één, min één, de grootste verwachte invoer en waarden op en vlak onder grenzen. Een testhulpmiddel controleert de paden die je uitvoert, dus blijf in de code over het bereik redeneren.
Belangrijkste punten
- Verbreed een operand vóór een risicovolle bewerking.
- Gebruik voorafgaande controles die rekening houden met het teken bij optelling van getallen met teken.
- De voorafgaande controle door deling voor vermenigvuldiging vereist positieve operanden.
- Ook aftrekken zonder teken vereist een bereikcontrole.
- De veiligere middenformule vereist geordende niet-negatieve eindpunten.
- Rekenen met overloopcontrole meldt of het resultaat in het doel past.
- Grenswaardetests en sanitizers vullen redeneringen over het bereik aan.
Typen en operatoren in C: voorbeelden
1Bouw een begroeting en onderzoek een mislukte wijziging
9 stappen
- 1IntroductieVolg het bouwproces
Een opgeslagen C-programma is tekst. Om het uit te voeren is een uitvoerbaar bestand nodig dat uit die tekst is gemaakt. Houd de rollen van de twee bestanden duidelijk: een bewerking verandert de broncode, terwijl een geslaagd bouwproces het uitvoerbare bestand vervangt.
Je bouwt een korte begroeting, bekijkt wat elke fase doet en onderzoekt een mislukte wijziging. De centrale werkwijze is het bouwresultaat controleren voordat je een uitvoering als bewijs voor de nieuwe broncode gebruikt.
- 2UitlegLees de opbouw van het programma
Het programma begint bij
main:#include <stdio.h> int main(void) { printf("Ready\n"); return 0; }De header levert de declaratie van
printf. De functie-inhoud drukt een regel af en geeft daarna succes terug.voidbetekent hier dat main geen parameters heeft;intnoemt het type van de retourwaarde. Accolades omsluiten de functie-inhoud en eenvoudige instructies eindigen met puntkomma’s. - 3VoorbeeldBouw het programma en voer het uit
Sla de broncode op als
ready.c. Voer in de map van het bestand in:gcc -std=c17 -Wall -Wextra ready.c -o ready ./readyHet bouwproces kiest C17 en nuttige waarschuwingen en geeft het uitvoerbare bestand de naam
ready. De uitvoering druktReadyaf, gevolgd door een nieuweregelteken. Op Linux of macOS kiest./de huidige map. Je kunt op dezelfde plaats in de opdracht clang gebruiken; uitvoerbare bestanden op Windows gebruiken het achtervoegsel.exe. - 4VerbandBekijk de rol van objectcode
De voorbewerking verwerkt de include-richtlijn. Daarna levert compilatie assembleertaal op en levert assemblage objectcode op. Koppeling lost verwijzingen op en voltooit het uitvoerbare bestand; bibliotheekcode kan tijdens de uitvoering door een gedeelde bibliotheek worden geleverd.
De opties
-E,-Sen-cstoppen in steeds latere fasen. In het bijzonder stopt-cbij een objectbestand: de koppelingsstap die nodig is voor het uitvoerbare bestand is dan nog niet voltooid. - 5UitdagingVoorspel de volgende uitvoering
Je bouwt de begroeting en slaat daarna een wijziging op die “Ready” in “Begin” verandert. Je voert het bestaande uitvoerbare bestand uit zonder te compileren. Welke tekst verschijnt en hoe verander je die?
---reveal---
Het programma drukt nog steeds
Readyaf. Het uitvoerbare bestand weerspiegelt het eerdere geslaagde bouwproces. Compileer de opgeslagen wijziging en voer het nieuwe uitvoerbare bestand uit omBeginaf te drukken. Alleen de broncode opslaan werkt de machinecode niet bij. - 6VoorbeeldHerstel de eerste fout
Door een wijziging ontbreekt de puntkomma op deze regel:
printf("Begin\n") return 0;De compiler kan naar de eerste regel wijzen of naar
return, waar de compiler het ontbrekende einde van de expressie-instructie opmerkt. Voeg de puntkomma na de aanroep toe en bouw opnieuw. Begin met deze eerste oorzaak, ook als latere meldingen andere regels noemen; één syntaxisfout kan de manier verstoren waarop de compiler de daaropvolgende code leest. - 7TipGebruik waarschuwingen als nuttige aanwijzingen
Een waarschuwing laat het bouwproces meestal doorgaan, maar kan op een ernstige fout wijzen.
printf("%d\n", 2.5);geeft een double door waar het formaat int vereist, wat ongedefinieerd gedrag is.Herstel het verschil tussen de typen in plaats van te accepteren wat een proefuitvoering toevallig afdrukt. Waarschuwingen van
-Wall -Wextrahelpen zulke fouten zichtbaar te maken;-Werrorlaat die het bouwproces stoppen. Druk bij een verkeerde berekening tussenwaarden af en vergelijk die met een klein uitvoeringsspoor dat je met de hand maakt. - 8UitdagingOnderscheid een mislukt bouwproces van een verkeerde uitvoering
Na een geslaagd bouwproces voor de begroeting compileert een nieuwe wijziging niet. Als je
./readyuitvoert, wordt nog steeds een begroeting afgedrukt. Laat dat zien dat de wijziging geldig is en wat moet je vervolgens bekijken?---reveal---
Nee. Het oude uitvoerbare bestand kan na een mislukt bouwproces blijven staan. Bekijk de eerste compilatiefout, inclusief de voorafgaande bronregel, herstel die en bouw opnieuw totdat het lukt. Voer daarna het nieuwe uitvoerbare bestand uit en vergelijk de uitvoer met je voorspelling. De bestaande begroeting zegt niets over de mislukte wijziging.
- 9SamenvattingGebruik één duidelijke herstelcyclus
Houd elke test verbonden met de broncode waaruit daadwerkelijk is gebouwd. Sla de wijziging op, compileer, lees de eerste diagnostische melding, herstel de oorzaak en bouw opnieuw voordat je het programma uitvoert.
Als het programma wordt gebouwd maar de verkeerde waarde berekent, gebruik dan een klein uitvoeringsspoor en grenswaarden als invoer. Als een bewerking ongedefinieerd gedrag heeft, wijzig de code dan zodat dat gedrag niet kan optreden; normaal ogende uitvoer is geen betrouwbaar resultaat voor die bewerking.
2Kies opslag voor aantallen en meetwaarden
13 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieVraag wat een waarde nodig heeft
- 2VoorbeeldEen gekopieerde waarde blijft een kopie
- 3UitlegHoud namen binnen hun blokken
- 4UitdagingVolg een verborgen naam
- 5UitlegVergelijk bereiken voordat je kiest
- 6VoorbeeldLees een bitpatroon en een literal
- 7UitdagingKies een overdraagbaar bereik
- 8UitlegBekijk waarom decimale breuken afwijken
- 9VoorbeeldVergelijk met een toegestane fout
- 10UitdagingOnderscheid conversie van afronding
- 11KernpuntPrecisie kan eerder tekortschieten dan bereik
- 12UitdagingStem het type af op de taak
- 13SamenvattingControleer opslag op twee manieren
3Lees meetwaarden en druk duidelijke resultaten af
11 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieVerbind getypeerde waarden met tekst
- 2UitlegLees het uitvoerformaat
- 3VoorbeeldMaak een uitgelijnde weergave
- 4UitdagingVoorspel een aangevuld geheel getal
- 5UitlegLees in de juiste bestemmingen
- 6VoorbeeldHerken een gedeeltelijke conversie
- 7TipLees niet per ongeluk een achtergebleven nieuweregelteken
- 8UitdagingVolg wat een mislukte leesactie behoudt
- 9VoorbeeldControleer het domein van de berekening
- 10UitdagingCombineer lezen en weergeven
- 11SamenvattingGebruik twee controles bij invoer
4Lees een expressie van typen tot effecten
17 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieNoteer zowel waarden als typen
- 2VoorbeeldSplits een hoeveelheid in hele delen
- 3UitlegOnderscheid de twee incrementaties
- 4UitdagingVolg een samengestelde bijwerking
- 5VoorbeeldPlaats de drijvendekommaoperand vroeg genoeg
- 6KernpuntKleine waarden zonder teken kunnen als int met teken worden berekend
- 7UitdagingVolg een waarde met teken naar rekenen zonder teken
- 8UitlegVerbind volledige vergelijkingen
- 9VoorbeeldLaat een controle eerst plaatsvinden
- 10VoorbeeldVerander één vlag zonder andere te verstoren
- 11UitdagingVergelijk waarheidswaarden met gemeenschappelijke bits
- 12UitlegGroepeer voordat je het verloop volgt
- 13TipPlaats het masker binnen de vergelijking
- 14VerbandKies een waarde zonder beide te evalueren
- 15UitdagingKunnen haakjes twee bijwerkingen herstellen?
- 16UitdagingCombineer groepering met een bereikcontrole
- 17SamenvattingDoorloop een expressie in drie stappen
5Volg keuzes en bouw een lopend totaal op
18 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieVolg het daadwerkelijk gekozen pad
- 2UitlegGebruik de eerste tak met een ware voorwaarde
- 3TipZowel accolades als gelijkheid zijn van belang
- 4UitdagingZoek de eerste passende categorie
- 5UitlegKies een ingang voor een opdracht
- 6VoorbeeldDeel bewust een actie
- 7UitdagingBepaal wat break verlaat
- 8UitlegLees invoer voordat je die gebruikt
- 9VoorbeeldVolg de berekening met de stopwaarde
- 10UitdagingHandel een onmiddellijke stop af
- 11VerbandVraag eenmaal met do-while
- 12UitlegTel precies het bedoelde aantal iteraties
- 13VoorbeeldBehoud een totaal tussen iteraties
- 14TipVermijd tellers die de uitgang missen
- 15VoorbeeldTel rijen afzonderlijk
- 16UitdagingVolg een voortijdige uitgang in geneste lussen
- 17UitdagingCombineer een vlag met een menulus
- 18SamenvattingControleer elke route naar de volgende iteratie
6Maak een product veilig voordat je het berekent
12 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieOnderzoek de berekening vóór de opslag
- 2VoorbeeldZoek de bewerking met het smalle type
- 3UitlegRekenen zonder teken heeft een gedefinieerd maar mogelijk verkeerd resultaat
- 4UitdagingHerstelt een late typeconversie het product?
- 5VoorbeeldVerbreed de bewerking zelf
- 6UitlegControleer optelling zonder op te tellen
- 7VoorbeeldControleer een positief product door te delen
- 8UitdagingControleer de grens correct
- 9KernpuntEen veilig antwoord kan een onveilige som verbergen
- 10TipGebruik hulpmiddelen en test de grenzen
- 11UitdagingControleer een volledig ontwerp voor het bereik
- 12SamenvattingLever het bewijs vóór de bewerking
Typen en operatoren in C: oefenvragen
Set 1: Bouw programma’s en volg opgeslagen waarden10 Oefenvragen
Vraag 1 van 10
Toon antwoordVerberg antwoord
Vraag 2 van 10
Toon antwoordVerberg antwoord
Vraag 3 van 10
Welke waarden worden door deze code afgedrukt?
int units = 6;
int total = units * 4;
units = 9;
printf("%d %d\n", units, total);
Toon antwoordVerberg antwoord
Vraag 4 van 10
Toon antwoordVerberg antwoord
Vraag 5 van 10
014 voor?Toon antwoordVerberg antwoord
Vraag 6 van 10
Toon antwoordVerberg antwoord
Vraag 7 van 10
printf("|%04d|", 23); af tussen de verticale strepen?Toon antwoordVerberg antwoord
Vraag 8 van 10
int a = 2, b = 9; ontvangt een aanroep van scanf("%d:%d", &a, &b) de invoer “17 42”. Welke retourwaarde en opgeslagen waarden zijn het resultaat?Toon antwoordVerberg antwoord
Vraag 9 van 10
Wat drukt dit af op een systeem met 8-bits unsigned char en 32-bits int?
unsigned char a = 230, b = 40;
int sum = a + b;
unsigned char small = sum;
printf("%d %u\n", sum, (unsigned)small);
Toon antwoordVerberg antwoord
Vraag 10 van 10
gcc -std=c17 -c sensor.c slaagt. Wat heeft die opgeleverd en welke bouwfase is nog nodig vóór gewone uitvoering?Toon antwoordVerberg antwoord
Set 2: Lees invoer en reken met C-typen10 Oefenvragen
Sla deze les op om elk antwoord te zien en elke set te oefenen.
Vraag 1 van 10
double distance_m;. Welke scanf-conversie en welk argument passen bij het type?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 2 van 10
-23 / 6 en -23 % 6 in C17?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 3 van 10
int distance = 19, time = 4; een snelheid als double waarbij het breukdeel behouden blijft?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 4 van 10
%d wordt gelezen, accepteert de invoer “46kg”. Welke waarde wordt opgeslagen en welke invoer blijft direct na de conversie ongelezen?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 5 van 10
total en count of het gehele gemiddelde boven 5 ligt zonder door nul te delen?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 6 van 10
2 + 5 * 3 % 7 op?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 7 van 10
printf("|%6.1f|", 8.375); af bij gewone afronding op het dichtstbijzijnde tiende?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 8 van 10
Welke waarden worden afgedrukt?
int n = 8;
int a = n++;
int b = ++n;
a *= b % 4;
printf("%d %d %d\n", a, b, n);
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 9 van 10
Wat drukt dit af met 32-bits int en unsigned int?
int delta = -3;
unsigned int limit = 2;
printf("%d %u\n", delta < limit, limit + delta);
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 10 van 10
flags = 10u. Welke waarde ontstaat door bit 2 te zetten, waarbij de laagste bit als bit 0 wordt geteld?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Set 3: Volg takken, switches en lussen10 Oefenvragen
Sla deze les op om elk antwoord te zien en elke set te oefenen.
Vraag 1 van 10
Een score is 76. Welk teken wordt toegewezen?
if (score >= 70) grade = 'C';
else if (score >= 85) grade = 'B';
else grade = 'F';
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 2 van 10
Wat wordt afgedrukt wanneer mode gelijk is aan 2?
switch (mode) {
case 1: printf("A"); break;
case 2: printf("B");
case 3: printf("C"); break;
default: printf("D");
}
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 3 van 10
Welke eindwaarden hebben n en passes?
int n = 45, passes = 0;
while (n > 2) {
n /= 2;
passes++;
}
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 4 van 10
for (int i = 4; i < 19; i += 4)?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 5 van 10
Wat wordt afgedrukt?
int level = 2;
if (level = 0) {
printf("Low");
} else {
printf("Other");
}
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 6 van 10
Welk totaal wordt afgedrukt?
int total = 0;
for (int r = 1; r <= 4; r++) {
for (int c = 1; c <= r; c++) {
if (c == 3) break;
total += c;
}
}
printf("%d\n", total);
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
Welke som ontstaat?
int sum = 0;
for (int i = 2; i <= 8; i++) {
if (i % 3 == 0) continue;
sum += i;
}
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 9 van 10
Hoe vaak wordt “tick” afgedrukt?
int running = 1, passes = 0;
while (running && passes < 3) {
switch (passes) {
case 1: break;
default: printf("tick\n");
}
passes++;
}
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 10 van 10
De invoer is “9 4 0 7”. Welk aantal en welk gemiddelde worden afgedrukt?
int x, sum = 0, count = 0;
while (scanf("%d", &x) == 1 && x != 0) {
sum += x;
count++;
}
if (count) printf("%d %.1f\n", count, (double)sum / count);
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Set 4: Onderzoek conversies en bereikfouten10 Oefenvragen
Sla deze les op om elk antwoord te zien en elke set te oefenen.
Vraag 1 van 10
0u - 1u op wanneer unsigned int 32 waardebits heeft?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 2 van 10
a = 60000 en b = 40000 hun exacte product veilig in een gangbare 64-bits long long?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 3 van 10
flags & 2u == 0u op voor flags = 6u zonder teken?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 4 van 10
a + b wordt geëvalueerd?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
f * i groter zou zijn dan INT_MAX?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 10 van 10
-fsanitize=undefined een melding van overloop bij getallen met teken. Welke conclusie moet de reparatie sturen?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Set 5: Combineer invoer, programmaverloop en veilig rekenen10 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
Welke waarde wordt afgedrukt?
int total = 0;
for (int r = 2; r <= 5; r++) {
for (int c = 1; c < r; c++) {
if (c % 2 == 0) continue;
total += r;
}
}
printf("%d\n", total);
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 3 van 10
days is 30000. Welke expressie berekent de tijdsduur correct in milliseconden in een 64-bits long long?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 4 van 10
Wat garandeert dit C17-fragment over zijn uitvoer?
int n = 4;
printf("%d %d\n", n++, n++);
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 5 van 10
Wat wordt afgedrukt wanneer a = -2 en b = 7?
if (a > 0)
if (b > 0) printf("Both");
else printf("A only");
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 6 van 10
Waarom maakt deze lus geen voortgang meer nadat eenmaal nul is afgedrukt?
int i = 0;
while (i < 4) {
if (i == 1) continue;
printf("%d\n", i);
i++;
}
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
Welke waarde laat deze code in flags achter bij een 32-bits unsigned int?
unsigned int flags = 13u;
flags &= ~(1u << 2);
flags |= 1u << 1;
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 9 van 10
Een menu begint met running = 1 en actions = 0. De invoer is “s q s”. Wat is actions na deze lus?
char cmd;
while (running && scanf(" %c", &cmd) == 1) {
switch (cmd) {
case 'q': running = 0; break;
case 's': actions++; break;
}
}
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 10 van 10
Welke waarde wordt aan rate toegewezen?
int completed = 14, planned = 20;
double rate = completed / planned * 100.0;
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Kom je niet uit je eigen opgave? Huiswerkhulp
Kernbegrippen
A
Aantal geslaagde toewijzingen
Accumulator
Afkappen naar nul
Afrondingsfout
Afsluitstatus
Associativiteit
24 / 4 / 2 als (24 / 4) / 2, wat drie geeft.B
Bereikcontrole
Bitmasker
1u << 5 selecteert bit vijf, waarbij de minst significante bit als positie nul wordt geteld.Bitsgewijze operator
& behoudt alleen de gemeenschappelijke gezette bits van twaalf en tien en geeft acht.Bitverschuiving
Booleaanse waarde
<stdbool.h> in C17 slaat bool ready = 2; de Booleaanse waarde waar op.break-instructie
Broncode
C
case-label
case 4: is geldig in C17; een gewone const int-variabele kan de waarde ervan niet leveren.Compilatiefout
Compiler
continue-instructie
Conversie buiten het waardenbereik
Conversiespecificatie
%lld past bij een long long-argument; %d kan dat niet automatisch converteren.D
Declaratie
int count = 0; geeft de naam een geheelgetaltype en levert ook de beginwaarde.default-label
do-while-lus
Door de implementatie bepaald gedrag
Doorvallen
Drijvendekommawaarde
E
Eén-ernaastfout
i < n in een tellende lus vanaf nul vervangt door i <= n, ontstaat een één-ernaastfout.Einde van de invoer
Escapesequentie
\n voegt een nieuweregelteken toe binnen een C-string; printf gebruikt %% voor een procentteken.Evaluatievolgorde
Expliciete typeconversie
(double)sum / count werkt vóór de deling, waardoor een quotiënt met een breukdeel behouden blijft.F
for-lus
i < n en i++, voert voor niet-negatieve n de lusinhoud n keer uit als de bijwerkingen passen.Formaatstring
printf("Mass: %.1f kg\n", mass) levert een label, decimale precisie en een eenheid.G
Gebruikelijke rekenkundige conversies
Geheel getal met teken
Geheel getal zonder teken
Geheelgetaltype met exacte bitbreedte
uint8_t bevat, wanneer beschikbaar, waarden van nul tot en met 255.Geheeltallige deling
22 / 5 geeft vier, ook wanneer het voltooide resultaat aan een double wordt toegewezen.Geheeltallige literal
0x30 gebruikt hexadecimale notatie, terwijl 48u die waarde decimaal zonder teken schrijft.Geldigheidsgebied van een blok
Geneste lus
Grenswaardetest
I
Identifier
distance_m is geldig; 2distance kan geen naam zijn omdat die met een cijfer begint.Impliciete conversie
Initialisatie
Instructieblok
Integerpromotie
Iteratie
K
Kommaoperator
i++, j--; komma’s tussen printf-argumenten doen dat niet.Kortsluitevaluatie
count != 0 && sum / count > 4 slaat de deling over wanneer count nul is.L
Linker
printf op; de bijbehorende bibliotheekcode kan door een gedeelde bibliotheek worden geleverd.Logische fout
Logische operator
&&, || of ! nul of één op.ready && valid test of beide opgeslagen waarden ongelijk aan nul zijn.Losse else
M
Macro
#define LIMIT 8 wordt het token LIMIT vervangen door 8 overal waar die macro van toepassing is.Modulaire terugloop bij gehele getallen zonder teken
N
NaN
Neveneffect
Niet-passende invoer
%d-conversie veroorzaken niet-passende invoer en blijven ongelezen voor een nieuwe poging.O
Oneindige lus
Ongedefinieerd gedrag
Operand
sum / count bepalen de typen van de operanden of de deling een resultaat met een breukdeel behoudt.Operatorprioriteit
4 * 2 in 3 + 4 * 2 vóór de optelling gegroepeerd.Overloop bij gehele getallen
P
Postfixincrementatie
n = 3 geeft de postfixincrementatie in old = n++; aan old de waarde drie en laat n op vier staan.Precisie
Preprocessor
#include en macrovervanging vóór de belangrijkste vertaalfasen van C.R
Rekenen met overloopcontrole
ckd_mul voor rekenen met overloopcontrole kan een product melden dat niet in de bestemming past.Relationele operator
<, <=, > of >= en levert de int-waarde nul of één op.height >= 1 controleert één grens; een andere vergelijking controleert de andere.Rest
%-bewerking van C; een resultaat anders dan nul heeft het teken van het deeltal.-22 % 5 is −2 omdat het afgekaptе quotiënt −4 is en aan de delingsidentiteit moet worden voldaan.S
Samengestelde toewijzing
total += item; werkt het lopende totaal bij zonder item te veranderen.Sanitizer
Standaardinvoer
Stopwaarde
switch-instructie
T
Tak
Tekenconstante
'A' kan met %c worden afgedrukt als de letter A of met %d als het getal 65.Toewijzing
count = count + 1; leest de oude waarde en vervangt die door een waarde die één groter is.Tolerantie
fabs(measured - target) met de tolerantie in plaats van exacte gelijkheid te eisen.Tussenresultaat
(low + high) / 2 is een tussenresultaat dat kan overlopen, ook als het midden past.Tweecomplement
10000000 −128 voor.Typeverbreding
a * b verandert het type van het product; een expliciete typeconversie na het product komt te laat.U
Uitvoerbaar bestand
./hello uitvoert, start het uitvoerbare bestand in de huidige map zonder de C-compiler opnieuw aan te roepen.Uitvoerbuffering
fflush(stdout) die vóór een invoeraanroep verzendt.Uitvoeringsspoor
V
Variabele
mass_kg slaat een meetwaarde op, terwijl count bijhoudt hoeveel monsters zijn ingevoerd.Veldbreedte
%6d voegt vier spaties toe vóór 42, terwijl een waarde van zeven cijfers nog steeds volledig wordt afgedrukt.Voorwaarde
if (items) is onwaar voor nul, terwijl een negatief aantal anders dan nul de tak uitvoert.Voorwaardelijke operator
condition ? a : b om na controle van de voorwaarde één waarde-expressie te kiezen.condition ? a : b de expressie b en slaat a over.W
Waarschuwing
%d is gekoppeld aan een double-argument, een ongeldige combinatie.while-lus
Witruimte
%c leest een achtergebleven nieuweregelteken tenzij de conversie opdracht krijgt dat over te slaan.Over deze les
Door StudeliFout melden
Maak je eigen Studiemateriaal
Maak van je aantekeningen, slides en documenten Leermodules, Oefenvragen, Flashcards en 6 andere tools.