Kennismaking met C

Leo Rutten


      
     

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. See www.gnu.org/copyleft/fdl.html.

Wijzigingen
Herziening $Revision: 1.2 $$Date: 2005/02/04 10:18:57 $RL
FDL licentie bijgevoegd
Herziening 0.1.22/10/2002RL
Tabellen bijgevoegd
Herziening 0.1.125/ 9/2002RL
Figuren bijgevoegd
Herziening 0.1.012/ 9/2002RL
Omzetting van Lotus WordPro

Samenvatting

Cursus C


Inhoudsopgave

1. Kennismaking met C
1.1. Geschiedenis
1.2. Eerste voorbeelden
2. Werken met gegevens
2.1. Een voorbeeld met verschillende types
2.2. Gegevens: variabelen en constanten
2.2.1. Het int type
2.2.2. Het char type
2.2.3. Het type float, double en long double
2.2.4. Het opsommingstype
2.2.5. De sizeof() functie
3. Character strings, #define, printf(),scanf()
3.1. Strings
3.2. Tekstvervanging met #define
3.3. De conversietekens bij printf()
3.4. De conversietekens bij scanf()
4. De Toekenning, operatoren en uitdrukkingen
4.1. Toekenning
4.2. Rekenkundige operatoren
4.3. Met 1 verhogen of verlagen ( ++ en -- )
4.4. Bitoperatoren
4.5. Samentrekking van toekenning en operator
4.6. Uitdrukkingen
4.7. Opdrachten
4.8. Typeomzetting
4.8.1. Automatische omzetting
4.8.2. cast bewerking
5. Keuze maken
5.1. De if opdracht
5.2. Relationele operatoren
5.3. Logische operatoren
5.4. Conditionele uitdrukking ?:
5.5. Meerdere keuzemogelijkheden: switch
6. Lussen en andere controlemiddelen
6.1. while herhalingsopdracht
6.2. for herhalingsopdracht
6.3. do while herhalingsopdracht
6.4. break en continue bij herhalingsopdrachten
6.4.1. break
6.4.2. continue
6.5. goto
7. Functions
7.1. Kennismaking
7.2. Parameters
7.3. Return en functietype
7.4. & operator
7.5. Pointers en adresparameters
8. Geheugenklassen
8.1. Automatische variabelen
8.2. Externe variabelen
8.3. Statische variabele
8.3.1. Gebruik static binnen functie
8.3.2. Gebruik static buiten functie
9. Arrays en pointers
9.1. Array voorbeelden
9.2. Initialisatie van arrays
9.3. Verband tussen pointers en arrays
9.4. Arrays als functieparameters
9.5. Arrays met meerdere dimensies
9.6. Pointers naar functies
10. Tekenstrings en stringfunctions
10.1. Strings definieren
10.2. Arrays van tekenstrings
10.3. Stringin- en uitgave
10.4. Enkele stringfuncties
10.4.1. strlen()
10.4.2. strcat()
10.4.3. strcmp()
10.4.4. strcpy()
10.5. Argumenten op de opdrachtregel
10.6. Strings sorteren
10.7. Overzicht string functies
10.7.1. strcpy
10.7.2. strncpy
10.7.3. strcat
10.7.4. strncat
10.7.5. strcmp
10.7.6. strncmp
10.7.7. strchr
10.7.8. strrchr
10.7.9. strspn
10.7.10. strcspn
10.7.11. strpbrk
10.7.12. strstr
10.7.13. strlen
10.7.14. strtok
11. Structuren
11.1. Typedef
11.2. Structuur
11.3. Arrays van structuren
11.4. Pointers naar structuren
11.5. Structuur binnen structuur
11.6. Unions
11.7. Bitvelden
11.8. Structuren en lijsten
11.8.1. Zelfreferentiele structuren
11.8.2. Niet gesorteerde gebonden lijsten
12. Bestandsin- en uitvoer
12.1. fopen
12.2. fclose
12.3. ferror
12.4. perror
12.5. strerror
12.6. _strerror
12.7. fwrite
12.8. fread
12.9. fseek
12.10. fgets
12.11. fputs
12.12. fgetc
12.13. fputc
12.14. fprintf
12.15. fscanf
A. Gereserveerde woorden
B. Prioriteiten van de operatoren
Bibliografie

Lijst van figuren

6.1. Werking for in flow-chart
7.1. Een variabele in het geheugen
7.2. Een pointer in het geheugen
9.1. Layout van een array in het geheugen
10.1. Strings in het geheugen
10.2. Array van pointers naar string
10.3. Arrays van strings
11.1. Bits in een struct
11.2. Drie structs zonder koppeling
11.3. Drie gekoppelde structs
11.4. Lijst met 1 element
11.5. Lijst na het bijvoegen van een element
11.6. lijst voor het uitvegen
11.7. Lijst na het uitvegen

Hoofdstuk 1. Kennismaking met C

De programmeertaal C vindt zijn oorsprong bij het ontstaan van het operating systeem UNIX. De eerste versie van UNIX was in assembler geschreven. Om de overdracht naar andere computersystemen mogelijk te maken hebben de auteurs de hogere programmeertaal C ontworpen. Deze taal kreeg een aantal eigenschappen die interessant zijn voor het ontwerp van systeemsoftware. Deze kenmerken zijn van assembler overgenomen: direkte toegang tot de hardware en datatypen die op maat gemaakt zijn van de interne registers. Deze goede eigenschappen hebben ertoe bijgedragen dat de taal C momenteel een grote populariteit kent in de computerwereld. Voor elke computer is er tegenwoordig een C compiler. Dankzij goede en goedkope compilers is de taal C ook beschikbaar op kleinere computers.

C wordt nu ook veel toegepast bij automatisering, procescontrole, telecommunicatie, CADCAM en andere technische domeinen. Het feit dat veel firma's C gebruiken voor het ontwerp van grote softwarepaketten, bewijst dat C ook toegepast wordt buiten het technische domein.

1.1. Geschiedenis

Dit zijn enkele markante punten uit de geschiedenis van C:

1976

De BCPL taal wordt ontworpen door Martin Richards.

1970

De eerste versie UNIX in B door Ken Thompson geschreven, verschijnt.

1972

De C taal wordt ontworpen voor versie UNIX op DEC PDP11 door Dennis Ritchie.

1978

Het boek 'The C programming language' door Brian Kernighan & Dennis Ritchie geschreven verschijnt bij Prentice Hall.

1982

ANSI start de standaardisatie van C.

1986

Een nieuwe uitgave van 'The C programming language' verschijnt.

De taal C is dus ontstaan bij het ontwerp van UNIX. Tot dan toe schreef men de programma's van operating systemen in assembler. Door in C te programmeren is UNIX overdraagbaar op elk type computer dat een C compiler kent.

Dit zijn de kenmerken van C:

  • algemeen

  • flexibel

  • overdraagbaar

  • laat programmeren op laag niveau toe

  • kan dus assembler vervangen

  • gebaseerd op types

  • veel verspreid

  • niet voor beginners

1.2. Eerste voorbeelden

Als kennismaking met de taal C beginnen we met enkele eenvoudige programma's. Ze zijn bijna zonder verdere uitleg te begrijpen.

We starten met een eerste voorbeeld:

#include <stdio.h>
void main() /* een eenvoudig programma */
{
   int num;

   num = 1;
   printf("dit is");
   printf(" een eenvoudig programma\n");
   printf("%d is het eerste gehele getal\n",  num );
}
            

Dit programma zet de volgende tekst op het scherm:

dit is een eenvoudig programma
1 is het eerste gehele getal
            

Als we dit programma nalezen, vinden we notaties die specifiek zijn voor C. De regel #include <stdio.h> geeft aan dat een ander bestand in dit programmabestand tussengevoegd wordt. De naam van het bestand is stdio.h en bevat definities die nodig zijn bij de standaard in- en uitvoer (stdio is de afkorting van standard input output). Deze definities hebben betrekking op de invoer van het toetsenbord en de uitvoer naar het scherm.

Op de volgende regel treffen we main() aan. Hiermee geven we aan dat dit programma bestaat uit een functie die main heet. Een functie is steeds te herkennen aan de 2 ronde haken achter de naam. Een C programma is opgebouwd uit functies die elk bepaalde taken uitvoeren. Wanneer het programma start, wordt steeds de functie main() gestart. Met behulp van de tekens { en } worden opdrachten gegroepeerd bij een functienaam. Verschillende opdrachten tussen accoladen noemt men een blok of een samengestelde opdracht.

Achter de naam main treffen we kommentaar aan. Dit wordt aangegeven door de symbolen /* en */. De begin- en eindeaanduiding moeten niet op dezelfde regel staan. Sommige compilers laten ook commentaar binnen commentaar toe. Dit kan handig zijn als we bijvoorbeeld een stuk programma in kommentaar zetten om de uitvoering ervan tijdelijk over te slaan.

Als eerste opdracht in dit blok treffen we een declaratie aan.

int num;
            

Hiermee wordt aangegeven dat het programma gebruik maakt van een variabele met de naam num. Deze variabele kan gehele getallen opslaan.

Variabeledeclaraties worden steeds aan het begin van een blok vermeld. Hiermee geven we aan dat een naam verder in het programma als variabele dienst doet en dat die variabele één waarde van een bepaald type kan opslaan.

Met een toekenning wordt een waarde in een variabele geplaatst.

num = 1;
            

Deze toekenningsopdracht plaatst de waarde 1 in de variabele.

De volgende opdracht die we aantreffen, doet schermuitvoer. printf is een van de standaard in- en uitgave functies en plaatst een tekst op het scherm. De tekst plaatsen we tussen de symbolen " ". In C noemen we een tekst tussen dubbele aanhalingstekens een string. In een string kunnen we alle leesbare tekens plaatsen. Tekens met een speciale betekenis worden door de backslash voorafgegaan. Bijvoorbeeld: \n, dit teken doet de cursor naar het begin van de volgende regel verplaatsen.

Met behulp van %d wordt duidelijk gemaakt dat een geheel getal in de tekst ingelast moet worden. De variabelenaam wordt na de string vermeld. Het % teken gevolgd door een letter specifieert het formaat.

We geven een tweede voorbeeld waarin een berekening voorkomt:

#include <stdio.h>
main()
{
   int voet,vadem;

   vadem = 2;
   voet = 6 * vadem;
   printf("in %d vadem zijn er %d voet\n",vadem,voet);
}
            

Bij de tweede toekenning van dit programma zien we aan de rechterzijde een uitdrukking. We kunnen niet alleen een waarde toekennen aan een variabele, maar ook het resultaat van een uitdrukking. In de tekst die door printf op het scherm wordt gezet, komen nu 2 getallen voor. De eerste %d wordt vervangen door de inhoud van vadem, de tweede %d door de inhoud van voet. Elke percentaanduiding komt overeen met een waarde die na de string vermeld wordt. Het aantal waarden moet precies overeenkomen met het aantal percentaanduidingen.

En nog een voorbeeld:

#include<stdio.h>

help()
{
   printf("hier is hulp\n");
}

main()
{
   printf("ik heb hulp nodig\n");
   help();
   printf("dank u");
}
            

In dit programma treffen we 2 functies aan: main() en help(). Het programma start per definitie met de uitvoering van main(). Deze functie doet eerst schermuitvoer met printf() en roept daarna de functie help() op. Dit betekent dat alle opdrachten van help() uitgevoerd worden. Daarna gaat het programma verder met de opdracht in main() die volgt na de oproep van help().

De programmeertaal laat dus toe dat we een aantal opdrachten groeperen in een functie. Als we de functie met zijn naam oproepen dan worden zijn opdrachten uitgevoerd. We besparen schrijfwerk door opdrachten die veel voorkomen in een programma, onder te brengen in een functie.

Hoofdstuk 2. Werken met gegevens

Bij de vorige voorbeelden hebben we telkens het type int gebruikt om de variabelen te declareren. Dit type slaat gehele getallen op. We zien nu een aantal programma's waar andere types worden gebruikt. De C programmeur moet voor elke variabele een type kiezen dat de gunstigste eigenschappen heeft voor de waarde die opgeslagen moet worden. Als we het type float voor berekeningen gebruiken, dan moeten we er rekening mee houden dat de rekentijden langer zijn en de nauwkeurigheid groter is dan bij het type int.

2.1. Een voorbeeld met verschillende types

Het volgende voorbeeld maakt gebruik van de types float en char.

/* omzetting gewicht in goudwaarde */
void main()
{
   float gewicht,waarde;
   char piep;

   piep = '\007';
   printf("geef uw gewicht in kg\n");
   scanf("%f", &gewicht);
   waarde = 415000 * gewicht; /* 1 kg goud = 415000 bef */
   printf("%cUw gewicht in goud is %10.2f waard.%c\n",
   piep,waarde,piep);
}
            

De variabelen gewicht en waarde zijn van het type float. Dit wil zeggen dat ze reele waarden kunnen opslaan. Een float type is hier nodig omdat we ook cijfers na de komma willen opslaan.

De variabele piep is van het type char. Hierin kunnen dus tekens opgeslagen worden. Door de toekenning krijgt piep de waarde '\007'. Dit is een teken met als code de waarde 7. Constanten van het type char worden steeds tussen ' ' geplaatst. We zien dat voor elk soort gegeven een ander type wordt gebruikt.

Dit programma werkt interactief. Het leest een getal van het toetsenbord, rekent hiermee en plaatst het resultaat op het scherm. Uitgave met printf() kennen we al. Ingave van het toetsenbord gebeurt met de functie scanf(). Bij de oproep van deze functie wordt ook een string doorgegeven net zoals bij printf(). Deze string gaat hier niet naar het scherm, maar geeft aan wat er ingelezen moet worden. In de string vinden we een %f terug. Hierdoor weet de functie scanf() dat een float waarde ingelezen moet worden. Na de string wordt er vermeld in welke variabele deze waarde terecht komt. Deze variabele wordt steeds voorafgegaan door &. Dit is de adresoperator die we later nog zullen zien.

In de printf() functie treffen we %c en %10.2f aan. De eerste aanduiding dient om een char variabele op het scherm te plaatsen. De tweede plaatst een float getal op het scherm met een breedte van 10 en 2 cijfers na de decimale punt. Elke % aanduiding heeft een corresponderende variabele.

2.2. Gegevens: variabelen en constanten

Met een computer kunnen we gegevens verwerken. Dit betekent dat een computer gegevens opslaat en daarna manipuleert. In een programma doen we dit met behulp van variabelen en constanten. Een constante kan toegekend worden aan een variabele en deze variabele kan in de loop van het programma gewijzigd worden. Een constante daarentegen kan niet gewijzigd worden.

De gegevens die in een C programma bijgehouden worden, zijn steeds van een welbepaald type. Een variabele wordt gedeclareerd en hierdoor weet de computer in welk formaat de informatie in die variabele opgeslagen wordt. De algemene vorm van een declaratie ziet als volgt uit:

typenaam variabelenaam;

In plaats van een enkele naam kunnen we ook meerdere namen bij een type plaatsten.

int a,b,c;
            

Variabelenamen bestaan uit maximum 32 letters en cijfers. Er mogen enkel letters en cijfers (de underscore _ is een letter) in voorkomen en het eerste teken moet een letter zijn.

We zullen nu de verschillende basistypes bespreken.

2.2.1. Het int type

Dit type wordt gebruikt om gehele getallen op te slaan.

int

geheugenruimte:2 bytes

bereik:-32768 tot +32767

Deze waarden gelden voor Borland C++ 3.1 op PC en zijn niet hetzelfde op andere computers.

Voorbeeld:

int regendagen;
int uren,minuten;

uren = 5;
scanf("%d",&minuten);
                

Bij de declaratie zelf kunnen de variabelen geinitialiseerd worden.

int regendagen = 25;
int uren,minuten = 3;
                

minuten krijgt de waarde 3 in het laatste voorbeeld, uren wordt niet geinitialiseerd.

Integer constanten worden voorgesteld door groepjes cijfers.

123

decimaal

0400

octaal

0xF3ca

hexadecimaal

Een geheel getal dat start met 0 is octaal. Een getal dat start met 0x is hexadecimaal.

