#Klooienmetcomputers

Het gedoe met strings

Arnout van Kempen over rommelen in een digitale wereld.

Als je aan de slag bent gegaan met de scanf() functie van vorige keer, dan is het ongetwijfeld opgevallen dat er iets misgaat als je een spatie gebruikt. Waar we een achternaam vroegen en "De Vries" invoerden, werd alleen "De" gebruikt. Waarom is dat?

C doet hier iets dat niet erg voor de hand ligt: bij invoer van een string geldt niet alleen de enter-toets als afsluiting, maar ook een spatie, of een tab. Daar zit vast logica achter, maar erg praktisch is het niet.

Maar wellicht is nog iets anders opgevallen: de scanf() functie neemt alle toetsaanslagen mee, of die nu in de opgegeven variabele passen of niet. Wat "over" is aan toetsaanslagen wordt gewoon in de volgende variabele geperst, of dat nu handig is of niet. C kan dat betrekkelijk makkelijk omdat uiteindelijk alle toets-invoer bestaat uit bytes, en alle data-types zijn opgebouwd in bytes. C maakt dat dus passend, goedschiks of kwaadschiks.

Ik gaf al eerder aan dat C bijzonder weinig doet aan het organiseren van gebruikersinvoer. Andere programmeertalen regelen dat input past in een variabele, zorgen dat de input niet buiten het bereik van de variabele valt, maar regelen veelal ook zelf dat de toetsaanslagen van de gebruiker worden geregistreerd en in de variabele worden geplaatst. C doet dat allemaal niet. Waar haalt C dan die input eigenlijk vandaan?

In feite laat C dat over aan het besturingssysteem van de computer. Ieder besturingssysteem dat met een toetsenbord en een scherm kan werken, heeft daar iets voor geregeld. In MS Windows, MacOS en Linux werkt dat allemaal net even anders, maar op het niveau dat voor ons van belang is doen al die systemen hetzelfde. Ze kennen input en output-kanalen "stdin" en "stdout" die een soort buffer vormen die zich gedragen als bestanden. Als je iets op je toetsenbord tikt, wordt dat door het besturingssysteem in stdin geplaatst. En als een programma iets naar het beeldscherm wil schrijven wordt dat in stdout geplaatst. Het besturingssysteem leest stdout uit en plaatst die gegevens op het scherm, terwijl een programma stdin kan uitlezen om te zien wat op het toetsenbord is getikt. Na verwerking verdwijnen de verwerkte gegevens uit stdin en stdout. Maar daar zit aan de invoering het punt om rekening mee te houden: alles wat niet verwerkt is, blijft aanwezig in stdin, tot het alsnog gelezen wordt, of stdin wordt leeggemaakt.

Het gebruik van scanf() leest dus feitelijk geen gegevens van het toetsenbord, het haalt de toetsaanslagen uit stdin die de functie nodig heeft. Zolang scanf() nog niet alle benodigde gegevens heeft, blijft de functie wachten, maar zodra de gevraagde gegevens zijn opgehaald sluit de functie af. Oók als er nog toetsaanslagen in stdin zitten. Bij een volgende scanf() wordt dan niet gelezen wat de gebruiker op dat moment intypt, maar wat al in stdin stond te wachten. Met op het eerste oog bizarre gevolgen.

Om dat netjes op te lossen maken we gebruik van twee nieuwe functies: fgets(string, lengte, bestand) en fgetc(bestand). De eerste leest een string in waarvan maximaal lengte karakter worden gebruikt uit een bestand. Als we als bestand stdin gebruiken, wordt het toetsenbord uitgelezen. Vervolgens moeten we nog alle resterende toetsaanslagen uitlezen tot we aan de <enter> zijn. Daar gebruiken we fgetc() voor. Dit is alleen nodig als de volledige lengte van string, en mogelijk dus meer, is gebruikt door de gebruiker.

