Copyright © 2004-2005 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.2 | 2/10/2002 | RL |
Tabellen bijgevoegd | ||
Herziening 0.1.1 | 25/ 9/2002 | RL |
Figuren bijgevoegd | ||
Herziening 0.1.0 | 12/ 9/2002 | RL |
Omzetting van Lotus WordPro |
Samenvatting
Cursus C
Inhoudsopgave
Lijst van figuren
Inhoudsopgave
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.
Dit zijn enkele markante punten uit de geschiedenis van C:
De BCPL taal wordt ontworpen door Martin Richards.
De eerste versie UNIX in B door Ken Thompson geschreven, verschijnt.
De C taal wordt ontworpen voor versie UNIX op DEC PDP11 door Dennis Ritchie.
Het boek 'The C programming language' door Brian Kernighan & Dennis Ritchie geschreven verschijnt bij Prentice Hall.
ANSI start de standaardisatie van C.
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
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.
Inhoudsopgave
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.
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.
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.
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.
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.
nieuwe lijn
tab
belsignaal
backspace
formfeed
carriage return
horizontale tab
vertikale tab
backslash
vraagteken
enkel aanhalingsteken
dubbel aanhalingsteken
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.
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); }
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;
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)); }
Inhoudsopgave
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().
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.
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.
We bespreken hier de volledige mogelijkheden van de uitvoer met de printf() functie.
De volgende conversietekens kunnen in printf() gebruikt worden:
geheel decimaal getal
octaal
Het getal wordt niet vooraf gegaan door een 0.
hexadecimaal
Het getal wordt niet voorafgegaan door 0x of 0X. De waarden 10 tot 15 worden voorgesteld door abcdef of ABCDEF.
decimaal getal zonder teken
een enkel teken
een string
De tekens tot de code '\0' worden afgedrukt.
reeel zonder e notatie
Het formaat is [-]m.dddddd, het aantal cijfers na de decimale punt is 6.
reeel in e notatie
Het formaat is [-]m.dddddde+xx of [-]m.ddddddE+XX, het aantal cijfers na de decimale punt is 6.
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.
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.
Als het eerste teken geen plus- of minteken is, wordt een spatie voor het getal gezet.
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.
minimum veldbreedte
Het geconverteerde argument wordt in een veld afgedrukt dat minimaal deze breedte heeft.
printf("%8d", 0x1234);
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:
short in plaats van int
long in plaats van int
printf("%ld", 0x1234L);
long double in plaats van double
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:
geheel decimaal getal
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.
octaal geheel getal
Het getal is al dan niet voorafgegaan door een 0.
hexadecimaal geheel getal
Het getal is al dan niet voorafgegaan door 0x of 0X.
decimaal getal zonder teken
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.
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.
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.
pointer
Een pointer wordt ingelezen. Het formaat is zoals het formaat bij het afdrukken met printf(). Dit wordt bepaald door de implementatie.
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.
Inhoudsopgave
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:
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.
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 |
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.
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.
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;
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.
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); }
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;
Inhoudsopgave
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");
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.
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.
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 );
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.
Inhoudsopgave
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:
Deze herhaling bestaat uit de initialisatie van de lusteller, het testen van de eindvoorwaarde en het verhogen van de lusteller.
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);
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');
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.
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; } }
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++; } }
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.
Inhoudsopgave
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.
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:
de variabele die de doorgegeven waarde ontvangt
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);
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.
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
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.
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:
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 );
Inhoudsopgave
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
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.
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.
Hiermee bedoelen we variabelen die altijd bestaan. De externe variabelen zijn statisch omdat ze altijd bestaan tijdens de levensduur van het programma.
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.
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 |
Inhoudsopgave
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.
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); }
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.
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:
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.
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.
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.
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);
Inhoudsopgave
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:
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';
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.
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" };
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.
We bespreken enkele van de belangrijkste stringfuncties.
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.
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.
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"); }
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.
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.
De prototypes van de functies voor stringmanipulatie zijn terug te vinden in de headerfile string.h.
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; }
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; }
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; }
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; }
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; }
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); }
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; }
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; }
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; }
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; }
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; }
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; }
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; }
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," "); } }
Inhoudsopgave
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.
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.
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().
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 );
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.
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.
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:
De bits 7 tot 10 zijn niet gebruikt.
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.
We maken nu de verbinding tussen a, b en c.
a.verder = &b; b.verder = &c;
Nu zijn de gegevens vanuit a bereikbaar.
a.data-->1 a.verder->data2 a.verder->verder->data3
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:
Toestand erna:
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:
Dit is 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.
Inhoudsopgave
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.
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.
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; }
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);
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.
Deze functie geeft als resultaat een foutmelding-string die overeenkomt met het doorgegeven foutnummer.
Prototype (ook in string.h):
char *strerror(int errnum);
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; }
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; }
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.
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
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; }
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.
Met deze functie wordt een teken gelezen uit een file.
Prototype:
int fgetc(FILE *stream);
De functie geeft het teken of EOF terug.
Met deze functie wordt een teken naar een file geschreven.
Prototype:
int fputc(int c, FILE *stream);
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.
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
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 |
Borland. 4585 Scotts Valley Drive, Scotts Valley, California 95066 USA. Turbo C User's Guide, Borland International, Inc..
Prentice Hall. Copyright © 1978. The C Programming Language.
The Benjamins/Cummings Publishing Company, Inc.. 2725 Sand Hill Road, Menlo Park, California 94025 USA. An introduction to C programming.
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.
KHLim. Universitaire Campus, gebouw B, 3590 Diepenbeek. Cursus Informatica.
Academic Service. Postbus 81, 2870 AB Schoonhoven. C handboek.
Addison - Wesley. C++: inleiding en gevorderd programmeren.