Het is mogelijk om bij de uitgave op scherm het getalstelsel te bepalen. De inhoud van een int variabele kan met een %d, %o of %x op het scherm geplaatst worden. Het resultaat is dan decimaal, octaal of hexadecimaal. In het voorbeeld wordt driemaal dezelfde waarde in een ander talstelsel op het scherm geplaatst.

void main()
{
   int x = 100;

   printf("dec = %d, octaal = %o, hex = %x\n",x,x,x);
}
                

Door middel van adjectieven short, long en unsigned kan het bereik van het int type aangepast worden. Het int type komt gewoonlijk overeen met de registergrootte van de computer. Dit is gewoonlijk 16 of 32 bit. Met short wordt het bereik verkleind, met long vergroot en met unsigned verkrijgen we een type waarin enkel positieve waarden opgeslagen kunnen worden. Hierbij gelden de volgende beperkingen: short en int moeten minstens 16 bit groot zijn en long minstens 32 bit, bovendien mag een short niet groter zijn dan int, die op zijn beurt niet groter mag zijn dan een long.

Bij Borland C++ 3.1 voor IBM PC levert dit de volgende mogelijkheden:

short int, short of int:

geheugenruimte: 2 bytes

bereik:-32768 tot +32767

long int of long:

geheugenruimte: 4 bytes

bereik:-2147483648 tot +2147483647

unsigned int of unsigned:

geheugenruimte: 2 bytes

bereik:0 tot +65535

unsigned long:

geheugenruimte: 4 bytes

bereik:0 tot +4294967295

Gehele getallen die eindigen met de letter L zijn long constanten:

123L 045L0x1234L
                

Dit achtervoegsel mag bij decimale, octale en hexadecimale constanten toegepast worden.

Bij schermuitgave moet precies aangeduid worden van welke soort elke variabele is en op welke wijze deze variabele op het scherm komt.

void main()
{
   unsigned un = 40000;
   long ln = 2000000000;
   unsigned long uln = 4000000000;

   printf("un: %u ln: %ld uln: %lu\n",un,ln,uln);
}
                

We zien hier nieuwe formaataanduidingen. %u gebruiken we bij tekenloze int variabelen en %l gebruiken we bij long variabelen. De combinatie %lu is voor tekenloze long variabelen.

Het volgende schema geeft een overzicht:

 

16 bit

32 bit

met teken

%d

%ld

zonder teken

%u

%lu

Indien niet de juiste percentaanduiding gebruikt wordt, komt er een onvoorspelbaar resultaat op het scherm. Dit komt omdat de compiler deze overeenkomst niet controleert. De programmeur moet er dus goed op letten dat er een juiste overeenkomst is tussen percentaanduiding en het type.

2.2.2. Het char type

Variabelen van het char type worden gebruikt voor de opslag van tekens. We zien hier een voorbeeld van een declaratie:

char letter,teken;
char cijfer;
                

Dit type variabele gebruikt 1 byte geheugen en hierin wordt het teken opgeslagen als een getal van -128 tot +127. Constanten van het char type worden tussen 2 aanhalingstekens genoteerd.

'A' 'c' '0'
                

Tekens met een speciale betekenis worden met een backslash voorgesteld.

'\n'

nieuwe lijn

'\t'

tab

'\a'

belsignaal

'\b'

backspace

'\f'

formfeed

'\r'

carriage return

'\t'

horizontale tab

'\v'

vertikale tab

'\\'

backslash

'\?'

vraagteken

'\''

enkel aanhalingsteken

'\"'

dubbel aanhalingsteken

'\0'

nulteken

We kunnen de code ook zelf samenstellen. Er wordt dan een getal achter de backslash geplaatst. Bijvoorbeeld '\0' is een char constante met 0 als code. Dit getal is octaal: '\12' heeft als waarde 10 decimaal. Als we de code in hexadecimaal willen uitdrukken, moet de letter x tussen de backslash en het getal geplaatst worden.

code in octaal: '\ddd'

                        '\123'
                

code in hexadecimaal: '\xddd'

                        '\x1b'
                

We kunnen deze speciale tekens ook in strings toepassen.

printf("\007wakker worden!\n");
                

In dit voorbeeld sturen we de code voor het belsignaal naar het scherm. Het teken '\n' plaatst de cursor op het begin van de volgende regel.

char variabelen kunnen via in- en uitvoer verwerkt worden.

void main()
{
   char ch;

   printf("geef een teken\n");
   scanf("%c", &ch);
   printf("de code van %c is %d\n",ch,ch);
}
                

In dit voorbeeld wordt %c gebruikt bij in- en uitvoer. Het ingelezen teken wordt tweemaal op het scherm geplaatst: eenmaal als code en eenmaal als teken. Denk eraan dat we bij scanf een & voor de variabele gebruiken en bij printf niet. Indien het & teken bij scanf vergeten wordt, dan geeft de compiler hiervoor geen foutmelding. Het programma zal dan wel starten maar de werking van de scanf functie is onvoorspelbaar.

2.2.3. Het type float, double en long double

Voor reele getallen biedt de taal C de types:

float

geheugenruimte:4 bytes

bereik:3.4e-38 tot 3.4e+38

double:

geheugenruimte:8 bytes

bereik:1.7e-308 tot 1.7e+308

long double

geheugenruimte:10 bytes

De aangegeven geheugenruimte en bereik gelden voor Borland C++ 3.1 .

Bij sommige implementaties is de precisie van double en long double dezelfde.

Deze types kunnen zo in een declaratie gebruikt worden:

float pi     = 3.14159;
double planck = 6.63e-34;
long double getal;
                

Wanneer een getalconstante met een decimale punt of een exponent genoteerd wordt, is dit een double constante. Wanneer het suffix f of F wordt toegevoegd is de constante van het type float. Indien het suffix l of L wordt bijgevoegd dan is de constante van het type long double.

123.45F
.556L
46.
12e-3F
15.5E20
                

Deze constanten zijn allemaal van het reele type en kunnen aan een long double, double of een float variabele toegekend worden.

Er zijn 3 mogelijkheden om double en float variabelen op het scherm te plaatsen:

%f gewone notatie

%e exponent notatie

%g gewone of exponent notatie

Als we %g gebruiken wordt indien mogelijk het getal in de gewone notatie op het scherm gedrukt; indien de exponent te groot of te klein verschijnt het getal in de exponent notatie op het scherm.

Voorbeeld:

void main()
{
   float getal = 32000.0;

   printf("%f is gelijk aan %e\n",getal,getal);
}
                

2.2.4. Het opsommingstype

Het opsommingstype laat toe zelf symbolen als waarde te definieren.

enum dagen =
{
   zondag,maandag,dinsdag,woensdag,donderdag,
   vrijdag,zaterdag
} vandaag, morgen;
                

De variabelen vandaag en morgen zijn van het type enum dagen. We kunnen hierin de namen van de dagen als waarde opslaan. Deze waarden worden als getallen opgeslagen. Het eerste symbool krijgt de waarde 0, het volgende de waarde 1 enzovoort. De uitdrukking enum dagen kan verder in het programma nog gebruikt worden voor de declaratie van andere variabelen.

enum dagen gisteren;
gisteren = woensdag;
                

We kunnen ook zelf een waarde koppelen aan elk symbool

enum jaar
{
   Guldensporen=1302, Bastille=1789, VanGogh=1890
} feit;
                

2.2.5. De sizeof() functie

Tot slot nog een voorbeeld dat gebruik maakt van de ingebouwde functie sizeof(). Deze functie levert als resultaat de lengte (in bytes) van het type of de variabele die doorgegeven wordt. Het type van het resultaat is int (dit is afhankelijk van de implementatie).

main()
{
   printf("lengte char: %d\n",sizeof(char));
   printf("lengte int: %d\n",sizeof(int));
   printf("lengte long: %d\n",sizeof(long));
   printf("lengte float: %d\n",sizeof(float));
   printf("lengte double: %d\n",sizeof(double));
   printf("lengte long double:
                        %d",sizeof(long double));
}
                

Hoofdstuk 3. Character strings, #define, printf(),scanf()

Met constanten bedoelen we het koppelen van een naam met een constante waarde. De #define opdracht wordt hiervoor gebruikt.

In dit hoofdstuk hebben we het verder over strings, printf() en scanf().

3.1. Strings

Een string is een aaneenschakeling van tekens. Wanneer we een tekst op het scherm plaatsen, geven we een stringconstante door aan printf(). Met stringconstanten hebben we al kennis gemaakt.

printf("abcde");
            

De stringconstante "abcde" wordt gevormd door een tekst tussen dubbele aanhalingstekens. In feite worden de afzonderlijke tekens als een char constante opgeslagen.

'a' 'b' 'c' 'd' 'e' '\0'
            

Als laatste teken wordt nog de code 0 bijgevoegd. Dit geeft het einde van de string aan. Een stringconstante vraagt dus altijd 1 byte meer geheugen dan het aantal tekens. Deze 0 wordt gebruikt om het einde van de string te herkennen. Een functie die het aantal tekens in een string moet tellen, doorloopt de string en verhoogt een teller voor elk karakter in de string. De 0 code is dus het criterium om de herhaling te beeindigen.

Hier is een voorbeeld met strings en constanten.

#define DENSITEIT 999 /* menselijke densiteit in
                    kg/m3 */
void main()
{
   float gewicht,volume;
   int grootte, letters;
   char naam[40];

   printf("geef je voornaam\n");
   scanf("%s", naam);
   printf("%s, geef je gewicht in kg\n",naam);
   scanf("%f",&gewicht);
   grootte = sizeof(naam);
   letters = strlen(naam);
   volume = gewicht/DENSITEIT;
   printf("%s, je volume is %10.4f m3\n",naam,volume);
   printf("je naam bestaat uit %d letters,\n",letters);
   printf("en we hebben %d bytes nodig",grootte);
   printf("om die op te slaan\n");
}
            

In dit programma treffen we een nieuw type variabele aan: array.

char naam[40];
            

Dit is de declaratie van de variabele naam. Hierdoor wordt er geheugenruimte gereserveerd voor 40 tekens. De scanf() functie zorgt ervoor dat deze variabele met een string gevuld wordt. Deze functie ontvangt 2 parameters: een stringconstante die een %s bevat en het adres van een string variabele. Deze %s aanduiding geeft aan dat een string van het toetsenbord gelezen wordt. Met het adres van de stringvariabele naam weet scanf() dat de ingegeven string terecht komt in de array naam. Wanneer een array variabele doorgegeven wordt aan scanf(), mag de adresoperator & niet gebruikt worden. Al de tekens die op het toetsenbord ingegeven worden, komen in de stringvariabele terecht. Na het laatste teken plaatst scanf() nog de code '\0' om het einde van de string aan te duiden.

Dezelfde %s aanduiding wordt gebruikt om met printf() een string op het scherm te plaatsen.

We moeten toch wel opmerken dat er een verschil is tussen enkele en dubbele aanhalingstekens. De constante "a" is een char array die uit 2 tekens bestaat: 'a' en '\0', terwijl de constante 'a' slechts een enkel teken is en bijgevolg van het type char is.

Met sizeof(naam) wordt er uitgerekend hoeveel bytes de variabele naam beslaat: dit zijn 40 bytes. Het programmavoorbeeld gebruikt een nieuwe functie: strlen(). Deze functie geeft ons de lengte van een string. De functie telt de tekens totdat de code 0 bereikt wordt. In het programma wordt strlen() gebruikt om na te gaan hoeveel tekens ingegeven zijn.

3.2. Tekstvervanging met #define

Bij de berekening van het volume zien we het symbool DENSITEIT. Dit is een constante die in de eerste programmaregel gedeclareerd wordt met #define. In C is het mogelijk om een tekst, die veel voorkomt, te koppelen aan een naam. Hiervoor dient de #define opdracht.

#define NAAM tekst
            

In de eerste faze van de vertaling worden alle namen die door #define zijn vastgelegd, vervangen door hun tekst. Deze taak wordt uitgevoerd door de preprocessor.

C programma ---> preprocessor ---> compiler

Hier zijn nog enkele voorbeelden:

#define PI 3.14159
#define DOLLAR '$'
            

De namen van deze constanten zijn in hoofdletters. Dit is niet verplicht, maar deze conventie wordt door veel C programmeurs gebruikt om het verschil tussen constanten en variabelen zichtbaar te maken.

We zien bij dit voorbeeld ook dat de #define werkt op basis van tekstvervanging. Telkens als de preprocessor een naam tegenkomt die met #define een betekenis heeft gekregen, wordt deze naam vervangen door de bijbehorende tekst. Men kan zelfs een hele opdracht bij een naam onderbrengen.

char slot[] = "tot ziens!";
void main()
{
   char naam[50];

   printf("geef je naam\n");
   scanf("%s", naam);
   printf("hallo %s\n", naam);
   printf("%d letters in %d bytes\n",
   strlen(naam),sizeof(naam) );
   printf("%d letters in %d bytes\n",
   strlen(slot),sizeof(slot) );
   printf("%s\n", slot);
}
            

Hiermee zien we dat de stringarray slot een extra byte nodig heeft voor de code 0.

3.3. De conversietekens bij printf()

We bespreken hier de volledige mogelijkheden van de uitvoer met de printf() functie.

De volgende conversietekens kunnen in printf() gebruikt worden:

%d %i

geheel decimaal getal

%o

octaal

Het getal wordt niet vooraf gegaan door een 0.

%x %X

hexadecimaal

Het getal wordt niet voorafgegaan door 0x of 0X. De waarden 10 tot 15 worden voorgesteld door abcdef of ABCDEF.

%u

decimaal getal zonder teken

%c

een enkel teken

%s

een string

De tekens tot de code '\0' worden afgedrukt.

%f

reeel zonder e notatie

Het formaat is [-]m.dddddd, het aantal cijfers na de decimale punt is 6.

%e %E

reeel in e notatie

Het formaat is [-]m.dddddde+xx of [-]m.ddddddE+XX, het aantal cijfers na de decimale punt is 6.

%g %G

reeel met of zonder e notatie

Het formaat is %e of %E als de exponent kleiner dan -4 of groter dan of gelijk aan de precisie is; gebruik anders %f. Nullen en/of een decimale punt worden niet afgedrukt.

%p

pointer

Deze formaataanduiding wordt gebruikt om een pointer af te drukken. Het formaat is bepaald door de implementatie.

%%

percent teken

Een extra controle is mogelijk met de volgende bijvoegsels. Deze bijvoegsels worden tussen de % en het conversieteken geplaatst.

-

Het element wordt links in plaats van rechts gelijnd in het veld.

printf("%-10d", 123);
            
+

Dit teken geeft aan dat voor het weer te geven getal altijd een plus- of een minteken moet worden gezet.

spatie

Als het eerste teken geen plus- of minteken is, wordt een spatie voor het getal gezet.

0

Deze nul zorgt ervoor dat het veld vooraan met nullen moet worden gevuld.

#

alternatieve vorm

Met dit teken wordt een alternatieve vorm van uitvoer gespecifieerd. Als op # een o volgt, wordt ervoor gezorgd dat het eerste cijfer een 0 is. Volgt op # een x of een X, dan wordt voor een resultaat dat ongelijk is aan 0 als prefix 0x of 0X gezet. Is het teken e, E, f, g of G, dan heeft de uitvoer altijd een decimale punt. Is het teken g of G, dan worden de nullen achteraan niet verwijdert. Waarden die met deze vorm afgedrukt worden, kunnen altijd weer met een scanf() ingelezen. Dit is van belang voor de varianten van printf() en scanf() die van en naar bestanden lezen of schrijven.

getal

minimum veldbreedte

Het geconverteerde argument wordt in een veld afgedrukt dat minimaal deze breedte heeft.

printf("%8d", 0x1234);
            
.getal

precisie

Het getal is de precisie. Bij een string bepaalt dit getal het maximum aantal af te drukken tekens, bij reele getallen is de precisie het aantal af te drukken cijfers na de decimale punt.

printf("%6.2f",10/3);
            
*

variabel formaat

In plaats van een getal voor de veldbreedte of de precisie mag ook het teken * gebruikt worden.

Dit betekent dat de veldbreedte en/of de precisie bepaald worden door variabelen. Deze variabelen moeten van het type int zijn.

printf("%*.*f", breedte,nauwk, 1/3 );
            

Een van de volgende tekens mag vlak voor het conversieteken geplaatst worden:

h

short in plaats van int

l

long in plaats van int

printf("%ld", 0x1234L);
            
L

long double in plaats van double

3.4. De conversietekens bij scanf()

Bij de scanf() functie wordt de invoer van het toetsenbord verwerkt. De conversie wordt bepaald door de conversietekens in de formaatstring. Elk van deze tekens neemt een deel van de ingave voor zich. De geconverteerde gegevens worden in variabelen geplaatst. Hiervoor worden na de formaatstring een reeks adressen van variabelen doorgegeven aan scanf(). Bij enkelvoudige variabelen is een & nodig om het adres van de variabele te berekenen.

In de formaatstring mogen buiten % tekens gevolgd door een conversieteken ook andere tekens staan:

  • Spaties of tabs: deze worden genegeerd.

  • Gewone tekens (geen %); deze moeten overeenstemmen met het volgende niet-witruimteteken van de invoer.

Een conversiespecificatie (% teken met een conversieteken) regelt de conversie van het eerstvolgende invoerveld. Als tussen % en het conversieteken een * wordt geplaatst, zoals in %*s, dan wordt de toekenning onderdrukt: het invoerveld wordt dan eenvoudig overgeslagen en er vindt geen toekenning plaats.

Een invoerveld wordt gedefinieerd als een string van niet-witruimtetekens. Zo 'n veld strekt zich uit tot aan het eerstvolgende witruimteteken of eindigt, als een veldbreedte is opgegeven, op de plaats waar die veldbreedte is bereikt. Dit betekent dus dat scanf() om zijn invoer te vinden over de regelgrenzen leest, omdat een newline als witruimte geldt. De witruimtetekens zijn: spatie, tab, newline, carriage return en formfeed.

Voor de conversietekens d, i, n, o, u en x mag een h of een l worden gezet: met h wordt aangegeven dat een short variabele gevuld moet worden; met l wordt aangegeven dat een long moet gevuld worden. Op dezelfde manier mag voor de conversietekens e, f en g een l of een L geplaatst worden: l leest een double en L leest een long double.

De volgende conversietekens kunnen bij scanf() gebruikt worden:

%d

geheel decimaal getal

%i

geheel getal

Het getal mag in decimaal, octaal of hexadecimaal ingegeven worden. Een decimaal getal start niet met een 0. Een octaal getal start met een 0 en een hexadecimaal getal start met 0x of 0x.

%o

octaal geheel getal

Het getal is al dan niet voorafgegaan door een 0.

%x %X

hexadecimaal geheel getal

Het getal is al dan niet voorafgegaan door 0x of 0X.

%u

decimaal getal zonder teken

%c

tekens

De volgende invoertekens worden in de opgegeven array gezet, en wel tot aan het in het breedteveld aangegeven aantal. Dit aantal is bij verstek 1. Er wordt geen '\0' toegevoegd bij de ingelezen tekens. Het gebruikelijke overslaan van witruimtetekens wordt onderdrukt. Om het volgende niet-witruimteteken te lezen moet %1s gebruikt worden.

%s

een string van niet-witruimtetekens

Aan de ingelezen tekens wordt nog de code '\0' bijgevoegd. De array variabele moet groot genoeg zijn om al de ingegeven tekens op te slaan. Als er meer ingegeven wordt dan er plaats is in de array, gebeuren er rare dingen.

%f %e %

geen reeel getal

Het invoerformaat is: een optioneel teken, een string van cijfers, mogelijk met een decimale punt en een optioneel exponentveld met een E of een e, gevolgd door een integer, mogelijk met teken.

%p

pointer

Een pointer wordt ingelezen. Het formaat is zoals het formaat bij het afdrukken met printf(). Dit wordt bepaald door de implementatie.

%n

aantal invoervelden

In een meegeleverde int variabele wordt het aantal tot nu toe door deze scanf() ingegelezen velden geplaatst. Er wordt geen invoer gelezen en de teller die intern in de scanf() functie het aantal gelezen velden telt, wordt niet verhoogd.

[...]

Dit correspondeert met de langste niet-lege string van invoertekens uit de verzameling tussen de haken. Aan het einde wordt een '\0' toegevoegd. Met []...] wordt het teken ] in de verzameling opgenomen.