In GitHub heb ik een programmaatje opgenomen dat eerst laat zien hoe je na het lezen van een getal de afsluitende <enter> uit stdin verwijdert. Vervolgens hoe een string gelezen wordt met fgetc(), met direct daarna een loop om eventuele overige toetsaanslagen uit stdin te verwijderen. Hierbij maken we gebruik van twee handigheidjes: sizeof(variabele) geeft de omvang van een variabele en bij een string is dat de maximale lengte van de string. De loop doen we met een lege while(), waarbij met fgetc() steeds een nieuwe byte uit stdin wordt gelezen, tot we een 10 vinden, de ascii-code voor \n, <enter> dus.

Aan het eind heb ik nog wat rekenwerk met de datum toegevoegd, zodat het programma nog iets aan “nuttig” resultaat laat zien. De techniek daarvan komt later wel een keer aan bod.

Om de laatste printf() te begrijpen moet je nog de “ternary operator” herkennen. Die werkt als volgt:

a = x ? b : c

als x true is, dan wordt a gelijk aan b, als x false is, dan wordt a gelijk aan c.

De code dan:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

int main()
{

     char naam[20], temp;
     int leeftijd, geboortejaar;
     time_t comp_tijd;
     struct tm *s_tijd;

     system("clear");

     printf("Leeftijd: ");
     scanf("%d",&leeftijd);
     scanf("%c", &temp);

     printf("Naam: ");
     fgets(naam, sizeof(naam), stdin);

     if(strlen(naam)+1==sizeof(naam))
     {
          while(fgetc(stdin)!=10){}
     }

     printf("Geboortejaar: ");
     scanf("%4d", &geboortejaar);

     printf("\n\nNaam: %s\nLeeftijd: %d\nGeboortejaar: %d\n\n", naam, leeftijd, geboortejaar);

     time(&comp_tijd);
     s_tijd = localtime(&comp_tijd);
     printf("\n%s",abs((s_tijd->tm_year+1900)-leeftijd-geboortejaar)>1 ? "Klopt je leeftijd wel?\n\n" : "Je leeftijd zou kunnen kloppen\n\n");

return 0;
}

Gebruik GitHub om te klooien met de computer!

Wie mee wil doen met #klooienmetcomputers, maar niet alle teksten van Arnout wil overtypen, of de eigen code wil delen met andere lezers, kan dat doen via GitHub:

1. Maak een account op www.github.com

2. Zoek naar Abmvk/kmc

Het account Abmvk volgen kan ook.

Arnout plaatst daar alle stukjes code voor #klooienmetcomputers, met als naam eerst een volgnummer van het stukje waar de code bij hoort en dan een term uit de titel. Zaken die bijvoorbeeld in een config-bestand thuishoren plaatst hij als .txt bestand, wat overtypen kan besparen. Ook plaatst hij stukjes code waar hij zelf mee rommelt en die niet in een stukje op accountant.nl terechtkomen. Die stukjes zijn te herkennen omdat er geen volgnummer voor staat.

Lezers zijn vrij te gebruiken wat ze willen en om zelf zaken toe te voegen of aan te passen, vragen te stellen of commentaar te leveren. Samen kom je een eind. Om dit goed te laten werken: maak een clone van Arnout’s repo, haal met pull nieuwe bestanden binnen en voeg met push zelf toe. Wie er niet uitkomt: laat het Arnout weten.

Arnout van Kempen di CCO CISA is Senior manager Risk & Compliance bij Baker Tilly. Hij schrijft op persoonlijke titel. Hij is lid van de Commissie Financiële verslaggeving & Accountancy van de AFM en lid van de signaleringsraad van de NBA. Daarnaast is hij diaken van het bisdom 's-Hertogenbosch.

Gerelateerd

reacties

Reageren op een artikel kan tot drie maanden na plaatsing. Reageren op dit artikel is daarom niet meer mogelijk.

Aanmelden nieuwsbrief

Ontvang elke werkdag (maandag t/m vrijdag) de laatste nieuwsberichten, opinies en artikelen in uw mailbox.

Bent u NBA-lid? Dan kunt u zich ook aanmelden via uw ledenprofiel op MijnNBA.nl.