[^...]

Dit correspondeert met de langste niet-lege string van invoertekens die niet in de verzameling tussen de haken voorkomen. Aan het einde wordt een '\0' toegevoegd. Met [^]...] wordt het teken ] in de verzameling opgenomen.

%%

percent teken

Er vindt geen toekenning plaats.

Hoofdstuk 4. De Toekenning, operatoren en uitdrukkingen

De toekenning is een essentieel element bij de imperatieve talen. Met deze opdracht kunnen we een waarde opslaan in een variabele. Zolang er geen nieuwe toekenning plaats heeft voor deze variabele, behoudt de variabele zijn waarde. De waarde die aan een variabele wordt toegekend, kan niet alleen een constante zijn maar ook de inhoud van een andere variabele of het resultaat van een uitdrukking. Een uitdrukking bestaat uit een aantal constante waarden en inhouden van variabele die met elkaar worden gecombineerd door operatoren.

Het volgende programma toont hoe we met behulp van operatoren en uitdrukkingen een waarde kunnen toekennen aan een variabele.

void main()
{
   float celsius,fahrenheit;
        
   printf("Temperatuurtabel\n");
   celsius = 0;
   while(celsius <= 100)
   {
      fahrenheit = 9.0/5*celsius + 32;
      printf("%4.1f celsius is %4.1f fahrenheit\n",
      celsius, fahrenheit);
      celsius = celsius + 5;
   }
}
        

Dit programma plaatst een omzettingstabel van graden Celsius naar graden Fahrenheit op het scherm. Alle Celsius waarden van 0 tot 100 worden in een stap van 5 omgezet naar Fahrenheit. De herhaling van deze berekening wordt met een while opdracht uitgevoerd. De herhaling gaat verder zolang de voorwaarde die bij de while vermeld wordt, waar is. De opdrachten die in deze herhaling betrokken zijn, worden tussen accolades vermeld na de while. De eerste Celsius waarde die op het scherm verschijnt, is de waarde 0. De laatste is 100. Hierna wordt de variabele celsius nog een keer verhoogd tot 105. De voorwaarde die bij de while hoort, is dan niet meer waar en de herhaling stopt.

We bespreken de volgende punten:

4.1. Toekenning

De algemene vorm van een toekenning is:

variabelenaam = uitdrukking;

De waarde van de uitdrukking wordt uitgerekend en dan in de variabele geplaatst. Er kan ook een waarde aan meerdere variabelen toegekend worden.

a = b = c = 1;
            

Bij deze toekenning wordt eerst c 1, daarna b en dan pas a.

4.2. Rekenkundige operatoren

Rekenkundige bewerkingen kunnen in uitdrukkingen toegepast worden. Als basisoperatoren hebben we +, -, * en /. In het voorbeeld

(-b + c)/a
            

is - een unaire operator( werkt op 1 operand) , + en / zijn binaire operatoren (werkt op 2 operanden). Bij de deling wordt dezelfde operator voor gehele en reele getallen gebruikt.

39/5 wordt 7

39./5 wordt 7.8

Als de deler of het deeltal reeel is, is het quotient ook reeel. Om de rest van een gehele deling te berekenen, wordt de % operator toegepast.

39%5 wordt 4

De rekenkundige operatoren kunnen in twee soorten verdeeld worden:

binair: + - * / unair: + -

In de uitdrukking a + +(b - c) zorgt de unaire + ervoor dat eerst het verschil van b en c uitgerekend wordt. Hierdoor kan een overflow van a+b vermeden worden.

Er bestaat geen operator voor machtsverheffing.

Dit zijn de prioriteiten met telkens de volgorde van uitvoering als meerdere operatoren van dezelfde prioriteit voorkomen.

prioriteit

operator

 

hoog

()

van links naar rechts

 

- + unair

 
 

* / %

 
 

+ - binair

van links naar rechts

laag

=

van rechts naar links

4.3. Met 1 verhogen of verlagen ( ++ en -- )

C kent een speciale notatie om een variabele met 1 te verhogen of te verlagen. Dit kan handig zijn zeker als de uitdrukking die de variabele voorstelt lang is.

Hier zijn enkele voorbeelden:

a++;
a--;
            

ofwel

++a;
--a;
            

De operatoren ++ en -- doen hetzelfde als:

a = a + 1;
a = a - 1;
            

Een ++ of -- is soms moeilijk te interpreteren. Wat betekent de volgende uitdrukking?

x*y++
            

Dit is hetzelfde als x*(y++) en dus niet (x*y)++. We zonderen y++ af met haken omdat ++ een hogere prioriteit heeft dan *. De 2de notatie is trouwens zinloos omdat we alleen een variabele kunnen verhogen en geen utdrukking.

De ++ en -- operatoren kunnen voor of na de variabele geplaatst worden. Dit betekent vooraf of achteraf verhogen.

  • postfix notatie

    i = 0; j = i++;/* j wordt 0 */
                

    eerst waarde gebruiken en daarna verhogen

  • prefix notatie

    m = 0; n = ++m;/* n wordt 1 */
                

    eerst verhogen en daarna waarde gebruiken

In het volgende voorbeeld wordt de verhoging van i in de voorwaarde ingebouwd.

void main()
{
   int i = 0;

   while(++i < 20)
      printf("%d\n", i );
}
            

Deze notatie levert kortere programma's op. Het nadeel is dat deze programma's minder goed leesbaar zijn en dat er soms ongewenste zijeffecten worden gecreeerd.

4.4. Bitoperatoren

Weinig programmeertalen hebben operatoren voor bewerkingen op bitniveau. De taal C vormt hierop een uitzondering. Dit is begrijpelijk als men weet dat de ontwerpers van C een taal hebben ontworpen om assembler gedeeltelijk te vervangen.

Deze bitoperatoren mogen uitsluitend op gehele getallen toegepast worden. Dit zijn de types char, short, int en long met of zonder teken.

&

bitsgewijs en

|

bitsgewijs inclusieve of

^

bitsgewijs exclusieve of

<<

verschuif naar links

>>

verschuif naar rechts

~

één complement (unair)

Alleen de laatste operator is unair, de overige zijn binair; ze vragen twee operands.

Om bit 3 in een variabele op 1 te zetten schrijven we:

x = x | 010;
            

Om dezelfde bit terug op 0 te zetten schrijven we:

x = x & 0177767;
            

ofwel

x = x & ~010;
            

De eerste vorm kan enkel gebruikt worden voor een variabele van het type int. De tweede vorm kan voor elk geheel type gebruikt worden.

De bitoperator ^ levert in een bit het resultaat 1 als de twee bits uit de operands verschillend zijn.

De verschuifoperatoren << en >> zorgen ervoor dat de bits van de linker operand verschoven worden. Het aantal bits dat verschoven wordt, is afhankelijk van de rechter operand. Bij x << 3 wordt de waarde 3 plaatsen naar links verschoven. De vrijgekomen bits worden met 0 bits opgevuld. Het resultaat is in dit geval hetzelfde als vermenigvuldigen met 8.

Bij het verschuiven naar rechts is er een verschil tussen tekenloze en getallen met teken. Bij unsigned waarden worden aan de linkerkant nullen ingeschoven. Dit noemt men logisch verschuiven. Bij waarden met teken wordt links de tekenbit ingeschoven (rekenkundig verschuiven) of bij sommige implementaties wordt in dit geval ook een nul ingeschoven.

De operator ~ zorgt voor de omkering van alle bits van het getal. Een 0 wordt 1 en een 1 wordt 0.

4.5. Samentrekking van toekenning en operator

Dit zijn kortere vormen voor toekenningen.

x = x + a

wordt

x += a
x = x - a x -= a
x = x * a x *= a
x = x / a x /= a
x = x % a x %= a
x = x << a x <<= a
x = x >> a x >>= a
x = x & a x &= a
x = x ^ a x ^= a
x = x | a x |= a

Ook deze notaties leveren kortere programma's op.

Let wel op de prioriteiten:

a *= b + 2;
            

betekent

a = a * (b + 2);
            

en niet

a = a * b + 2;
            

4.6. Uitdrukkingen

Dit is een combinatie van bewerkingen, constanten en variabelen. Een uitdrukking stelt steeds een waarde voor. Deze waarde kan berekend worden door de bewerkingen volgens hun prioriteiten uit te rekenen. Enkele voorbeelden:

5
-125
1+1
a = 3
b = ++b % 4
c > 3.14
            

Ook de toekenning stelt een waarde voor. Dit is de waarde die aan de variabele toegekend wordt.

4.7. Opdrachten

Opdrachten zijn de bouwstenen van een programma. Elke opdracht voert een actie uit.

/* de som van de eerste 20 getallen */
void main()
{
   int teller, som;/* declaratie*/

   teller = 0;/* toekenning*/
   som = 0;/*   idem*/
   while (teller++ < 20)/* while*/
      som = som + teller;/*   opdracht*/
   printf("som = %d\n",som);/* functie oproep*/
}
            

Elke opdracht wordt met een ; afgesloten.

Een samengestelde opdracht bestaat uit meerdere opdrachten tussen { en }.

while (i++ < 100)
   j = i * i;/* alleen deze opdracht in herhaling */
printf("%d\n",j);
            

In het vorige voorbeeld hoort er bij de while slechts een opdracht. In het volgend voorbeeld plaatsen we twee opdrachten bij de while.

while (i++ < 100)
{
   j = i * i;
   printf("%d\n",j);
}
            

4.8. Typeomzetting

4.8.1. Automatische omzetting

Bij het uitrekenen van uitdrukkingen waarin constanten en variabelen van hetzelfde type voorkomen, is geen typeomzetting nodig. Wanneer er verschillende types voorkomen, gebeurt er automatisch een omzetting van een lager type naar een hoger type.

De omzetting vindt alleen maar plaats als er geen verlies van informatie is. Bij de uitdrukking f + i wordt de int variabele automatisch omgezet tot float omdat f van het type float is. Bij de toekenning gebeurt er een omzetting naar het type van de variabele, die de waarde ontvangt. Dit betekent dus een promotie of degradering. Dit laatste kan problemen geven, wanneer de waarde niet in het bereik past. In dit geval kan de compiler een waarschuwing geven.

char k;

k = 200 + 321;
k = 2.3e45;
                

4.8.2. cast bewerking

Dit is een geforceerde omzetting.

intm;

m = 1.6 + 1.5;/* geeft 3 */
m = (int) 1.6 + (int) 1.5;/* geeft 2 */
                

Het type wordt tussen haken voor de om te zetten waarde geplaatst. De omzetting (int) geeft afkapping en geen afronding.

Hoofdstuk 5. Keuze maken

5.1. De if opdracht

Met de if opdracht kunnen we de uitvoering van het programma beinvloeden. Afhankelijk van een voorwaarde wordt de ene of de andere opdracht uitgevoerd.

In het volgende voorbeeld wordt de if gebruikt om na te gaan of een getal oneven is.

void main()
{
   int teller = 0;
   int som    = 0;

   while (teller++ < 100)
      if ( teller % 2 != 0)
         som += teller;
   printf("de som van de oneven getallen is %d\n",som);
}
            

De algemene vorm is:

if (uitdrukking)
   opdracht

Het resultaat van de uitdrukking bepaalt of de opdracht al dan niet uitgevoerd wordt.

niet 0 : uitvoeren

0 : niet uitvoeren

Het is mogelijk om meerdere opdrachten bij een if te plaatsen. We plaatsen de opdrachten tussen accolades.

if (a == b)
{
   printf("twee gelijke getallen:\n");
   printf("%d en %d\n", a, b);
}
            

We kunnen ook een opdracht laten uitvoeren als de voorwaarde niet waar is. Dit wordt aangegeven door het woord else.

if (a == 0)
   printf("het getal is nul\n");
else
   printf("het getal is niet nul\n");
            

De algemene vorm is:

if (uitdrukking)
   opdracht
else
   opdracht

Als opdracht bij een if of else kan een andere if gebruikt worden.

if (a == 0)
   printf("het getal is nul\n");
else
   if (a > 0)
      printf("het getal is positief\n");
   else
      printf("het getal is negatief\n");
            

Indien we veel if opdrachten met elkaar combineren, kunnen we de insprong beter weglaten.

if (bedrag < 1000)
   korting = 0;
else if (bedrag < 2500)
   korting = 2;
else if (bedrag < 5000)
   korting = 5;
else if (bedrag < 10000)
   korting = 8;
else
   korting = 10;
bedrag *= 1 - korting/100;
            

De structuur in het vorige voorbeelden komt in praktijk veel voor. In deze structuur wordt een opdracht uit vele uitgevoerd.

Wanneer een else volgt na meerdere if opdrachten, kunnen we ons afvragen bij welke if deze else hoort.

if (getal > 5)
if (getal < 10)
   printf("goed\n");
else
   printf("slecht\n");
            

Bij dit programma zouden we kunnen denken dat de else bij de eerste if hoort, maar deze interpretatie is fout. Een else hoort steeds bij de laatste else-loze if.

Dit is de verbeterde versie:

if (getal > 5)
   if (getal < 10)
      printf("goed\n");
   else
      printf("slecht\n");
            

Als we de else toch bij de eerste if willen plaatsen, dan kan dit zo:

if (getal > 5)
{
   if (getal < 10)
      printf("goed\n");
}
else
   printf("slecht\n");
            

5.2. Relationele operatoren

Met deze operatoren kunnen we uitdrukkingen schrijven die vergelijkingen uitvoeren.

                    < kleiner dan


                    > groter dan


                    <= kleiner dan of gelijk aan


                    >= groter dan of gelijk aan


                    == gelijk aan


                    != verschillend van

            

Alleen waarden van de types (un)signed char, short, int, long, pointer, float en double kunnen met elkaar vergeleken worden.

Het resultaat van deze vergelijkingen is 1 (waar) of 0 (niet waar); het resultaat is van het type int. C kent dus geen boolse constanten of variabelen. We kunnen dit uitproberen met de volgende opdracht.

printf("waar %d, niet waar %d\n", 5>1, 0!=0);
            

Dit voorbeeld toont dat we gehele getallen krijgen als resultaat van vergelijkingen.

Let wel op voor het verschil tussen = (toekenning) en == (test gelijkheid). Het verwisselen van deze twee operatoren is een veel voorkomende fout, die niet door de meeste compilers gesignaleerd worden.

a = 5 levert 5

a == 5 levert 1 als a gelijk aan 5 anders 0

Deze twee operatoren worden verschillend geschreven omdat ze tegelijkertijd bij een if gebruikt kunnen worden.

if ((a = b) == 0)
            

Deze opdracht plaatst eerst de inhoud van b in a en test dan of deze waarde gelijk is aan 0. a = b staat tussen haken omdat de toekenning een lagere prioriteit heeft dan de gelijkheidsvergelijking. Soms wordt de vergelijking verschillend van 0 weggelaten.

if (aanwezigen != 0) is identiek aan

if (aanwezigen)

De laatste notatie die wel korter is, is niet aan te bevelen wegens de slechte leesbaarheid.

De prioriteit van relationele operatoren is lager dan die van rekenkundige operatoren. De uitdrukking a + b == 0 kunnen we dus als (a + b) == 0 interpreteren.

5.3. Logische operatoren

Met deze operatoren kunnen we meerdere voorwaarden logisch met elkaar koppelen.

/* tel kleine letters in een regel */
void main()
{
   char t;
   int aantal = 0;

   while ( ( t=getchar() ) != '\n')
   {
      if (t >= 'a' && t <= 'z')
         aantal++;
   }
   printf("het aantal is %d\n", aantal);
}
            

De uitdrukking bij de while kent eerst een waarde toe aan de variabele t. Deze waarde komt van de functie getchar(). Deze functie wacht tot een toets ingedrukt wordt en levert de code van deze toets als resultaat. Hierna wordt er getest of de ingegeven toets geen return is. Deze while herhaling gaat verder totdat alle tekens van de ingaveregel verwerkt zijn. Dit programma telt het aantal kleine letters in een regel tekst.

Bij de if opdracht zien we dat de twee voorwaarden gekoppeld zijn met de && operator. De opdracht bij de if wordt dus enkel uitgevoerd als de twee voorwaarden waar zijn.

Er zijn drie logische operatoren:

  • && logische en

  • || logische of

  • ! logische niet

De werking is:

                    uitdr1 && uitdr2

            

waar als beide uitdr1 en uitdr2 waar zijn

                    uitdr1 || uitdr2

            

waar als ofwel een van de twee ofwel beide uitdrukkingen waar zijn

                    ! uitdr1

            

waar als uitdr1 niet waar is

We mogen de logische operatoren niet verwarren met de bitoperatoren &, | en ~. De bitoperatoren werken op de bits apart en de logische operatoren worden uitgevoerd op de getalwaarden. Bij deze laatsten is het alleen van belang of getal nul is of niet.

                    4 && 2  // geeft 1


                    4 & 2  // geeft 0


                    4 || 2 // geeft 1


                    4 | 2 // geeft 6
            

Hier zijn nog enkele voorbeelden:

                    6 > 1 && 10 == 5    // niet waar


                    6 > 1 || 10 == 5   //waar


                    !(3 > 9)  // waar


                    of 3 <= 9
            

De volgorde van evaluatie is steeds van links naar rechts. Als het eindresultaat al vastligt na evaluatie van de eerste uitdrukking, wordt de tweede niet meer geevalueerd.

                    0 && uitdr2  // geeft altijd 0


                    1 || uitdr2  // geeft altijd 1

            

Deze kortsluitmogelijkheid is handig om bepaalde fouten te vermijden.

if ( n != 0 && 12/n == 2)
   printf("n is 5 of 6\n");
            

Hier wordt de deling door n enkel uitgevoerd als n verschillend is van 0.

De prioriteiten van logische operatoren zijn:

! heeft een hogere prioriteit dan && en ||

&& heeft een hogere prioriteit dan || .

De logische operatoren hebben een lagere prioriteit dan relationele operatoren.

Dus i == 1 && j == 2 || i == j is hetzelfde als ((i == 1) && (j == 2)) || (i == j)

Let erop dat de bitoperatoren een lagere prioriteit hebben dan de relationele operatoren. Hierdoor moeten we in de volgende voorwaarde haken gebruiken.

(x & 0x8) == 0
            

Deze voorwaarde test of bit 3 nul is.

5.4. Conditionele uitdrukking ?:

Deze opdracht maakt een keuze uit twee waarden afhankelijk van een voorwaarde.

a = (b < 0) ? -b : b;
            

We kunnen dit ook met een if schrijven.

if (b < 0)
   a = -b;
else
   a = b;
            

De conditionele uitdrukking bestaat uit:

uitdr1 ? uitdr2 : uitdr3

Het resultaat van deze uitdrukking is uitdr2 als uitdr1 waar is, anders is het resultaat uitdr3.

Tenslotte nog een voorbeeld waarbij twee getallen in stijgende volgorde op het scherm geplaatst worden.

printf("%d,%d\n", (a > b) ? b : a, (a > b) ? a : b );
            

5.5. Meerdere keuzemogelijkheden: switch

Wanneer we een keuze uit meerdere mogelijkheden maken, dan is de switch opdracht de beste oplossing.

void main()
{
   char letter;

   printf("geef een letter en ik geef je een vogelnaam\n");
   while ( ( letter=getchar() ) != '#')
   {
      switch (letter)
      {
         case 'a' :
            printf("aalscholver, phalacrocorax carbo\n");
            break;
         case 'b' :
            printf("bontbekplevier, charadrius hiaticula\n");
            break;
         case 'c' :
            printf("citroensijs, serinus citrinella\n");
            break;
         case 'd' :
            printf("duinpieper, anthus campestris\n");
            break;
         case 'e' :
            printf("eidereend, somateria mollissima\n");
            break;
         default :
            printf("vandaag alleen van a to e\n");
            break;
      }
   }
}
            

Dit programma leest een letter in en voert dan een actie uit die bij deze letter hoort. Dit wordt herhaald tot een # ingegeven wordt. De switch opdracht neemt de inhoud van de variabele letter en zoekt dan met welke constante deze waarde overeenkomt. De constanten worden elk na case vermeld. De opdracht die bij de gevonden constante hoort, wordt uitgevoerd. Indien de inhoud van letter niet als constante voorkomt, dan wordt de default opdracht uitgevoerd. In dit programma wordt er dus voor elke ingegeven letter een printf() opdracht uitgevoerd.

Dit is de algemene vorm:

switch ( uitdrukking )
{
   case constante1 :
      opdrachten;
      break;
   case constante2 :
      opdrachten;
      break;
   default :
      opdrachten;
   break;
}
            

De uitdrukking en constanten moeten van type int of char zijn. We kunnen hier dus geen float of double gebruiken. De opdrachten break en default mogen weggelaten worden. Bijvoorbeeld het uitvoeren van dezelfde opdracht voor 2 constanten:

case 'F' :
case 'f' :
   printf("fitis, phylloscopus trochilus\n");
   break;
            

Als default met bijbehorende opdracht en break weggelaten worden, dan wordt geen opdracht uitgevoerd wanneer de geteste waarde niet als constante voorkomt.

Hoofdstuk 6. Lussen en andere controlemiddelen

6.1. while herhalingsopdracht

Met deze herhalingsopdracht hebben we al kennis gemaakt. De algemene vorm is:

while (uitdrukking)
   opdracht;
            
while (uitdrukking)
{
   opdracht1;
   opdracht2;
}
            

De opdrachten worden herhaald zolang de voorwaarde waar is. In de opdrachten moet er steeds een voorkomen die de waarde van de geteste uitdrukking verandert. Indien dit niet zo is stopt de herhaling nooit.

In de volgende voorbeelden wordt i als lusteller gebruikt. In elk voorbeeld wordt i op een andere wijze verhoogd.

  • geen einde:

i = 1;
while (i < 10)
   printf("dit is i: %d\n", i);
            
  • resultaat: 2 - 9

i = 1;
while (++i < 10)
   printf("dit is i: %d\n", i);
            
  • resultaat: 2 - 10

i = 1;
while (i++ < 10)
   printf("dit is i: %d\n", i);
            
  • resultaat: 1 - 9

i = 1;
while (i < 10)
{
   printf("dit is i: %d\n", i);
   i++;
}
            

De structuur van het laatste voorbeeld:

Figuur 6.1. Werking for in flow-chart

Werking for in flow-chart

Deze herhaling bestaat uit de initialisatie van de lusteller, het testen van de eindvoorwaarde en het verhogen van de lusteller.

6.2. for herhalingsopdracht

Het laatste voorbeeld van while is nu met een for herschreven zonder dat de werking verandert. Ook voor dit voorbeeld geldt het stroomdiagramma.

for (i = 1; i < 10; i++)
   printf("dit is i: %d\n", i);
            

Dit zijn nog andere voorbeelden:

  • een lege opdracht in herhaling

for (n = 1; n <= 10000; n++)
   ;
            
  • stap verschillend van 1

for (n = 2; n < 100; n += 11)
   printf("%d\n", n);
            
  • stap verhogen met *

for (bedrag = 100; bedrag < 200; bedrag *= 1.08)
   printf("bedrag: %.2f\n", bedrag);
            
  • char als lusteller

for (t = 'a'; t <= 'z'; t++)
   printf("%c", t);
            
  • een opdracht minder in for

for (u = 1; u < 1000; )
   u *= 2;
            
  • geen opdrachten in for

for ( ; ; )
   printf("hallo\n");
            

De algemene vorm van de for opdracht is:

for ( initialisatie ; test ; aanpassen )
   opdracht

Tussen de haakjes van de for opdracht kunnen we 3 opdrachten onderbrengen. Als we bijvoorbeeld een extra opdracht willen laten uitvoeren bij de initialisatie, dan wordt deze opdracht met een komma bijgevoegd. Dit noemt men in C de komma bewerking.

for (j=1, bedrag = 100; bedrag < 200; j++, bedrag *= 1.08)
   printf("jaar: %d bedrag: %.2f\n", j, bedrag);
            

6.3. do while herhalingsopdracht

Bij deze herhalingsopdracht wordt de voorwaarde getest nadat de opdracht uitgevoerd is. Dit betekent dat de opdracht minstens eenmaal uitgevoerd wordt, ook als de voorwaarde steeds false is.

De algemene vorm is:

do
   opdracht
while ( voorwaarde );

In het volgende voorbeeld worden de tekens van een ingegeven regel omgezet in de decimale ASCII code.

do
{
   scanf("%c", &teken);
   printf("%c heeft als code %d\n", teken, teken);
} while (teken != '\n');
            

6.4. break en continue bij herhalingsopdrachten

Bij complexere problemen is het wenselijk om een herhaling voortijdig af te breken of te herstarten. Hiervoor voorziet C de opdrachten break en continue. Met een break kunnen we op een handige manier de herhaling stopzetten midden in een reeks opdrachten. Dit probleem kan ook opgelost worden zonder gebruik te maken van break. Dit vraagt dan wel iets meer denkwerk. Het is dan ook om deze reden dat niet alle programmeertalen deze mogelijkheid kennen.

6.4.1. break

In het volgende voorbeeld wordt in de herhaling telkens een getal ingelezen en het kwadraat hiervan op het scherm gedrukt. De herhaling gaat verder totdat ofwel het ingegeven getal nul is ofwel het aantal ingelezen getallen groter dan 20 is. Hier heeft de while opdracht een voorwaarde die steeds waar is. Het stopzetten van de herhaling wordt met break uitgevoerd.

i = 0;
while (1 == 1)
{
   printf("geef een getal: ");
   scanf("%d", &getal);
   if (getal == 0)
      break;
   printf("kwadraat van %d is %d\n",
      getal, getal*getal);
   if (++i > 20)
      break;
}
                

Het is mogelijk om dit te herschrijven zonder de break opdracht.

#define FALSE 0
#define TRUE 1
einde = FALSE;
i = 0;
while ( ! einde )
{
   printf("geef een getal: ");
   scanf("%d", &getal);
   if (getal == 0)
      einde = TRUE;
   else
   {
      printf("kwadraat van %d is %d\n",
         getal, getal*getal);
      if (++i > 20)
         einde = TRUE;
   }
}
                

6.4.2. continue

Dit is een opdracht die de uitvoering van de herhalingsopdracht laat herstarten. Anders geformuleerd: de opdrachten na continue worden overgeslagen. In het volgende voorbeeld wordt in de while opdracht de verwerking van spaties overgeslagen.

while( (ch = getchar() ) != EOF)
{
   if (ch == ' ')
      continue;
   putchar( ch );
   teller++;
}
                

Dit voorbeeld kan herschreven worden zonder continue.

while( (ch = getchar() ) != EOF)
{
   if (ch != ' ')
   {
      putchar( ch );
   teller++;
   }
}
                

6.5. goto

De goto opdracht maakt het mogelijk om naar een andere plaats in het programma te springen. Dit is een opdracht die nog stamt uit het FORTRAN tijdperk. Deze opdracht wordt bijna nooit meer gebruikt. De goto is inmiddels overbodig geworden. Met gestructureerd programmeren kan men immers iedere mogelijke constructie opbouwen zonder goto te gebruiken. Programma's met goto zijn dikwijls moeilijk leesbaar en daardoor slecht onderhoudbaar. Gebruik daarom geen goto en beschouw het als onbestaande. Om deze redenen wordt de goto niet verder besproken. Prof. Dijkstra heeft indertijd een opmerkelijk artikel over de goto geschreven.

Hoofdstuk 7. Functions

7.1. Kennismaking

Bij een van de eerste programmavoorbeelden hebben we al kennisgemaakt met functies. Een functie groepeert meerdere opdrachten bij een naam. Deze opdrachten kunnen uitgevoerd worden, als we de functienaam als een gewone opdracht gebruiken.

In het volgende voorbeeld wordt de functie lijn gebruikt om tweemaal een lijn van sterretjes op het scherm te schrijven.

void lijn()
{
   int i;

   for (i=0; i<18; i++)
      printf("*");
   printf("\n");
}

void main()
{
   lijn();
   printf("Dit is de cursus C\n");
   lijn();
}
            

Uit dit voorbeeld blijkt ook dat we variabelen kunnen declareren binnen de functie. De variabele i mag alleen maar gebruikt worden binnen de functie. De declaratie van de variabelen binnen de functie worden vlak na de openingsaccolade vermeld. Deze vorm van lokale variabelen is trouwens niet beperkt tot functies. De syntax is algemeen geldig: na elke openingsaccolade die opdrachten groepeert, mogen we variabelen declareren.

7.2. Parameters

We kunnen de flexibiliteit van een functie verhogen als we bij de oproep een waarde doorgeven. Dit betekent dat we een gedeelte van de werking verschuiven naar de oproep van de functie. In het voorbeeld wordt vastgelegd dat de functie spatie() een waarde van het type int ontvangt bij de oproep. De waarde komt terecht in de variabele aantal en wordt door de functie gebruikt om het aantal spaties te bepalen. Bij de oproep wordt de door te geven waarde tussen de functiehaken geplaatst.

void spatie(int aantal)
{
int i;

   for (i=0; i < aantal; i++)
      printf(" ");
}

void main()
{
   printf("Dit is de cursus C\n");
   spatie(16);
   printf("+++\n");
}
            

In verband met parameters kent men de volgende terminologie:

formele parameter

de variabele die de doorgegeven waarde ontvangt

actuele parameter

de werkelijke waarde die doorgegeven wordt.

We kunnen een functie met meerdere parameters voorzien. De formele en actuele parameters worden gescheiden door komma's.

tlijn(char t, int n)
{
   int i;

   for (i=0; i < n; i++)
      printf("%c", t);
}
            

De functie tlijn() kan zo opgeroepen worden:

tlijn('+',20);
tlijn('=',45);
            

7.3. Return en functietype

Als we een resultaat van een functie willen bekomen, dan wordt dit doorgegeven met de return opdracht. We moeten dan wel aangeven wat voor soort waarde met de return doorgegeven wordt. Daarom plaatsen we een type voor de functienaam. Dus niet alleen variabelen en constanten zijn van een bepaald type, ook functies worden met een type verbonden. Als we de functieoproep in een uitdrukking plaatsen, dan wordt de oproep vervangen door het resultaat van de functie.

int eigen_abs(int a)/* int : functietype */
{
   if (a < 0)
      return( -a );
   else
      return( a );
}

void main()
{
   int x,y,z;

   printf("geef 2 getallen:");
   scanf("%d %d",&x,&y);
   z = eigen_abs(x) + eigen_abs(y);
   printf("%d\n", z);
}
            

Indien we het functietype weglaten, dan wordt int verondersteld. Deze functie moet dan een int waarde teruggeven. Als we helemaal geen resultaat willen teruggeven, dan moet dit expliciet aangegeven worden met het woord void (leeg). Hetzelfde kunnen we doen als een functie geen parameters ontvangt. We plaatsen dan niets tussen de functiehaken. Een functie die geen parameters ontvangt en geen resultaat geeft schrijven we zo:

void main()
{

}
            

We geven nu nog een voorbeeld met een ander functietype.

float gemiddelde(float a, float b, float c)
{
   return( (a + b + c)/3 );
}
            

We moeten hier toch nog zeggen dat het niet mogelijk is om een functie een doorgegeven variabele te laten wijzigen.

void verhoog(int a)
{
   a++;
}

void main()
{
   int b = 1;

   verhoog(b);
}
            

Omdat de functie verhoog() met een copie van b werkt, wordt alleen a verhoogd. De variabele b blijft hier ongewijzigd. Men spreekt in dit geval van waardeparameter.

7.4. & operator

De & operator bij een variabelenaam geeft het adres van die variabele. We kunnen nagaan waar een variabele zich in het geheugen bevindt.

v = 12;
printf("het getal %d staat in adres %u\n",
                    v, &v);
            

Resultaat:

het getal 12 staat in adres 65502
            

Met het volgende voorbeeld zien we dat twee variabelen met dezelfde naam een verschillend adres hebben. Het zijn dus verschillende variabelen.

void fu()
{
   int a = 7;

   printf("fu: a = %d &a = %u\n", a, &a);
}

void main()
{
   int a = 5;

   printf("main: a = %d &a = %u\n", a, &a);
   fu();
}
            

Resultaat:

main: a = 5 &a = 65502
fu: a = 7 &a = 65496
            

7.5. Pointers en adresparameters

De volgende functie is bedoeld om de inhoud van twee variabelen te verwisselen. Deze versie is niet correct omdat alleen de copies van de doorgegeven variabelen verwisseld worden en niet de originelen.

void verwissel(int u, int v)
{
   int   help;

   help = u;
   u = v;
   v = help;
}

void main()
{
   int   x = 3, y = 4;

   printf("x: %d, y %d\n", x, y);
   verwissel(x,y);
   printf("x: %d, y %d\n", x, y);
}
            

De variabelen x en y blijven dus ongewijzigd. We kunnen hier ook geen return gebruiken omdat deze slechts 1 waarde teruggeeft. De oplossing is als volgt: we geven als actuele parameters niet de inhoud van x en y door, maar wel de adressen van x en y. Dit kunnen we doen met de adres operator. Dit betekent dan wel dat we als formele parameters in de functie verwissel() variabelen moeten voorzien, die in staat zijn om adressen op te slaan. Deze soort variabelen noemt men pointers.

Vooraleer we pointers uitleggen, verklaren we eerst de declaratie van een gewone variabele. Bij de declaratie

int   getal = 123;
            

is getal van het type int en is &getal het adres van deze variabele.

Figuur 7.1. Een variabele in het geheugen

Een variabele in het geheugen

De inhoud van getal is 123 en het adres van getal is 1000. De uitdrukking &getal is van het pointertype en stelt een constante voor. We kunnen deze constante toekennen aan een pointervariabele:

ptr = &getal;
            

Dit wil zeggen dat ptr moet gedeclareerd worden als een pointervariabele.

int *ptr;
            

Dit wordt zo gelezen: ptr is een pointer naar een int. De operator * betekent hier pointer. De variabele ptr kan als volgt gebruikt worden:

ptr = &getal;
a = *ptr;
            

De eerste opdracht plaatst het adres van getal in ptr. De tweede opdracht neemt de inhoud van de int variabele die aangewezen wordt door ptr en plaatst deze waarde in a. De variabele a krijgt dus de waarde van getal. De * operator is hier de operator voor indirecte verwijzing.

De situatie van deze variabelen kan zo weergegeven worden:

Figuur 7.2. Een pointer in het geheugen

Een pointer in het geheugen

Bij de declaratie wordt vastgelegd dat ptr een pointer naar int is. We kunnen dus wel het adres van een int variabele in ptr plaatsen maar niet het adres van een char variabele.

De functie verwissel() is nu herschreven met pointers als formele parameter:

void verwissel(int *u, int *v)
{
   int   help;

   help = *u;
   *u = *v;
   *v = help;
}

void main(void)
{
   int   x = 3, y = 4;

   verwissel(&x,&y);
   printf("x: %d, y %d\n", x, y);
}
            

Wanneer verwissel() opgeroepen wordt, krijgt de variabele u als inhoud het adres van x en v het adres van y. De inhoud van deze twee aangewezen variabelen wordt dan verwisseld.

We kunnen parameters als volgt samenvatten. Als we informatie doorgeven, kunnen we de inhoud van die variabele doorgeven:

// waarde:
int x;
fun1( x );
            

Ofwel kunnen we het adres van die variabele doorgeven:

// adres:
intx;
fun2( &x );
            

Hoofdstuk 8. Geheugenklassen

Elke variabele in een C programma behoort tot een geheugenklasse. Deze klasse bepaalt de levensduur en de bereikbaarheid van de variabele. Voor elke variabele kiezen we een gepaste klasse.

De klasse waartoe een variabele behoort, kunnen we bepalen met een sleutelwoord bij de declaratie. De volgende sleutelwoorden worden hier besproken: auto, extern, static en register. Een van deze woorden kan voor het type geplaatst worden bij een declaratie.

geheugenklasse + type + variabelenaam

8.1. Automatische variabelen

Dit zijn alle variabelen binnen een functie. We kunnen deze variabelen ook aanduiden met de term lokale variabelen. De ruimte voor deze varabelen en ook voor de formele parameters wordt gereserveerd op de stack. Vermits de stack een beperkte geheugenruimte omvat, moeten we de hoeveelheid lokale variabelen beperken.

void fu(void)
{
   int klad;

   klad = 1;
}
            

Deze variabelen bestaan alleen tijdens de uitvoering van de functie. Dit betekent dat er bij de start van de functie geheugen wordt gereserveerd voor de automatische variabelen. Dit geheugen wordt terug vrijgegeven bij het verlaten van de functie. We zouden het woord auto kunnen gebruiken, maar dit wordt altijd weggelaten. Variabelen binnen een functie gedeclareerd zonder een geheugenklasse zijn altijd automatisch of lokaal.

Het is duidelijk dat we geen lokale variabele kunnen gebruiken voor gegevens op te slaan die tijdens de hele uitvoering van het programma moeten blijven bestaan.

8.2. Externe variabelen

De term extern wordt bij gebruikt voor de globale variabelen. Hiermee bedoelen we de variabelen die buiten de functies gedeclareerd worden.

Het woord extern kan bij een declaratie buiten een functie voorkomen. We hebben hier te maken met een verwijzing en geen geheugenreservatie.

extern int waarde;   /* geen geheugen allocatie */

void fu(void)
{
   waarde = 3;
}
            

Hier wordt aangegeven dat de variabele waarde in een andere file gedeclareerd is. In C kunnen we met meerdere programmabestanden werken die gemeenschappelijke variabelen hebben.

8.3. Statische variabele

Hiermee bedoelen we variabelen die altijd bestaan. De externe variabelen zijn statisch omdat ze altijd bestaan tijdens de levensduur van het programma.

8.3.1. Gebruik static binnen functie

We geven een voorbeeld.

void probeer(void)
{
   int  tijdelijk = 1;
   static int altijd = 1;

   printf("tijdelijk %d , altijd %d\n",
   tijdelijk++, altijd++);
}
void main(void)
{
   int i;

   for (i=1; i < 10; i++)
   {
      printf("%d :", i);
      probeer();
   }
}
                

De functie probeer() heeft twee variabelen tijdelijk en altijd. De variabele tijdelijk is automatisch, ze bestaat enkel tijdens de uitvoering van probeer(). De variabele altijd is statisch en bestaat tijdens de hele uitvoering van het programma. De variabele tijdelijk krijgt de initialisatiewaarde bij elke oproep van probeer(). De variabele altijd wordt slechts eenmaal geinitialiseerd, namelijk bij de start van het programma.

Het woord statisch maakt van een tijdelijke variabele een variabele die altijd bestaat. Dit kan soms handig zijn, maar het kan ook ongewenste zijeffecten leveren.

8.3.2. Gebruik static buiten functie

Hiermee creeren we een externe variabele die enkel bekend is binnen de file. Het volgende voorbeeld maakt dit duidelijk.

bestand 1

#include <header.h>

int   a;
static int b;

static void fu1(void)
{
   fu2();
}

void main(void)
{
   fu1();
   fu3();
}

bestand 2

#include <header.h>

void fu3(void)
{
   printf("%d\n",a);
}

void fu2(void)
{
   fu3();
}

Met behulp van de #include aanwijzing wordt het bestand header.h ingelast in bestand 1 en bestand 2. Deze bevat de volgende tekst:

void fu2(void);
void fu3(void);

extern int a;
                

Dit zijn aanwijzingen hoe de functies fu2(), fu3() en de variabele a gebruikt moeten worden. De notatie voor de functies noemt men een functieprototype. Hierdoor is het mogelijk dat de compiler een foutmelding geeft als een functie uit een andere file, verkeerd opgeroepen wordt. De prototypes worden ook ingelast in het bestand waar de functies vastgelegd worden. Hierdoor wordt gegarandeerd dat de prototypes precies overeenstemmen met de functies zelf. De twee bestanden worden afzonderlijk gecompileerd en daarna samengevoegd in de linkfaze.

In het voorbeeld is de variabele a is bekend in main(), fu1(), fu2() en fu3(). De variabele b is alleen bekend in main() en fu1().

Tot slot geven we nog een overzicht dat al de geheugenklassen weergeeft.

 

soort klasse

woord

levensduur

bereik

binnen functie

automatisch

register

statisch

auto

register

static

tijdelijk

tijdelijk

altijd

lokaal

lokaal

lokaal

buiten functie

extern

extern static

extern

static

altijd

altijd

in alle bestanden

in 1 bestand

Hoofdstuk 9. Arrays en pointers

Arrays zijn variabelen die meerdere waarden van een zelfde soort kunnen opslaan. Pointers zijn verwijzingen naar andere variabelen. We behandelen eerst arrays en daarna het verband met pointers.

9.1. Array voorbeelden

int getal[10];
float r[100];
char t[20];
            

Elk van deze variabelen is een array. De array getallen bevat 10 elementen:

getal[0], getal[1], ... , getal[9]
            

De index die gebruikt wordt om de elementen te bereiken, start bij 0 en loopt tot het aantal elementen - 1. We kunnen dus niet zelf een bereik voor de index kiezen zoals in Pascal. Het volgende voorbeeld toont hoe arrays gebruikt kunnen worden.

#define DIM 10
void main()
{
   int som, i, getallen[DIM];

   for (i=0; i<DIM; i++)
      scanf("%d",&getallen[i]);
   printf("dit zijn de getallen\n");
   for (i=0; i<DIM; i++)
      printf("%5d",getallen[i]);
   printf("\n");
   for (i=0, som=0; i<DIM; i++)
      som += getallen[i];
   printf("het gemiddelde is %d\n",som/DIM);
}
            

9.2. Initialisatie van arrays

Net zoals enkelvoudige variabelen kunnen ook arrays geinitialiseerd worden. Dit kan alleen bij externe en statische arrays.

/* dagen per maand */
int dagen[12] = {31,28,31,30,31,30,31,31,30,31,30,31};
void main()
{
   int i;

   for (i=0; i<12; i++)
      printf("%d dagen in maand %d\n",dagen[i],i+1);
}
            

De waarden waarmee de array gevuld wordt, worden tussen accolades geplaatst. Indien er te weinig waarden zijn, dan worden de laatste elementen van de array met 0 gevuld. In de extern verwijzing binnen main() mag de afmeting van de array weggelaten worden.

Hier is een andere versie:

/* dagen per maand */
int dagen[] = {31,28,31,30,31,30,31,31,30,31,30,31};
void main(void)
{
   int i;

   for (i=0; i<sizeof(dagen)/sizeof(int); i++)
      printf("%d dagen in maand %d\n",dagen[i],i+1);
}
            

In deze versie is de lengte van de array weggelaten. De lengte wordt nu bepaald door het aantal getallen tussen accolades. De lengte mag alleen maar weggelaten worden als de array geinitialiseerd wordt.

9.3. Verband tussen pointers en arrays

De arraynaam is een pointer naar eerste element. Dit verband verduidelijken we met een voorbeeld.

int rij[20];
            

Bij deze array is rij[0] het eerste element. Het adres hiervan is &rij[0]. Dit kan ook korter geschreven worden: rij en &rij[0] zijn hetzelfde. Ze duiden allebei het adres van de array aan. Het zijn allebei pointerconstanten.

In het volgende voorbeeld wordt er met pointers gerekend.

void main()
{
   int getallen[4], *pget, i;
   char tekens[4], *ptek;

   pget = getallen;
   ptek = tekens;
   for (i=0; i<4; i++)
      printf("pointers + %d: %u %u\n",
         i, pget + i, ptek + i);
}
            

De eerste toekenning plaatst het adres van de array getallen in de pointervariabele pget. De tweede toekenning doet een gelijkaardige bewerking. In de printf() opdracht wordt de lusteller i opgeteld bij de inhoud van de pointers. Dit resultaat komt op het scherm:

pointers + 065486 65498
pointers + 165488 65499
pointers + 265490 65500
pointers + 365492 65501
            

De eerste regel geeft de adressen van de eerste elementen van de arrays. De volgende regel geeft de adressen van de tweede elementen enzovoort. We zien dus het volgende: als we de inhoud pointer verhogen met 1, dan wordt het adres, dat in de pointer variabele wordt opgeslagen, verhoogd met de breedte van het aangeduide element. De pointer pget wijst naar int, int is 2 bytes breed dus wordt er 2 opgeteld bij de inhoud van pget. Dezelfde regel kunnen we toepassen voor de pointer ptek. Die wordt verhoogd met 1 ( breedte char ).

De array getal kan zo voorgesteld worden:

Figuur 9.1. Layout van een array in het geheugen

Layout van een array in het geheugen

getal + 2 en &getal[2] stellen beide hetzelfde adres voor.

*(getal + 2) en getal[2] stellen beide dezelfde waarde voor.

Opgelet: *getal + 2 is de waarde van het eerste element verhoogd met 2. Deze uitdrukking is dus niet hetzelfde als *(getal + 2). De haken zijn nodig omdat * een hogere prioriteit heeft dan +.

Hetzelfde probleem ontstaat bij de interpretatie van *p++ . Is dit (*p)++ of *(p++) ? Het antwoord is de tweede uitdrukking omdat * en ++ dezelfde prioriteit hebben en unaire operatoren van rechts naar links groeperen.

9.4. Arrays als functieparameters

Als formele parameter kunnen we arrays gebruiken. De afmeting van de array mag weggelaten worden.

void druk(int rij[])
{

}

void main()
{
   int reeks[50];

   druk(reeks);
}
            

Bij formele parameters is int rij[] een pointer variabele, geen array variabele. We geven hier niet de inhoud van de array door, maar wel het adres. Dus int rij[] en int *rij zijn hetzelfde als formele parameter.

We kunnen de lengte van de array doorgeven:

druk(int rij[], int lengte)
{
   int i;

   for (i=0; i<lengte; i++)
      printf("%d\n", rij[i]);
}
            

Deze versie doet identiek hetzelfde, alleen de toegang tot de array is gewijzigd:

druk(int rij[], int lengte)
{
   int i;

   for (i=0; i<lengte; i++)
      printf("%d\n", *(rij + i) );
}
            

En tot slot de snelste versie:

druk(int rij[], int lengte)
{
   register int*p, *peinde;

   p = rij;/* bew 1 */
   peinde = &rij[lengte];
   while (peinde - p >0)/* bew 5 */
   {
      printf("%d\n", *p );/* bew 2 */
      p++;/* bew 4 */
   }
}
            

In deze laatste versie wordt de pointervariabele p gebruikt om de elementen van de array te bereiken. Met de ++ operator wijst p telkens naar het volgende element in de array. De herhaling stopt als p het eerste adres aanwijst dat niet tot de array behoort.

Hier is een samenvatting van de pointerbewerkingen:

  • 1. toekenning

    Een adres wordt toegekend aan een pointervariabele.

  • 2. waarde ophalen

    De * bewerking vindt de waarde die door de pointer wordt aangeduid.

  • 3. een pointeradres nemen

    De pointer int *p bevindt zich op adres &p. Dit kan dienen als actuele parameter voor een functie die de doorgegeven pointer wijzigt.

  • 4. een pointer verhogen

    Na deze bewerking wijst de pointer naar het volgende element.

  • 5. het verschil tussen 2 pointers

    Dit geeft het aantal elementen dat zich tussen de 2 aangeduide posities bevindt.

9.5. Arrays met meerdere dimensies

Bij de declaratie plaatsen we meerdere indexen na de arraynaam. Elke index staat apart tussen de rechte haken.

double matrix[3][4];
            

In het volgende voorbeeld zien we de initialisatie en het gebruik van een meerdimensionele array.

#include <stdio.h>
void main(void)
{
   static double matrix[3][4] =
   {
      { 2,5,9,7 },
      { 8,1,3,4 },
      { 10,5,45,23 }
   };
   int i,j;

   for (i=0; i<3; i++)
   {
      for (j=0; j<4; j++)
         printf("%5.2f ", matrix[i][j] );
      printf("\n");
   }
}
            

De variabele matrix kunnen we voorstellen als een matrix die bestaat uit 3 rijen met elk 4 elementen. De variabele wordt rij per rij geinitialiseerd (alleen statische en externe arrays kunnen geinitialiseerd worden). De getallen 2, 5, 9 en 7 komen terecht in de eerste rij. We kunnen ze terugvinden in de elementen matrix[0][0], matrix[0][1], matrix[0][2] en matrix[0][3]. Op dezelfde wijze worden de twee andere rijen gevuld. Het is ook mogelijk om de binnenste paren accolades, die telkens een rij getallen afsluiten, weg te laten. Dit is identiek in werking maar is minder overzichtelijk.

9.6. Pointers naar functies

Zoals reeds vermeld moet bij de declaratie van een pointer aangegeven worden naar welk type deze pointer wijst. We kunnen in de taal C ook een functietype gebruiken als het aangewezen type. We geven een voorbeeld:

int (*pf)(int a,int b);
            

De variabele pf is een pointer die wijst naar een functie die 2 int verwacht en een int als resultaat. Deze variabele krijgt met een toekenning een waarde.

int fu(int a,int b)
{
   return( a + b);
}

pf = fu;
            

De pointer pf krijgt als waarde het adres van de functie fu. We kunnen de aangewezen functie oproepen via de pointervariabele.

c = (*pf)(1,2);
            

Hoofdstuk 10. Tekenstrings en stringfunctions

10.1. Strings definieren

Een string is een opeenvolging van char constanten, waarbij het einde aangeduid wordt door 0. We kunnen een stringconstante samenstellen met 2 aanhalingstekens:

"dit is een string"
            

Deze constante heeft een dubbele functie: ze zorgt voor opslag van de tekens in het geheugen en ze fungeert als constante van het type pointer naar char. In het volgende voorbeeld wordt het adres van een stringconstante opgeslagen in een pointervariabele.

char *pstr;

pstr = "dit is een string";
printf("%s",pstr);
            

Via initialisatie wordt een stringconstante opgeslagen in een char arrayvariabele. Tussen de rechte haken hoeft geen afmeting vermeld te worden.

char str1[] = {
                    'a','b','c','d','e','\0' };
            

ofwel

char str1[] = "abcde";
            

De lengte van array is 6: 5 tekens + 1 nul. Als we de naam str1 gebruiken, dan is dit een pointer naar het eerste element. Zo kunnen we enkele gelijke uitdrukkingen opstellen:

str1 en &str1[0]

*str1 en 'a'

*(str1+2) en str[2] en 'c'

Er is een verschil tussen de array en de pointer declaratie, maar wel zijn het allebei geinitialiseerde variabelen:

char *ptekst = "een";
char atekst[]= "twee";
            

De variabele ptekst is een pointer die geinitialiseerd wordt met het adres van de string "een". Deze string bevindt zich elders in het geheugen. De variabele atekst is een array die geinitialiseerd wordt met de string "twee". Dit betekent dat atekst plaats heeft voor 5 tekens. We kunnen de geheugenverdeling zo voorstellen:

Figuur 10.1. Strings in het geheugen

Strings in het geheugen

We hebben de volgende overeenkomsten:

&ptekst--->30
ptekst120
*ptekst'e'
ptekst[0]'e'
atekst34
*atekst't'
atekst[0]'t'
            

ptekst is een pointervariabele en kan dus gewijzigd worden; atekst niet:

while ( *ptekst != 0)
   putchar ( *ptekst++ );
            

Deze herhaling drukt alle tekens van de string op het scherm.

atekst is een pointerconstante die wijst naar het eerste element van de array. atekst kan niet gewijzigd worden.

                    atekst++;FOUT

            

De inhoud van de array kan wel gewijzigd worden:

atekst[0] = 'p';
            

10.2. Arrays van tekenstrings

We declareren de volgende variabele:

char *kleuren[3] ={ "wit",
                    "zwart", "azuurblauw" };
            

De variabele kleuren is een array van pointers die wijzen naar char elementen. De pointers zijn elk geinitialiseerd met het adres van een string. De uitdrukkingen kleuren[0], kleuren[1], en kleuren[2] zijn de 3 pointers. Als we er een * bijplaatsen krijgen we: *kleuren[0] is de eerste letter van de eerste string. In kleuren worden alleen adressen opgeslagen; de strings zelf worden elders in het geheugen opgeslagen.

Figuur 10.2. Array van pointers naar string

Array van pointers naar string

Deze variabele kan ook anders gedeclareerd worden. Het is nu een array met 2 dimensies. De strings worden in de array zelf opgeslagen. Voor de string "wit" betekent dit dat slechts een deel van de rij gebruikt wordt. Een deel van de array blijft dus onbenut.

char kleuren[3][11] ={ "wit",
                    "zwart", "azuurblauw" };

            

Figuur 10.3. Arrays van strings

Arrays van strings

10.3. Stringin- en uitgave

We creeren eerst plaats voor de in te lezen string.

char *naam;

scanf("%s", naam);
            

Deze declaratie van naam levert een crash op; naam is een pointervariabele, die geen waarde gekregen heeft. scanf() gebruikt de inhoud van naam als adres om de ingelezen tekens op te slaan. Het resultaat is dus onvoorspelbaar. Een betere declaratie is dit:

char naam[81];
            

Stringin- en uitgave doen we met de gets() en puts() functies.

void main()
{
   char naam[20][81]; /* plaats voor 20 namen */
   int   i,n;

   n = 0;
   while (gets(naam[n]) != NULL)
      n++;
   for (i=0; i<n; i++)
      puts(naam[i]);
}
            

Het programma leest een aantal strings in met gets() en geeft daarna deze strings weer op het scherm. De functie gets() levert als resultaat het adres van de ingelezen string. Als de voorwaarde EOF (dit is end of file) voorkwam tijdens de ingave, is het resultaat 0. Deze eigenschap wordt in het programma gebruikt om de herhaling van de ingave stop te zetten. Let op de notatie naam[n], dit is hetzelfde als &naam[n][0].

Er zijn een aantal verschillen ten opzichte van printf("%s") en scanf("%s"). gets() leest alle ingegeven tekens in tot de return; de return zelf wordt niet opgenomen in de string. scanf("%s") start de string na de eerste whitespace (tab, newline of spatie) en stopt voor de volgende whitespace. Dit kan gebruikt worden om woorden uit een regel in te lezen. puts() doet altijd een newline op het einde van de string, printf() alleen als \n vermeld wordt.

10.4. Enkele stringfuncties

We bespreken enkele van de belangrijkste stringfuncties.

10.4.1. strlen()

Deze functie berekent de lengte van een string in bytes.

void pas(char *string, int lengte)
{
   if (strlen(string)>lengte)
      *(string + lengte) = '\0';
}
                

De functie pas() kort een string in tot een gegeven lengte. Dit wordt gedaan door een 0 in de string bij te plaatsen.

10.4.2. strcat()

Deze functie voegt 2 strings samen.

void main()
{
   char naam[80];

   gets(naam);
   strcat(naam," is een mooie naam\n");
   puts(naam);
}

                

De functie strcat() ontvangt 2 char pointers. De string aangeduid door de eerste pointer wordt uitgebreid met de string aangeduid door de tweede pointer. De eerste string moet voldoende plaats hebben, anders worden andere variabelen overschreven.

10.4.3. strcmp()

Deze functie vergelijkt 2 strings. Als de strings identiek zijn, is het resultaat 0, anders is het resultaat verschillend van 0.

void main()
{
   char antw[40];

   puts("waar woonde Julia ?");
   gets(antw);
   if (strcmp(antw, "Verona") == 0)
      puts("goed");
   else
      puts("fout");
}
                

10.4.4. strcpy()

Deze functie copieert een string.

char copie[40],*p;
p = "origineel";
strcpy(copie,p);
                

In dit voorbeeld worden de letters van de string een voor een gecopieerd naar de array copie.

10.5. Argumenten op de opdrachtregel

De argumenten die bij de programmastart worden doorgegeven, zijn bereikbaar vanuit het programma. Hiervoor wordt main() voorzien met twee formele parameters.

void main(int argc,char *argv[])
{
   int i;

   for (i=0; i<argc; i++)
      printf("%s ",argv[i]);
   printf("\n");
}
            

De eerste parameter argc geeft aan hoeveel argumenten er bij de programmastart meegegeven zijn. In dit aantal is de programmanaam meegerekend. De tweede parameter argv is een tabel van pointers naar char. Elke pointer wijst naar het eerste teken van een argumentstring. Deze strings zijn afgesloten met een 0. argv[0] wijst naar de programmanaam, argv[1] is het eerste argument, enzoverder. Het gebruik van een pointertabel laat een variabel aantal argumenten toe.

10.6. Strings sorteren

Tot slot is hier nog een programmavoorbeeld, dat strings sorteert.

#include <stdio.h>
#include <string.h>
#define SLEN 81
#define DIM 20
#define STOP""

void strsort(char *strings[], int num)
{
   char *temp;
   int klein, zoek;

   for (klein=0; klein<num-1; klein++)
      for (zoek=klein+1; zoek<num; zoek++)
         if ( strcmp(strings[klein],strings[zoek]) >0)
         {
            temp = strings[klein];
            strings[klein] = strings[zoek];
            strings[zoek] = temp;
         }
}

void main()
{
   static char ingave[DIM][SLEN]; /* array voor ingave */
   char *pstr[DIM];/* pointer tabel */
   int tel = 0;
   int k;

   printf("geef strings in\n");
   printf("eindig met een lege string\n");
   while( tel<DIM && gets(ingave[tel]) != NULL && strcmp(ingave[tel],STOP) != 0)
   {
      pstr[tel] = ingave[tel];
      tel++;
   }

   /* sorteer met pointers */
   strsort(pstr, tel);

   puts("\ndit is gesorteerd\n");
   for (k=0; k<tel; k++)
      puts(pstr[k]);
}
            

Dit programma leest eerst een aantal strings in. De strings komen in de tweedimensionele array ingave terecht. De herhaling van de ingave stopt als er geen plaats meer is voor strings of als EOF optreedt of als er een lege string ingegeven wordt. Tijdens de ingave wordt de pointertabel pstr gevuld met het adres van elke string.

Met deze tabel pstr wordt het sorteren uitgevoerd. In plaats van strings te copieren (veel tekens copieren) worden er pointers gecopieerd. De pointertabel pstr wordt samen met het aantal strings doorgegeven aan de functie strsort(). Deze functie start bij de eerste string en gaat na of er verder nog strings zijn die kleiner zijn. Kleiner betekent hier: komt eerst in de alfabetische rangschikking. Hier wordt gebruik gemaakt van de eigenschap dat strcmp() iets zegt over de alfabetische volgorde als de 2 strings verschillend zijn. De mogelijke resultaten zijn:

strcmp("a", "a")    // geeft 0
strcmp("b", "a")    //       1 (positief)
strcmp("a", "b")    //      -1 (negatief)
            

Indien een kleinere string gevonden wordt, dan worden de pointers die wijzen naar de eerste en de gevonden string verwisseld. Hetzelfde wordt herhaald voor de tweede tot en met de voorlaatste string.

10.7. Overzicht string functies

De prototypes van de functies voor stringmanipulatie zijn terug te vinden in de headerfile string.h.

10.7.1. strcpy

Kopieert string src naar dest.

Prototype:

char *strcpy(char *dest, const char *src);
                

Geeft dest terug.

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

int main()
{
   char string[10];
   char *str1 = "abcdefghi";

   strcpy(string, str1);
   printf("%s\n", string);
   return 0;
}
                

10.7.2. strncpy

Kopieert maximum maxlen tekens van src naar dest.

Prototype:

char *strncpy(char *dest, const char *src, size_t maxlen);
                

Indien maxlen tekens gekopieerd worden, wordt geen nul teken achteraan bijgevoegd; de inhoud van dest is niet met een nul beeindigd.

Geeft dest terug.

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

int main()
{
   char string[10];
   char *str1 = "abcdefghi";

   strncpy(string, str1, 3);
   string[3] = '\0';
   printf("%s\n", string);
   return 0;
}
                

10.7.3. strcat

Voegt src bij dest.

Prototype:

char *strcat(char *dest, const char *src);
                

Geeft dest terug.

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

int main()
{
   char destination[25];
   char *blank = " ", *c =
                        "C++", *Borland = "Borland";

   strcpy(destination, Borland);
   strcat(destination, blank);
   strcat(destination, c);

   printf("%s\n", destination);
   return 0;
}
                

10.7.4. strncat

Voegt maximum maxlen tekens van src bij dest

Prototype:

char *strncat(char *dest, const char *src, size_t maxlen);
                

Geeft dest terug.

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

int main()
{
   char destination[25];
   char *source = " States";

   strcpy(destination, "United");
   strncat(destination, source, 7);
   printf("%s\n", destination);
   return 0;
}
                

10.7.5. strcmp

Vergelijkt een string met een andere

Prototype:

int strcmp(const char *s1, const char *s2);
                

Geeft een waarde terug:

< 0 indien s1 kleiner dan s2

== 0 indien s1 gelijk is aan s2

> 0 indien s1 groter is dan s2

Voert een vergelijking met teken uit.

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

int main()
{
   char *buf1 = "aaa", *buf2 =
                        "bbb", *buf3 = "ccc";
   int ptr;

   ptr = strcmp(buf2, buf1);
   if (ptr > 0)
      printf("buffer 2 is greater than
                        buffer 1\n");    // ja
   else
      printf("buffer 2 is less than buffer 1\n");

   ptr = strcmp(buf2, buf3);
   if (ptr > 0)
      printf("buffer 2 is greater than
                        buffer 3\n");
   else
      printf("buffer 2 is less than buffer
                        3\n");       // ja

   return 0;
}
                

10.7.6. strncmp

Vergelijkt maximum maxlen tekens van de ene string met de andere.

Prototype:

int strncmp(const char *s1, const char *s2,
                        size_t maxlen);
                

Geeft een waarde terug:

< 0 indien s1 kleiner dan s2

== 0 indien s1 gelijk is aan s2

> 0 indien s1 groter is dan s2

Voert een vergelijking met teken (signed char) uit.

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

int main()
{
   char *buf1 = "aaabbb", *buf2 =
                        "bbbccc", *buf3 = "ccc";
   int ptr;

   ptr = strncmp(buf2,buf1,3);
   if (ptr > 0)
      printf("buffer 2 is greater than
                        buffer 1\n");    // ja
   else
      printf("buffer 2 is less than buffer 1\n");

   ptr = strncmp(buf2,buf3,3);
   if (ptr > 0)
      printf("buffer 2 is greater than
                        buffer 3\n");
   else
      printf("buffer 2 is less than buffer
                        3\n");       // ja

   return(0);
}
                

10.7.7. strchr

Zoekt een teken c in s

Prototype:

char *strchr(const char *s, int c);
                

Geeft een pointer terug naar de eerste plaats waar het teken c in s voorkomt; indien c niet voorkomt in s, geeft strchr NULL terug.

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

int main()
{
   char string[15];
   char *ptr, c = 'r';

   strcpy(string, "This is a string");
   ptr = strchr(string, c);
   if (ptr)
      printf("The character %c is at
                        position: %d\n", c,
                   ptr-string); // 12
   else
      printf("The character was not found\n");
   return 0;
}
                

10.7.8. strrchr

Zoekt de laatste plaats waar c in s voorkomt.

Prototype:

char *strrchr(const char *s, int c);
                

Geeft een pointer terug naar de laatste plaats waar c voorkomt, of NULL indien c niet voorkomt in s.

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

int main()
{
   char string[15];
   char *ptr, c = 'i';

   strcpy(string, "This is a string");
   ptr = strrchr(string, c);
   if (ptr)
      printf("The character %c is at
                        position: %d\n", c,
                   ptr-string); // 13
   else
      printf("The character was not found\n");
   return 0;
}
                

10.7.9. strspn

Doorzoekt een string naar een segment dat is een subset van een reeks tekens.

Prototype:

size_t strspn(const char *s1, const char *s2);
                

Geeft de lengte van het initiele segment van s1 dat volledig bestaat uit tekens uit s2.

#include <stdio.h>
#include <string.h>
#include <alloc.h>

int main()
{
   char *string1 = "1234567890";
   char *string2 = "123DC8";
   int length;

   length = strspn(string1, string2);
   printf("strings different at position
                        %d\n",length); // 3
   return 0;
}
                

10.7.10. strcspn

Doorzoekt een string

Prototype:

size_t strcspn(const char *s1, const char *s2);
                

Geeft de lengte van het initiele segment van s1 terug dat volledig bestaat uit tekens niet in s2.

#include <stdio.h>
#include <string.h>
#include <alloc.h>

int main()
{
   char *string1 = "1234567890";
   char *string2 = "747DC8";
   int length;

   length = strcspn(string1, string2);
   printf("strings intersect at position
                        %d\n", length); // 3

   return 0;
}
                

10.7.11. strpbrk

Doorzoekt een string voor de eerste plaats waar een willekeurig teken uit de tweede string voorkomt.

Prototype:

char *strpbrk(const char *s1, const char *s2);
                

Geeft een pointer terug naar de eerste plaats waar een van de tekens uit s2 in s1 voorkomt. Indien geen van de s2 tekens in s1 voorkomen, wordt NULL teruggegeven.

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

int main()
{
   char *string1 = "abcdefghijklmnopqrstuvwxyz";
   char *string2 = "onm";
   char *ptr;

   ptr = strpbrk(string1, string2);

   if (ptr)
      printf("found first character:
                        %c\n",*ptr);// 'm'
   else
      printf("strpbrk didn't find
                        character in set\n");

   return 0;
}
                

10.7.12. strstr

Zoekt de eerste plaats waar een substring in een andere string voorkomt.

Prototype:

char *strstr(const char *s1, const char *s2);
                

Geeft een pointer terug naar het element in s1 dat s2 bevat (wijst naar s2 in s1), of NULL indien s2 niet voorkomt in s1.

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

int main()
{
   char *str1 = "Borland International";
       char *str2 = "nation", *ptr;

   ptr = strstr(str1, str2);
   printf("The substring is: %s\n",
                        ptr); // "national"
   return 0;
}
                

10.7.13. strlen

Berekent de lengte van een string

Prototype:

size_t strlen(const char *s);
                

Geeft het aantal tekens in s terug, de eindnul wordt niet meegeteld.

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

int main()
{
   char *string = "Borland International";

   printf("%d\n", strlen(string)); // 21
   return 0;
}
                

10.7.14. strtok

Zoekt in s1 naar het eerste teken dat niet voorkomt in in s2

Prototype:

char *strtok(char *s1, const char *s2);
                

s2 definieert scheidingstekens. strtok interpreteert de string s1 als een reeks tokens gescheiden door een reeks tekens uit s2.

Indien geen tokens gevonden worden in s1, wordt NULL teruggegeven.

Indien het token gevonden is , wordt een nulteken in s1 geschreven volgend op het token, en strtok geeft een pointer terug naar het token.

Volgende oproepen van strtok met NULL als eerste argument gebruiken de vorige s1 string, vanaf het laatst gevonden token.

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

void main()
{
  char s[] ="aze ry   iio sdf";
  char *p;

  p = strtok(s," ");
  while(p != NULL)
  {
     printf("%s\n",p);
     p = strtok(NULL," ");
  }
}
                

Hoofdstuk 11. Structuren

11.1. Typedef

We bespreken eerst de mogelijkheid om aan zelf gedefinieerde types een naam te geven. Dit bespaart schrijfwerk bij het declareren van variabelen en parameters. Dit laat ook toe om globaal het type van een soort gegeven te wijzigen. We creeren types METER, VECTOR en STRING met behulp van typedef.

typedef int METER;
typedef double VECTOR[3];
typedef char STRING[80];
            

Met typedef leggen we vast dat het nieuwe type METER overeenkomt met het type int. Net zoals bij #define wordt het type METER in hoofdletters geschreven. Dit is geen verplichting, maar wordt door veel programmeurs toegepast om het onderscheid te maken tussen constanten, types en variabelen. Met deze nieuwe types declareren we variabelen.

METER afstand;
VECTOR vect1,vect2;
STRING tekst="abcdefghijklmnopqrstuvwxyz";
            

Een zelf gedefinieerd type kan ook bij functies gebruikt worden.

druk(STRING v)
{
   printf("dit is de string: %s\n", v);
}
            

Het is zo dat het gebruik van typedef de leesbaarheid van het programma verbetert.

11.2. Structuur

Met een structuur kunnen we een type ontwerpen, dat gegevens van een verschillende soort samenbrengt. We doen dit met het woord struct.

struct boek
{
   char titel[40];
   char auteur[40];
   float prijs;
};
            

Deze declaratie creeert een structuur met de naam boek. Ze bestaat uit 2 char arrays en een float. Met deze structuur declareren we een variabele.

struct boek roman1;
            

Een variabele kan gedeclareerd worden met initialisatie.

struct boek roman2 = {
"De loteling","Conscience",399.5 };
            

De roman2 bevat de velden titel, auteur en prijs. Het veld titel krijgt de waarde "De loteling", auteur wordt "Conscience" en prijs wordt 399.5 .

We kunnen een structuur vastleggen als een nieuw type met typedef.

typedef struct
{
   double x;
   double y;
} PUNT;
            

Merk op dat er geen structuurnaam, maar alleen een typenaam is. Met het type PUNT declareren we enkele variabelen.

PUNT p1,p2,p3;
            

Deze variabelen hebben elk de velden x en y, waarin een double opgeslagen wordt. De toegang tot velden wordt getoond in de volgende functie.

PUNT helft(PUNT pa,PUNT pb)
{
   PUNT pc;

   pc.x = (pa.x + pb.x)/2;
   pc.y = (pa.y + pb.y)/2;
   return( pc );
}
            

Deze functie ontvangt 2 variabelen van het type PUNT en levert een resultaat van hetzelfde type. We kunnen een veld bereiken door de variabelenaam uit te breiden met een punt en de veldnaam.

p1.x = 1;
p1.y = 2;
p2 = p1;/* x en y velden worden gecopieerd */
p3 = helft( p1, p2 );
            

Wanneer een structuurvariabele wordt toegekend aan een andere, wordt de hele structuur gecopieerd. Dus elk veld van de ene variabele wordt gecopieerd naar elk veld van de andere.

11.3. Arrays van structuren

We declareren polygoon als een array van 50 elementen van het type PUNT.

PUNT polygoon[50];
            

Dit zijn de velden van het eerste element van polygoon.

polygoon[0].x
polygoon[0].y
            

Dit zijn de velden van het laatste element.

polygoon[49].x
polygoon[49].y
            

Het volgende voorbeeld berekent de lengte van een polygoon als de som van de afstand tussen de opeenvolgende punten.

double afstand(PUNT pa, PUNT pb)
{
   double x,y;

   x = pa.x - pb.x;
   y = pa.y - pb.y;
   return( sqrt(x*x + y*y) );
}

lengte = 0;
for (i=0; i<49; i++)
   lengte += afstand(polygoon[i], polygoon[i+1]);
            

Bij de oproep van afstand() zien we de notatie polygoon[i]. Deze uitdrukking is van het type PUNT en dit komt overeen met de declaratie van de formele parameters van afstand().

11.4. Pointers naar structuren

Net zoals een pointer naar een int type kunnen we een pointer declareren die naar het type PUNT wijst.

PUNT *p_ptr;
            

We zien dat door het gebruik van het type PUNT in plaats van de hele structuurnotatie, de declaratie leesbaar blijft. Vermits p_ptr een pointervariabele is, kan hierin het adres van een PUNT variabele geplaatst worden.

p_ptr = &p1;
            

Om een veld van de aangewezen structuur te bereiken, schrijven we:

(*p_ptr).x
            

Deze waarde is dezelfde als p1.x omdat p_ptr naar p1 wijst. De haken zijn nodig omdat . een hogere prioriteit heeft dan * . We schrijven dit in een andere vorm.

p_ptr->x
            

De notatie -> is dus een samentrekking van het sterretje en het punt.

In de functie maaknul() wordt geen structuur doorgegeven maar wel een pointer naar een structuur. Dit is nodig omdat de PUNT variabele waarvan het adres doorgegeven wordt aan de functie, gewijzigd wordt.

void maaknul(PUNT *p)
{
   p->x = 0;
   p->y = 0;
}
            

Dit is het gebruik van de functie:

PUNT p1,p2,p3;

maaknul( &p1 );
maaknul( &p2 );
maaknul( &p3 );
            

11.5. Structuur binnen structuur

Het is mogelijk om als type voor een veld een zelf gedefinieerd type te gebruiken.

typedef struct
{
   int jaar;
   int maand;
   int dag;
} DATUM;

typdef struct
{
   DATUM van;
   DATUM tot;
} PERIODE;

PERIODE contract;
            

De variabele contract is van het type PERIODE en bestaat dus uit de velden van en tot. Deze velden zijn op hun beurt structuren. Ze bestaan uit de velden jaar, dag en maand.

De velden kunnen zo bereikt worden:

contract.tot.jaar
contract.van.dag
            

Deze uitdrukkingen moeten we zo interpreteren:

(contract.van).dag
            

De . operator groepeert dus van links naar rechts.

11.6. Unions

Soms is het nodig om een bepaalde waarde onder verschillende vormen bereikbaar te maken. Dit doen we met union.

typedef union
{
   float fwaarde;
   long lwaarde;
} MASKER;
            

De schrijfwijze is identiek met die van struct. We hoeven maar het woord struct te vervangen door union. De betekenis is anders. In tegenstelling tot struct wordt hier maar een keer geheugenruimte gereserveerd. In dit voorbeeld hebben de types float en long dezelfde afmeting. Er wordt dus 4 bytes geheugen gereserveerd. Indien de velden een verschillende afmeting hebben, dan bepaalt het grootste veld de hoeveelheid gereserveerd geheugen.

MASKER getal;
getal.fwaarde = 3.14159;
printf("voorstelling van %f in hex is %lx\n",
getal.fwaarde,getal.lwaarde);
            

Resultaat:

voorstelling van 3.141590 in hex is 40490fcf
            

In dit voorbeeld wordt er voor getal 4 bytes gereserveerd. Dit geheugen is bereikbaar met twee namen: getal.fwaarde en getal.lwaarde. We plaatsen een float-constante in getal en daarna toont printf() op welke wijze dit opgeslagen wordt.

11.7. Bitvelden

In sommige gevallen is het bereik van de werkelijke waarden van een veld slechts een fractie van het maximale bereik. In dat geval is het wenselijk om de velden te declareren op bitniveau.

typedef struct
{
   unsigned int jaar:12;/* 0 - 4095*/
   unsigned int maand:4;/* 0 - 15*/
   unsigned int dag:5;/* 0 - 31*/
   unsigned int ongeveer:1;/* 0 - 1*/
} CDATUM;
            

Elk veld heeft nu een aangepast bereik. Dit verkrijgen we door na elke veldnaam dubbele punt en bitbreedte bij te voegen. De veldbreedte in bit mag niet groter zijn dan de woordbreedte van de computer (voor IBM PC - TURBO C: 16 bit). Als type voor een bitveld mogen we alleen maar unsigned of signed int gebruiken.

De veldnaam mag weggelaten worden. Hiermee kan men ongebruikte bits overslaan.

struct metbits
{
   int i:2;
   unsigned j:5;
   int:4;
   int k:1;
   unsigned m:4;
} a,b,c;
            

De bitverdeling van a, b en c ziet er als volgt uit:

Figuur 11.1. Bits in een struct

Bits in een struct

De bits 7 tot 10 zijn niet gebruikt.

11.8. Structuren en lijsten

11.8.1. Zelfreferentiele structuren

Dit zijn structuren die een of meerdere pointers bevatten die naar eenzelfde soort structuur verwijzen. Deze structuren kunnen gebruikt worden om gegevens op een dynamische manier te organiseren. Men maakt bijvoorbeeld een ketting van zelfreferentiele structuren. Elk knooppunt in deze ketting bevat 1 of meerdere gegevenselementen en bevat ook een verwijzing naar het volgende knooppunt.

Hier is een voorbeeld van een zelfreferentiele structuur.

struct knoop
{
   int  data;
   struct knoop *verder;
};
typedef struct knoop KNOOP;
                

Het veld verder in deze structuur verwijst naar een andere variabele van het type struct knoop. Het type KNOOP is een synoniem voor struct knoop. We declareren enkele variabelen.

KNOOP a,b,c;
                

Deze variabelen worden gevuld met gegevens.

a.data = 1;
b.data = 2;
c.data = 3;
a.verder = b.verder = c.verder = NULL;
                

De velden verder worden voorlopig niet gebruikt en ze krijgen daarom de waarde NULL ( is gelijk aan 0 ). NULL wordt gebruikt om aan te geven dat een pointer naar niets wijst. De huidige toestand stellen we grafisch voor.

Figuur 11.2. Drie structs zonder koppeling

Drie structs zonder koppeling

We maken nu de verbinding tussen a, b en c.

a.verder = &b;
b.verder = &c;
                

Figuur 11.3. Drie gekoppelde structs

Drie gekoppelde structs

Nu zijn de gegevens vanuit a bereikbaar.

a.data-->1
a.verder->data2
a.verder->verder->data3
                

11.8.2. Niet gesorteerde gebonden lijsten

De gegevensorganisatie die we daarnet besproken hebben, is een gebonden lijst. We hebben het nu verder over functies die een niet gesorteerde gebonden lijst manipuleren. We creeren een pointervariabele die naar het eerste element wijst.

KNOOP *p_eerste = NULL;
                

Deze variabele wordt met NULL geinitialiseerd. Hiermee wordt aangegeven dat de lijst leeg is.

void voegbij(KNOOP **ptr, int getal)
{
   register KNOOP *nieuw_p;

   nieuw_p = (KNOOP *) malloc(sizeof(KNOOP));
   nieuw_p->data = getal;
   nieuw_p->verder = *ptr;
   *ptr = nieuw_p;
}
                

Deze functie kan zo gebruikt worden:

voegbij( &p_eerste, 4 );
voegbij( &p_eerste, 5 );
voegbij( &p_eerste, 6 );
                

Het eerste wat opvalt, is de formele parameter KNOOP **ptr. Dit is een dubbele pointer: ptr heeft als inhoud het adres van een pointer die wijst naar een KNOOP. De actuele parameter is van hetzelfde type: &p_eerste is het adres van een pointer. We geven niet de inhoud van een pointer door, maar wel het adres van die pointer. Dit is nodig omdat p_eerste in de functie gewijzigd moet kunnen worden. Dit gebeurt als het eerste element van de lijst gewist wordt of als er een ander element het eerste wordt.

Het eerste wat voegbij() doet, is het oproepen van malloc(). Dit is een functie die geheugen reserveert. De functie verwacht als actuele parameter het aantal benodigde bytes en levert als resultaat een pointer naar het aangevraagde geheugen. In dit geval hebben we geheugen nodig voor een element van het type KNOOP: dit is sizeof(KNOOP) bytes. Het resultaat van malloc() is een pointer naar char. Dit adres wordt met een cast omgezet tot een pointer naar KNOOP. Het bij te voegen getal wordt in het veld data geplaatst. Het veld verder van het nieuwe element moet nu wijzen naar het eerste element van de oude lijst.

nieuw_p->verder = *ptr;
                

Het nieuwe element wijst dus naar het element dat vroeger door p_eerste aangewezen werd. ptr bevat het adres van p_eerste. Dus *ptr is hetzelfde als de inhoud van p_eerste. Tot slot wordt p_eerste gewijzigd.

Dit gebeurt onrechtstreeks:

*ptr = nieuw_p;
                

p_eerste wijst naar het nieuwe element en dit op zijn beurt wijst naar de oude lijst.

Toestand voor het bijvoegen van het getal 5:

Figuur 11.4. Lijst met 1 element

Lijst met 1 element

Toestand erna:

Figuur 11.5. Lijst na het bijvoegen van een element

Lijst na het bijvoegen van een element

De inhoud van een lijst kan zichtbaar gemaakt worden met de functie druk().

void druk(KNOOP *ptr)
{
   while (ptr != NULL)
   {
      printf("%d\n", ptr->data);
      ptr = ptr->verder;
   }
}
                

De functie wordt zo gebruikt:

druk(p_eerste);
                

De formele parameter ptr is een copie van de inhoud van p_eerste. Deze copie mag zondermeer gewijzigd worden zonder dat p_eerste verandert.

De pointer doorloopt de hele lijst tot het einde bereikt is en drukt bij elke herhaling een getal op het scherm.

Hier is een andere versie:

void druk(KNOOP *ptr)
{
   if (ptr != NULL)
   {
      printf("%d\n", ptr->data);
      druk( ptr->verder );
   }
}
                

Deze versie werkt recursief. Dit wil zeggen dat druk() zichzelf oproept. De functie drukt het getal, dat aangeduid wordt door ptr en drukt dan de rest van de lijst door zichzelf nog eens op te roepen.

De volgende functie zoekt een getal in een lijst en veegt het uit als het gevonden wordt.

void veeguit(KNOOP **ptr; int getal)
{
   KNOOP **p1, *p2;

   /* zoeken */
   p1 = ptr;
   while ( *p1 != NULL && (*p1)->data != getal)
   {
      p1 =&((*p1)->verder);
   }

   if (*p1 != NULL)/* gevonden */
   {
      /* uitvegen */
      p2 = *p1;/* bewaar adres gevonden element */
      *p1 = (*p1)->verder;
      free(p2);/* geheugenvrijgave */
   }
   else
      printf("niet gevonden\n")
}
                

Aan deze functie wordt het adres van p_eerste doorgegeven, omdat ook hier p_eerste gewijzigd moet kunnen worden. Na het starten wordt een copie gemaakt naar ptr. Deze pointer wordt gebruikt om telkens op te schuiven naar het volgende element tijdens het zoeken. Dit proces gaat verder zolang het einde van de lijst niet bereikt is

*p1 != NULL
                

en het getal niet gevonden is.

(*p1)->data != getal
                

We zien telkens een * voor p1. Dit is nodig omdat p1 het adres bevat van een KNOOP pointer. *p1 is dus een pointer naar KNOOP.

Tijdens de herhaling wordt p1 gewijzigd: p1 wijst dan naar het adres van de pointer die wijst naar het volgende element.

Welke waarde krijgt p1?

*p1                 // is adres huidige KNOOP element
(*p1)->verder       //    adres volgende KNOOP element
&((p1)->verder)     //    adres van de pointer die het adres bevat van het volgende KNOOP element
                

Als de herhaling stopt en *p1 is NULL, dan is het einde van de lijst bereikt en is het getal niet gevonden. In het andere geval moet het gevonden element geschrapt worden.

Toestand voor het uitvegen van 5:

Figuur 11.6. lijst voor het uitvegen

lijst voor het uitvegen

Dit is het uitvegen:

Figuur 11.7. Lijst na het uitvegen

Lijst na het uitvegen
veeguit( &p_eerste, 5);
                

Toestand erna:

Na het vinden van het getal 5 wijst *p1 naar het gevonden KNOOP element. Dit adres wordt opzijgezet in p2 en de pointer *p1 krijgt een nieuwe waarde. Hierdoor wordt het uit te vegen element overgeslagen.

Merk op dat er zich een speciale situatie voordoet als het eerste element van de lijst geschrapt wordt. In dat geval doet de while opdracht geen herhaling en bevat p1 het adres van p_eerste. Hieruit volgt dat p_eerste gewijzigd wordt.

Hoofdstuk 12. Bestandsin- en uitvoer

In dit hoofdstuk worden een aantal functies beschreven voor het lezen en schrijven van bestanden. Tenzij anders aangegeven zijn de prototypes allemaal terug te vinden in stdio.h. De in- en uitvoerfuncties maken deel uit van de ANSI standaard en maken gebruik van filepointers. Meestal bestaan er op elk systeem ook nog functies die dichter bij de hardware staan en specifiek zijn voor het desbetreffende operating system. Om die reden worden deze functies hier niet beschreven.

12.1. fopen

Vooraleer er gelezen of geschreven wordt van of naar een file, moet de file geopend worden. Bij deze actie wordt een filepointer geassocieerd met de file. In de overige functies moet een filepointer meegegeven worden als referentie naar de file. In C zijn er twee voorgedefinieerde filepointers voor in- en uitvoer: stdin, stdout en stderr. Ze worden gebruikt voor de invoer van het toesenbord, de uitvoer naar het scherm en foutuitvoer.

Prototype:

FILE *fopen(const char *filename, const char *mode);
            

Hierbij is filename een string die het pad van de file bevat en kunnen in de string mode de volgende letters voorkomen.

Letter

Betekenis

r

open enkel om te lezen

w

creeer voor schrijven; overschrijft een bestaande file

a

bijvoegen, open voor schrijven op het einde van de file of creeer voor schrijven

+

nog een letter volgt (bv combinatie lezen en schrijven)

b

open in binaire modus

t

open in tekstmodus

In de binaire modus worden bytes letterlijk geschreven en gelezen. In de tekstmodus wordt de carriage return/linefeed combinatie vervangen door een enkele linefeed. Bij het lezen van een tekstfile wordt dus nooit een carriage return aan het programma gegeven. Bij het schrijven gebeurt het omgekeerde.

De functie fopen geeft als resultaat een filepointer of NULL bij fout.

12.2. fclose

Met deze functie wordt een file gesloten.

Prototype:

int fclose(FILE *stream);
            

De functie geeft als resultaat een 0 bij succes of EOF bij fout.

/* Programma dat een kopie maakt van een file */

#include <stdio.h>

int main(void)
{
  FILE *in, *out;

  if ((in = fopen("file.dat",
                    "rt")) == NULL)
  {
     fprintf(stderr, "Cannot open input file.\n");
     return 1;
  }

  if ((out = fopen("file.bak",
                    "wt")) == NULL)
  {
     fprintf(stderr, "Cannot open output file.\n");
     return 1;
  }

  while (!feof(in))
     fputc(fgetc(in), out);

  fclose(in);
  fclose(out);
  return 0;
}
            

12.3. ferror

Deze macro that geeft als resultaat een waarde verschillend van nul als er een fout is opgetreden bij deze filepointer.

Prototype:

int ferror(FILE *stream);
            

12.4. perror

Deze functie drukt een foutmelding op het scherm via stderr.

Prototype:

void perror(const char *s);
            

Eerst wordt de string s gedrukt. Daarna volgt een dubbele punt en een foutmelding die overeenkomt met de huidige waarde van errno.

12.5. strerror

Deze functie geeft als resultaat een foutmelding-string die overeenkomt met het doorgegeven foutnummer.

Prototype (ook in string.h):

char *strerror(int errnum);
            

12.6. _strerror

Deze functie geeft een string met een foutmelding terug. Het formaat is zoals bij perrror.

Prototype (ook in string.h):

char *_strerror(const char *s);
            

Het volgende voorbeeld toont hoe foutmeldingen op het scherm kunnen geplaatst worden.

#include <stdio.h>
#include <errno.h>

int main(void)
{
  FILE *stream;

  /* open a file for writing */
  stream = fopen("DUMM.FIL", "r");

  /* force an error condition by attempting to read */
  (void) getc(stream);

  if (ferror(stream)) /* test for an error on the
                    stream */
  {
     /* display an error message */
     printf("Error reading from DUMMY.FIL\n");
     perror("fout");

     printf("%s\n",strerror(errno));

     printf("%s\n", _strerror("Custom"));

     /* reset the error and EOF indicators */
     clearerr(stream);
  }

  fclose(stream);
  return 0;
}
            

12.7. fwrite

Met deze functie kan informatie naar een file geschreven worden. De functie schrijft n elementen van afmeting size bytes naar de file. De pointer ptr wijst naar de te schrijven informatie.

Prototype:

size_t fwrite(const void *ptr, size_t size, size_t n,
                    FILE *stream);
            

De functie geeft als resultaat het aantal elementen (niet bytes) dat weggeschreven is.

Het volgende voorbeeld toont hoe een structuur naar een binaire file geschreven wordt.

#include <stdio.h>

struct mystruct
{
int i;
char ch;
};

int main(void)
{
  FILE *stream;
  struct mystruct s;

      /* open file */
  if ((stream = fopen("TEST.$$$",
                    "wb")) == NULL)
  {
     fprintf(stderr, "Cannot open output file.\n");
     return 1;
  }
  s.i = 0;
  s.ch = 'A';

      /* write struct s to file */
  fwrite(&s, sizeof(s), 1, stream);
  fclose(stream); /* close file */
  return 0;
}
            

12.8. fread

Deze functie leest uit een file. Er worden n elementen van afmeting size bytes in de array ptr gelezen.

Prototype:

size_t fread(void *ptr, size_t size, size_t n, FILE *stream);
            

De functie levert als resultaat het aantal elementen (niet bytes) dat effectief gelezen is.

12.9. fseek

Deze functie verplaatst de wijzer die de positie aangeeft waar eerstvolgend gelezen of geschreven wordt.

Prototype:

int fseek(FILE *stream, long offset, int fromwhere);
            

offset is de nieuwe positie relatief ten opzichte van de positie gespecifieerd met fromwhere.

De functie geeft 0 bij succes of nonzero bij fout.

De parameter fromwhere kan een van de volgende waarden zijn:

  • SEEK_SET verplaats vanaf het begin van de file

  • SEEK_CUR verplaats vanaf de huidige positie

  • SEEK_END verplaats vanaf het einde van de file

12.10. fgets

Deze functie leest een regel uit een file.

Prototype:

char *fgets(char *s, int n, FILE *stream);
            

De parameter n geeft aan voor hoeveel tekens er plaats is in de buffer s.

Bij succes, wordt de string s of NULL bij einde van de file of fout teruggegeven.

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

int main(void)
{
  FILE *stream;
  char string[] = "This is a test";
  char msg[20];

  /* open a file for update */
  stream = fopen("DUMMY.FIL", "w+");

  /* write a string into the file */
  fwrite(string, strlen(string), 1, stream);

  /* seek to the start of the file */
  fseek(stream, 0, SEEK_SET);

  /* read a string from the file */
  fgets(msg, strlen(string)+1, stream);

  /* display the string */
  printf("%s", msg);

  fclose(stream);
  return 0;
}
            

12.11. fputs

Met deze functie wordt een regel naar een file geschreven.

Prototype:

int fputs(const char *s, FILE *stream);
            

De functie geeft als resultaat bij succes het laatst weggeschreven teken of EOF bij fout.

12.12. fgetc

Met deze functie wordt een teken gelezen uit een file.

Prototype:

int fgetc(FILE *stream);
            

De functie geeft het teken of EOF terug.

12.13. fputc

Met deze functie wordt een teken naar een file geschreven.

Prototype:

int fputc(int c, FILE *stream);
            

12.14. fprintf

Dit is de file-variant van printf.

Prototype:

int fprintf(FILE *stream, const char *format, ...);
            

De functie geeft als resultaat het aantal geschreven bytes of EOF bij fout.

12.15. fscanf

Dit is de file-variant van scanf.

Prototype:

int fscanf(FILE *stream, const char *format, ... );
            

De functie geeft als resultaat het aantal in variabelen opgeborgen waarde.

Aanhangsel A. Gereserveerde woorden

auto      extern   sizeof
break     float    static
case      for      struct
char      goto     switch
const     if       typedef
continue  int      union
default   long     unsigned
do        register void
double    return   volatile
else      short    while
enum      signed
        

Aanhangsel B. Prioriteiten van de operatoren

Operator

Groepering

() [] -> .

 

! ~ ++ -- - (type) * & sizeof

rechts -> links (unair)

* / %

links -> rechts

+ -

links -> rechts

<< >>

 

< <= > >=

links -> rechts

== !=

links -> rechts

&

links -> rechts

^

links -> rechts

|

links -> rechts

&&

links -> rechts

||

links -> rechts

?:

links -> rechts

= += -= *= /= %= ^= &= |=

rechts -> links

,

links -> rechts

Bibliografie

Borland. 4585 Scotts Valley Drive, Scotts Valley, California 95066 USA. Turbo C User's Guide, Borland International, Inc..

Prentice Hall. Copyright © 1978. Brian W. Kernighan en Dennis M. Ritchie. The C Programming Language.

Al Kelley en Ira Pohl. The Benjamins/Cummings Publishing Company, Inc.. 2725 Sand Hill Road, Menlo Park, California 94025 USA. An introduction to C programming.

Mitchell Waite, Stephen Prata, en Donald Martin. The Waite Group, Howard W. Sams & Company. A Division of Macmillan, Inc., 4200 West 62nd Street, Indianapolis, Indiana 46268 USA. C Primer Plus, User-friendly Guide to the C Programming Language.

A. Bellen. KHLim. Universitaire Campus, gebouw B, 3590 Diepenbeek. Cursus Informatica.

Brian W. Kernighan en Dennis M. Ritchie. Academic Service. Postbus 81, 2870 AB Schoonhoven. C handboek.

Stanley B. Lippman. Addison - Wesley. C++: inleiding en gevorderd programmeren.