C++ Gratis PDF-udgave Forlaget Libris

Størrelse: px
Starte visningen fra side:

Download "C++ Gratis PDF-udgave Forlaget Libris 1996-2004"

Transkript

1 C++ Gratis PDF-udgave Forlaget Libris

2 Gratis PDF-udgave af C++ I 1996 udgav vi for første gang C++ af Kris Jamsa. Første udgave var på bogform, som dengang kostede kr. 228,-. I 1999 udgav vi bogen på hæfteform og prisen var nu kr. 69,-. Bogens sidste kapitel måtte vi lægge til download. Fordi der ikke var plads i hæftet! Samlet er der solgt over eksemplarer af denne udgivelse og salget er ved at gå i stå bl.a. pga. vores nye bog om C++, som i sagens natur er mere up-to-date og mere omfattende. Vi har nu valgt at vi sender hæftet på pension som e-bog i en kort tid endnu kan hæftet købes til kr. 20,-. Sådan så første udgave ud. Og sådan ser anden udgave ud. Sælges så længe lager haves for kr. 20,-.

3 Kolofon fra den seneste trykte udgave: C++ 2. udgave, 2. oplag 1999 Original titel: Rescued by C++ Copyright 1996 by Jamsa Press Copyright 1996,1999 IDG Danmark A/S Forfatter: Kris Jamsa Forlagsredaktion: Carl Rosschou Oversættelse: Jakob Kristiansen Omslag: Lotte Mathiasen DTP: Jakob Kristiansen/Jes Nyhus Korrektur: Jens Lyng Tryk: Trykcentralen Fyn ISBN:

4 Indholdsfortegnelse Forord 6 1. Kom i gang med C++ 7 Vores første C++ program 7 Vi kigger nærmere på C++ 11 Udskrift til skærmen 14 Information opbevares i variabler 19 Grundlæggende operationer 24 Input fra tastatur 30 Programmer foretager valg 33 Gentagelse af en sætning eller en blok Funktioner 46 Introduktion til funktioner 46 Ændring af parameter-værdier 52 Brug af run-time biblioteket 56 Lokale variabler og scope 59 Funktion overload 62 Referencer i C++ 64 Default parameter-værdier Arrays og struktur-variabler 69 Opbevaring af mange værdier i arrays 69 Tekst-strenge 72 Struktur-variabler 77 Union-strukturer 80 Pointere Klasser 87 Klasser i C++ 87 Private og offentlige elementer 90 Constructor- og destructor-funktioner 94 Overload af operatorer 99 Statiske funktioner og data Arv, konstanter og makroer 108 Arv 108 Arv fra flere klasser 112 Filer 116 Kommandolinie-argumenter Avanceret C Konstanter og makroer 125 Brug af free store 129 Styring af free store operationer 132 Polymorfisme 135 Private elementer og friends 140 Funktions-skabeloner 144 Klasse-skabeloner 146 Få mere ud af cin og cout 151 Stikordsregister 155 5

5 Forord Ligesom hardware gennemgår software en udvikling der optimerer og raffinerer programmerne. Op igennem 70'erne vandt brugen af Kernighan og Richies programmeringssprog C stigende popularitet i professionelle programmør-kredse, men dels var det svært at lære og dels var det meget let at lave fejl i programmeringen. Træt af C's begrænsninger og inspireret af sprogene Simula 67 og Algol 68 udviklede danskeren Bjarne Stroustrup fra Bell Laboratories i New Jersey, USA, i 1980 en ny, objektorienteret version af C. Sproget blev kaldt 'C med klasser' men ændrede i 1983 navn til C++. I dag er C++ det førende programmeringssprog, og med Internet's eksplosive vækst har kendskab til C++ givet endnu en fordel idet programmeringsstandarden på Internet sproget Java stort set er identisk med C++. Der kræves ingen særlige forudsætninger for at gå i gang med denne bog: Dog er det en fordel hvis man ved lidt om hvordan computere fungerer og kender lidt til brugen af en teksteditor. Kort sagt: Hvis du kender forskel på RAM og fil er du klar til at gå i gang. Bogen er opbygget som en række øvelser der gennemgår et emne med forklaringer og demonstrerer det med program-eksempler. I kodeeksemplerne i bogen er det nogle steder nødvendigt at dele en tekst over flere linier på grund af spaltebredden. Tegnet betyder at teksten skal skrives i forlængelse af den ovenstående linie, ellers fungerer koden ikke. I andre tilfælde er linjer blevet delt, sådan at programeksemplerne funger, selvom linjerne deles. Det er markeret ved, at linjen efter delingen er rykket ind. Men i C++ slutter en sætning almindeligvis med et semikolon: Sætninger der er blevet delt af hensyn til bogens spalte-bredde, bør i programmet skrives på samme linie for overskuelighedens og læsevenlighedens skyld. Nogle af program-eksemplerne strækker sig over flere spalter. Når et program-eksempel fortsætter i næste spalte er det markeret med tegnet 4 længst til højre i spalten. God fornøjelse! 6

6 1. Kom i gang med C++ Øvelse 1 Vores første C++ program Vi har alle sammen arbejdet med programmer som f.eks. tekstbehandlere og regneark. Computer-programmer også kaldet software er filer, der indeholder instruktioner som tilsammen gør computeren i stand til at udføre en opgave. Arbejder man i DOS eller i Windows indeholder filer med COM- og EXE-extensions kommandoer som computeren kan udføre. Eller sagt med andre ord: Filerne indeholder specifikke instruktioner der får computeren til at udføre en given opgave. Når man skriver programmer definerer man de instruktioner, man ønsker computeren skal udføre. I den første øvelse skal vi se hvordan man laver disse instruktioner med C++ sætninger. Når vi er igennem øvelsen vil du have lært følgende: Når man laver et C++ program bruger man en teksteditor til at skrive C++ sætningerne som tilsammen udgør kildeteksten (sourcekoden). C++ sætningerne i kildeteksten skal oversættes til et eksekverbart program bestående af nuller og et-taller som computeren forstår. Dette gøres ved hjælp af et specielt program kaldet en kompiler. Skal man rette eller tilføje i sit program gør man det i kildeteksten ved hjælp af en teksteditor. Hvis den kode, man har skrevet, indeholder ikke-gyldige C++ sætninger, kommer kompileren med en fejlmeddelelse. Når man har fundet og rettet fejlen kan man kompilere programmet igen. Programmering består i at definere en serie instruktioner, som computeren skal udføre for at løse en given opgave. Til at angive instruktionerne anvendes et programmeringssprog som f.eks. C++. Ved hjælp af en teksteditor, skriver man programsætningerne i en kildetekst-fil og ved hjælp af et specielt program, kaldet en kompiler, omdannes denne tekstfil fra tekst til en form, som computeren forstår. Lad os lave et lille C++ program der viser hvad det vil sige at skrive et program og kompilere det. Vi laver et lille program Det første C++ program, vi laver, kalder vi START.CPP. Kildetekst-filer bør have extension (fil-efternavn) CPP som angiver at filen indeholder et C++ program. Når dette lille program startes viser det teksten Programmering i C++! på skærmen. I det følgende eksempel står vi ved prompten (i roden af C:-drevet) og skriver kommandolinien (programmets navn efterfulgt af ENTER) hvorefter programmet skriver teksten på skærmen: C:\CPP\> START <Enter> Programmering i C++! På de fleste computere arbejder man enten i et kommandolinie (tekstbaseret) miljø eller et grafisk miljø (som f.eks. Windows). Eksemplerne i denne bog er lavet så de kan afvikles i et tekst-baseret miljø. Først skal vi bruge en teksteditor til at skrive en fil som skal indeholde programsætningerne: kildetekst-filen. Du kan f.eks. bruge EDIT som følger med MS-DOS. Det er ikke en god idé at bruge en decideret tekstbehandler som f.eks. Word eller WordPerfect da disse som regel laver skjulte koder og tegn i teksten som bruges til formatering af dokumentets udseende fed tekst, lige marginer osv. Hvis C++ kompileren møder sådanne tegn, vil den ikke kunne forstå dem, og den vil komme med en fejlmeddelelse. 7

7 C++ Brug teksteditoren til at skrive følgende C++ sætninger. Skriv dem som de står her med samme brug af store og små bogstaver: cout << "Programmering i C++!"; Du skal ikke fortvivle hvis du ikke forstår disse C++ sætninger: Vi skal se på dem enkeltvis i øvelse 2. Men indtil videre skal du være omhyggelig med indskrivningen: Check at du har skrevet det rigtige antal anførselstegn, semikoloner og parenteser. Når dit program er identisk med kildeteksten ovenfor, gemmer du filen under navnet START.CPP. CPP angiver at filen indeholder et C++ program. Man bør altid vælge filnavne, som siger noget om programmets formål. Laver du f.eks. et bogholderiprogram kan du f.eks. kalde kildetekstfilen BOGHOLD.CPP. Et program der udregner skatteprocenter kunne hedde SKAT.CPP. Programmer bør ikke hedde det samme som eksisterende programmer f.eks. som DOSkommandoer: COPY, DEL osv. Vi kompilerer programmet En computer arbejder med nuller og et-taller i en form som kaldes maskinsprog. Nullerne og et-tallerne repræsenterer tilstedeværelsen eller fraværet af et elektrisk signal. Hvis der er et signal (1), udfører computeren måske én operation, mens den udfører en anden hvis signalet er fraværende (0). Heldigvis er det ikke mere nødvendigt at skrive programmer udelukkende med et-taller og nuller, som man skulle tilbage i 40'erne og 50'erne: I dag bruges et specielt program en C++ kompiler til at konvertere de skrevne programsætninger (kildeteksten) til maskinsprog. Eller sagt på en anden måde: Kompileren læser kildetekst-filen som indeholder C++ sætningerne igennem, og hvis sætningerne overholder C++ sprogets regler, bliver de oversat til maskinsprog (nuller og et-taller) som computeren kan udføre. Computeren gemmer maskinsproget i en eksekverbar fil som typisk har extension EXE. Når der først er en EXE-fil kan man køre programmet ved at skrive filnavnet ved prompten og trykke ENTER. Den kommando man bruger til at kompilere sit program kan have forskellige navne alt efter hvilket mærke kompiler man har købt. Bruger du f.eks. Borland C++ skal vores lille START-program kompileres med BCC på denne måde: C:\CPP\> BCC START.CPP <ENTER> Hvis du bruger en anden kompiler end Borlands kan du i dokumentationen som fulgte med kompileren, se, hvilken kommando du skal bruge for at starte kompileren. Når kompileren er færdig har den lavet et eksekverbart program som den gemmer i en fil på harddisken. Arbejder du i DOS, vil denne fil hedde EXE til efternavn f.eks. START.EXE. Hvis der kommer fejlmeddelelser under kompileringen skal du gennemgå kildetekst-filen og sikre dig at den svarer til de eksempler der står i bogen. Ret eventuelle fejl, og gem så kildetekst-filen igen. Når det lykkes at kompilere programmet kan du starte det ved at skrive dets navn og trykke ENTER. Vi laver endnu et program Du havde forhåbentlig ikke problemer med at kompilere og starte vores første program START.CPP. Skriv nu i teksteditoren et nyt program, og gem det som NEMT.CPP. Programmet skal se sådan ud: cout << "Det er nemt at programmere C++!"; 8

8 1. Kom i gang med C++ Ligesom i det første eksempel skal du også her gemme C++ kildeteksten i en fil, hvorefter du kalder kompileren ved at skrive dens navn på kommandolinien. Bruger du f.eks. Borland C++ skal du skrive følgende linie for at kompilere programmet: C:\CPP\> BCC NEMT.CPP <Enter> Hvis programmet kompilerer uden problemer, laves en fil ved navn NEMT.EXE. Når du starter programmet skal det helst vise følgende meddelelse på skærmen: C:\CPP\> NEMT <Enter> Det er nemt at programmere C++! Hent nu kildetekst-filen NEMT.CPP ind i din editor, og lav den viste besked om, så den indeholder ordet meget: cout << "Det er meget nemt at programmere C++!"; Gem den ændrede kildetekst-fil og kompilér programmet. Når det er kompileret vil programmet vise følgende tekst når det bliver startet: C:\CPP\> NEMT <ENTER> Det er meget nemt at programmere C++! Hver gang du ændrer i et programs kildetekstfil, skal programmet kompileres igen for at ændringerne skal træde i kraft. Prøv at ændre i kildetekst-filen igen. Tilføj følgende nye linie til programmet: cout << "Det er meget nemt at programmere C++!"; cout << endl << "Og det er også sjovt!"; Gem kildetekst-filen og start programmet: C:\CPP\> NEMT <ENTER> Det er meget nemt at programmere C++! Som du ser skriver programmet ikke den nye tekstlinie. For at ændringerne i en kildetekst-fil skal virke, er det nødvendigt at kompilere programmet. Prøv at kompilere programmet igen og start det. Nu har kompileren medtaget de nye linier fra kildeteksten, og det vil nu skrive følgende linier på skærmen når det bliver startet. C:\CPP\> NEMT <ENTER> Det er meget nemt at programmere C++! Og det er også sjovt! Syntaksfejl Alle sprog dansk, engelsk, tysk, fransk og også C++ indeholder et sæt regler syntaks som man skal overholde når man bruger sproget. På dansk begynder sætninger som regel med stort bogstav og slutter med enten punktum, udråbstegn eller spørgsmålstegn. C++ syntaksen benytter sig af semikolon, parenteser, hårde klammer og forskellige andre tegn. Hvis man glemmer et af disse tegn eller hvis man bruger dem forkert kommer C++ kompileren med en fejlmeddelelse, som fortæller hvad det er for en fejl, og på hvilken linie i kildetekst-filen fejlen findes. C++ kompileren kan ikke lave kildeteksten om til et eksekverbart program, hvis der er syntaksfejl i det. Indtast følgende program som skal demonstrere, hvordan man finder og retter syntaksfejl. Gem kildeteksten som SYNTAKS.CPP. cout << Meddelelser skal skrives i anførselstegn; Som du sikkert kan se, er forskellen på dette program og dem vi tidligere skrev, at meddelelsen ikke står i anførselstegn. C++ syntaksen kræver anførselstegn. Prøver man at kompilere programmet, vil man få en fejlmeddelelse. 9

9 C++ F.eks. vil Borland C++ kompileren give følgende besked: C:\CPP\> BCC SYNTAKS.CPP <ENTER> Borland C++ Version 3.1 Copyright (c) 1992 Borland International syntaks.cpp: Error syntaks.cpp 5: Undefined symbol Meddelelser in function main() Error syntaks.cpp 5: Statement missing ; in funktion main() *** 2 errors in Compile *** I dette tilfælde har kompileren fundet to syntaksfejl. Begge fejl forekommer i linie fem i kildetekst-filen. Gå ind i kildetekst-filen og skriv anførselstegn omkring teksten: at vise fejlmeddelelser i, og når programmet er kompileret kan man starte det fra en anden knap eller menu og se output i et specielt vindue. cout << "Meddelelser skal skrives i anførselstegn"; Nu kan programmet kompileres uden problemer. Uanset hvilket programmeringssprog man begynder på, skal man regne med en masse syntaksfejl hver gang man kompilerer. Men når man har programmeret et stykke tid bliver det lettere at finde og rette fejl. Windows-baserede miljøer For nemheds skyld har vi lavet vores små eksempler i et kommandolinie-miljø som f.eks. DOS eller UNIX. Men i dag programmerer de fleste C++ programmører i et Windows-baseret miljø f.eks. Visual C++ eller Borland's Integrated Development Environment. Alligevel er det de samme programsætninger der bruges i begge miljøer. C++ sætningerne i START.CPP vil se ud på samme måde begge steder. Forskellen er at man i et Windows-baseret miljø kompilerer og eksekverer sine programmer på en anden måde. I figur 1.1 ser vi et Windows-baseret udviklingsmiljø. I udviklingsmiljøet kan kildetekstfilerne skrives i en speciel, indbygget teksteditor, og de kan kompileres ved at vælge et menupunkt eller en knap på værktøjslinien. Måske bruger programmet et specielt vindue til Figur 1.1. Et Windows-baseret udviklingsmiljø. Man kalder det et udviklingsmiljø, fordi det indeholder alle de funktioner, der skal bruges til at skrive programkode, kompilere den og teste programmet. Opsummering I denne øvelse så vi på, hvordan man skriver og kompilerer et C++ program. I øvelse 2 skal vi se nærmere på de C++ sætninger, vi brugte i denne øvelse. Vi skal lære at arbejde med krøllede parenteser, nøgleord som void og endelig skal vi se hvordan et program ved, at det skal vise output på skærmen. Men før du kaster dig over øvelse 2 bør du lige sikre dig at du har forstået følgende: Et program er en fil, som indeholder instruktioner, computeren kan udføre. C++ programmer skrives i en tekst-editor. C++ kildetekster gemmes i filer med extension CPP. Kompileren er et program, der oversætter C++ sætningerne til maskinsprog, bestående af nuller og et-taller som computeren forstår. 10

10 1. Kom i gang med C++ Som andre sprog har C++ et sæt sprog-regler, kaldet syntaks. Hvis man ikke overholder syntaks, kommer kompileren med en fejlmeddelelse der fortæller hvad fejlen er. Alle syntaks-fejl i et program skal rettes, før kompileren kan lave det færdige program. Efter man har rettet i et programs kildetekst skal man kompilere det igen for at ændringerne kan træde i kraft. Øvelse 2 Vi kigger nærmere på C++ I øvelse 1 skrev vi et par C++ programmer. Det var ikke nødvendigt, at du skulle forstå C++ sætningerne men bare prøve at skrive og kompilere et C++ program. I øvelse 2 skal vi kigge nærmere på de sætninger, et C++ program består af, og du vil opdage at de fleste C++ programmer er opbygget på samme måde: De starter med en eller flere #include-sætninger efterfulgt af linien og derpå en række sætninger skrevet mellem krøllede parenteser. Du vil sikkert opdage, at sætningerne ikke er så svære at forstå som de umiddelbart virker. Når du har læst kapitlet igennem, vil du have lært følgende, centrale begreber: #include sætningen giver dig adgang til C++ sætninger og program-definitioner i header-filer. Hovedstrukturen i C++ programmet begynder med sætningen. Programmer består af en eller flere funktioner sammensat af sætninger der tilsammen udfører en opgave. De programmer vi skriver, vil bruge cout output-strømmen til at skrive tekst på skærmen. Når man laver et C++ program, arbejder man med sætninger ikke instruktioner. I de kommende øvelser skal vi se på tildelings-sætninger, som tildeler værdier til variabler. Derudover skal vi se på if-sætningen, som giver dig mulighed for at lave betingelser i dine programmer. Indtil videre vil vi omtale elementerne i programmet som programsætninger. Programsætninger I øvelse 1 lavede vi C++ programmet START.CPP, som bestod af følgende sætninger: cout << "Programmering i C++!"; Programmet indeholder tre sætninger. De krøllede parenteser kaldet grupperings-symboler bruges til at samle grupper af sætninger: I de følgende afsnit kigger vi nærmere på de enkelte programsætninger. #include sætningen Alle programmer i øvelse 1 startede med denne #include-sætning: Når man programmerer i C++ kan man benytte sætninger og definitioner som kompileren stiller til rådighed. Under kompileringen af programmet sørger #include-sætningen for at indholdet af den angivne fil inkluderes i starten af programmet. I dette tilfælde vil kompileren således inkludere indholdet af filen iostream.h. 11

11 C++ Filer med extension h som man på denne måde kan inkludere i starten af sit program, kaldes headerfiler. Hvis du kigger i biblioteket hvor dine kompilerfiler ligger, vil du finde et underbibliotek ved navn INCLUDE som indeholder en række forskellige headerfiler. Hver af disse filer indeholder definitioner som kompileren stiller til rådighed og som kan bruges til forskellige operationer. For eksempel er der en headerfil med definitioner til matematiske operationer og en headerfil til fil-operationer osv. Headerfilerne er tekstfiler, så hvis du har lyst, kan du se deres indhold på skærmen eller printe dem ud på papir. Foreløbig skal du ikke tænke mere på, hvad der står i headerfilerne, men blot vide at #include-sætningen giver dig mulighed for at bruge funktionerne fra disse filer. I alle C++ programeksemplerne i bogen vil der stå, hvilke #include-sætninger, du skal skrive i programmet. Headerfiler Ethvert C++ program, begynder med en eller flere #include-sætninger. Disse sætninger instruerer kompileren om at inkludere inholdet af en bestemt fil en headerfil i dit program, fuldstændig som havde du selv skrevet de sætninger, filen indeholder. Headerfiler indeholder definitioner som kompileren bruger til forskellige operationer: Nogle headerfiler definerer C++'s I/O operationer andre definerer operationer i operativsystemet, f.eks. funktioner der returnerer tidspunkt og dato, osv. Bemærk: Man må ikke ændre i en header-fil, da det kan medføre kompilerings-fejl. Void main(void) Kildeteksterne i de C++ programmer vi skriver, indeholder mange sætninger. Du vil opdage at den rækkefølge sætningerne har i kildeteksten ikke nødvendigvis er den rækkefølge de bliver udført i, når programmet kører. Men der er et fast sted, hvor udførelsen af programmet altid starter: hoved programmet. Sætningen markerer, at her starter hovedprogrammet, og her vil programmet altid starte. C++ programmer må derfor kun indeholde én sætning med ordet main. I takt med at programmerne vokser i størrelse og bliver mere komplicerede begynder man at opdele dem i mindre, mere overskuelige blokke. Men vil altid være stedet hvor program-afviklingen starter. Hvis man prøver at sætte sig ind i et stort C++ program, bør man lede efter main-sætningen for at kunne se hvor programmet starter. Hvad betyder void? Jo mere omfattende et program bliver, jo vigtigere er det at bryde det ned i mindre, lettere overskuelige, stykker kaldet funktioner (functions). En funktion er en samling sætninger i programmet der udfører en given opgave. Skal man f.eks. skrive et lønstyrings-program, kan det være en fordel at lave en funktion ved navn gage som udregner medarbejderens løn. Eller hvis man skriver et regne-program og har brug for at udregne kvadratroden af et tal: Så laver man en funktion der løser opgaven. Når et program bruger en funktion, udfører funktionen sin opgave og returnerer resultatet til programmet. Ethvert program består af mindst én funktion og enhver funktion har sit eget, unikke navn. I de programmer vi lavede i øvelse 1 var der kun en enkelt funktion, nemlig main. I øvelse 9 kommer vi nærmere ind på funktioner, men indtil da skal du bare huske, at en funktion består af én eller flere sætninger, som tilsammen udfører en given opgave. Når man kigger i forskellige C++ programmer møder man ofte ordet void. Void betyder enten, at en funktion ikke returnerer nogen værdi, eller at en funktion ikke får overført værdier til sig. DOS og UNIX programmer kan slutte med at overføre en status-værdi til operativsystemet, som f.eks. en batch-fil kan arbejde videre med. DOS batch-filer checker et programs exit-status ved hjælp af ordet ERROR- LEVEL. Forestil dig et program ved navn GA- GE.EXE som afslutter med en af følgende exitværdier afhængig af programkørslens resultat: 12

12 1. Kom i gang med C++ Status Betydning 0 Programmet forløb OK 1 En fil kunne ikke findes 2 Ikke mere papir i printeren Programmet kunne så kaldes fra en batch-fil som bruger ERRORLEVEL til at udføre en passende respons: GAGE IF ERRORLEVEL 0 IF NOT ERRORLEVEL 1 GOTO OK IF ERRORLEVEL 1 IF NOT ERRORLEVEL 2 GOTO FILMANGLER IF ERRORLEVEL 2 IF NOT ERRORLEVEL 3 GOTO PAPIRMANGLER REM Her følger øvrige batch-linier.. Programmerne i denne bog vil typisk ikke returnere nogen exit-status til operativsystemet: Derfor vil vi som regel skrive void foran main, på denne måde: Programmet returnerer ikke nogen værdi I senere øvelser skal du lære hvordan et program kan bruge information filnavne for eksempel som brugeren skriver i sin kommando-linie når han kalder programmet. Hvis et program ikke bruger information overført fra kommandolinien, skriver man ordet void mellem de parenteser som står efter main. Sådan her: Programmet bruger ikke overførte værdier På et tidspunkt får du måske brug for at skrive et program, som returnerer information til operativsystemet eller skal arbejde med information, brugeren har skrevet på kommandolinien. Men indtil videre skriver vi void i vores main sætning. Gruppering af sætninger Programsætninger, der skal udføres sammen, samler man i grupper ved hjælp af grupperingstegnene. F.eks. så vi tidligere, hvordan alle sætningerne i en funktion stod imellem to grupperings-symboler. Men inde i funktioner kan man også gruppere sætninger, som skal udføres sammen. Ved hjælp af betingelser kan man f.eks. udføre 20 sætninger hvis ét er tilfældet og nogle andre sætninger hvis noget andet er tilfældet. Starten af en grupperet blok angives med og efter den sidste sætning i blokken skrives. Output til skærmen med cout De programmer vi lavede i første øvelse skrev alle sammen tekst på skærmen. For at gøre det, brugte programmerne cout og et dobbelt mindre-end-tegn << på denne måde: cout << "Hej, C++!"; Cout er en output-strøm (output stream) som for C++ programmet er identisk med operativsystemets standard output. Almindeligvis viser operativsystemet standard output på skærmen, så for at vise en besked til brugeren, skal man bruge operatoren << - sammen med cout output-strømmen. I øvelse 3 skal vi se hvordan man bruger << operatoren til at skrive bogstaver, tal og andre tegn på skærmen. cout output-strømmen Som vi så, bruger C++ cout output-strømmen til at skrive tekst på skærmen. Det er måske lettere at forstå cout hvis man forestiller sig at programmet angiver den rækkefølge hvormed operativsystemet skal vise en række tegn på skærmen. Eller sagt på en anden måde: Tekster bliver vist på skærmen i den rækkefølge de står i programmet. I et program med disse sætninger: cout << "Først kommer denne sætning,"; cout << " efterfulgt af denne."; 13

13 C++ vil operativsystemet vise teksten på skærmen således: Først kommer denne sætning, efterfulgt af denne. << operatoren giver de programmer, man skriver mulighed for at indsætte tegn og tekst i output-strømmen - f.eks. til skærmen. Output-strømmen cout refererer til skærmen, hvilket betyder at tekst sendt til cout kommer til at stå på skærmen. Men ved hjælp af operativsystemets omdirigerings-operatorer kan man sende et programs output til en printer eller en fil: Følgende kommando omdirigerer DOS til at sende output fra START.EXE til printeren i stedet for til skærmen, som det ville have gjort hvis man havde skrevet START uden noget. En kildetekst-fil kan indeholde mange sætninger. Sætningen angiver hvor hovedprogrammet starter I mere omfattende programmer grupperer man relaterede sætninger i mindre, mere overskuelige, blokke kaldet funktioner. Inde i funktioner kan relaterede sætninger yderligere grupperes ved hjælp af parenteser. I C++ bruger man almindeligvis cout output-strømmen til at skrive information på skærmen, men hvis brugeren kalder programmet med en omdirigerings-operator kan output i stedet sendes til en fil eller anden enhed (f.eks. en printer) eller sågar fungere som input til et helt andet program. C:\CPP\> START > PRN <ENTER> I øvelse 3 skal vi se hvordan man i C++ skriver bogstaver, heltal og decimaltal til skærmen ved hjælp af cout. Og i øvelse 8 skal vi lære om cout's modsætning: input-strømmen cin som vores programmer kan bruge til at læse information som brugeren skriver på tastaturet. Opsummering I denne øvelse prøvede vi mange af de ting man skal kunne når man programmerer i C++. I øvelse 3 går vi mere i detaljer med cout som vi skal bruge til at skrive tegn og tal på skærmen. Vi skal også se hvordan man formaterer output altså hvordan man styrer hvordan teksten kommer til at stå på skærmen. Før vi går videre bør du sikre dig at du har forstået følgende vigtige ting: De fleste C++ programmer starter med en eller flere #include-sætninger, som instruerer kompileren om at inkludere indholdet af den angivne header-fil i det færdige program. Header-filerne følger med kompileren og indeholder definitioner som man kan bruge i sine programmer. Øvelse 3 Udskrift til skærmen I programmerne i de to foregående øvelser, brugte vi cout output-strømmen til at skrive tekst på skærmen. I denne øvelse vil vi bruge cout til foruden at skrive bogstaver også at skrive heltal som f.eks og decimaltal som f.eks. 0, Efter øvelsen vil vi have gennemgået følgende centrale ting: cout bruges til at skrive bogstaver og tal på skærmen. cout bruges også til at lave special-tegn, f.eks. spring til ny linie, tabulator-indrykning eller til at få computeren til at bippe. I C++ er det let at vise tal i decimal, octal (base 8) og hexadecimal (base 16) format. Ved hjælp af operativsystemets omdirigerings-operator kan man omdirigere cout's output fra skærmen til en fil eller printer. Ved hjælp af output-strømmen cerr kan et program skrive til standard error enheden 14

14 1. Kom i gang med C++ og på den måde undgå, at brugeren af programmet omdirigerer dets output. Ved at bruge setw modifieren i en outputstrøm kan man bestemme bredden af output. De fleste C++ programmer i denne bog bruger cout til at vise tekst på skærmen. I denne øvelse skal vi se, hvordan man bedst bruger cout. Skrivning af tal ved hjælp af cout I de programmer, vi har lavet indtil nu, har vi kun brugt cout til at skrive tekst-strenge (bogstaver og tal, skrevet mellem anførselstegn) men cout kan også vise tal. Det følgende program 1001.CPP skriver tallet 1001 på skærmen: cout << 1001; Skriv kildeteksten, kompilér den, og start programmet. På skærmen vil der stå følgende: C:\CPP\> 1001 <ENTER> 1001 Lav derefter programmet om så det viser tallet 2002 på denne måde: cout << 2002; Ud over at kunne vise heltal kan cout også bruges til at skrive decimaltal som f.eks. 1,2345. Det følgende program DECIMAL.CPP bruger cout til at vise tallet 0,12345 på skærmen. cout << ; Skriv, kompilér og start programmet. Skærmen vil vise følgende output. C:\CPP\> DECIMAL <ENTER> Skrivning af flere værdier i én sætning Når man bruger cout kan man sagtens bruge insertion operatoren flere gange i hver sætning. F.eks. bruger det følgende program, 1001FLER.CPP, operatoren fire gange for at vise tallet 1001 på skærmen cout << 1 << 0 << 0 << 1; Kompilér og kør programmet. Skærm-outputtet kommer til at se sådan ud: C:\CPP\> 1001FLER <ENTER> 1001 Når C++ møder << operatoren smides de anførte tegn ganske enkelt i enden på de tegn, der allerede måtte være i output-strømmen. Det følgende program, VIS1001.CPP skriver en tekst-streng og et tal til skærmen ved hjælp af cout. cout << "Mit yndlingstal er " << 1001; Læg mærke til mellemrummet efter ordet er. Hvis ikke det var der, ville 1001 stå helt op imod teksten (er1001). På samme måde vises 15

15 C++ tallet 1001 midt i en tekst-streng i dette program: output delt op i to linier ved hjælp af nylinietegnet: cout << "Tallet " << 1001 << " er mit yndlingstal"; Her adskiller vi også tallet med to mellemrumstegn. cout << "Her er første linie\nher er anden linie"; Kompilér og kør programmet. Brugen af nylinie-tegnet medfører følgende udskrift: Lad os til sidst bruge cout til at skrive en kombination af tekst, heltal og decimaltal i den samme sætning: C:\CPP\> TOLINIER Her er første linie Her er anden linie <ENTER> cout << "Da jeg var " << 20 << " år, var min månedsløn på " << << " kroner." << endl; Gem kildeteksten som TEKSTTAL.CPP. Når du kompilerer og kører programmet vil skærmen vise denne tekst: C:\CPP\> TEKSTTAL <ENTER> Da jeg var 20 år, var min månedsløn på kroner. Specielle output-tegn De programmer vi har lavet indtil nu, har kun vist tekst på skærmen i en enkelt linie. Men ofte har man brug for at udskrive til flere linier: Det vil f.eks. være en fordel at teksten står fordelt på flere linier, hvis man vil lave et program der skriver en adresse på skærmen. Når man vil flytte skrivningen til en ny linie, skriver man nylinie-tegnet (\n) i output-strømmen. Dette kan gøres på to måder. Den ene måde er ganske enkelt at placere \n i selve strengen. I det følgende program, TOLINIER.CPP, bliver Hvis der ikke skal være tekst i den linie, man vil skrive, men kun tal, skal man skrive nylinie-tegnet mellem enkelte anførselstegn. Det følgende program, NYLINIER.CPP, viser tallene 1, 0, 0 og 1 på hver sin linie: cout << 1 << \n << 0 << \n << 0 << \n << 1; I stedet for nylinie-tegnet, kan man benytte konstanten endl (end line) som f.eks. i følgende program, ENDL.CPP, som viser hvordan endl flytter output-markøren til starten af næste linie: cout << "Jeg er..." << endl << "Vild med C++"; Ligesom i de to andre eksempler, vil skærmen vise teksten på to linier, når du har kompileret og startet programmet: 16

16 1. Kom i gang med C++ C:\CPP\> ENDL <ENTER> Jeg er... Vild med C++ Til sidst kan vi prøve et program lad os kalde det ADRESSE.CPP der udskriver en adresse på flere linier: cout << "Flyttemand Olsen"<< endl; cout << "Amagergade 6,2.tv"<< endl; cout << "1423 København K."<< endl; Andre specialtegn Foruden nylinie-tegnet som får skærmen til at springe til næste linie, er der en række andre specialtegn. I tabel 3.1 kan du se hvad de hedder og hvad de gør. Bemærk: Når du bruger special-tegnene fra tabel 3.1 skal du skrive dem i enkelte anførselstegn hvis tegnene skal stå for sig selv, f.eks. \n. Hvis du bruger tegnene inde i en tekst-streng, skal de stå mellem dobbelte anførselstegn, som f.eks. "Hello\nWorld" Tegn Virkning \a Alarm (bip-lyd) \b Backspace \f Papir-indføring (Formfeed) \n Spring til ny linie \r Retur (uden nylinie) \t Horisontal tabulator-indrykning \v Vertikal tabulator-indrykning \\ Bagudvendt skråstreg (Backslash) \? Spørgsmålstegn \ Enkelt anførselstegn \" Dobbelt anførselstegn \0 NULL tegnet \ooo Octal værdi f.eks. \007 \xhhhh Hexadecimal værdi f.eks. \xffff Tabel 3.1. Specielle tegn til brug med cout I det følgende program, SPECIAL.CPP, bruger vi alarm-tegnet (\a) og tabulator-tegnet (\t) til først at bippe med computerens indbyggede højtaler og derefter at vise ordene Bip Bip Bip adskilt af tab. cout << "Bip\a\tBip\a\tBip\a"; Skrivning af octale og hexadecimale værdier Foreløbig har vi i vores programmer kun arbejdet med decimaltal men man kan få brug for at skrive tekst som octal eller hexadecimal. For at gøre det, placeres output modifierne dec, oct og hex i output-strømmen. Det følgende program, OCTHEX.CPP, bruger disse modifiers til at skrive værdier i decimal, octal og hexadecimal: cout << "Octal: " << oct << 10 << << 20 << endl; cout << "Hexadecimal: " << hex << 10 << << 20 << endl; cout << "Decimal: " << dec << 10 << << 20 << endl; Gem kildeteksten, kompilér den, og start programmet. Skærmen skulle gerne vise følgende output: C:\CPP\> OCTHEX <ENTER> Octal: Hexadecimal: a 14 Decimal: Bemærk: Hvis der i dit program er brugt en output-modifier, vil den valgte modifier være aktiv 17

17 C++ indtil en ny vælges: Hvis man f.eks. har brugt octmodifieren vil alle tal vises som octal, indtil programmet bruger dec-modifieren hvorefter alle tal vil vises som decimaltal. Skrivning til standard error enheden Som vi så tidligere, kan output fra et program der bruger cout, omdirigeres til en anden enhed eller fil ved hjælp af operativsystemets omdirigerings-operatorer. Men hvis et program møder en fejl, er det ikke ønskeligt at fejlmeddelelsen bliver bortdirigeret fra skærmen. Det kunne betyde at brugeren ikke bliver klar over, at der er opstået en fejl. Så hvis et program skal vise en fejlmeddelelse eller hvis man bare vil undgå at brugeren omdirigerer output kan man bruge outputstrømmen cerr. I C++ forbindes cerr altid med operativsystemets standard error enhed. Det følgende program, CERR.CPP, bruger cerr output-strømmen til at skrive 'Denne tekst bliver altid skrevet på skærmen' på skærmen: cerr << "Denne tekst bliver skrevet på skærmen"; Kompilér og start programmet, og prøv så at omdirigere programmets output til en fil: C:\CPP\> CERR > FILNAVN.TXT <ENTER> Denne tekst bliver skrevet på skærmen Operativsystemet tillader ikke at tekst til standard error enheden bliver omdirigeret, så derfor vil teksten blive skrevet på skærmen. Styring af output-bredde Vi har allerede skrevet flere programmer, der viser tal på skærmen, og for at sikre os at tallene stod i en pæn afstand fra teksten, placerede vi mellemrum før og efter dem. Men når man bruger cout og cerr, kan man også angive, hvor meget et tal skal fylde på skærmen. Dette gøres med modifieren setw (set width sæt bredde). Med den kan programmet specificere, hvor mange tegn en værdi mindst skal fylde. I det følgende program, SETW.CPP, bruger vi setw til at skrive tallet 1001 med bredder på 3, 4, 5 og 6 tegn. For at bruge setw, skal programmet inkludere headerfilen iomanip.h: #include <iomanip.h> cout << "Mit yndlingstal er" << setw(3) << 1001 << endl; cout << "Mit yndlingstal er" << setw(4) << 1001 << endl; cout << "Mit yndlingstal er" << setw(5) << 1001 << endl; cout << "Mit yndlingstal er" << setw(6) << 1001 << endl; Når dette program kompileres og startes, vil det skrive dette på skærmen: C:\CPP\> SETW <ENTER> Mit yndlingstal er1001 Mit yndlingstal er1001 Mit yndlingstal er 1001 Mit yndlingstal er 1001 Som det fremgår af første linie i programmet, angiver setw den minimums-bredde en værdi vil fylde: er værdien på flere tegn end den angivne minimumsbredde, vil værdien stadigvæk vises i sin fulde længde. I modsætning til oct, hex og dec-modifierne, virker setw kun på den værdi, der følger umiddelbart efter modifieren. Et program der vil sætte output-bredden for flere tal, skal bruge setw før hvert tal der skal vises. Bemærk: Nu hvor vi har introduceret en ny headerfil IOMANIP.H er det en god idé at printe den og se hvad den indeholder. IOMANIP.H ligger almindeligvis i INCLUDE-biblioteket under det bibliotek, hvor kompileren ligger. 18

18 1. Kom i gang med C++ Opsummering I denne øvelse har vi set på forskellige måder at vise tekst og tal på skærmen med cout. I resten af bogen bruges cout til at vise output. I øvelse 4 skal vi se hvordan man bruger variabler til at gemme værdier, som kan ændres under program-afviklingen. Men før vi går i gang med det, bør du sikre dig at du har forstået følgende: Output-strømmen cout bruges til at vise tekst og tal på skærmen. Ved at bruge special-tegn i output-strømmen kan programmer springe til ny linie, lave tabulator-indrykninger mm. Et program kan flytte udskrivningen til næste linie på skærmen ved at bruge \ntegnet eller endl-konstanten. Modifierne dec, oct og hex gør det let at vise tal som decimal, octal og hexadecimal. Output-strømmen cerr bruges til at skrive meddelelser til operativsystemets standard output enhed (skærmen). Ved hjælp af setw modifieren kan man styre hvor mange tegn en værdi mindst skal fylde på skærmen. Øvelse 4 Information opbevares i variabler Foreløbig har vi arbejdet med nogle enkle eksempler, men efterhånden som vores programmer skal udføre mere komplekse og brugbare opgaver, er de nødt til at kunne opbevare information, mens programmet afvikles. Et program der skal printe en fil skal f.eks. kunne huske navnet på filen og evt. hvor mange eksemplarer, der skal printes. Disse værdier skal ligge i computerens hukommelse, og for at kunne arbejde med hukommelsen skal man bruge variabler. En variabel er et navn som peger på en adresse i hukommelsen hvor der ligger en værdi. Denne værdi kan programmet læse og ændre, men navnet på variablen ændrer sig ikke under program-afviklingen. I denne øvelse skal vi se hvordan man i C++ opretter og bruger variabler. Efter øvelsen skulle du gerne have forstået følgende: Man skal oprette erklære de variabler, man vil bruge i et program. En variabels type fortæller hvad slags værdi f.eks. heltal eller decimaltal variablen kan opbevare. Typen fortæller også hvilke operationer, man kan bruge på variablen. For at tildele en variabel en værdi, bruger man C++'s tildelings-operator som er et lighedstegn. Man kan bruge output-strømmen cout til at skrive værdien af en variabel til skærmen. Variabelnavne bør være forståelige og gøre programmet lettere at læse. Skriv mange kommentarer i programmet der detaljeret forklarer hvad programmet gør. Det gør det lettere senere at ændre i programmet, både for dig selv og eventuelle andre programmører. En variabel kan opfattes som en kasse, hvori man lægger en værdi: Når man på et andet tidspunkt skal bruge værdien, skriver man blot variabelnavnet. Så bruger computeren den værdi, variablen indeholder. Erklæring af variabler i programmet Et program bruger variabler til at gemme information. Variabler kan have forskellig type, alt efter om variablen skal indeholde et heltal, et bogstav, et decimaltal eller en type, man selv definerer. Variablens type fortæller også hvad man i programmet kan gøre med variablens værdi: om man f.eks. kan lægge noget til den, gange den osv. C++ kan bruge de variabel-typer der er angivet i tabel 4.1: 19

19 C++ Type char Men før man i sit program kan bruge en variabel, skal den erklæres hvilket betyder at man gør kompileren opmærksom på den nye variabel. Erklæringen foregår ved at man specificerer den type man skal bruge, efterfulgt af det navn, man vil give variablen. Dette gøres efter start-parentesen i hoved-programmet main: variabel_type variabel_navn; Almindeligvis vil typen være en af typerne angivet i tabel 4.1. Det navn man giver variablen bør være et forståeligt ord, der beskriver hvad variablen skal bruges til. Man kan f.eks. bruge variabelnavne som medarbejdernavn, medarbejderalder osv. I det følgende program erklæres tre variabler af typerne int, float og long og da C++ opfatter en variabel-erklæring som en sætning, er det nødvendigt at afslutte linierne med semikolon: int test_point; float gage; long afstand_til_mars; Mulige værdier Værdier i området 128 til 127. Bruges typisk til at indeholde tegn og bogstaver fra alfabetet. int Værdier mellem og unsigned Værdier mellem 0 og long float double Værdier mellem og Værdier mellem 3,4 x og 3,4 x Værdier mellem 1,7 x og 1,7 x Tabel 4.1. Variabel-typer i C++ Dette program gør ikke andet end at erklære de tre variabler. Hver variabel-erklæring slutter med semikolon. I C++ kan man erklære flere variabler af samme type ved at skrive dem i samme sætning, adskilt af komma. I det følgende eksempel erklæres tre variabler af typen float: float gage, skatte_procent, pensions_opsparing; Brug forståelige variabelnavne Hver variabel i et program skal have sit eget navn, og for at gøre det lettere at læse og forstå programmet bør man bruge forståelige variabelnavne. Der er ganske vist ikke noget teknisk i vejen for at erklære variabler sådan her: int x, y, z; Men hvis variablerne skal bruges til at gemme en elevs alder, point-antal og karakter, vil det være bedre at erklære variablerne således: int elev_alder, test_point,karakter; Variabelnavne må sammensættes af bogstaver, tal og understregninger (_). Det første tegn i variabelnavnet skal dog være enten et bogstav eller en understregning aldrig et tal. Man må heller ikke bruge æ, ø og å i variabelnavne kun de engelske bogstaver i ASCII-tabellen. C++ skelner også mellem store og små bogstaver. Brug derfor foreløbig kun små bogstaver til variabler senere kan du udvide med store bogstaver som f.eks. her: float MaanedsLoen, SkatteProcent; Da æ, ø og å ikke må bruges i variabelnavne, skriver danske programmører tit ø som oe, æ som ae, og å som aa. Ulovlige variabelnavne C++ indeholder nogle reserverede ord som har en speciel betydning for kompileren og som derfor ikke må bruges som variabelnavne. Disse ord er anført i tabel

20 1. Kom i gang med C++ asm auto break case catch char class const continue default delete do double else enum extern float for friend goto if inline int long new operator private protected public register return short signed sizeof static struct switch template this throw try typedef union unsigned virtual void volatile while Tabel 4.2. Reserverede ord i C++ Derfor bruger programmer variabler Jo mere omfattende vores programmer bliver, jo mere nødvendigt er det at de kan udføre den samme kode på forskellige elementer: Hvis man f.eks. skriver et lønstyrings-program, skal det kunne udføre skatteberegning for hver ansat i virksomheden. For at kunne gøre det, kan man bruge variabler med navne som ansat_navn, ansat_id, ansat_gage osv. Når programmet starter vil det tildele variablerne værdier for én ansat og behandle hans data, hvorefter programmet vil tildele variablerne en ny medarbejders data og bruge de samme funktioner til at beregne data. Mens programmet kører bliver de samme variabler gentagne gange tildelt nye værdier. Disse værdier giver forskellige resultater som igen lægges i de samme variabler. Tildeling af værdier Variabler indeholder altså værdier mens programmet kører. Når en variabel er erklæret, kan man tildele den en værdi ved hjælp af C++'s tildelings-operator lighedstegnet (=). I de følgende sætninger tildeler vi værdier til forskellige variabler. Læg igen mærke til brugen af semikolon som afslutning på en sætning: alder = 32; gage = ; afstand_til_maanen = ; Bemærk: Man må ikke bruge komma i de værdier, man tildeler en variabel (f.eks ,75 eller som tusind-separator: 25,000.75). C++ kompileren kommer med en fejlmeddelelse hvis den møder et komma i en tildeling af en tal-værdi. I det følgende program erklæres de nævnte variabler hvorefter de hver især tildeles en værdi ved hjælp af tildelings-operatoren: int alder; float gage; long afstand_til_maanen; alder = 32; gage = ; afstand_til_maanen = ; Tildeling af en værdi ved erklæringen Ofte er det en fordel at kunne give en variabel en startværdi samtidig med at den erklæres. Som det ses her er det let at gøre i C++: int alder = 32; float gage = ; long afstand_til_maanen = ; Disse tre linier svarer til de seks i foregående eksempel. I resten af bogen vil vi ofte tildele en variabel en værdi samtidig med at den erklæres. 21

21 C++ Brug af variabel som værdi Når man i sit program har tildelt en værdi til en variabel, kan man bruge variablen på samme måde som man vil kunne bruge dens værdi. Har man f.eks. tildelt værdien 2 til variablen tal, vil følgende to udregninger give samme resultat: 2 * 4 tal * 4 I det følgende program, VISVAR.CPP, tildeler vi tre værdier til hver sin variabel og viser derefter variablernes indhold på skærmen med cout. int alder = 32; float gage = ; long afstand_til_maanen = ; cout << "Medarbejderen er " << alder << " år gammel" << endl; cout << "Medarbejderens månedsløn er kr. " << gage << endl; cout << "Månen er " << afstand_til_maanen << " km. fra Jorden" << endl; Bemærk: De sidste sætninger kan ikke stå på én linie hvorfor de er delt op i flere: Dette er helt i orden i C++ da kompileren opfatter semikolon som afslutningen af en sætning. Dog kan man ikke dele en linie midt i en tekststreng men helst før eller efter en operator. For læsevenlighedens skyld bør man også rykke den ombrudte linie ind så den ikke opfattes som starten på en ny sætning. Når programmet kompileres og startes, skriver det følgende på skærmen: C:\CPP\> VISVAR <ENTER> Medarbejderen er 32 år gammel Medarbejderens månedsløn er kr Månen er km. fra Jorden Som det fremgår, bruger man en variabels værdi ved i programmet at skrive variablens navn. Overskridelse af en variabels kapacitet Som vi har set, definerer variablens type de mulige værdier, variablen kan indeholde: F.eks. kan en variabel af typen int indeholde værdier i området fra til Men hvis man i programmet tildeler en variabel en værdi som er uden for det acceptable område, vil man få en overflow-fejl. I det følgende program ser vi, hvad der sker, hvis man tildeler variabler værdier som falder uden for det mulige område: int positiv = 40000; long stor_positiv = ; char lille_positiv = 210; cout << "positiv indeholder nu " << positiv << endl; cout << "stor_positiv indeholder nu " << stor_positiv << endl; cout <<"lille_positiv indeholder nu " << lille_positiv << endl; Når dette program kompileres og køres, vil skærmen vise følgende output: positiv indeholder nu stor_positiv indeholder nu lille_positiv indeholder nu Ò Programmet tildeler variabler værdier som overstiger det højeste tal, de hver især kan indeholde, hvilket resulterer i en overflow-fejl. 22

22 1. Kom i gang med C++ Der skal ikke meget til for at lave overflowfejl, og de kan være meget svære at finde og rette. Bemærk den værdi som programmet viser for lille_positiv: Da den er af typen char vil programmet forsøge at skrive et tegn på skærmen. I dette tilfælde skrives et tegn svarende til ASCII-tegnet med værdien 210. Præcision Vi har nu set, at overflow-fejl opstår, når et program tildeler en variabel en værdi, der falder uden for det område, variablens type kan rumme. Ligeledes har computere ikke ubegrænset præcision, når de gemmer tal i variabler. Sommetider kan computeren f.eks. ikke repræsentere den rigtige værdi af en float-variabel (decimaltal). Sådanne præcisions-fejl kan være svære at opdage i et program. I det følgende program, PRECISE.CPP, tildeler vi en værdi som er lavere end 0.5 til float- og double-variabler, men da computerens evne til at repræsentere tal er begrænset, vil variablerne ikke indeholde den tildelte værdi, men derimod værdien 0.5: float f_knap_halv = ; double d_knap_halv = ; cout << "Float er " << f_knap_halv << endl; cout << "Double er " << d_knap_halv << endl; Når programmet kompileres og startes vil det vise dette output: Float er 0.5 Double er 0.5 Den værdi, et program tildeler en variabel er altså ikke nødvendigvis den værdi, variablen rent faktisk kommer til at indeholde. Disse præcisions-fejl opstår fordi computeren kun kan bruge et begrænset antal nuller og et-taller til at indeholde et tal. Selvom variabler ofte indeholder de rigtige værdier, kan man altså også komme ud for at værdien er næsten rigtig men ikke helt. Hav risikoen for præcisions-fejl i baghovedet, når du programmerer: Så kan du undgå irriterende fejl og kedelig fejlsøgning. Kommentarer gør programmer lettere at læse Jo mere omfattende et programs kildetekst bliver og jo flere sætninger det indeholder, jo sværere bliver det at læse og forstå. Måske skal andre programmere i dit program, og for at de kan forstå detaljerne i programmet, er det en god idé at skrive sin kode på den mest læsevenlige måde. Der er flere teknikker, man kan bruge, når man programmerer: Brug variabelnavne der beskriver hvad variablen bruges til. Hold en konsekvent indrykning af program-sætninger (se øvelse 7). Saml sammenhængende sætninger i grupper adskilt af tomme linier. Skriv kommentarer i programmet der beskriver hvad der foregår. Det er en god ide at skrive kommentarer i kilde-teksten som beskriver programmets funktion. Sådanne kommentarer hjælper dig selv (og eventuelle andre programmører) med dels at huske og dels at forstå, hvad det er, programmet gør. I C++ kan man skrive en kommentar ved ganske enkelt at placere to skråstreger (//) foran kommentaren. // Her er en kommentar Når C++kompileren møder to skråstreger, ignorerer den resten af den pågældende linie. F.eks. er det altid en god idé i det mindste at skrive, hvem der har lavet programmet, hvornår det er skrevet, og hvad programmet skal bruges til. Dette gøres typisk i starten af kildetekst-filen: 23

23 C++ // Program: BUDGET.CPP // Programmør: Kris Jamsa // Dato skrevet: // // Formål: samler budget-oversigter. De forskellige sætninger i programmet bør også beskrives. Enten ved at skrive en kommentar på linien før sætningen, eller hvis der er plads til det på samme linie som programsætningen. // Afstand i kilometer afstand_til_maanen = ; Kommentaren til højre for tildelings-operatoren giver læseren af kildeteksten yderligere information om, hvad sætningen gør. Som nystartet programmør kan det være svært at vurdere, hvad der skal kommenteres og hvornår: Som tommelfingerregel kan man gå ud fra, at man ikke kan skrive for mange kommentarer i sit program. Til gengæld er det vigtigt, at kommentarerne giver mening. Følgende kommentar giver ikke læsere af program-teksten nogen relevant information. //Tildeler værdien 32 til alder alder = 32; //Tildeler værdien til gage gage = ; Meningen med kommentarer er at forklare hvorfor de enkelte sætninger står i programmet. Derudover er det en god ide at adskille sætningsgrupper der udfører forskellige ting med tomme linier. Kompileren er ligeglad med tomme linier det gør ingen forskel for det færdige program. Opsummering I denne øvelse så vi, at et program gemmer information i variabler. En variabel er et navn som peger på en værdi som dels kan ændres i løbet af programmet og dels kan bruges som værdi i sætninger. Variabler skal erklæres med angivelse af den ønskede type, før de kan bruges i programmet. I øvelse 5 skal vi udføre beregninger f.eks. addition og subtraktion hvor der indgår variabler. Men før vi kaster os over det bør du lige sikre dig at du har forstået følgende: Variabler skal erklæres før de kan bruges. To variabler må ikke have samme navn, og navnene skal give en forståelse for hvad variablen bruges til. Variabelnavne skal starte med enten et bogstav eller en understregning. C++ skelner mellem store og små bogstaver. Variablens type bestemmer hvilken slags værdier, den kan indeholde. Det kan f.eks. være char, float eller long. Kommentarer øger læsevenligheden af et program ved at forklare forløbet for læseren. Kommentarer starter med //. Øvelse 5 Grundlæggende operationer I øvelse 4 så vi hvordan variabler erklæres og bruges i programmer. Men det er også nødvendigt at kunne lægge en værdi til den værdi, en variabel indeholder, eller at kunne trække en værdi fra. Ligeledes skal et program naturligvis kunne gange og dividere variablernes værdier. I denne øvelse skal vi lære følgende: C++ har en række aritmetiske operatorer som bruges til regnefunktioner. Disse operatorer er prioriteret efter i hvilken rækkefølge de bliver udført hvis flere operatorer optræder i samme sætning. Ved hjælp af parenteser kan man styre rækkefølgen hvormed C++ udfører de enkelte del-beregninger. 24

24 1. Kom i gang med C++ En hurtig måde at lægge 1 til, eller trække 1 fra en variabel er at bruge C++'s inkrementerings- (++) og dekrementerings- (--) operatorer. Med kendskab til C++'s aritmetiske operatorer er det let at foretage regne-operationer i sine programmer. Grundlæggende regne-operationer Uanset hvad ens program skal bruges til, vil det før eller senere blive nødvendigt at lægge værdier sammen, trække værdier fra, gange og dividere. Foruden regne-operationer på konstanter (f.eks. 3 * 5) kan programmet udføre regneoperationer på variabler (f.eks. indkomst - skat). I tabel 5.1 kan du se en oversigt over de fundamentale regneoperationer i C++: Operator Bruges til Eksempel + Addition udgift = pris + moms; Subtraktion byttepenge = betalt pris; * Multiplikation moms = pris * moms_sats; / Division gennemsnit = total / antal; Tabel 5.1. Fundamentale regneoperationer i C++ I det følgende program, BEREGN.CPP, bruger vi cout til at vise resultaterne af nogle enkle beregninger: cout << "5 + 7 = " << 5+7 << endl; cout << "12-7 = " << 12-7 << endl; cout << " * 2 = " << * 2 << endl; cout << "15 / 3 = " << 15/3 << endl; Begyndelsen af linierne som bliver vist på skærmen starter med en tekst-konstant (f.eks. "5 + 7 = "). For kompileren er det ikke en beregning, men bare en tekst som bliver udskrevet. Efter streng-konstanten kommer så selve udregningen som bliver udskrevet som resultatet af beregningen. Sætningerne afsluttes med nylinie-tegnet. Kompilér og start programmet det skulle gerne vise følgende output: C:\CPP\> BEREGN <ENTER> = = * 2 = / 3 = 5 I dette eksempel brugte vi kun konstanter som værdier i beregningen, men i det følgende program, REGNVAR.CPP, vil vi lave nogle beregninger hvori der indgår variabler: // En vares pris float pris = 14.00; // momssatsen er 25 % float momssats = 0.25; // Beløbet, kunden betaler float betalt = 20.00; // moms, købers byttepenge + // pris m. moms float moms, byttepenge, totalpris; moms = pris * momssats; totalpris = pris + moms; byttepenge = betalt - totalpris; cout << "Varens pris: kr." << pris << "\t+moms: kr." << moms <<endl; cout << "Ialt: kr." << totalpris << endl; cout << "Betalt: kr." << betalt << endl; cout << "Byttepenge: kr." << byttepenge << endl; 25

25 C++ I dette program bruger vi kun variabler af typen float, og vi tildeler dem værdier ved erklæringen. Derefter beregner programmet moms, totalpris og hvor mange byttepenge kunden skal have tilbage. Programmet skriver følgende på skærmen når det bliver kompileret og startet: C:\CPP\> REGNVAR <ENTER> Varens pris: kr.14 +moms: kr.3.5 Ialt: kr.17.5 Betalt: kr.20 Byttepenge: kr.2.5 Inkrementering af en variabels værdi med 1 En operation, man ofte udfører, er at forhøje en variabels værdi med én. F.eks. kunne et program bruge en variabel, antal, til at holde styr på hvor mange sider, det havde printet: Hver gang det har printet en side, forhøjer den værdien af antal med én. I C++ kan det gøres sådan her: antal = antal + 1; I beregningen (den del af sætningen som står til højre for lighedstegnet) henter programmet først værdien af antal og lægger derefter ét til denne værdi. Resultatet tildeles så variablen antal (som står til venstre for tildelings-operatoren lighedstegnet). I det følgende program, ANTAL.CPP, bruger vi tildelings-operatoren til at inkrementere variablen antal med én: int antal = 1000; cout <<"Startværdien af antal er " << antal << endl; antal = antal + 1; cout << "Slutværdien af antal er " << antal << endl; Når programmet kompileres og startes, vil det vise følgende tekst på skærmen: C:\CPP\> ANTAL <ENTER> Startværdien af antal er 1000 Slutværdien af antal er 1001 Men da dét at forhøje en variabels værdi med én, er en hyppigt anvendt operation findes der i C++ en speciel inkrementerings-operator det dobbelte plus (++). Inkrementerings-operatoren er en hurtig måde at forhøje en variabels værdi på. Følgende to sætninger inkrementerer begge variablen antal med én: antal = antal + 1; antal++; I det følgende program, INKOP.CPP, bruger vi inkrementerings-operatoren: int antal = 1000; cout<< "Startværdien af antal er " << antal << endl; antal++; cout << "Slutværdien af antal er " << antal << endl; Dette program gør præcis det samme som ANTAL.CPP hvor vi brugte tildelings-operatoren til at forhøje variablens værdi. Programmet tager simpelthen variablens værdi, forhøjer værdien med én, og lægger resultatet tilbage i samme variabel. Forskellen på prefix (foranstillet) og postfix (bagvedstillet) inkrementerings-operator Inkrementereingsoperatoren kan placeres enten foran eller bag ved variablen: 26

26 1. Kom i gang med C++ ++variabel; //prefix inkrementering variabel++; //postfix inkrementering Når operatoren skrives foran variablen kaldes den prefix inkrementerings-operatoren og når den skrives bagved kaldes den postfix inkrementerings-operatoren. C++ behandler de to måder forskelligt se f.eks. følgende tildelinger: antal_nu = antal++; Tildelingen instruerer C++ om at tildele antal's nuværende værdi til varablen antal_nu. Først når det er sket, bliver antal forhøjet med én. Eftersom vi brugte postfix-operatoren svarer sætningen nøjagtig til at have skrevet følgende to sætninger: antal_nu = antal; antal = antal + 1; Sammenlign ovenstående med dette eksempel, hvor vi bruger prefix-operatoren: antal_nu = ++antal; I denne sætning forhøjes antal med én hvorefter resultatet tildeles variablen antal_nu. Prefix-operatoren betyder at sætningen svarer til følgende sætninger: antal = antal + 1; antal_nu = antal; Det er vigtigt at kunne skelne mellem prefixog postfix-operatorerne idet de bruges i de fleste C++ programmer. Lad os prøve operatorerne i et lille program, PREPOST.CPP: int lille_antal = 0; int stort_antal = 1000; cout << "lille_antal er " << lille_antal << endl; cout << "lille_antal++ giver " 4 << lille_antal++ << endl; cout << "lille_antal slutter på " << lille_antal << endl; cout << "stort_antal er " << stort_antal << endl; cout << "++stort_antal giver " << ++stort_antal << endl; cout << "stort_antal slutter på " << stort_antal << endl; Når programmet kompileres og startes skriver det følgende på skærmen: C:\CPP\> PREPOST <ENTER> lille_antal er 0 lille_antal++ giver 0 lille_antal slutter på 1 stort_antal er stort_antal giver 1001 stort_antal slutter på 1001 Fordi programmet bruger postfix inkrementerings-operatoren på lille_antal, skrives variablens nuværende værdi (0) til skærmen hvorefter værdien forhøjes med én. Omvendt med stort_antal hvor vi bruger prefix inkrementerings-operatoren: Her starter C++ med at forhøje variablens værdi ( ) for derefter at udskrive resultatet til skærmen (1001). Prøv at bytte om på programmets postfix- og prefixoperatorer og se hvordan det påvirker programmets output. C++ har også en dekrementerings-operator Som vi lige har set, kalder man i C++ det dobbelte plus-tegn for inkrementerings-operatoren. I C++ er der også en såkaldt dekrementerings-operator, som skrives som et dobbelt minus-tegn, og som formindsker en variabels værdi med én. På samme måde som med inkrementerings-operatoren kan dekrementerings-operatoren også skrives som enten prefix eller postfix. Det følgende program, DEKAN- 27

27 C++ TAL.CPP, viser brugen af C++'s dekrementerings-operator: int lille_antal = 0; int stort_antal = 1000; cout << "lille_antal er " << lille_antal << endl; cout << "lille_antal-- giver " << lille_antal-- << endl; cout << "lille_antal slutter på " << lille_antal << endl; cout << "stort_antal er " << stort_antal << endl; cout << "--stort_antal giver " << --stort_antal << endl; cout << "stort_antal slutter på " << stort_antal << endl; Kompilér og kør programmet. Det skulle gerne vise følgende output på skærmen: C:\CPP\> DEKANTAL <ENTER> lille_antal er 0 lille_antal-- giver 0 lille_antal slutter på -1 stort_antal er stort_antal giver 999 stort_antal slutter på 999 Som det fremgår af eksemplerne, bruges prefix- og postfix-operatorerne på samme måde hvadenten der er tale om inkrementering eller dekrementering. Andre C++ operatorer Denne øvelse har behandlet de fundamentale regne-operatorer i C++ samt inkrementeringsog dekrementerings-operatorerne. Tabel 5.2 viser en liste over andre operatorer i C++: Operator Funktion % Modulo (rest) operator; returnerer rest efter en heltals-division ~ Bitvis komplement: vender en variabels bit-værdi. & Operator-prioritet Når man laver beregninger i sine programmer er det vigtigt at huske at C++ udfører del-beregningerne i en bestemt rækkefølge prioriteret efter operator: F.eks. vil en multiplikation udføres før en addition. Følgende eksempel demonstrerer operator-prioriteringen: resultat = * 3; Afhængig af den rækkefølge, C++ udfører beregningen i, vil det resultere i to forskellige resultater, enten: resultat = * 3; = 7 * 3; = 21; eller: Bitvis AND operator; AND'er bittene fra to værdier Bitvis OR operator; OR'er bittene fra to værdier ^ Bitvis exclusive OR (XOR); XOR'er bittene mellem to værdier << Bitvis venstre skift; Rykker bittene i en værdi til venstre så mange gange som angivet >> Bitvis højre skift; Rykker bittene i en værdi til højre så mange gange som angivet Tabel 5.2. C++ operatorer resultat = * 3; = 5 + 6; = 11; For at man som programmør ikke behøver at være i tvivl om udfaldet af en beregning har 28

28 1. Kom i gang med C++ C++ tildelt operatorerne en prioritet som styrer rækkefølgen af beregningerne i en sætning. Denne prioritering kan ses i tabel 5.3: Operatorerne i den øverste kasse har højeste prioritet og alle operatorer i samme kasse har samme prioritet. Som du kan se, har multiplikation højere prioritet end addition. Mange af operatorerne i tabellen har vi ikke behandlet endnu men det kommer vi til senere i bogen. Kontrol af rækkefølgen hvormed C++ udfører en beregning Selvom C++ har en fast operator-prioritet vil man sommetider gerne bestemme en anden prioritet i en beregning: F.eks. i et program der skal addere to priser og derefter gange resultatet med moms-satsen: udgift = pris_a + pris_b * 1.25; I dette tilfælde vil C++ udføre multiplikationen først (pris_b * 1.25) for derefter at lægge resultatet til pris_a. For at kunne styre beregningsrækkefølgen i C++ anvender man parenteser, da C++ altid vil udføre disse beregninger først. Se f.eks. i dette eksempel: resultat = (2 + 3) * (3 + 4); Når C++ beregner denne sætning vil mellemregningerne se sådan ud: resultat = (2 + 3) * (3 + 4); = (5) * (3 + 4); = 5 * (7); = 5 * 7; = 35; Ved at gruppere udtrykkene i parenteser, kan man styre rækkefølgen hvormed beregninger udføres. Ovenstående problem kan derfor løses på denne måde: udgift = (pris_a + pris_b) * 1.25; Operator Navn Eksempel :: Scope resolution klasse_navn:: klasse_felt_navn :: Global resolution ::variabelnavn. Felt selector objekt.felt_navn > Felt selector pointer->felt_navn [] Subscript pointer[element] () Funktions-kald udtryk(parametre) () Value constuction type(parametre) sizeof Størrelse af objekt sizeof udtryk sizeof Størrelse af type sizeof(type) ++ Postfix inkrement variabel++ ++ Prefix inkrement ++variabel Postfix dekrement variabel Prefix dekrement variabel & Adresse operator &variabel * Dereference operator new delete *pointer Allokerings operator new type Deallokerings operator delete pointer delete[] Dealloker array delete pointer ~ Etkomplement ~ udtryk! NOT operator! udtryk + Monært plus +1 - Monært minus 1 () Cast operator (type) udtryk.* Element selektor objekt.*pointer > Element selektor objekt >*pointer * Multiplikation udtryk * udtryk / Division udtryk / udtryk % Modulo udtryk % udtryk + Addition udtryk + udtryk Subtraktion udtryk udtryk Tabel 5.3. C++ Operator prioritet 29

29 C++ Undgå overflow i beregninger I øvelse 4 lærte vi at der opstår overflow-fejl hvis man tildeler variabler værdier som ligger uden for det tilladte område. Derfor er det også vigtigt at huske på overflow-problemet når man foretager beregninger. Det ses i det følgende program, REGNOVER.CPP, hvor 200 ganges med 300 og resultatet tildeles en variabel af typen int. Da resultatet af beregningen (60.000) ligger over den tilladte maksimumsværdi for en int (32.767), kommer der en overflow-fejl: int resultat; C++ har en prefix (foranstillet) og en postfix (bagvedstillet) dekrementerings-operator som trækker 1 fra en variabels værdi. Prefix-operatoren ændrer værdien (plus eller minus én) før den bruges i sætningen. Postfix-operatoren ændrer først værdien (plus eller minus én) efter at den er blevet brugt i sætningen. For at sikre en ensartet beregning har C++ fastlagt en prioritering af de enkelte operatorer som bestemmer i hvilken rækkefølge del-beregningerne i en sætning udføres. Parenteser kan bruges til at styre beregnings-rækkefølgen i en sætning da C++ altid udfører beregninger i parenteser først. resultat = 200 * 300; cout << "200 * 300 = " << resultat << endl; Når dette program kompileres og køres vil det skrive følgende på skærmen: C:\CPP\> REGNOVER <ENTER> 200 * 300 = Opsummering I denne øvelse har vi gennemgået de almindeligste regne-operatorer i C++ samt inkrementerings- og dekrementerings-operatorerne. For at opnå ensartethed i beregningerne er der i C++ fastlagt en operator-prioritet som bestemmer i hvilken rækkefølge del-beregningerne i en sætning udføres. I næste øvelse skal vi se hvordan programmer kan læse input fra tastaturet, men før vi går videre bør du sikre dig at du har forstået følgende: C++ bruger operatorerne +, -, * og / til addition, subtraktion, multiplikation og division. C++ har en prefix (foranstillet) og en postfix (bagvedstillet) inkrementerings-operator som lægger 1 til en variabels værdi. Øvelse 6 Input fra tastatur I de foregående kapitler har vi flere gange brugt output-strømmen cout til at vise tekst og tal på skærmen. C++ har også en input-strøm kaldet cin som giver programmerne mulighed for at læse input som brugeren skriver på tastaturet. Med cin kan man angive én eller flere variabler hvor den tekst, brugeren skriver skal gemmes. I denne øvelse skal vi lære følgende centrale ting: Input-strømmen cin kan bruges til at indlæse bogstaver og tal fra tastaturet og gemme dem i angivne variabler. Variabler, der har fået overført værdier ved hjælp af cin, kan bruges på præcis samme måder som variabler, der har fået tildelt værdier med tildelings-operatoren. Programmer der læser input ved hjælp af cin skal sikre at der ikke opstår overflowfejl samt tage højde for at brugeren kan skrive værdier af forkert type. 30

30 1. Kom i gang med C++ Når et program skriver data på skærmen ved hjælp af output-strømmen cout bruger det << operatoren. På lignende vis bruges >> operatoren når et program skal indlæse data fra tastaturet ved hjælp af cin. Input-strømmen cin Et program der bruger cin til at læse tastaturinput skal angive en variabel hvori cin kan placere data. I det følgende program, START- CIN.CPP, læser vi et tal fra tastaturet ved hjælp af cin. Programmet tildeler tallet til en variabel ved navn tal og viser derefter variablens værdi med cout output-strømmen. int tal; //tal som skal indlæses cout << "Skriv dit yndlings-tal og tryk Enter: "; cin >> tal; cout << "Dit yndlingstal er " << tal << endl; Når dette program kompileres og startes vil der komme en besked på skærmen om at brugeren skal indtaste sit yndlingstal. Når man skriver et tal og trykker Enter, gemmer programmet tallet i variablen tal og minder herefter brugeren om dennes yndlingstal ved hjælp af cout. I det følgende program, TO_TAL.CPP bedes brugeren om at skrive to tal som programmet så tildeler til variablerne tal_et og tal_to. Til sidst vises tallene på skærmen med cout. // Tal som skal skrives // på tastaturet 4 int tal_et, tal_to; cout << "Skriv to tal og tryk Enter: "; cin >> tal_et >> tal_to; cout << "Du skrev tallene " << tal_et << " og " << tal_to <<endl; Læg mærke til at vi her bruger to >> operatorer i cin-sætningen: cin >> tal_et >> tal_to; Den første værdi, brugeren skriver vil blive tildelt variablen tal_et mens den anden værdi vil blive tildelt tal_to. Og skal programmet bruge tre tal er det bare at tilføje en tredje >> operator og variabel: cin >> tal_et >>tal_to >> tal_tre; Cin bruger de såkaldte mellemrums-tegn (mellemrum, tab og return) til at skelne mellem værdierne og se hvor en værdi slutter og en anden begynder. Med cin er det ikke nødvendigt at trykke Enter mellem hver værdi i en sætning: Bare skriv de enkelte værdier adskilt af mellemrum. Prøv også at se, hvad der sker hvis brugeren skriver for få eller for mange værdier når programmet prompter for input og se hvordan programmet reagerer hvis værdierne adskilles af tab og Enter i stedet for mellemrum. Pas på overflow-fejl Når man skriver programmer, der bruger cin, er det vigtigt at tage højde for at brugeren kan indtaste ugyldige tal. Prøv at køre programmet STARTCIN som vi lavede før, og skriv og tryk Enter. Programmet viser ikke værdien selvom det var det, du skrev: Tallet overstiger nemlig den højeste værdi, en int kan rumme hvilket resulterer i en overflow-fejl. Som vi lærte i øvelse 4 kan variabler af typen int kun indeholde tal i området fra til Prøv programmet med 31

31 C++ flere forskellige værdier, både positive og negative og se, hvad der sker når man skriver et tal, der ligger uden for det tilladte område. Indtastning af forkert type STARTCIN.CPP forventer altså at brugeren taster en værdi mellem og men hvad sker der hvis han i stedet for et tal, skriver noget tekst? Så kommer computeren med en forkert-type-fejl: Programmet forventer en værdi af en bestemt type (int) men brugeren skrev en værdi af en anden type (f.eks. noget tekst). Kør programmet igen og skriv bogstaverne ABC når programmet prompter dig for input programmet forventer et heltal, ikke tekst, og der kommer en fejlmeddelelse. Prøv det samme med programmet, TO_TAL, og skriv både heltal, tekst og decimaltal. Også her vil det resultere i en forkert-type-fejl. I senere kapitler skal vi se, hvordan man kan tage højde for fejl-input fra brugeren og undgå at det medfører at programmet stopper. Men husk på risikoen for disse fejl på samme måde som risikoen for overflow-fejl. Indlæsning af bogstaver fra tastaturet Foruden at indlæse tal, kan cin selvfølgelig også bruges til at indlæse bogstaver og tekst. I det følgende program, CINTEGN.CPP, bruger vi cin input-strømmen til at læse et bogstav fra tastaturet ind i en variabel af typen char: char bogstav; cout << "Skriv et tilfældigt bogstav og tryk Enter: "; cin >> bogstav; cout << "Du skrev bogstavet " << bogstav << endl; Kompilér programmet og prøv det med forskellige input f.eks. flere bogstaver ad gangen. Du vil opdage at programmet kun kan rumme ét tegn ad gangen da det benytter sig af en variabel af typen char. Indlæsning af store tal fra tasturet Nu vil vi lave et program der indlæser værdier af typen long. Skriv følgende program, CIN_LONG.CPP: long tal; cout << "Skriv et stort tal og tryk Enter: "; cin >> tal; cout << "Du skrev tallet " << tal << endl; Prøv også med dette program at se hvad der sker, hvis man skriver store tal, negative tal, decimaltal og tekst. I/O omdirigering og cin input-strømmen I øvelse 3 så vi hvordan tekst skrevet med cout kan omdirigeres til en fil eller til en printer. Det var fordi cout refererer til operativsystemets standard output. På samme måde refererer cin til systemets standard input og kan på lignende vis omdirigeres: Da cin bruges til input kan man således med omdirigerings-operatoren (>>) få et program til at indlæse værdier fra en fil eller et andet program i stedet for fra tastaturet. Det kommer vi nærmere ind på i et senere kapitel. Opsummering I denne øvelse brugte vi cin input-strømmen til at indlæse data fra tastaturet, og vi så at man 32

32 1. Kom i gang med C++ bruger variabler til at opbevare de indlæste værdier. I øvelse 7 skal vi se hvordan vores programmer ved hjælp af if-sætningen kan foretage valg, men inden da sikrer vi os lige hvad det er vi har lært: I C++ kan man bruge cin input-strømmen til at indlæse data fra tastaturet. Når man bruger cin skal man anføre én eller flere variabler til at gemme de indlæste data i. For at overføre indlæste data til en variabel bruges >> operatoren. Cin-sætninger der indlæser flere værdier opfatter mellemrum, tab og Enter som adskillelse af de enkelte værdier. Der opstår fejl (overflow og forkert-type-fejl) hvis brugeren skriver værdier der ligger uden for det acceptable område eller ikke svarer til den forventede type. Øvelse 7 Programmer foretager valg Som sagt er et program en serie instruktioner som tilsammen får en computer til at udføre en opgave. De C++ programmer, vi har lavet indtil nu har alle startet fra toppen og udført sætningerne sekventielt indtil de nåede slutningen af programmet. Men i mere komplekse programmer er det måske nødvendigt somme tider at udføre én gruppe sætninger mens der i andre situationer skal udføres en anden gruppe sætninger. Programmet skal med andre ord kunne foretage valg og handle forskelligt alt efter hvilket valg, der blev truffet. Til det formål findes betingelses-sætningen if som vi skal se på i denne øvelse. Vi skal lære følgende: C++ programmer bruger relationelle operatorer til at bestemme om to værdier er ens eller om en af værdierne er større eller mindre end den anden. If-sætningen bruges i C++ programmer til at foretage valg. C++ sætninger er enten simple (én operation) eller sammensatte (mange operationer grupperet i krøllede parenteser ). Et program bruger if-else sætningen til at udføre en gruppe sætninger hvis en betingelse er sand og en anden gruppe sætninger hvis betingelsen er falsk. Et program kan vælge mellem mange forskellige betingelser ved at sammensætte flere if-else sætninger. Ved hjælp af AND og OR operatorerne kan C++ vurdere sammensatte betingelser, f.eks. har brugeren en hund AND er hunden en dalmatiner? Programmer der foretager valg udfører konditionel (betingelses-styret) databehandling. Programmet vil optræde forskelligt alt afhængig af resultatet af en eller flere sammenligninger. Brugen af betingelser sætter programmøren i stand til at lave alment brugbare programmer. Sammenligning af to værdier Når et program foretager et valg er det oftest baseret på en sammenligning. Ét program tester f.eks. om en elevs karakter er lig med 13, et andet tester om en vare koster mere end 1000 kr. For at kunne lave en sådan sammenligning bruger C++ de relationelle operatorer som er opført i tabel 7.1. Relationelle operatorer sætter programmet i stand til at sammenligne én værdi med en anden. Man kan med andre ord teste om en værdi er lig med, mindre end eller større end en anden værdi. Når et program ved hjælp af de relationelle operatorer sammenligner to værdier, bliver resultatet altid enten sand eller falsk: Værdierne står enten i det anførte forhold til hinanden eller ikke. Alle de if-sætninger, vi skal arbejde med i bogen bruger de relationelle operatorer som er anført i tabel

33 C++ Operator Test Eksempel == Ser om to værdier er ens!= Ser om to værdier er forskellige > Ser om den første værdi er større end den anden < Ser om den første værdi er mindre end den anden >= Ser om den første værdi er større end, eller lig med, den anden <= Ser om den første værdi er mindre end, eller lig med, den anden If-sætningen i aktion Med if-sætningen kan man få sine programmer til at teste et udsagn og udføre forskellige sætninger alt efter resultatet af testen. If-sætningen fungerer på følgende måde: if (betingelse_er_sand) sætning; Almindeligvis bruger if-sætningen C++'s relationelle operatorer i testen og hvis udsagnet i testen er sandt bliver sætningen efter betingelsen udført. I det følgende program, IF.CPP, bruger vi en if-sætning til at sammenligne værdien af variablen karakter med værdien 10. Hvis karakter er større end, eller lig med 10, skriver programmet en tekst der roser eleven. Hvis karakter er lavere end 10, slutter programmet uden at skrive noget. (karakter == 13) (gammel!= ny) (pris > ) (gage < ) (aktie_pris >=130) (alder <= 18) Tabel 7.1. De relationelle operatorer i C++ 4 int karakter = 11; if (karakter >= 10) cout << "Tillykke med den fine karakter!" << endl; I dette program bruger vi C++'s større-end-eller-lig-med operator (>=) i testen. Det er en af de relationelle operatorer også kaldet sammenlignings-operatorer. Hvis udtrykket er sandt vil cout-sætningen blive udført. Er udtrykket derimod forkert f.eks. hvis karakter havde fået tildelt værdien 7 vil cout-sætningen ikke blive udført. Prøv at lave programmet om så det ved hjælp af en cin-sætning prompter brugeren for en karakter som så bliver testet i if-sætningen. Enkeltstående sætninger og blokke af sætninger Vi har set hvordan et program kan udføre en enkelt sætning hvis en given betingelse er sand. Fordi der kun bliver udført en enkelt sætning efter betingelsen kalder man det en enkeltstående sætning: if (karakter >= 10) cout << "Tillykke med den fine karakter!" << endl; Men sommetider kan man have brug for at udføre mere end en enkelt sætning, hvis betingelsen er sand. Den gruppe af sætninger, der skal udføres kaldes en blok. For at angive at en gruppe af sætninger skal optræde som en blok skrives sætningerne imellem krøllede parenteser. En blok kaldes også en sammensat sætning. I dette eksempel vil begge cout-sætningerne blive udført hvis betingelsen er sand: if (karakter >= 10) cout << "Tillykke med den fine karakter!" << endl; cout << "Du fik karakteren " << karakter << endl; 34

34 1. Kom i gang med C++ Du behøver ikke huske betegnelserne enkeltstående sætning og blok, bare du husker at gruppere sammenhængende sætninger mellem krøllede parenteser. Nu skal vi prøve at indbygge en sætnings-blok i et program som vi kalder BLOK.CPP: int karakter = 11; if (karakter >= 10) cout << "Tillykke med den fine karakter!" << endl; cout << "Du fik karakteren " << karakter << endl; Sætninger der udføres hvis betingelsen er falsk I de forrige eksempler så vi hvordan man kalder en eller flere sætninger hvis en given betingelse var sand. Men programmet skrev ikke nogen besked hvis betingelsen ikke var opfyldt, hvis udsagnet var falsk: F.eks. hvis karakter var lig med 7. I de fleste situationer vil det være smartest at programmet udfører én sætning eller en blok hvis betingelsen er sand, og en anden sætning eller blok hvis betingelsen er falsk. Det kan gøres ved at skrive en else-sætning efter if-sætningen: if (betingelse_er_sand) sætning; else sætning; I det følgende program, IF_ELSE.CPP, bruger vi en if-sætning til at teste om en karakter er større end, eller lig med 10. Hvis den er det, skriver programmet en lykønskning, men hvis karakteren er lavere end 10 opfordrer programmet eleven til at tage sig sammen: int karakter = 11; if (karakter >= 10) cout << "Tillykke med den fine karakter!" << endl; else cout << "Næste gang giver du den hele armen!" << endl; Blokke kan også bruges ved else Som du lærte, er en blok en gruppe af sammenhængende sætninger omgivet af krøllede parenteser. En blok kan naturligvis også bruges efter else-sætningen. I det følgende eksempel, BLOKELSE.CPP, skriver vi en blok både efter if-betingelsen og efter else-sætningen: int karakter = 6; if (karakter >= 10) cout << "Tillykke med den fine karakter!" << endl; cout << "Det er flot at få " << karakter << endl; else cout << "Det var ikke helt perfekt!" << endl; cout << "Du er " << 13 - karakter << " point fra toppen" << endl; 35

35 C++ Prøv også at ændre dette program så du kan afprøve det med flere karakterer. I det følgende program, SKAFKAR.CPP, bruger vi cin input-strømmen til at skaffe en karakter fra brugeren. Karakteren sammenlignes med tallet 10, og en meddelelse udskrives: int karakter; cout << "Skriv karakteren og tryk Enter: "; cin >> karakter; if (karakter >= 10) cout << "Tillykke med den fine karakter!" << endl; cout << " Det er flot at få " << karakter << endl; else cout << "Det var ikke helt perfekt!" << endl; cout << "Du er " << 13 - karakter << " point fra toppen" << endl; Kompilér og kør programmet. Du begynder sikkert at kunne se de mange muligheder der ligger i betingelses-styret databehandling. Indrykning øger læsevenligheden Når man programmerer er det almindeligt at indrykke sætninger så de forskellige blokke står på en mere læsevenlig måde. Når man læser programmet er det lettere at få øje på en ifsætning og dens else-del, og det er let at se at denne blok bliver udført hvis betingelsen er sand og den anden bliver udført hvis betingelsen er falsk. Når man bruger en kontrolstruktur (foreløbig har vi kun set på if) bliver strukturens sætning eller blok af sætninger rykket et niveau ind. Det er ligegyldigt hvor mange tegn, man bruger til indrykning, men de fleste programmører bruger to eller tre mellemrum: if (karakter >= 10) cout << "Tillykke med den fine karakter!" << endl; cout << " Det er flot at få " << karakter << endl; else cout << "Det var ikke helt perfekt!" << endl; cout << "Du er " << 13 - karakter << " point fra toppen" << endl; Indrykning gør ingen forskel for C++ kompileren, men for den der læser programmet er det en stor hjælp. Sammensatte betingelser Som vi har set, sætter if-sætningen vores programmer i stand til at teste en betingelse. Men det er ikke altid nok, kun at teste på en enkelt betingelse, så derfor kan man i C++ lave sammensatte betingelser: F.eks. kunne et program teste om en elevs karakter er større end, eller lig med 10 og samtidig i samme sætning teste om elevens gennemsnit er over 8. Et andet program kunne teste om brugeren har hund og om hunden er en dalmatiner. Til at lave disse sammensatte if-sætninger bruges C++'s logiske AND operator (&&). På samme måde kan man bruge den logiske OR operator ( ) til at teste om én blandt flere betingelser er sand 36

36 1. Kom i gang med C++ f.eks. om brugeren har enten en kat eller en hund. I et program hvor man bruger logisk AND eller logisk OR operatorerne, skal de enkelte betingelser skrives i hver sin parentes, som vist her: if ((brugeren_har_hund) && (hund == dalmatiner)) Hver betingelse skrives i sine egne parenteser, og disse betingelser omgives af if-sætningens parenteser. if ((brugeren_har_hund) && (hund == dalmatiner)) I en sammensat betingelse hvor der er brugt logisk AND (&&), skal begge udsagn være sande for at hele betingelsen bliver sand: Hvis bare én af sætningerne er falsk, bliver hele betingelsen falsk. Hvis ikke brugeren har hund er forrige betingelse falsk, og betingelsen er også falsk hvis brugeren har hund, men hunden ikke er en dalmatiner. Brugeren skal have hund, og hunden skal være en dalmatiner for at betingelsen er sand. I den følgende sætning bruger vi logisk OR operatoren ( ) til at teste om brugeren enten har hund eller kat: if ((brugeren_har_hund) (brugeren_har_kat)) For at denne betingelse skal være sand behøver kun en af de to del-betingelser at være det. Har brugeren hund er sætningen sand uanset om han har kat eller ej og omvendt. Betingelsen er selvfølgelig også sand hvis brugeren har både hund og kat den er kun falsk hvis brugeren hverken har hund eller kat. I C++ svarer værdien 0 til falsk og alle andre værdier svarer til sand Som programmør kan man tit gøre brug af at C++ opfatter en værdi, som er forskellig fra 0, som sand, og at værdien 0 svarer til falsk. F.eks kan man i et program bruge en variabel ved navn brugeren_har_hund til at huske om brugeren har en hund: Hvis brugeren ikke har hund, tildeler programmet variablen værdien 0 (for falsk): brugeren_har_hund = 0; Hvis brugeren derimod har en hund, tildeler man en værdi som er forskellig fra 0 til variablen: brugeren_har_hund = 1; Nu kan programmet bruge variablen i en ifsætning: if (brugeren_har_hund) Denne if-sætning er sand hvis variablen indeholder en værdi som er forskellig fra 0, og modsat vil den være falsk hvis variablen indeholder værdien 0. Sætningen er derfor identisk med denne sætning: if (brugeren_har_hund == 1) I det følgende program, HUND_KAT.CPP, bruger vi variablerne brugeren_har_hund og brugeren_har_kat til at fastslå hvilke husdyr brugeren har: int brugeren_har_hund = 1; int brugeren_har_kat = 0; if (brugeren_har_hund) cout << "Hunde er sjove dyr" << endl; if (brugeren_har_kat) cout << "Katte er sjove dyr" << endl; 37

37 C++ if ((brugeren_har_hund) && (brugeren_har_kat)) 4 cout << "Forhåbentlig kan de enes!" << endl; if ((brugeren_har_hund) (brugeren_har_kat)) cout << "Det er rart at have husdyr!" << endl; Prøv at bytte om på 1 og 0 og også at skrive to 1-taller eller to 0'er og se hvad der sker når programmet kompileres og startes. Som du kan se er det let at teste sammensatte betingelser ved hjælp af de logiske operatorer AND og OR. Den logiske NOT operator Nu har vi set hvordan man udfører en sætning eller en blok af sætninger hvis en betingelse er sand. Men man kan også få brug for at skulle udføre sætninger hvis en given betingelse er falsk. Derfor er der i C++ en logisk operator der hedder NOT og skrives som tegnet!. I følgende eksempel bruger vi NOT til at teste om brugeren ikke har hund: if (! brugeren_har_hund) cout << "Du burde købe en hund" << endl; NOT operatoren laver sand om til falsk og falsk om til sand. Hvis ikke brugeren har hund indeholder variablen værdien 0, men coutsætningen bliver kun udført hvis betingelsen er sand. Derfor vender vi værdien om til det modsatte (0 bliver til et, falsk til sand) hvorefter betingelsen er sand og teksten skrives på skærmen. Men hvis brugeren har hund (værdien er 1, sand) vil værdien blive vendt om (til 0) og betingelsen vil være falsk hvorefter coutsætningen ikke bliver udført (hvilket den heller ikke skal da brugeren jo har hund). Følgende program, NOT.CPP, viser brugen af NOT operatoren: 38

38 1. Kom i gang med C++ int brugeren_har_hund = 0; int brugeren_har_kat = 1; if (! brugeren_har_hund) cout << "Du burde købe en hund" << endl; if (! brugeren_har_kat) cout << "Du burde købe dig en kat" << endl; NOT operatoren er uundværlig. Senere i bogen skal vi f.eks. se hvordan et program kan blive ved med at udføre nogle sætninger så længe slutningen af en fil ikke (NOT) er nået. Test af flere værdier Indtil nu har vi i vores betingelser angivet en sætning eller blok, der skal udføres hvis betingelsen er sand, og en anden sætning eller blok der skal udføres hvis betingelsen er falsk. Men det ville være rart at kunne være lidt mere detaljeret i betingelserne: I stedet for at skelne om karakteren er over eller under 10 skal programmet måske kunne fastslå den præcise karakter. Det gør det følgende program, VISKA- RAK.CPP, ved at bruge en række if-else sætninger: int karakter; cout << "Skriv din prøvekarakter og tryk Enter: "; cin >> karakter; if (karakter == 13) cout << "Fantastisk flot!" 4 << endl; else if (karakter == 11) cout << "Stærkt gået, mand!" << endl; else if (karakter == 10) cout << "God indsats!" << endl; else if (karakter == 9) cout << "Fint arbejde!"<< endl; else if (karakter == 8) cout <<"Acceptabel præstation!" << endl; else if (karakter == 7) cout << "Lidt under middel!" << endl; else if (karakter == 6) cout << "Det var på et hængende hår!" << endl; else cout << "Du dumpede desværre!" << endl; Når programmet kommer til den første if-sætning ser det om karakter er lig med 13. Hvis det er tilfældet, skriver den fantastisk flot på skærmen, ellers går programmet videre til næste betingelse og ser om den er sand. Den første betingelse som programmet finder som er sand vil få sin sætning eller blok udført. Kun den og ingen andre. Switch-sætningen Skal man teste på mange muligheder er det ofte lettere at bruge switch-sætningen i stedet for de mange if-else-sætninger. Når et program bruger switch-sætningen skal det angive en betingelse efterfulgt af én eller flere mulige værdier som programmet skal sammenligne betingelsen med. Det følgende program, SWITCH.CPP, bruger en switch-sætning til at kommentere en elevs karakter: 4 39

39 C++ int karakter = 10; switch (karakter) case 13: cout << "13 er en fornem karakter" << endl; break; case 11: cout << "11 er en flot karakter" << endl; break; case 10: cout << "10 er en pæn karakter" << endl; break; case 9: cout << "9 tyder på interesse for faget" <<endl; break; default: cout << "Dette er vist ikke dit yndlingsfag!" << endl; break; Switch-sætningen består af to dele: Først står det reserverede ord switch efterfulgt af betingelsen. Derefter står de mulige match-værdier. Når programmet kommer til en switch-sætning ser det først på betingelsen og prøver derpå at finde en værdi der svarer til betingelsen. Finder programmet en matchende værdi udfører det sætningerne efter kolon. I eksemplet bliver teksten 10 er en pæn karakter skrevet på skærmen. Finder programmet ingen matchende værdier vil sætningen efter default blive udført. Læg mærke til break-sætningen efter sætningerne i programmet: Når C++ finder en matchende værdi i en switch-struktur, vil alle efterfølgende sætninger blive udført også dem for de andre case-værdier. For at undgå det, skriver man break som får programmet til at springe ud af hele switchen og på den måde sikrer man at kun sætningerne ud for den matchende værdi bliver udført. Opsummering I denne øvelse så vi hvordan man i C++ kan programmere betingelser der foretager valg ved hjælp af if-sætningen. If-sætningen kan udføre én bestemt sætning eller blok hvis betingelsen er opfyldt og en anden sætning eller blok hvis betingelsen ikke er opfyldt. I øvelse 8 skal vi bruge iterationer til at gentage sætninger et bestemt antal gange eller indtil en betingelse er sand. F.eks. kan man med en enkelt sætning lægge 100 elevers karakterer sammen. Men før vi går videre bør du sikre dig, at du har forstået følgende: C++'s relationelle operatorer giver programmer mulighed for at teste om en værdi er lig med, større end, eller mindre end en anden værdi. If-sætningen giver programmer mulighed for at teste en betingelse og derefter udføre en eller flere sætninger hvis betingelsen er sand. Else-sætningen giver programmet mulighed for at udføre en eller flere sætninger hvis den testede betingelse er falsk. For C++ svarer 0 til falsk og enhver værdi forskellig fra 0 svarer til sand. De logiske operatorer AND (&&) og OR ( ) gør det muligt at teste mere end én betingelse ad gangen. Den logiske operator NOT (!) gør det muligt at udføre sætninger hvis en værdi ikke er sand. Sætninger kan grupperes i blokke ved hjælp af krøllede parenteser. Indrykning gør programmer mere læsevenlige. Switch-sætningen kan bruges til at teste en betingelse på specifikke værdier. Når programmet møder en matchende case vil det også opfatte de følgende case-blokke som sande og udføre de sætninger der står i dem. Ved at bruge break-sætningen kan man sikre at programmet kun udfører sætningerne ud for den matchende case-sætning. 40

40 1. Kom i gang med C++ For-sætningen bruges til at gentage sætninger et bestemt antal gange. While-sætningen bruges til at blive ved med at gentage sætninger så længe en betingelse er opfyldt. Do while udfører en sætning mindst én gang måske flere, afhængig af betingelsen. Det, at kunne gentage sætninger, iterere, er en fantastisk mulighed i programmering. Når du kan stoffet i denne øvelse vil dine programmerings-evner være kraftigt udbygget. Gentagelse af sætninger et bestemt antal gange En af de ting man oftest får brug for i sine programmer er at kunne gentage sætninger et givet antal gange. F.eks. i et program, der skal printe mange filer og som bruger den samme kode igen og igen. Eller et program der skal vurdere 30 aktiekurser for at se om de enkeltvis er faldet eller steget. C++'s for-sætning gør det let at gentage kode-sætninger et bestemt antal gange. Et program, der bruger en for-sætning (det kaldes også en for-løkke) skal angive en variabel en kontrol-variabel som husker hvor mange gange løkken er kørt igennem. I det følgende eksempel bruger for-løkken variablen antal til at huske det antal gange, løkken har kørt. Løkken vil i eksemplet blive kaldt 10 gange. for (antal = 1; antal <= 10;antal++) sætning; For-løkken består af fire dele hvoraf de tre første bestemmer hvor mange gange løkken køres igennem. Først står sætningen antal = 1 som tildeler kontrol-variablen en startværdi. Denne initialisering bliver kun udført én gang, nemlig når for-løkken startes. Derefter tester løkken betingelsen antal <= 10. Hvis betingelsen er falsk slutter løkken og programmet springer til den første sætning efter løkken. Hvis betingelsen derimod er sand udføres sætingen i forløkken hvorefter variablen antal inkrementeres med 1 (sætningen antal++). Herpå tester programmet betingelsen antal <= 10 igen, og er den stadig sand bliver sætningen udført igen. Denne proces med at forhøje variablen for derpå at teste betingelsen bliver ved indtil betingelsen ikke mere er sand. for(antal = 1; antal <= 10; antal++) Initialisering Test Inkrementering I det følgende program, FOR.CPP, bruger vi for-løkken til at skrive alle tal fra 1 til 100 på skærmen: int antal; Øvelse 8 Gentagelse af en sætning eller en blok I øvelse 7 så vi hvordan man kan bruge if-sætningen til at foretage valg. Det er ikke så forskelligt fra det vi skal se på nu: hvordan man får et program til at gentage en sætning eller en blok af sætninger et fastlagt antal gange eller indtil en bestemt betingelse er opfyldt. Vi skal i øvelsen lære at bruge C++'s iterations strukturer. Efter denne øvelse vil vi have gennemgået følgende: for (antal = 1; antal <= 100; antal++) cout << antal << ; 41

41 C++ For-løkken starter med at initialisere kontrolvariablen antal til 1. Så tester løkken om antal er mindre end eller lig med 100; hvis det er tilfældet, bliver cout-sætningen udført, og antal bliver forhøjet med én. Det bliver ved indtil antal er forhøjet til 101 hvorefter betingelsen er falsk, og løkken afsluttes. Eksperimentér med programmet ved at lade løkken køre f.eks. 10, 20 eller endog 5000 gange. Det følgende program, ANTAL.CPP, beder brugeren om at skrive et tal som løkken skal slutte med, hvorefter programmet viser tallene fra 1 til det angivne tal: for at programmet kunne udføre flere sætninger i en if-else betingelse. Det samme gør sig gældende hvis et program skal udføre flere sætninger i en for-løkke. Det følgende program, ADD1_100.CPP, udfører tre sætninger for hver gennemkørsel af løkken: int antal; int total = 0; int antal; int slut_tal; cout << "Skriv det tal, løkken skal køre til: "; cin >> slut_tal; for (antal = 1; antal <= 100; antal ++) cout << total " plus " << antal; total = total + antal; cout << " giver " << total << endl; for (antal = 0; antal <=slut_tal; antal ++) cout << antal << ; Når du kompilerer og kører programmet, ser du, hvordan den samme kode kan gøre forskellige ting, alt efter hvor mange gange løkken gennemkøres. Skriver man værdien 0 eller et negativt tal vil løkken ikke blive kaldt en eneste gang da betingelses-sætningen (antal <= slut_tal) er falsk når programmet kommer til løkken. Husk også at værdier uden for det tilladte område som en int kan indeholde vil medføre overflow-fejl: Det vil f.eks. ske hvis brugeren skriver Sætnings-blokke kan også bruges i løkker I øvelse 7 så vi at man skulle gruppere sætninger i blokke (ved at omgive blokken med ) Først fortæller løkken brugeren, at den aktuelle værdi af antal bliver lagt til total, derefter beregnes total's nye værdi, og til sidst vises resultatet. Når sætningerne skrives som en blok kan programmet udføre mere end én sætning for hver gennemkørsel af løkken. For-løkker behøver ikke inkrementere med 1 I de for-løkker, vi har brugt indtil nu, har vi for hver gennemkørsel af løkken inkrementeret kontrol-variablen med 1. Men en for-løkke kan ændre sin kontrol-variabel med hvilken som helst værdi, positiv som negativ. Det ser vi i dette program, MED_FEM.CPP, som viser tallene fra 0 til 100 med spring på fem (5-tabellen med andre ord): 4 42

42 1. Kom i gang med C++ int antal; Når antal har nået værdien 0 og betingelsen derfor er falsk slutter løkken. for (antal = 0; antal <= 100; antal += 5) cout << antal << ; Læg mærke til den sætning, som for-løkken bruger til at inkrementere kontrol-variablen antal: antal += 5; Når man vil lægge en værdi til eller trække en værdi fra en variabel, kan man gøre det på to måder. Skal man f.eks. lægge 5 til den værdi, en variabel allerede har, kan man gøre det sådan her: antal = antal + 5; Men man kan som i eksemplet med løkken opnå samme resultat ved at skrive på denne måde: antal += 5; Og da det er hurtigere at skrive det på den korte måde, bruges metoden ofte. For-løkker er ikke begrænset til at tælle opad. Følgende program, NEDAD.CPP, bruger en for-løkke til at tælle nedad fra 100 til 1: int antal; for (antal = 100; antal >= 1; antal--) cout << antal << ; Her initialiserer løkken antal til at være 100, og for hver iteration dekrementeres antal med 1. Undgå uendelige løkker Man kan let komme til at lave en lille fejl i sit program der medfører at en løkke-betingelse aldrig bliver falsk og løkken derfor aldrig afsluttes. En sådan løkke kaldes en uendelig løkke da programmet ikke har mulighed for at stoppe den. Her er et eksempel på en uendelig løkke: for (antal = 0; antal < 100; forkert_variabel++) // Sætninger Løkkens kontrol-variabel hedder antal, og løkke-betingelsen er at antal skal være mindre end 100. Men da løkken inkrementerer den forkerte variabel bliver betingelsen aldrig falsk og løkken bliver ved i det uendelige (eller til strømmen går, eller brugeren afbryder programmet) Det er også vigtigt at vide, at en for-løkke ikke er begrænset til at arbejde med kontrol-variabler af typen int. Følgende program, LOOPVAR.CPP, bruger f.eks. en variabel af typen char som kontrol-variabel til at vise bogstaverne i alfabetet. Derefter bruger programmet en variabel af typen float til at vise decimaltal: char bogstav; float tal; for (bogstav = A ; bogstav <= Z ; bogstav++) cout << bogstav; cout << endl; for (tal = 0.0; tal < 1.0; tal += 0.1) 4 43

43 C++ cout << tal << ; cout << endl; // Bliver sand når bruger // skriver j eller n int slut = 0; char bogstav; Når dette program kompileres og startes, vil det skrive følgende på skærmen: ABCDEFGHIJKLMNOPQRSTUVWXYZ While-løkken Som vi så bruges for-løkken til at gentage en sætning eller blok et bestemt antal gange men ofte er det nødvendigt at kunne gentage sætninger eller blokke så længe en betingelse er sand, uden at man som programmør nødvendigvis ved, hvor mange gange det er. F.eks. hvis et program skal læse indholdet af en fil: Så er det nødvendigt at det læser i filen indtil filen er slut, og da filer har forskellig længde kan man ikke mens man programmerer løkken vide hvornår læsningen skal slutte. Til den brug er der i C++ en while-løkke som bruges sådan her: while (betingelse_er_sand) sætning; Når et program møder en while-sætning, tester det om betingelsen er sand. Hvis den er det, udføres sætningen (eller blokken). Når sætningen/blokken er udført tester while-løkken betingelsen igen og er den stadig sand udføres sætningen eller blokken igen. Først når betingelsen bliver falsk, udføres sætningen ikke, og programmet springer videre til sætningen efter strukturen. Følgende program, JANEJ.CPP, beder brugeren om at skrive J eller N (for ja eller nej). While-løkken læser tegn fra tastaturet, og hvis brugeren skriver andet end j eller n, bipper computeren ved at skrive '\a' tegnet til output-strømmen. 4 while (! slut) cout << "\nskriv J eller N og tryk Enter: "; cin >> bogstav; if ((bogstav == J ) (bogstav == j )) slut = 1; else if ((bogstav == N ) (bogstav == n )) slut = 1; else cout << \a ; // Forkert: // bip-lyd cout << "Du skrev bogstavet " << bogstav << endl; Her ser vi at while-strukturen også kan bruges med blokke (sætninger skrevet mellem krøllede parenteser). Programmet bruger kontrolvariablen slut i løkken som fortsætter så længe, brugeren ikke taster j eller n. Når brugeren taster j eller n bliver slut sand, og løkken slutter. Programmer der læser og skriver filer gør ofte brug af while-løkken. Løkker der udfører en sætning mindst én gang Som vi har set, bruges while-løkken til at gentage en sætning eller blok så længe en given betingelse er sand: Når programmet kommer til while-sætningen vurderer det, om betingelsen er sand. Men hvis betingelsen er falsk før programmet kommer til løkken, bliver sætningen/blokken ikke udført én eneste gang. For at kunne udføre sætninger mindst én gang, findes der i C++ en løkke der hedder do while: 44

44 1. Kom i gang med C++ do sætninger; while (betingelse_er_sand); Når programmet kommer til en do while-løkke udfører den sætningerne i løkken. Så tester den betingelsen, og hvis den er sand, springer programmet tilbage til starten af løkken: do sætninger; while (betingelse_er_sand); Hvis betingelsen er falsk bliver sætningerne ikke gentaget programmet fortsætter med de sætninger der måtte komme efter løkken. Do while-løkken kan bl.a. bruges til at vise brugeren nogle menu-muligheder: Programmet skal vise menuen mindst én gang, og hvis brugeren vælger en funktion på menuen, udfører programmet funktionen og returnerer til løkken som gentegner menuen. Dette bliver ved indtil brugeren vælger exit, hvorefter programmet springer ud af løkken. Opsummering Når programmer gentager en sætning eller blok kaldes det en iteration. I denne øvelse gennemgik vi C++'s iterations (løkke) strukturer: For-løkken som gentager sætninger et specifikt antal gange og while-løkken som gentager sætninger så længe en bestemt betingelse er opfyldt. Endelig så vi også på do while-løkken som er ligesom while-løkken, bortset fra at sætningerne i løkken altid vil blive udført mindst én gang uanset om betingelsen er sand eller falsk. I øvelse 9 skal vi se hvordan man samler sætninger i funktioner som gør et program mere overskueligt, men før vi går i gang med det bør du checke at du har forstået følgende: Med C++'s for-sætning kan sætninger gentages et angivet antal gange. For-sætningen består af fire dele: En initialisering, en test, de sætninger som skal udføres, og endelig en inkrementering. For-løkken kan inkrementeres med andre værdier end 1, og den er heller ikke begrænset til kun at tælle opad. While-løkken gentager sætninger så længe en betingelse er sand. While-løkken bruges ofte til at indlæse filer, da den kan indlæse indtil slutningen af filen uden at kende filens størrelse. Do while-sætningen udfører sætninger mindst én gang med mulighed for flere gentagelser. Do while-løkken bruges ofte til interaktive menuer (mest i DOS eller andet tekst-miljø). Når betingelsen i en løkke (for, while eller do while) bliver falsk, fortsætter programmet med sætningen efter løkken. 45

45 2. Funktioner Øvelse 9 Introduktion til funktioner Når de programmer, man skriver, begynder at stige i kompleksitet og omfang, bør de enkelte del-opgaver samles i funktioner. Det gør programmerne mere overskuelige og lettere at fejl-rette. Hver funktion i et program bør udføre en konkret opgave. F.eks. kunne et lønstyrings-program bestå af en funktion der tæller arbejdstimer, og en anden funktion der udskriver løn-checks osv. Når så programmet skal udføre den givne opgave, kalder det den relevante funktion. I denne øvelse skal vi lære at programmere funktioner, og når du er igennem øvelsen har vi gennemgået følgende: Relaterede sætninger der tilsammen udfører en specifik opgave bør programmeres som funktioner. For at udføre en funktion, kalder programmet den, ved at angive dens navn, efterfulgt af parentes f.eks. beep(). En funktion returnerer ofte en værdi af en bestemt type, f.eks. en int eller float, som det kaldende program kan tildele til en variabel. Den kaldende kode kan overføre parametre (data) til en funktion. Det kan f.eks. være en medarbejders navn eller alder. Overførslen sker ved at placere parametrene i den parentes, som alle funktioner har efter navnet. C++ bruger funktions-prototyper til at definere typen, som funktionen returnerer såvel som antal og type af de parametre, som funktionen får overført fra det kaldende program. vokse i størrelse. Du vil opdage at funktioner er lette at programmere og bruge. Vores første funktion Funktioner bør tilrettelægges så de udfører hver sin specifikke opgave. Hvis en funktion udfører mere end en ting bør den splittes op i to eller flere funktioner. Hver funktion i et program skal have et unikt navn, og ligesom med navngivning af variabler er det vigtigt at hver funktion har et navn der svarer til opgaven, den udfører. Se f.eks. eksemplerne i tabel 9: Ved at se på deres navne, får man en klar idé om, hvad hver funktion gør. Funktionens navn print_karakterer beregn_saldo skaf_bruger_navn print_dokument Funktionens formål Printer en klasses karakterer Beregner saldoen på en konto Prompter brugeren for dennes navn Printer en angivet fil beregn_indkomstskat Beregner en brugers indkomstskat Tabel 9. Eksempler på fornuftige funktions-navne En funktion har næsten samme struktur som main-delen i et C++ program (faktisk er main også en funktion). Foran funktionens navn står den type som funktionen returnerer og efter navnet står parameterlisten i parenteser. Sætningerne i en funktion skrives mellem krøllede parenteser som vist her: returnerer_type funktion_navn(parameter_liste) variabel_erklæringer; Funktioner er en vigtig del af C++ programmering især når programmerne begynder at sætninger; 46

46 2. Funktioner Se hvor meget en funktions struktur minder om main-programmet: Funktionen: type navn(parameter_liste) variabel_erklæringer; sætninger; Main-programmet: int antal; for (antal = 0; antal< 10; antal++) cout << antal << ; Denne funktion viser en tekst på skærmen, når den bliver kaldt: void vis_tekstlinie(void) cout << "Hej, jeg programmerer i C++" << endl; Som du sikkert husker fra øvelse 2, betyder ordet void foran funktions-navnet at funktionen ikke returnerer en værdi. På samme måde betyder ordet void i parentesen at funktionen ikke får overført data fra det kaldende program. Det følgende program BESKED.CPP, kalder funktionen vis_tekstlinie som skriver en besked på skærmen: void vis_tekstlinie(void) cout << "Hej, jeg program- merer i C++" << endl; 4 cout << "Før funktionen bliver kaldt" << endl; vis_tekstlinie(); cout << "Nu er funktionen udført" << endl; Som vi så tidligere startes ethvert program i main. Her står funktions-kaldet som kalder vis_tekstlinie: vis_tekstlinie(); Parenteserne efter funktions-navnet fortæller C++ kompileren at programmet kalder en funktion. Senere i øvelsen skal vi se hvordan programmet kan overføre data til funktionen. Kompilér og start programmet. Følgende tekst vil fremkomme på skærmen: C:\CPP\> BESKED <ENTER> Før funktionen bliver kaldt Hej, jeg programmerer i C++ Nu er funktionen udført Når programmet kommer til funktions-kaldet, begynder det at udføre funktionens sætninger. Når alle sætningerne i funktionen er udført, springer programmet tilbage til det kaldende program og fortsætter med sætningerne efter funktions-kaldet: void vis_tekstlinie(void) cout << "Hej, jeg program- merer i C++" << endl; cout << "Før funktionen bliver kaldt" << endl; vis_tekstlinie(); cout << "Nu er funktionen udført" << endl; 47

47 C++ Programmet fortæller brugeren, at det skal til at kalde en funktion. Derefter møder programmet funktions-kaldet og begynder at udføre sætningerne i vis_tekstlinie. Når det er gjort, fortsætter programmet med at udføre sætningerne efter funktions-kaldet. Det følgende program, TOBESKED.CPP, bruger to funktioner vis_titel og vis_oevelse til at skrive tekst på skærmen: void vis_titel(void) cout <<"Bog: Programmering i C++" << endl; void vis_oevelse(void) cout << "Øvelse: Introduktion til funktioner" << endl; vis_titel(); vis_oevelse(); Når programmet starter, vil det kalde funktionen vis_titel som skriver noget tekst på skærmen. Når vis_titel er færdig fortsætter programmet med at kalde vis_oevelse som også skriver noget tekst på skærmen. Når vis_oevelse er færdig er der ikke flere sætninger i main, og programmet slutter. De funktioner, vi har lavet indtil nu, har kun udført begrænsede opgaver, da meningen har været, at vise hvordan teknikken bruges. Først i større programmer bliver fordelene med funktioner åbenlyse, og da sætningerne er splittet op i overskuelige stykker bliver programmerne lettere at læse og ændre. Derudover kan man også være heldig at en funktion, man har skrevet i ét program, kan bruges i et andet. Og med tiden opbygger man et bibliotek af funktioner og sparer på den måde programmeringstid. Overførsel af data til funktioner Fordelen ved funktioner stiger, når man lærer at overføre data (parametre) til dem. Der skal erklæres en variabel for hver overført parameter i parentesen efter funktionens navn. Disse variabler fungerer på samme måde som normalt erklærede variabler: De eksisterer kun mens funktionen kører og kan kun ses af den funktion, hvor de er erklæret. Den følgende funktion, vis_tal, får overført en parameter af typen int. void vis_tal(int vaerdi) cout << "Parameterens værdi er " << vaerdi << endl; Når programmet kalder vis_tal-funktionen skal det angive en værdi: vis_tal(1001); Værdi som overføres til funktionen Når funktionen bliver kaldt, udskifter den parameteren vaerdi med den overførte værdi på denne måde: vis_tal(1001); void vis_tal(int vaerdi) cout << "Parameterens værdi er " << vaerdi << endl; void vis_tal(int 1001) cout << "Parameterens værdi er " << 1001 << endl; 48

48 2. Funktioner Funktionen bruger den overførte værdi, og derfor skrives tallet 1001 på skærmen, når funktionen kaldes. Det følgende program, PARAM.CPP, kalder funktionen vis_tal flere gange, men med forskellige parameter-værdier: void vis_tal(int vaerdi) cout << "Parameterens værdi er " << vaerdi << endl; vis_tal(1); vis_tal(1001); vis_tal(-532); Når programmet kompileres og startes, viser det følgende tekst på skærmen: C:\CPP\> PARAM <ENTER> Parameterens værdi er 1 Parameterens værdi er 1001 Parameterens værdi er -532 Hver gang funktionen kaldes, får den overført et nyt tal som den tildeler variablen vaerdi. Hver parameter i en funktion har en bestemt type som det kaldende program skal følge: Vis_tal har defineret sin parameter som en int og hvis programmet kalder funktionen med en anden type f.eks. en char vil kompileren komme med en fejl-meddelelse. Ofte overfører man mere end én værdi til en funktion, og for hver overført parameter skal der i funktionens hoved erklæres en variabel. I det følgende program, STORLIL.CPP, overfører vi tre værdier til funktionen ydertal som udskriver det største og det mindste af de tre tal. void ydertal(int a, int b, int c) int lille = a; int stor = a; if (b > stor) stor = b; if (b < lille) lille = b; if (c > stor) stor = c; if (c < lille) lille = c; cout << "Det største tal er " << stor << endl; cout << "Det mindste tal er " << lille << endl; ydertal(1, 2, 3); ydertal(500, 0, -500); ydertal(1001, 1001, 1001); Når programmet kalder funktionen, tildeler C++ parametrene på denne måde: ydertal(1, 2, 3); void ydertal(int a, int b, int c); Når programmet kompileres og køres, viser det følgende output på skærmen: C:\CPP\> STORLIL <ENTER> Det største tal er 3 Det mindste tal er 1 Det største tal er 500 Det mindste tal er

49 C++ Det største tal er 1001 Det mindste tal er 1001 Her kommer et program, VISANSAT.CPP, som bruger funktionen vis_ansat, til at vise en ansats alder (som en int) og gage (som en float): void vis_ansat(int alder,float gage) cout << "Medarbejderen er " << alder << " år gammel"<< endl; cout << "Medarbejderens månedsløn er kr. " << gage << endl; vis_ansat(32, ); Nu kan programmet vise data for mange ansatte og hver gang bruge den samme kode i funktionen vis_ansat. Tænk på de muligheder der ligger i at kalde funktioner fra løkker: Hvis et program skal printe en lønoversigt kan det åbne en fil med dataene og læse dem fra en løkke der kalder to funktioner: skaf_ansat_data og print_ansat_data. Funktioner kan returnere værdier til det kaldende program En funktion bør udføre en specifik opgave i et program. I mange tilfælde udfører en funktion en eller anden udregning hvorefter den returnerer resultatet til det kaldende program. En funktion skal have at vide, hvad type værdi, den skal returnere, f.eks. int, float, char, osv. Det gøres ved at skrive typen foran funktionsnavnet, øverst i funktionen. Følgende funktion adderer to overførte tal (af typen int) og returnerer værdien (her også en int) til det kaldende program. int adder_tal(int a, int b) int resultat; resultat = a + b; return(resultat); Ordet int foran funktionens navn definerer funktionens retur-type. Inde i funktionen bruges return-sætningen til at returnere en værdi til den kaldende kode. Når programmet møder en return-sætning, returneres den angivne værdi, og funktionen afsluttes. I programmet kan funktionen kaldes på denne måde: sum = adder_tal(1, 2); I dette tilfælde tildeles funktionens retur-værdi til variablen sum. Man kan også bruge funktionen i en cout-sætning: cout << "Summen af de to tal er " << adder_tal(500, 501) << endl; Vi brugte tre sætninger i adder_tal-funktionen for at gøre den mere læsevenlig, men man kunne have nøjedes med return-sætningen: int adder_tal(int a, int b) return(a+b); Det følgende program, ADDER.CPP, bruger funktionen adder_tal til at addere forskellige tal: int adder_tal(int a, int b) return(a+b); 4 50

50 2. Funktioner cout << " = " << adder_tal(100, 200) << endl; cout << " = " << adder_tal(500, 501) << endl; cout << " = " << adder_tal(-1, 1) << endl; Prøv at kalde adder_tal med flere værdier end dem i eksemplet her: Se hvad der sker hvis du kalder funktionen med tallene og (du har sikkert gættet at funktionen vil lave en overflow-fejl) Funktioner er ikke begrænset til kun at kunne returnere værdier af typen int. Denne funktion, gennemsnit, returnerer gennemsnittet af to heltal: float gennemsnit(int a, int b) return((a + b) / 2.0); Her bliver funktionen erklæret som værende af typen float. Returnering af værdier Hvis en funktion ikke skal returnere en værdi, skrives ordet void foran funktionens navn. I modsat fald skrives typen f.eks. int eller float. Når en funktion skal returnere en værdi til det kaldende program, skrives værdien efter return-sætningen som afslutter afviklingen af funktionen. Hvis funktionen ikke skal returnere en værdi skrives return uden noget, på denne måde: return; Det bruges når funktionen er af typen void (den returnerer ikke en værdi). Return-sætningen gør derfor ikke andet end at afslutte funktionen. Sætninger i en funktion, som står efter return-sætningen vil derfor ikke blive udført. Funktions-prototyper Før et program kan kalde en funktion, skal C++ kende funktionen, den type, der returneres samt antallet og typerne på de parametre, der overføres til funktionen. Foreløbig har vi skrevet funktionerne foran selve main-programmet, men ofte vil man skrive funktioner forskellige steder i kilde-tekstfilen, og ofte vil funktioner kalde andre funktioner. Og da kompileren læser filen fra start til slut, kan den ikke vurdere et funktions-kald uden at have set første linie i funktionen også kaldet headeren. Derfor kan man i starten af programmet skrive en funktions-prototype som fortæller kompileren om funktionens type og parametre. Her er nogle funktions-prototyper for de funktioner, vi har lavet i denne øvelse: void vis_tekstlinie(void); void vis_tal(int); void vis_ansat(int, float); int adder_tal(int, int); float gennemsnit(int, int); Prototypen definerer den type, funktionen returnerer samt typen for hver parameter der overføres til funktionen. Hver prototype afsluttes med semikolon: float gennemsnit(int, int); Retur-type Parameter-typer Hvis C++ kompileren møder et funktions-kald som den ikke har set en funktions-erklæring eller prototype for, kommer den med en syntaksfejl. Også i C++ header-filerne vil du se mange prototyper til funktioner. Det følgende program, PROTO.CPP, viser brugen af funktions-prototyper: 4 51

51 C++ // Funktion-prototype float gennemsnit(int, int); cout<< "Gennemsnit af 20 og 2 er " << gennemsnit(20, 2) << endl; float gennemsnit(int a, int b) return((a + b) / 2.0); I dette eksempel kalder programmet funktionen gennemsnit på et sted i teksten hvor funktionen endnu ikke er erklæret. Derfor står prototypen i starten af teksten, foran main. Hvis prototypen fjernes opstår der syntaksfejl når programmet kompileres. Funktions-prototyper er til for programmøren Funktions-prototypen fortæller C++ kompileren hvilken type funktionen returnerer, og hvilke typer den får overført. Når programmet kompileres, bruger C++ kompileren prototyperne til at tjekke at funktionerne bliver brugt korrekt og at der ikke overføres data af forkert type til funktionen. I gamle dage var der mange kompilere, der ikke udførte dette type-tjek. Derfor var det almindeligt at programmører brugte timevis på at finde fejl som opstod fordi de overførte int-værdier til funktioner der forventede float. Det er derfor en stor hjælp at kompileren fortæller om syntaksfejl under kompileringen i stedet for at lave et program der kan gå ned uden at man som programmør ved hvorfor. Opsummering I denne øvelse gennemgik vi funktioner i C++. Vi så på centrale ting som parametre, retur-typer, og funktions-prototyper. I øvelse 10 skal vi se, hvordan man kan ændre værdien af parametrene inde i en funktion. Men før du går videre bør du sikre dig at du har forstået følgende: I større programmer bør elementerne brydes ned i funktioner som er lettere at overskue og bruge. Hver funktion i et program skal have sit eget navn. Funktions-navne bør være forståelige og svare til den opgave, funktionen udfører. Funktioner kan returnere en værdi til det kaldende program. I så fald skal retur-typen (int, char, osv) skrives foran funktionsnavnet. Hvis der ikke skal returneres en værdi skrives blot void foran funktionens navn. Data overføres til funktioner som parametre. Funktioner som får overført parametre skal i parentesen efter funktionens navn definere et navn og en type for hver parameter, der skal overføres. Hvis der ikke skal overføres parametre skrives her blot void. Da C++ skal kende typen af retur-værdi og de overførte parametre når kompileren møder et funktions-kald bør man skrive en funktions-prototype for hver funktion i starten af program-filen. Øvelse 10 Ændring af parameter-værdier I øvelse 9 så vi hvordan programmer kan struktureres i overskuelige funktioner, og vi lærte også hvordan man overfører data (parametre) til funktioner. Men vi brugte de overførte værdier uden at ændre dem. I øvelse 10 skal vi se hvordan man ændrer en overført variabels værdi inde i en funktion så variablen beholder den nye værdi også i det kaldende 52

52 2. Funktioner program. Det er ikke helt så enkelt som det lyder, men i øvelsen skal vi lære hvordan man gør. Efter øvelsen vil vi have gennemgået følgende: Medmindre funktionen bruger pointere eller C++ referencer kan den ikke ændre værdien af en overført variabel. For at kunne ændre en variabels værdi, skal funktionen kende variablens adresse i memory. Adresse-operatoren (&) bruges til at se en variabels memory-adresse. Hvis et program kender en variabels adresse kan det læse variablens værdi på adressen ved hjælp af indirection-operatoren (*). Når et program ønsker at ændre værdien af en overført variabel skal det i stedet for selve variablen overføre variablens adresse. Man har ofte brug for at ændre værdien af en variabel inde fra en funktion, og når man lige har forstået teknikken er den faktisk ikke så svær at bruge. Funktioner kan normalt ikke ændre overførte variabler Det følgende program, NOCHANGE.CPP, overfører to parametre stor og lille til funktionen vis_vaerdier. Denne funktion tildeler så værdien 1001 til begge parametre og skriver deres værdier på skærmen. Når funktionen slutter og programmet igen overtager, udskriver det igen variablernes værdier: void vis_vaerdier(int a, int b) a = 1001; b = 1001; cout << "Inde i vis_vaerdier er værdierne på " << a << 4 " og " << b << endl; int stor = 2002, lille = 0; cout << "Værdier før funktionskald er " << stor << " og " << lille << endl; vis_vaerdier(stor, lille); cout<< "Værdier efter funktionskald er " << stor << " og " << lille << endl; Når programmet kompileres og startes vil det skrive følgende på skærmen: C:\CPP\> NOCHANGE <ENTER> Værdierne før funktions-kaldet er 2002 og 0 Inde i vis_vaerdier er værdierne på 1001 og 1001 Værdierne efter funktions-kaldet er 2002 og 0 De overførte parametres værdier er i funktionen blevet ændret til 1001, men når funktionen slutter er værdierne af variablerne stor og lille uændret. For at kunne forstå hvorfor ændringerne inde i funktionen ikke har påvirket de overførte variabler, er det nødvendigt at forstå hvordan C++ overfører data til funktioner. Når et program overfører data til en funktion laver C++ almindeligvis en kopi af variablens værdi og placerer kopien i et midlertidigt område af memory som kaldes stakken. Funktionen bruger så denne kopi af værdien i sine operationer. Når funktionen slutter fjernes kopien og de ændringer, der måtte være sket med den, automatisk fra stakken. Som vi omtalte tidligere er en variabel et navn som programmet giver et bestemt sted i memory og som kan indeholde en værdi af en gi- 53

53 C++ ven type. Lad os f.eks. forestille os at variablerne stor og lille findes på memory-adresserne 10 og 12. Når så variablernes værdier overføres til en funktion, bliver kopier af værdierne placeret i stakken. Som det fremgår af figur 10.1, bruger funktionen så kopierne af værdierne. En pointer er en variabel som indeholder en memory-adresse. Inde i funktionen skal man gøre opmærksom på at man arbejder med pointere til variablen og ikke en reel variabel. Derfor bruges også her asterisker foran variabelnavnene: *stor = 1001; Adresse stor 2002 a 12 0 lille 0 b Stak Memory vis_vaerdier(stor, lille); void vis_vaerdier(int a, int b); // sætninger Figur C++ kopierer parameter-variablernes værdi og placerer kopien i et midlertidigt område af memory, kaldet stakken. Funktionen har adgang til indholdet af stakken hvor der ligger kopier af værdierne 2002 og 0. Men da funktionen ikke ved noget om stor og lille's adresser (10 og 12) kan den ikke ændre deres værdier. Ændring af en overført variabel For at kunne ændre værdien af en variabel, skal en funktion kende variablens adresse i memory. For at overføre en variabels adresse til en funktion skal man bruge C++'s adresse operator (&). Det følgende program viser hvordan man kan overføre adresserne på stor og lille til funktionen skift_vaerdier: skift_vaerdier(&stor, &lille); Funktionen skal nu have at vide at det kallende program ikke overfører en værdi, men en adresse på en variabel. Det gøres ved i funktions-headeren at erklære variablerne som pointere ved at skrive en asterisk foran variabelnavnet: void skift_vaerdier(int *stor, int *lille) *lille = 1001; Følgende program, CHGPARAM.CPP, bruger adresse-operatoren til at overføre variablerne stor og lille's adresser til funktionen skift_vaerdier som så bruger pointere til at pege på variablernes memory-adresser. Og da det er de rigtige variabler der ændres inde i funktionen, er ændringerne også gældende når funktionen er slut: void skift_vaerdier(int *a, int *b) *a = 1001; *b = 1001; cout << "Inde i skift_vaerdier er værdierne " << *a << " og " << *b << endl; int stor = 2002, lille = 0; cout << "Værdier før funktionskald er " << stor << " og " << lille << endl; skift_vaerdier(&stor, &lille); cout<< "Værdier efter funktionskald er " << stor << " og " << lille << endl; 54

54 2. Funktioner Når programmet kompileres og startes, skriver det følgende på skærmen: C:\CPP\> CHGPARAM <ENTER> Værdierne før funktions-kaldet er 2002 og 0 Inde i skift_vaerdier er værdierne 1001 og 1001 Værdierne efter funktions-kaldet er 1001 og 1001 Som det fremgår er de værdier som skift_vaerdier tildeler variablerne de samme efter at funktionen er slut. For at forstå hvorfor dette fungerer skal man huske at funktionen har fået overført adresserne på variablerne til stakken som vist i figur 10.2 pointere til variablerne bytter om på deres værdier: void byt_vaerdier(float *a, float *b) // Temporær variabel til at // opbevare en værdi float temp; temp = *a; *a = *b; *b = temp; Adresse a b Memory Stak float stor = ; float lille = ; skift_vaerdier (&stor, &lille); void skift_vaerdier(int *a, int *b); // sætninger byt_vaerdier(&stor, &lille); Figur Overførsel af værdier som adresse. Ved at bruge pointere (memory-adressen) inde i funktionen skift_vaerdier, kan den læse og ændre variablens værdi som den vil. For at mindske risikoen for fejl tillader C++ kompileren ikke at man overfører en variabels adresse til en funktion som ikke har en pointer som parameter. Og modsat vil kompileren normalt også komme med en advarsel hvis man prøver at overføre en variabel til en funktion som forventer en adresse. cout << "Stor indeholder nu " << stor << endl; cout << "Lille indeholder nu " << lille << endl; Programmet overfører parametrene som adresser. Funktionen bruger pointere som peger på de overførte adresser. Lad os lige kigge lidt på hvad det er der sker i funktionen byt_vaerdier. Som det fremgår erklærer funktionen variablerne a og b som værende pointere til værdier af typen float: Et eksempel mere Alle typer af variabler kan overføres som pointere. I funktionen erklærer man bare en pointer til den respektive type f.eks. int, float, eller char. I det følgende program, OMBYT.CPP, overfører vi adressen på to variabler af typen float til funktionen byt_vaerdier som så ved hjælp af void byt_vaerdier(float *a,float *b) Derimod bliver temp erklæret som en float ikke som en pointer til en float: float temp; Hvorfor skal temp ikke også være en pointer? Se på denne sætning: 55

55 C++ temp = *a; Her instrueres C++ om at tildele den værdi som a peger på (nemlig stor's værdi ) til temp. Og da temp er af typen float er tildelingen korrekt. Følgende sætning derimod erklærer temp som en pointer til en adresse indeholdende en værdi af typen float. float *temp; Temp kan nu indeholde en adresse på en decimaltals-værdi, men ikke værdien selv. Hvis man i tildelingen i funktionen glemmer at skrive indirection-operatoren (*) foran variablen a, vil sætningen forsøge at tildele a's værdi (som jo er en adresse) til variablen temp. Og da temp kan indeholde et decimaltal men ikke adressen på et decimaltal, ville der opstå en fejl. Hvis du stadig er forvirret over pointere kan jeg berolige dig med at vi går mere i detaljer med dem i senere øvelser. Alt hvad du behøver at huske nu, er at man skal bruge pointere, hvis man vil ændre værdien af en variabel i en funktion. Se også i assembler-koden Hvis man gerne vil forstå hvordan C++ kompileren behandler pointere, er det en god idé at kigge i kompilerens assembler-kode output. I de fleste C++ kompilere kan man angive en option på komandolinien når man kompilerer for at få kompileren til at lave en fil med programmets assembler-kode. Ved at læse den kan man få en klarere forståelse for, hvordan kompileren bruger stakken når der overføres parametre til en funktion. fra funktioner. I øvelse 11 skal vi se hvordan man kan bruge funktionerne i C++'s run-time bibliotek til at skrive brugbare programmer på kortere tid. Men før vi kaster os over det, bør du sikre dig at du har forstået følgende: Medmindre man bruger pointere kan man ikke i en funktion ændre værdien af en variabel som ikke er erklæret i funktionen men som bliver overført til den. Når et program overfører en værdi til en funktion, placerer C++ en kopi af værdien i et midlertidigt memory-område, kaldet stakken. Ændringer som funktionen laver i værdien påvirker kun kopien af værdien i stakken. For at kunne ændre en variabels værdi skal funktionen kende dens memory-adresse. Ved brug af C++'s adresse-operator (&) kan programmer overføre en variabels adresse til en funktion En funktion der får overført en variabels adresse skal erklære en pointer til samme type som den overførte adresses variabel har. Det gøres ved at skrive en asterisk (*) foran variabelnavnet. Når man i funktionen skal bruge værdien som pointeren peger på, skal man skrive en asterisk (kaldet indirection operatoren) foran pointer-variablens navn. Øvelse 11 Brug af run-time biblioteket Opsummering I denne øvelse så vi på hvordan man kan ændre variablers værdier fra funktioner. For at kunne gøre det skal man bruge pointere. Det kan virke uforståeligt i starten. I øvelse 14 skal vi se hvordan man bruger C++ referencer som gør det lidt lettere at ændre variablers værdier I øvelse 9 så vi hvordan man opdeler et program i mindre dele, kaldet funktioner som hver især udfører en specifik opgave. En af fordelene er at en funktion lavet i et program sommetider kan genbruges i et andet. I denne øvelse skal vi se hvordan mange kompilere kommer med en omfattende samling af funk- 56

56 2. Funktioner tioner som man kan bruge i sine programmer. Denne samling af funktioner kaldes run-time biblioteket. Ved at bruge funktionerne herfra sparer man programmeringstid fordi man ikke selv skal programmere funktionerne. Antallet af medfølgende funktioner kan være så højt som tusind, og i denne øvelse ser vi på hvordan disse funktioner kan bruges i ens egne programmer. Vi skal gennemgå følgende: Run-time biblioteket er en samling af funktioner som følger med kompileren og som let kan bruges i egne programmer. For at kunne bruge en funktion fra run-time biblioteket skal man skrive dens prototype i starten af sit program. Prototypen findes i funktionens header-fil. Nogle kompilere omtaler run-time biblioteket som application programming interface (API). De fleste run-time biblioteker indeholder hundrevis af funktioner som kan spare én for en masse udviklings-arbejde og være med til at gøre éns programmer mere brugbare. Du vil opdage at det er meget let at bruge funktionerne fra run-time biblioteket. Brug af funktioner fra run-time biblioteket I øvelse 9 så vi at for at et program kan kalde en funktion skal kompileren have læst funktionens definition eller prototype. Og da funktionerne i run-time biblioteket ikke er defineret i det program, man selv skriver, er det nødvendigt at erklære prototyper for de funktioner man vil bruge. For at gøre det lettere følger der med kompileren filer, der indeholder de rigtige prototyper. Alt hvad programmet skal gøre, er at inkludere den respektive header-fil ved hjælp af #include-sætningen. Derefter kan funktionen kaldes uden problemer. I det følgende program, VISTID.CPP, bruger vi run-time biblioteks-funktionerne time og ctime til at skrive den aktuelle systemtid og dato. Prototyperne for disse funktioner findes i header-filen time.h: // Indeholder nødvendige prototyper #include <time.h> time_t system_tid; system_tid = time(null); cout << "Tidspunktet er nu " << ctime(&system_tid) << endl; Når programmet kompileres og køres vil det skrive dato og klokkeslæt på skærmen: C:\CPP\> VISTID <ENTER> Tidspunktet er nu Mon Jan 01 16:13: Programmet bruger funktionerne time og ctime. Ctime kaldes med adressen på variablen system_tid ved hjælp af adresse-operatoren som vi lærte at bruge i øvelse 10. Hvis headerfilen time.h ikke havde været inkluderet øverst i kilde-teksten, havde programmet ikke kunnet bruge funktionerne time og ctime. I det følgende program, KVDRTROD.CPP, bruger vi funktionen sqrt til at beregne kvadratroden af nogle værdier. Prototypen for sqrt findes i header-filen math.h: // Indehol- #include <math.h> der prototype for sqrt cout <<"Kvadratroden af er " << sqrt(100.0) << endl; cout << "Kvadratroden af 10.0 er " << sqrt(10.0) << endl; cout << "Kvadratroden af 5. 0 er " << sqrt(5.0) << endl; 57

57 C++ I det næste program, SYSCALL.CPP, bruger vi funktionen system hvis prototype er defineret i headerfilen stdlib.h. System funktionen gør det let at kalde funktioner i operativsystemet som f.eks. "DIR": header-filerne i INCLUDE-biblioteket. Selvom meget af det kan virke forvirrende, er der alligevel nogle interessante ting i f.eks. math.h, ctime.h og stdlib.h som vi har brugt i denne øvelse. #include <stdlib.h> system("dir"); Her kaldes MS-DOS' DIR kommando ved hjælp af system funktionen. Mere information om run-time biblioteket Med din kompiler fulgte der i hundredevis af run-time funktioner, og dokumentationen til kompileren bør også indeholde en komplet beskrivelse af alle funktionerne. Typisk vil funktionen være beskrevet med et eksempel på en prototype. F.eks. som denne beskrivelse af sqrt funktionen: double sqrt(double); Her viser funktionens prototype at den returnerer en værdi af typen double og også skal have overført en værdi af typen double. En anden funktion, time er sikkert beskrevet med følgende prototype: time_t time(*time_t); Denne funktion returnerer en værdi af typen time_t (som også er defineret i header-filen time.h) og funktionen skal have overført en pointer til en variabel af typen time_t. Man kan altid udvide sine muligheder i C++ ved at kigge på forskellige funktioners prototyper. En anden måde at lære noget om funktionerne i kompilerens run-time bibliotek på er at se på API funktioner Foruden standard run-time biblioteket understøtter mange kompilere et eller flere API (Application Program Interface). Hvis du f.eks. programmerer under Windows, vil der være adgang til grafiske funktioner, multimedia funktioner, og meget mere. Derfor er det meget vigtigt at finde ud af om der allerede findes en funktion der kan gøre det, man skal til at programmere. Der er virkelig tid at spare. Opsummering C++'s run-time bibliotek er en stærk samling af funktioner, som man kan bruge i sine programmer. Find dokumentationen til run-time biblioteket som fulgte med din kompiler, og få en fornemmelse for hvor meget arbejde man kan spare med disse funktioner. I øvelse 12 skal vi se på lokale variabler og scope (området i et program hvor en variabel kan bruges). Men før du går videre bør du sikre dig at du har lært følgende: Run-time biblioteket er en samling af funktioner som kompileren stiller til programmørens rådighed. For at bruge en funktion fra run-time biblioteket skal man skrive dens prototype i starten af sit program. De fleste C++ kompilere kommer med header-filer som indeholder prototyperne til run-time bibliotekets funktioner. Foruden et run-time bibliotek, understøtter mange C++ kompilere et eller flere API som er funktioner der udfører opgaver som f.eks. grafik-rutiner (tegning af vinduer, menuer, osv) og multimedie (afspilning af lyd, video mm). 58

58 2. Funktioner Øvelse 12 Lokale variabler og scope I de funktioner, vi har programmeret indtil nu, har vi stort set kun brugt de værdier, som blev overført til funktionen. Men jo mere anvendelige funktioner man skriver, jo mere omfattende bliver de, og jo mere nødvendigt er det at de har deres egne variabler til at udføre beregninger med. Disse variabler kaldes lokale variabler, og deres eksistens og værdi er kun kendt for den funktion, de er erklæret i. Erklærer man f.eks. en lokal variabel ved navn gage i funktionen loen_udbetaling så kan andre funktioner ikke se eller manipulere variablen gage de aner end ikke at den eksisterer. I denne øvelse skal vi se på variablers scope: det område af programmet hvor en given variabel er kendt og kan bruges. Vi skal gennemgå følgende: Lokale variabler erklæres inde i en funktion på samme måde som man erklærer variabler i main ved at angive en type og et navn. Det navn, man giver en variabel behøver kun at være unikt i den aktuelle funktion. En variabels scope er det område i programmet hvor variablen kan ses og bruges. I modsætning til lokale variabler kan globale variabler ses og bruges overalt i et program og dets funktioner. Den globale resolutions-operator (::) gør det muligt at kontrollere en variabels scope. Det er meget let at erklære lokale variabler i en funktion. Faktisk er det det vi har gjort når vi erklærede variabler i main. Eklæring af lokale variabler En lokal variabel er en variabel som er erklæret inde i en funktion. Den kaldes lokal fordi det kun er den ene funktion, der kender til den. De lokale variabler erklæres i starten af en funktion på denne måde: void en_eller_anden_funktion(void) int antal; float resultat; Det følgende program, BIP.CPP, kalder funktionen bip_lyd som bipper med computerens højtaler det antal gange som er angivet med antalbip. Bip_lyd-funktionen bruger den lokale variabel taeller til at styre hvor mange gange der er blevet bippet: void bip_lyd(int antalbip) int taeller; for (taeller = 1; taeller <= antalbip; taeller++) cout << '\a'; bip_lyd(2); bip_lyd(3); Bip_lyd funktionen erklærer variablen taeller lige efter at funktionen er kaldt. Og da taeller er erklæret lokalt i bip_lyd, er det kun funktionen bip_lyd der ved at den findes. Navnekonflikter Når man erklærer lokale variabler, er det ikke ualmindeligt at man kan bruge samme navn til to forskellige variabler i hver sin funktion. Men da lokale variabler kun er kendt af deres egen funktion opstår der ingen navne-konflikt som der ellers ville hvis man erklærede to variabler med samme navn i den samme funktion. Enhver lokal variabel bliver behandlet 59

59 C++ separat i sin funktion. Det følgende program, LOKNAVN.CPP, kalder funktionen adder_tal for at lægge to heltal sammen. Resultatet af sammenlægningen tildeles den lokale variabel vaerdi. Men en af de variabler, hvis værdi bliver overført til funktionen fra main hedder også vaerdi. Men da de to variabler er lokale hver sit sted, opstår der ingen navne-konflikt: int adder_tal(int a, int b) int vaerdi; vaerdi = a + b; return(vaerdi); int vaerdi = 1001; int anden_vaerdi = 2002; cout << vaerdi << " + " << anden_vaerdi << " = " << adder_tal(vaerdi, anden_vaerdi) << endl; Globale variabler Foruden lokale variabler kan man i C++ også arbejde med globale variabler som kan ses og bruges overalt i programmet (globalt). En global variabel erklæres i starten af programmet, uden for enhver funktion: int global_variabel; // Her står programsætningerne Følgende program, GLOBAL.CPP, bruger en global variabel ved navn tal. Alle funktionerne i programmet kan se og ændre den globale variabels værdi. I eksemplet viser hver af funktionerne variablens værdi og inkrementerer den med 1: int tal = 1001; void lav_tal_om_1(void) cout << "Efter første ændring er tal lig med " << tal << endl; tal++; void lav_tal_om_2(void) cout << " Efter anden ændring er tal lig med " << tal << endl; tal++; cout << "i main har tal værdien " << tal << endl; tal++; lav_tal_om_1(); lav_tal_om_2(); Som tommelfingerregel bør man undgå at bruge globale variabler da det som programmør kan være svært at holde rede på hvor en variabel bliver ændret. I stedet bør variablerne erklæres i main og overføres til de forskellige funktioner som parametre: På den måde er det jo kopier af data der placeres på stakken og som funktionen arbejder med originalen er uændret. 60

60 2. Funktioner Konflikter mellem lokale og globale variabler Selvom man bør undgå brugen af globale variabler, kan der være situationer hvor det er berettiget at bruge dem. Til gengæld kan man så komme ud for at den globale variabel har samme navn som en lokal variabel, og der derved opstår en navne-konflikt. Når det sker har den lokale variabel førsteret: Programmet antager ganske enkelt at det ved enhver brug af variablen skal bruge den lokale. Men skal man bruge en global variabel i en funktion hvor der findes en lokal variabel med samme navn, skal man bruge den globale resolutions-operator (::). Hvis et program f.eks. har en global variabel der hedder antal, og man i en funktion der har en lokal variabel med samme navn vil bruge den lokale, skal man bare skrive som man plejer: // Funktionen bruger // den lokale variabel number = 1001; Men hvis man i funktionen vil bruge den globale variabel, skal man skrive den på denne måde: // Funktionen bruger // den globale variabel ::number = 2002; Det følgende program, GLOBLOK.CPP, bruger den globale variabel tal. Men funktionen vis_tal har også en variabel der hedder tal: int tal = 1001; // Global variabel void vis_tal(int tal) cout << "Den lokale variabel har værdien " << tal << endl; cout << "Den globale variabel har værdien " << ::tal << endl; 4 int tilfaeldigt_tal = 2002; vis_tal(tilfaeldigt_tal); Når programmet kompileres og køres, vil det skrive følgende på skærmen: C:\CPP\> GLOBLOK <ENTER> Den lokale variabel har værdien 2002 Den globale variabel har værdien 1001 Man kan i funktionen således bruge både lokale og globale variabler. Men husk på at de globale variabler meget let fører til uoverskuelighed i programmerne og bør undgås hvor det er muligt. Variablers scope Scope (på dansk kan det kaldes virkeområde) er det område i et program hvor en variabel kan ses og ændres (adresseres). Lokale variabler kan kun bruges lokalt, og deres scope er således den funktion, hvor de er erklæret. Globale variabler, derimod, kan adresseres overalt i et program, og deres scope er derfor hele programmet. Opsummering I denne øvelse har vi set hvordan man erklærer lokale variabler i funktioner. Vi så også på globale variabler som kan ses af alle funktioner overalt i et program, men som også kan gøre det sværere for programmøren at overskue programmet og dermed føre til fejl. I øvelse 13 skal vi se hvordan man kan erklære to eller flere funktioner med samme navn men som har forskellige parameteroverførsler eller som returnerer data af forskellig type. Men før vi kaster os over det, bør du checke at du har forstået følgende: En lokal variabel er en variabel erklæret inde i en funktion. 61

61 C++ En lokal variabel kan kun adresseres i den funktion, hvor den er erklæret. Der er ikke noget i vejen for at bruge variabler med samme navn i forskellige funktioner. Globale variabler kan ses og ændres overalt i programmet og af alle funktioner. Globale variabler erklæres i toppen af kilde-teksten, uden for enhver funktion. Da alle funktioner har adgang til de globale variabler, kan der let opstå fejl pga manglende overblik over hvor variablerne ændres. De globale variabler bør derfor bruges mindst muligt. Øvelse 13 Funktion overload Når man programmerer en funktion skal man angive hvilken type værdi, funktionen returnerer samt typen for hver af de overførte parametre. Et program har måske en funktion der hedder adder_tal, som får overført to værdier der bliver lagt sammen. I tidligere programmeringssprog f.eks. i C kunne man ikke overføre tre tal til denne funktion selv om man et sted i sit program skulle have lagt tre tal sammen. Man måtte løse problemet ved at lave en anden funktion, med et andet navn, som så kunne modtage tre parametre. Derefter var det så nødvendigt at huske på to funktioner når man skulle have lagt tal sammen: én, f.eks. adder_tal, lægger to tal sammen og en anden, adder_tre_tal, lægger tre tal sammen. I C++ kan det gøres på en meget smartere måde: flere funktioner kan nemlig have samme navn. De forskellige funktioner får så f.eks. overført et forskelligt antal parametre. Under kompileringen ser C++ kompileren så hvor mange parametre, der er i funktions-kaldet og vælger så den rigtige funktion. Denne metode med at lade kompileren vælge mellem flere funktioner af samme navn, kaldes overload. Man kunne også forestille sig at man havde to funktioner der begge lagde to tal sammen, men hvor den ene fik overført heltal og den anden fik overført decimaltal. Kompileren er altså i stand til at finde den funktion som matcher funktionskaldet. I øvelsen vil vi komme ind på følgende: Med funktion overload kan man lave flere funktioner med samme navn men med forskellige parametre. Overload bruges ved at skrive to eller flere funktioner med samme navn og retur-type men som enten har forskelligt antal parametre eller parametre af forskellig type. Funktion overload er en af de ting, der findes i C++ men som ikke er i programmeringssproget C. Overloading er en bekvem fordel som kan gøre ens programmer lettere at læse og vedligeholde. Funktion overload eksempel Det følgende program, OVERLOAD.CPP, har overload på funktionen adder_tal. Den første adder_tal-funktion adderer to heltal, og den anden adderer tre heltal. Under kompileringen bruger C++ kompileren så den rigtige version som passer til funktions-kaldet i programmet: int adder_tal(int a, int b) return(a + b); int adder_tal(int a, int b, int c) return(a + b + c); cout << " = " 4 62

62 2. Funktioner << adder_tal(200, 801) << endl; cout << " = " << adder_tal(100, 201, 700) <<endl; På samme måde overloader det følgende program, OLOAD2.CPP, funktionen vis_tekst. Den første funktion viser en fast tekst hvis vis_tekst bliver kaldt uden parametre. Den anden viser den overførte tekst og den tredje viser to overførte tekster: void vis_tekst(void) cout << "Standard besked: Program- mering i C++" << endl; void vis_tekst(char *besked) cout << besked << endl; void vis_tekst(char *besked1, char * besked2) cout << besked1 << endl; cout << besked2 << endl; vis_tekst(); vis_tekst("mit navn er Olsen!"); vis_tekst("c++ er ikke svært!", "Overloading er meget smart!"); Hvad kan overload bruges til i praksis? Et af de mest anvendte områder for overload er at returnere et resultat fra en funktion selvom parameter-typerne er forskellige. Hvis man har et program hvori der er en funktion, ugedag, som returnerer en ugedag (0 for søndag, 1 for mandag, osv. op til 6 for lørdag) kan man overloade ugedag så man dels kan kalde funktionen med en angivet som nummer, og dels med angivelse af dato, måned og år: int ugedag(int dag_nr) // Sætninger int ugedag(int maaned, int dag_i_maaned, int aar) // Sætninger Senere i bogen kommer vi til de mere objektorienterede elementer i C++ og her kan funktion overload anvendes i stor stil. Opsummering Funktion overload er muligheden for at definere flere funktioner med det samme navn. Når programmet kompileres, vælger C++ kompileren den rigtige funktion som har matchende parameter-type og antal. I øvelse 14 skal vi se hvordan referencer i C++ gør det lettere at ændre parametre i funktioner. Før vi går videre bør du dog lige sikre dig, at du har forstået følgende: Med funktion overload kan et program bruge den 'samme' funktion på forskellige måder. Overload laves ved at skrive to eller flere funktioner der har samme navn og returnerer data af samme type, men som har forskellige antal parametre eller forskellige parameter-typer. C++ kompileren finder selv den rigtige funktion som matcher funktions-kaldet i parameter antal og type. Funktion-overload kan være en hjælp i programmeringen fordi man ikke skal huske mange forskellige funktioners navne for lignende opgaver. 63

63 C++ Øvelse 14 Referencer i C++ I øvelse 10 så vi hvordan man ændrer variablers værdier i en funktion ved hjælp af pointere. Vi lærte også at man skriver en asterisk (*) foran pointeren for at læse den værdi, pointeren peger på, eller ændre den. Brugen af pointere stammer fra C, men for at gøre det lettere at ændre variablers værdier i funktioner findes der i C++ et begreb der hedder en reference. I øvelsen skal vi se at en reference er et alias (et andet navn) som man i sit program kan bruge til at adressere en variabel med. Vi skal i øvelsen gennemgå følgende punkter: En reference erklæres og initialiseres på denne måde: man erklærer en variabel hvor man skriver et et-tegn (&) lige efter typen, og bruger derefter tildelings-operatoren (=) til at oprette et alias: f.eks. int& alias_navn = variabel;. Et program kan overføre en reference til en funktion som så kan ændre den refererede variabels værdi, uden brug af pointere. I funktionen skal parameteren erklæres som en reference. Det gøres ved at skrive et et-tegn (&) efter parameter-typen. Når man bruger referencer er det meget let at ændre variablers værdier fra funktioner. En reference er et alias En C++ reference er et alias (et andet navn) man erklærer for en variabel. En reference erklæres ved umiddelbart efter type-angivelsen at skrive et et-tegn (&). I erklæringen af referencen skal man angive hvilken variabel, referencen refererer til, som vist her: int& alias_navn = variabel; Når referencen er erklæret kan den bruges ligesom den variabel, den refererer til. Det er samme variabel men med to forskellige navne. Disse to sætninger ændrer begge variablen variabel: alias_navn = 1001; variabel = 1001; Det følgende program, VISREF.CPP, erklærer en reference ved navn, alias_navn, og lader den være et alias for variablen tal. Derefter bruger programmet både variablen og referencen: int tal = 501; // Erklær referencen int& alias_navn = tal; cout << "Variablen tal har værdien " << tal << endl; cout << "Referencen til tal har værdien " << alias_navn << endl; alias_navn = alias_navn + 500; cout << "Variablen tal har værdien " << tal << endl; cout << "Referencen til tal har værdien " << alias_navn << endl; Programmet lægger 500 til referencen alias_navn og lægger dermed også 500 til variablen tal som alias_navn er en reference til. Når programmet kompileres og køres, viser det følgende på skærmen: C:\CPP\> VISREF <ENTER> Variablen tal har værdien 501 Referencen til tal har værdien 501 Variablen tal har værdien 1001 Referencen til tal har værdien 1001 Du bør almindeligvis ikke bruge referencer på den måde vi lige har gjort det, da det kan gøre det sværere at overskue programmet. Men 64

64 2. Funktioner som vi skal se gør referencer det lettere at ændre variablers værdier fra funktioner. Referencer som parametre Hovedformålet med referencer er at gøre det lettere at ændre en variabels værdi fra en funktion. Det følgende program, REFERENC.CPP, erklærer en reference ved navn tal_alias til variablen tal. Programmet overfører referencen til funktionen lav_tal_om, som ændrer dens værdi og dermed også tal's til 1001: void lav_tal_om(int &alias) alias = 1001; int tal; int& tal_alias = tal; lav_tal_om(tal_alias); cout << "Tal har værdien " << tal << endl; Som det fremgår, overfører programmet referencen til funktionen lav_tal_om som erklærer parameteren alias som en reference til en værdi af typen int: void lav_tal_om(int& alias) Inde i funktionen, lav_tal_om, kan man så ændre variablens værdi uden at skulle bruge en pointer hvorved programmet bliver lettere læseligt for programmøren. Brug kommentarer til at forklare referencerne Mange C++ programmører er i C blevet så vant til at bruge pointere i funktioner der skal ændre variablers værdier at de fejlagtigt kan tro at en funktion ikke ændrer en variabels værdi, blot fordi der ikke er en pointer. For at undgå den slags misforståelser er det en god idé at skrive kommentarer i koden: Både ud for funktions-kaldet og ved selve erklæringen af funktionen. Et andet eksempel I øvelse 10 lavede vi denne funktion, som bytter om på værdierne af to decimaltal: void byt_vaerdier(float *a,float *b) float temp; temp = *a; *a = *b; *b = temp; Her laver programmet en beregning hvori der indgår både almindelige variabler og pointervariabler. Det følgende program har også en byt_vaerdier-funktion men bruger referencer i stedet for pointere: void byt_vaerdier(float& a,float& b) float temp; temp = a; a = b; b = temp; float stor = ; float lille = ; float& stor_alias = stor; float& lille_alias = lille; byt_vaerdier(stor_alias, lille_alias); 4 65

65 C++ cout << "Stor har værdien " << stor << endl; cout << "Lille har værdien " << lille << endl; Byt_vaerdier-funktionen bliver lettere at forstå til gengæld har man nu to ekstra navne at holde øje med: referencerne stor_alias og lille_alias. Regler for referencer En reference er ikke en variabel: Når man først har lavet en reference til en variabel kan man ikke lave den om så den refererer til en anden variabel. Og modsat pointere kan man ikke udføre følgende operationer med referencer: Man kan ikke skaffe en references adresse ved hjælp af C++'s adresse-operator. Man kan ikke tildele en pointer til en reference. Man kan ikke sammenligne referencer ved hjælp af de relationelle operatorer. Man kan ikke udføre aritmetiske operationer på en reference, som f.eks. tilføjelse af en offset værdi. Man kan ikke under program-afvikling lade en reference skifte til at referere til en anden variabel. Vi vil komme ind på referencer igen senere i bogen, når vi skal i gang med de objekt-orienterede muligheder i C++. Opsummering I dette kapitel så vi hvordan man med referencer kan lave et alias eller andet navn til en variabel. Referencer kan gøre det lettere at ændre variablers værdier fra funktioner. I øvelse 15 skal vi se hvordan man kan lave optionelle parametre i C++: Hvis programmet kalder en funktion med færre end de angivne parametre kan funktionen bruge en default-værdi i stedet. Men først bør du sikre dig, at du har forstået følgende: En reference er i C++ et alias (andet navn) for en variabel. Når man erklærer en reference skriver man et et-tegn (&) foran typenavnet, fulgt af reference-navnet, tildelings-operatoren (=), og til sidst navnet på den variabel, som der refereres til. Når man først har valgt hvilken variabel en reference skal referere til kan man ikke mens programmet kører lave det om. Man bør kommentere brugen af referencer indgående for at gøre det lettere for andre programmører at se hvad der foregår. Referencer bør ikke bruges for meget da det kan gøre programmerne sværere at læse. Øvelse 15 Default parameter-værdier Som vi tidligere har set, overføres data til funktioner som parametre. I øvelse 13 så vi hvordan man kan overloade funktioner ved at have flere funktioner med samme navn men med forskellige antal parametre eller parameter-typer. Derudover kan man helt undlade at angive parametre når man kalder en funktion selvom funktionen forventer parametre. Når det sker, bruger C++ default værdier i stedet for de manglende parametre. I denne øvelse skal vi lære hvordan man definerer default parameter-værdier i funktioner. Vi vil gennemgå følgende: I C++ kan man definere default parameterværdier. Default værdier erklæres i funktionens header når den defineres. Hvis der i et funktions-kald mangler en eller flere værdier, bruger C++ de erklærede default-værdier. 66

66 2. Funktioner Hvis man udelader en parameter i et funktions-kald skal resten af de følgende parametre også udelades. Brugen af default parameter-værdier gør funktioner mere anvendelige og derfor også lettere at genbruge (i andre programmer). Erklæring af default-værdier Det er meget let at definere default værdier til en funktions parametre. Man bruger ganske enkelt tildelings-operatoren til at tildele hver parameter en værdi i funktionens header, som vist her: void en_funktion(int masse=12, float pris=19.95) // Funktionens sætninger Det følgende program, DEFAULT.CPP, tildeler default-værdier til parametrene a, b og c i funktionen vis_parametre. Selve programmet kalder så funktionen fire gange: første gang uden parametre overhovedet, anden gang med én parameter, tredje gang med to og fjerde gang med tre parametre: void vis_parametre(int a=1, int b=2, int c=3) cout << "a " << a << " b " << b << " c " << c << endl; vis_parametre(); vis_parametre(1001); vis_parametre(1001, 2002); vis_parametre(1001, 2002, 3003); Når programmet kompileres og startes, viser det følgende output på skærmen: C:\CPP\> DEFAULT <ENTER> a 1 b 2 c 3 a 1001 b 2 c 3 a 1001 b 2002 c 3 a 1001 b 2002 c 3003 Som det fremgår har funktionen brugt defaultværdierne når der ikke blev overført nok parametre. Hvornår kan man udelade parametre? Hvis man i et funktions-kald udelader en parameter, skal man udelade alle parametre, der følger efter. Man kan altså ikke udelade en parameter i midten af en serie. Man kan således ikke kalde funktionen vis_parametre og kun angive værdier til a og c, og udelade b. Opsummering I denne øvelse så vi, at man i C++ kan erklære default værdier for funktioners parametre. Udelades en parameter i kaldet til funktionen, bruger funktionen den erklærede default-værdi. Senere, når vi kommer til det mere objektorienterede stof, skal vi bruge default parametre til at initialisere forskellige variabler i en klasse. En variabel kan som bekendt indeholde én værdi af en bestemt type (int, float, osv). I øvelse 16 skal vi se hvordan man kan gemme mange værdier af samme type i et array. F.eks. hvis man vil lave et program der kan arbejde med 50 aktie-kurser eller karaktererne for 100 elever. Når man bruger arrays er det ingen kunst at opbygge og bruge store mængder data. Men før vi når så langt bør du sikre dig at du har forstået følgende: Default værdier erklæres ved hjælp af C++'s tildelings-operator. Dette gøres i funktionsdefinitionen. 67

67 C++ Når et program udelader parameter-værdier i et funktions-kald, bruger funktionen default-værdierne. Når man udelader en værdi i et funktionskald, skal alle følgende værdier i samme funktions-kald udelades: Man kan ikke udelade en parameter i midten. Default parametrene gør det lettere at arbejde med funktioner og genbruge dem i andre programmer. 68

68 3. Arrays og struktur-variabler Øvelse 16 Opbevaring af mange værdier i arrays Programmer arbejder med data i variabler. Men indtil nu har vi kun haft én værdi i hver variabel ad gangen. Ofte har man dog brug for at arbejde med mange informationer på samme tid, f.eks. karakterer for en hel klasse eller bogtitler af en bestemt forfatter. Når man i sit program har brug for at arbejde med mange værdier, bruger man en speciel data-struktur, kaldet et array. Det ville være uoverskueligt at skulle erklære en enkelt variabel for f.eks ordrenumre. Et array erklæres på næsten samme måde som en variabel, der kun skal indeholde én værdi: Man angiver typen som arrayet skal indeholde, derefter skrives navnet på arrayet, og til sidst hvor mange elementer, arrayet skal indeholde. I øvelsen skal vi lære hvordan man erklærer et array og læser og skriver data til det. Vi vil komme ind på følgende: Et array er en data-struktur som gør det muligt at gemme mange værdier i en enkelt variabel. Når et array erklæres skal man angive typen af data, som det skal indeholde, samt hvor mange værdier det skal kunne rumme (kaldet array-elementer). Alle elementer i et array skal være af samme type, f.eks. int, float, char osv. Man læser og skriver data til et element i arrayet ved at angive nummeret på det element i arrayet, hvor værdien skal læses eller skrives. Ligesom variabler kan også arrays initialiseres ved erklæringen. Et array kan fungere som parameter i et funktions-kald, præcis som almindelige variabler. I C++ og de fleste andre programmeringssprog gør man rig brug af arrays. Når vi i øvelse 17 skal til at bruge strenge (rækker af tegn f.eks. en bogtitel, et filnavn, osv) er det arrays af tegn (char), man bruger. Erklæring af et array Et array er en variabel som kan indeholde mere end én værdi. Præcis som de variabler vi har brugt hidtil, skal også et array være af en bestemt type (int, char, osv) samt have sit eget navn. Derudover skal man angive hvor mange værdier arrayet skal kunne indeholde. Alle værdier i et array vil altid være af samme datatype, man kan således ikke gemme int-værdier og float-værdier i samme array. Den følgende sætning erklærer et array ved navn karakterer, som kan rumme 100 int-værdier: int karakterer[100]; Når kompileren møder denne erklæring, laver den plads (allokerer) i hukommelsen til at indeholde 100 int-værdier. De værdier man opbevarer i et array kaldes array-elementer. Her kommer tre array-erklæringer som laver plads til 50 priser, 100 personers alder og 25 aktiekurser: float vare_pris[50]; int person_alder[100]; float aktie_kurs[25]; Adressering af elementer i arrayet De enkelte elementer i et array adresseres ved at angive et index som peger på det ønskede element. Vil man f.eks. læse eller skrive i det 69

69 C++ første element, bruger man index-værdien 0. Element nr. to adresseres med index 1. Det er vigtigt at huske at første element i et array altid har index 0. Derfor ligger den 8. værdi altså i element 7 og den 511. værdi ligger i element 510 og skal derfor adresseres med index 510. Se figur karakterer[0] karakterer[1] karakterer[2] karakterer[97] karakterer[98] karakterer[99] Figur Sådan adresserer C++ elementer i karakter-arrayet. Som tommelfingerregel kan man huske at i C++ peger index 0 på det første element og arrayets størrelse minus 1 giver index på sidste element. Det følgende program, ARRAY.CPP, erklærer et array med navnet vaerdier, som kan rumme fem heltals-værdier. Derefter tildeles værdierne 100, 200, 300, 400 og 500 til de enkelte elementer i arrayet, og værdierne vises på skærmen: int vaerdier[5]; // Array- // erklæringen vaerdier[0] = 100; vaerdier[1] = 200; vaerdier[2] = 300; vaerdier[3] = 400; vaerdier[4] = 500; cout << "Værdier i array:"<< endl; 4 cout << vaerdier[0] << << vaerdier[1] << << vaerdier[2] << << vaerdier[3] << << vaerdier[4] << endl; Den første værdi tildeles element 0 (vaerdier[0]) og den sidste værdi tildeles element 4 (arraystørrelsen som er 5, minus 1) Index-variabler Når man arbejder med arrays, er det almindeligt at bruge en index-variabel til at adressere elementer. Hvis f.eks. variablen i indeholder værdien 3, vil følgende sætning tildele værdien 400 til vaerdier[3]: vaerdier[i] = 400; Det følgende program, VISARRAY.CPP, viser de enkelte elementer i et array ved hjælp af en index-variabel i en for-løkke. For-løkken initialiserer i til 0 så den kan adressere element 0 i arrayet. Løkken ender når i bliver større end 4 (som også er sidste element i arrayet): int vaerdier[5]; // Array- // erklæringen int i; vaerdier[0] = 100; vaerdier[1] = 200; vaerdier[2] = 300; vaerdier[3] = 400; vaerdier[4] = 500; cout << "Værdier i array:"<<endl; for (i = 0; i < 5; i++) cout << vaerdier[i] << ; 70

70 3. Arrays og struktur-vairabler Hver gang for-løkken inkrementerer variablen i, kan programmet adressere det næste element i arrayet. Prøv at ændre for-løkken så den ser sådan ud: for (i = 4; i >= 0; i--) cout << vaerdier[i] << ; Nu vises værdierne i omvendt rækkefølge: Fra slutningen af arrayet til starten. Initialisering af arrays ved erklæringen Ligesom med almindelige variabler kan værdierne i et array initialiseres når det bliver erklæret. Det gøres ved at skrive de enkelte værdier, adskilt af kommaer, mellem to krøllede parenteser. Følgende sætning erklærer og initialiserer arrayet vaerdier: int vaerdier[5] = 100, 200, 300, 400, 500 ; På samme måde kan et array af decimaltal initialiseres under erklæringen: float gager[3] = , , ; Hvis der i erklæringen ikke angives lige så mange værdier som der er elementer i arrayet, vil de fleste C++ kompilere initialisere de manglende elementer til 0. I den følgende erklæring initialiseres de første tre elementer i et fem-elements-array: int vaerdier[5] = 100, 200, 300 ; Programmet initialiserede ikke elementerne 3 og 4: De fleste kompilere vil give dem værdien 0. Test eventuelt om din kompiler kan initialisere dele af et array på denne måde. Hvis man undlader at angive array-størrelse i erklæringen, allokerer kompileren plads til at arrayet kan rumme det antal værdier der er angivet i initialiseringen. F.eks. opretter følgende erklæring et array, som kan indeholde fire heltals-elementer: int heltal[] = 1, 2, 3, 4 ; Overførsel af arrays til funktioner Ligesom med variabler, får man også brug for at kunne overføre et helt array til en funktion. Funktionen skal måske bruges til at udskrive elementerne på skærmen eller til at udregne gennemsnittet af elementernes værdier. Når man overfører et array skal man angive arrayets type og dets navn fulgt af tomme klammer. Egentlig er det bare start-positionen for arrayet, man overfører til funktionen. Men for at man kan arbejde med det rigtige antal elementer i funktionen bør man også overføre en parameter der angiver arrayets størrelse: void tilfaeldig_funktion( int array[], int antal_elementer); Det følgende program, ARRAYFUN.CPP, overfører arrays til funktionen vis_array, som så ved hjælp af en for-løkke viser værdien af de enkelte array-elementer: void vis_array(int array[], int antal_elementer) int i; for (i=0; i< antal_elementer; i++) cout << array[i] << ; cout << endl; int smaa_tal[5] = 1, 2, 3, 4, 5 ; int store_tal[3] = 1000, 2000, 3000 ; vis_array(smaa_tal, 5); vis_array(store_tal, 3); 71

71 C++ Programmet overfører arrayet til funktionen ved at angive arrayets navn og angiver samtidig hvor mange elementer arrayet indeholder: vis_array(smaa_tal, 5); Det følgende program, SKAFARR.CPP, bruger en funktion ved navn skaf_vaerdier til at tildele værdier til arrayet tal: void skaf_vaerdier(int array[], int antal_elementer) int i; for (i = 0; i < antal_elementer; i++) cout << "Skriv tal nr. " << i << ": "; cin >> array[i]; int tal[3]; int i; skaf_vaerdier(tal, 3); cout << "Arrayet indeholder disse værdier:" << endl; funktionen er slut. Vi skal se nærmere på pointere i øvelse 20. Opsummering Et program kan gemme flere værdier af samme type i et array. Arrays bruges overalt i C++ programmering. I øvelse 17 skal vi se hvordan C++ bruger arrays til at arbejde med strenge af tegn, men først bør du sikre dig at du har forstået følgende: Et array er en variabel, som kan rumme flere værdier af samme type. Et array erklæres ved at angive type, navn, og hvor mange værdier, arrayet skal kunne rumme. De enkelte værdier i et array kaldes elementer. Det første element i et array hedder element 0 (array[0]); Det sidste elements nr er arrayets størrelse minus 1. Til at adressere elementerne i et array kan man bruge en index-variabel. Når man overfører et array til en funktion, skal man angive dets type og navn, men ikke størrelsen. Et arrays størrelse overføres normalt til en funktion som en separat værdi. Da arrays overføres til funktioner som adresser (pointere) kan funktionen lave ændringer i arrayets værdier som også er gældende når funktionen er afsluttet. for (i = 0; i < 3; i++) cout << tal[i] << endl; I øvelse 10 så vi at en funktion ikke kan ændre værdierne i en variabel medmindre man overfører variablens adresse til funktionen. Men i dette program bliver arrayet tal ændret i funktionen skaf_vaerdier. Det skyldes at C++ overfører arrayet til funktionen som en pointer: Det er derfor, programmet kan ændre arrayets værdier så ændringen også er gældende når Øvelse 17 Tekst-strenge Tegn-strenge bruges til at arbejde med tekstdata som f.eks. filnavne, bogtitler og medarbejdernavne. I C++ er tekst et array af typen 72

72 3. Arrays og struktur-vairabler char hvor sidste tegn er NULL-tegnet (ASCIItegn nr 0). I denne øvelse skal vi se nærmere på, hvordan tekst gemmes i strenge og bl.a lære hvordan man kan bruge funktioner fra runtime biblioteket til at manipulere strenge. I øvelsen skal vi gennemgå følgende: Man definerer en streng ved at erklære et array af typen char. Man skriver tekst i strengen ved at tildele tegn til elementerne i arrayet. Tegnet NULL (ASCII 0) markerer det sidste tegn i en streng. Strenge kan initialiseres med en værdi når de bliver erklæret. Strenge overføres til funktioner på samme måde som andre arrays. C++ run-time biblioteket indeholder funktioner til manipulation af strenge. I C++ gemmes tekst-strenge som arrays af typen char. Og da en streng er et array, er det lige så let at arbejde med dem som med andre typer af arrays som vi så på i øvelse 16. Erklæring af en streng En streng erklæres ved at erklære et array af typen char. Størrelsen afhænger af hvor lang en tekst der skal stå i arrayet. Følgende erklæring laver plads til 64 tegn: char filnavn[64]; I figur 17.1 ser vi hvordan arrayet er indexeret fra filnavn[0] til filnavn[63]. Husk at lægge 1 til størrelsen når du erklærer arrayet: Der skal være plads til NULL-tegnet efter selve teksten. Til gengæld sker der ikke noget ved at erklære for mange elementer i arrayet: Teksten vil altid slutte hvor NULL-tegnet er. Hovedforskellen mellem strenge og andre typer arrays er måden hvorpå C++ angiver det sidste element i arrayet. I en streng er det sidste tegn NULL, som kan skrives som '\0'. Når man tildeler tegn til en streng skal man huske filnavn[0] filnavn[1] filnavn[62] filnavn[63] Figur C++ opfatter strenge som arrays af typen char. at afslutte tildelingen med NULL ('\0'). Det følgende program, ALFABET.CPP, bruger en for-løkke til at tildele bogstaverne fra A til Z til streng-variablen alfabet. Derefter tilføjer programmet NULL-tegnet til streng-variablen og viser den på skærmen ved hjælp af cout: // 26 bogstaver: // (a - z) plus NULL-tegnet char alfabet[27]; char bogstav; int index; for (bogstav = A, index = 0; bogstav <= Z ; bogstav++, index++) alfabet[index] = bogstav; alfabet[index] = NULL; cout << "Strengen indeholder nu " << alfabet; Programmet tilføjer NULL-tegnet til strengen for at indikere at der ikke er mere tekst: alfabet[index] = NULL; 73

73 C++ Når cout output-strømmen udskriver tegnstrengen, gør den det med ét tegn ad gangen, indtil den kommer til NULL-tegnet. Men se lige en ekstra gang på for-løkken. Som det fremgår initialiserer og inkrementerer løkken to variabler (bogstav og index). Når flere variabler skal initialiseres eller inkrementeres, adskilles de med kommaer. for (bogstav = A, index = 0; bogstav <= Z ; bogstav++, index++) Streng-konstanter bliver automatisk efterfulgt af NULL Hver gang vi har arbejdet med tekst har vi brugt streng-konstanter, skrevet i anførselstegn: "Dette er en streng-konstant" Når man skriver en streng-konstant afslutter C++ den automatisk med NULL som vist i figur De t t e e r e n s t r e n g - k o n s t a n t \0 Figur C++ kompileren tilføjer automatisk NULL-tegnet til streng-konstanter. Strenge skal altid slutte med NULL-tegnet for at cout kan skrive dem på skærmen. I det følgende program, LOOPNULL.CPP, har vi ændret cout-sætningen så programmet nu viser tegnene i strengen, et ad gangen indtil for-løkken møder værdien NULL i det element den tester på (alfabet[index]): char alfabet[27]; char bogstav; int index; for (bogstav = A, index = 0; bogstav <= Z ; bogstav++, index++) alfabet[index] = bogstav; alfabet[index] = NULL; for (index = 0; alfabet[index]!= NULL; index++) cout << alfabet[index]; cout << endl; For-løkken undersøger tegnene i strengen, et ad gangen og så længe tegnet ikke er NULL (som markerer at strengen er slut) bliver løkken ved med at vise tegnene et ad gangen mens den inkrementerer index-variablen. 'A' er forskellig fra "A" Sommetider ser man bogstaver skrevet i enkelte anførselstegn (f.eks. 'A') og andre gange står tegnet skrevet i dobbelte anførselstegn (f.eks. "A"). Et tegn skrevet i enkelte anførselstegn (') er en tegn-konstant af typen char. Da kompileren ved at en char altid kun er et tegn behøver der ikke være noget NULL-tegn efter tegnet og der bliver derfor kun afsat en byte til konstanten. Derimod er et bogstav i dobbelte anførselstegn en streng-konstant og optager derfor 2 bytes i hukommelsen: bogstavet selv og NULL-tegnet (som kompileren tilføjer). Figur 17.3 viser hvordan C++ lagrer tegn-konstanten 'A' og streng-konstanten "A". // 26 bogstaver: // (a - z) plus NULL-tegnet 4 A\0 "A" A A Figur C++ lagrer tegn-konstanten 'A' på en anden måde end streng-konstanten "A". 74

74 3. Arrays og struktur-vairabler Hvis en konstant er længere end ét tegn kan man ikke bruge de enkelte anførselstegn de bruges kun til tegn-konstanter. Initialisering af strenge Som vi så i øvelse 16, kan et array tildeles en værdi når det erklæres, og det samme gælder tekst-strenge. Den tekst, man vil have initialiseret strengen med, skrives efter erklæringen på denne måde: char navn[64] = "Ole Olsen"; Hvis arrayet er større end det antal tegn man initialiserer det med, tildeler de fleste C++ kompilere NULL til resten af tegnene i strengarrayet. Og hvis man ikke skriver et tal i parentesen, allokerer kompileren automatisk plads til at strengen kan rumme den skrevne tekst plus NULL-tegnet: char navn[] = "Ole Olsen"; Det følgende program, INIT_STR.CPP, initialiserer en streng ved erklæringen: char titel[64] = "Programmering I C++"; char oevelse[64] ="Tekst-strenge"; cout << "Bog:\t" << titel << endl; cout << "Øvelse:\t" << oevelse << endl; I mange af de følgende programmer i bogen bliver strenge initialiseret på denne måde. Eksperimenter med programmet og lav om på den tekst, variablerne bliver intialiseret med. Overførsel af strenge til funktioner At overføre en streng til en funktion minder meget om måden, hvorpå arrays bliver overført. I funktionen skal man skrive array-typen som er char og en variabel efterfulgt af hårde parenteser. Det er ikke nødvendigt at overføre arraystørrelsen, da strenge altid slutter med NULL i modsætning til almindelige arrays hvor man bør overføre størrelsen af arrayet (da funktionen ikke kan vide hvor arrayet slutter). Det følgende program, VISSTR.CPP, bruger funktionen vis_streng til at skrive en tekst-streng på skærmen: void vis_streng(char streng[]) cout << streng << endl; vis_streng("hej, C++!"); vis_streng("programmering i C++"); For funktionen vis_streng er tekst-strengen et almindeligt array, som den får overført en pointer til. Cout sørger selv for at udskrive tegnene indtil den møder tegnet NULL. Derfor er det ikke nødvendigt at overføre længden af strengen til funktionen. I det følgende program, STRLEN.CPP, laver vi en funktion, streng_laengde, som finder NULL i den overførte streng og derfor kan returnere strengens længde til det kaldende program: int streng_laengde(char streng[]) int i; for (i=0; streng[i]!= \0 ; i++); // Løkken gør ikke andet end // at køre strengen igennem return(i); // Strengens længde 4 75

75 C++ char titel[] = "Programmering I C++"; char oevelse[] = "Tekst-strenge"; cout << "Strengen " << titel << " indeholder " << streng_laengde(titel) << " tegn" << endl; cout << "Strengen " << oevelse << " indeholder " << streng_laengde(oevelse) << " tegn " << endl; For-løkken kigger på hvert tegn i strengen alt imens den inkrementerer i. Når løkken kommer til NULL-tegnet er betingelsen falsk og løkken stopper. Værdien af i svarer nu til strengens længde. Fordele ved at NULL er det samme som ASCII 0 Som sagt er NULL-tegnet det samme som tegn nummer 0 i ASCII-tabellen. Og da vi fra øvelse 7 ved at C++ bruger 0 til at repræsentere falsk, giver det mulighed for at programmere enklere løkker. Denne for-løkke viser hvordan man kan finde NULL-tegnet i en streng: for (index=0; streng[index]!= NULL; index++); Men da NULL svarer til 0, kan det skrives enklere: for (indeks = 0; streng[index]; index++); Løkken fortsætter så længe det element, index peger på, ikke indeholder tegnet NULL (0 og dermed falsk). Streng-funktioner fra run-time biblioteket I øvelse 11 omtalte vi det store run-time bibliotek som følger med de fleste C++ kompilere. Biblioteket indeholder også en række funktioner der kan bruges til streng-manipulation. For eksempel kan man bruge funktionen strupr til at lave en streng om til store bogstaver, og funktionen strlen til at returnere antallet af tegn i en streng. Med de fleste kompilere følger også funktioner der kan lede efter bestemte ord i en streng. Det følgende program, STRSTOR.CPP, viser hvordan man bruger funktionerne strupr og strlwr fra run-tim biblioteket: #include <string.h> // Indeholder // prototyper char titel[] = "Programmering I C++"; char oevelse[] = "Tekst-streng"; cout << "Store bogstaver: " << strupr(titel) << endl; cout << "Små bogstaver: " << strlwr(oevelse) << endl; Som programmør kan man spare meget tid ved at bruge de funktioner der findes til streng-manipulation i run-time biblioteket. Opsummering De fleste C++ programmer gør brug af tekststrenge. I denne øvelse så vi hvordan man arbejder med strenge. I øvelse 18 skal vi se hvordan relaterede informationer kan gemmes i en struktur-variabel. En struktur-variabel kan f.eks. indeholde en ansats navn, adresse, løndata, telefonnummer alt sammen i én variabel. Men før vi går i gang med øvelse 18 skal vi lige resumere hvad vi har gennemgået i denne øvelse: 76

76 3. Arrays og struktur-vairabler En tekst-streng er et array af tegn (char) som slutter med ASCII 0 også kaldet NULL-tegnet. En streng oprettes ved at erklære et array af typen char. Man skal huske at tilføje NULL efter sidste tegn i en streng. C++ tilføjer automatisk NULL til strengkonstanter i dobbelte anførselstegn. Strenge kan ved erklæringen initialiseres med en værdi som angives i dobbelte anførselstegn. Med de fleste C++ kompilere følger en omfattende samling af funktioner til strengmanipulation. Disse funktioner findes i run-time biblioteket Øvelse 18 Struktur-variabler Øvelse 16 beskrev hvordan man kan samle mange værdier af samme type i et array. Men sommetider kan det være nødvendigt at samle værdier af forskellig type. Hvis man f.eks. skal lave et lønstyrings-program og skal behandle data for en ansat: Så skal programmet arbejde med medarbejderens id, navn, adresse, alder, løn, osv. Disse informationer skal gemmes i forskellige typer: char, int, float, og tekst-strenge. For at samle informationer af forskellig type kan man bruge en struktur. En struktur er en variabel hvori man kan gemme forskellige informationer (kaldet elementer) af forskellig type. Brugen af strukturer gør et program mere overskueligt og lettere at arbejde med for programmøren. F.eks. bliver det lettere at overføre flere informationer af forskellig type til funktioner. I øvelsen skal vi se hvordan man erklærer og bruger strukturer. Vi vil komme ind på følgende: Strukturer gør det muligt at gruppere data af forskellig type i én variabel. En struktur består af en eller flere dataenheder kaldet elementer. Man definerer en struktur ved at angive dens navn og dens elementer. Hvert element i en struktur har et navn og en type, f.eks. char eller int. Når strukturen er defineret kan man i programmet erklære variabler af strukturens type. For at en funktion skal kunne ændre værdien af enkelt-elementerne i en struktur, skal man overføre strukturens adresse til funktionen. Kendskab til brugen af strukturer vil gøre det lettere at forstå C++'s objekt-orienterede klasse-struktur når vi kommer dertil. Definering af en struktur En struktur er en sammensat type, som programmet senere kan bruge til erklæring af variabler: På samme måde som en int er en type som man kan erklære variabler af. Man definerer derfor først strukturen hvorefter man kan erklære variabler af strukturens type. I definitionen af strukturen bruger man det reserverede ord struct fulgt af et navn og en krøllet parentes. Derefter angives type og navn for et eller flere elementer. Efter sidste element afsluttes med endnu en krøllet parentes efter hvilken man evt. kan erklære en variabel af strukturens type: struct navn int element_navn_1; float element_navn_2; variabel; Følgende eksempel definerer en struktur som kan indeholde persondata: 77

77 C++ struct ansat char navn[64]; long id; float gage; char telefon[10]; int kontor_nr; ; Her erklærer vi ikke en variabel af strukturens type. Variabler af en struktur (også kaldet forekomster) kan erklæres hvor som helst efter at strukturen er defineret. Det gør man ved at angive strukturens navn (også kaldet dens tag), på denne måde: ansat chef, medarbejder, ny_medarbejder; Her erklæres tre variabler af strukturen ansat. Nogle programmører skriver det reserverede ord struct foran erklæringen: struct ansat chef, medarbejder, ny_medarbejder; Det reserverede ord struct skal bruges når man programmerer i C, så mange finder det naturligt også at bruge det i C++, selvom det ikke er nødvendigt. Adressering af strukturens elementer For at tildele en værdi til et element i en struktur eller for at læse dens værdi skal man bruge C++'s punktum-operator (.). I de følgende sætninger tildeles forskellige værdier til forskellige elementer i variablen medarbejder (som er af typen ansat): medarbejder.id = 12345; medarbejder.gage = ; medarbejder.kontor_nr = 102; Elementet adresseres ved at angive variablens navn, efterfulgt af punktum og element-navnet. Det følgende program, ANSAT.CPP, bruger en struktur af typen ansat: #include <string.h> struct ansat char navn[64]; long id; float gage; char telefon[12]; int kontor_nr; medarbejder; // Kopier et navn til streng navn strcpy(medarbejder.navn, "Ole Olsen"); medarbejder.id = 12345; medarbejder.gage = ; medarbejder.kontor_nr = 102; // Kopier et telefon-nr til // strengen telefon strcpy(medarbejder.telefon, " "); cout << "Medarbejder: " << medarbejder.navn << endl; cout << "Telefon-nr: " << medarbejder.telefon << endl; cout << "Id: " << medarbejder.id << endl; cout << "Gage: " << medarbejder.gage << endl; cout << "Kontor: " << medarbejder.kontor_nr << endl; Det er ikke noget problem at tildele værdier til strukturens heltals- og decimaltals-elementer ved hjælp af tildelings-operatoren. Men programmet er nødt til at bruge funktionen strcpy for at kopiere strenge til elementerne navn og telefon. Medmindre man tildeler en streng en værdi som del af erklæringen, kan tekst kun kopieres til en streng ved hjælp af strcpy-funktionen. 78

78 3. Arrays og struktur-vairabler Strukturer og funktioner Hvis en funktion ikke skal ændre en struktur kan strukturen overføres ved at angive dens navn. Det følgende program, VISANSAT.CPP, bruger funktionen vis_ansat til at vise værdierne af de enkelte elementer i en variabel erklæret som ansat: #include <string.h> struct ansat char navn[64]; long id; float gage; char telefon[12]; int kontor_nr; ; void vis_ansat(ansat medarbejder) cout << "Medarbejder: " << medarbejder.navn << endl; cout << "Telefon-nr: " << medarbejder.telefon << endl; cout << "Id: " << medarbejder.id << endl; cout << "Gage: " << medarbejder.gage << endl; cout << "Kontor: " << medarbejder.kontor_nr << endl; ansat ny_medarbejder; // Kopier et navn til streng navn strcpy(ny_medarbejder.navn, "Ole Olsen"); ny_medarbejder.id = 12345; ny_medarbejder.gage = ; ny_medarbejder.kontor_nr = 102; 4 // Kopier et telefon-nr til // strengen telefon strcpy(ny_medarbejder.telefon, " "); vis_ansat(ny_medarbejder); Programmet overfører struktur-variablen ny_medarbejder til funktionen vis_ansat som så viser værdierne af de enkelte elementer. Læg mærke til at ansat nu defineres uden for main og før funktionen vis_ansat. Det er nødvendigt da funktionen erklærer variablen medarbejder som værende en ansat. Ændring af elementer i funktioner Som vi så tidligere skal man overføre en variabels adresse til en funktion for at de ændringer, funktionen laver i adressen, har nogen effekt når funktionen er slut. Det samme gælder for strukturer. For at overføre en strukturs adresse til en funktion skriver man C++'s adresse-operator (&) foran variabelnavnet: funktion(&medarbejder); //funktions- //kaldet I en funktion der skal ændre værdien af struktur-variabler adresserer man de enkelte elementer ved hjælp af pointere. Når man skal bruge en pointer til at pege på et element i en struktur gøres det lettest på følgende måde: pointer_variabel->element=ny_vaerdi; Det følgende program, ELEMENT.CPP, overfører (som reference) en struktur af typen ansat til funktionen skaf_id som beder brugeren om at indtaste en medarbejders id. Id-værdien tildeles dernæst til strukturens id-element. Da medarbejder er en reference (pointer) til den overførte variabel er det den rigtige variabel der bliver ændret, og værdien vil stadig være der efter at funktionen er afsluttet. #include <string.h> 4 79

79 C++ struct ansat char navn[64]; long id; float gage; char telefon[10]; int kontor_nr; ; void skaf_id(ansat *medarbejder) cout << "Skriv id for en medarbejder: "; cin >> medarbejder->id; ansat ny_medarbejder; // Kopier et navn til streng navn strcpy(ny_medarbejder.navn, "Ole Olsen"); skaf_id(&ny_medarbejder); mange elementer den har. Men før vi når så langt bør du sikre dig at du har forstået følgende: Strukturer gør det muligt at gruppere relaterede data af forskellig type i én variabel. Informations-enhederne i en struktur kaldes dens elementer. En struktur definerer en type som kan bruges til at erklære variabler med. Variablen erklæres ved at angive strukturens navn (også kaldet tag). En struktur-variabels elementer adresseres ved hjælp af punktum-operatoren, f.eks. variabel.element. Hvis en funktion skal ændre værdien af en struktur-variabels elementer skal variablen overføres som en adresse (reference). Når en funktion skal adressere elementer i en struktur-variabel som er overført som reference, bruges denne syntaks: variabel- >element. cout << "Medarbejder: " << ny_medarbejder.navn << endl; cout << "Id: " << ny_medarbejder.id << endl; Programmet kalder skaf_id med parameteren ny_medarbejder som reference (pointer til en adresse). I skaf_id tildeles elementet id en værdi med sætningen: cin >> medarbejder->id; Opsummering Strukturer gør det muligt at gruppere relaterede informationer af forskellig type i én variabel. På den måde bliver det lettere at arbejde med objekter der består af mere end ét element. I øvelse 19 skal vi lære om union-strukturen som også indeholder elementer men på en lidt anden måde: En union kan nemlig kun indeholde én værdi ad gangen uanset hvor Øvelse 19 Union-strukturer I øvelse 18 så vi hvordan data kunne grupperes i strukturer. Men sommetider kan det være en fordel at kunne betragte den samme information på flere måder. Andre gange kan det være nødvendigt at arbejde med to eller flere værdier men kun én af værdierne ad gangen. Til det formål kan man bruge en union. I denne øvelse skal vi se hvordan man erklærer og arbejder med unioner. Og du vil opdage hvor meget en union minder om de strukturer, vi arbejdede med i øvelse 18. Vi vil i øvelsen behandle følgende centrale ting: En C++ union minder meget om en struktur, bortset fra måden som en værdi gem- 80

80 3. Arrays og struktur-vairabler mes i hukommelsen på. En union kan kun indeholde værdien for ét element ad gangen. En union består ligesom en struktur af elementer. En union definerer en type som programmet kan erklære variabler med. En unions elementer adresseres ved hjælp af punktum-operatoren. Hvis værdien af en unions element skal ændres i en funktion skal union-variablen overføres som adresse (reference). En anonym union er en union uden navn (tag). Denne union indeholder to elementer, miles og meter. Erklæringerne opretter variabler der kan indeholde afstanden til de respektive lande. Ligesom med en struktur kan man tildele værdier til de to elementer. Forskellen fra en struktur er, at man kun kan tildele værdier til ét af elementerne ad gangen. Når man erklærer en union-variabel allokerer kompileren kun plads, svarende til det største element i unionen. For union-variabler af typen afstand bliver der kun sat plads af til at kunne rumme en long som det fremgår af figur Erklæring af en union En union minder meget om en struktur. Her defineres en union ved navn afstand. Unionen har to elementer: Union afstand int miles; long meter; japan; 4 byte union afstand int miles; long meter; ; Ligesom strukturen fungerer en union som en type der kan bruges i en variabel-erklæring. Union-variabler kan erklæres på følgende to måder. Enten på denne måde: union afstand int miles; long meter; japan, tyskland, frankrig; eller sådan her: union afstand int miles; long meter; ; afstand japan, tyskland, frankrig; Figur C++ allokerer kun plads svarende til en unions største element. Programmet tildeler nu en værdi til miles-elementet: japan.miles = 12123; Hvis programmet på et senere tidspunkt tildeler en værdi til meter-elementet, bliver værdien i miles overskrevet. I det følgende program, UNION.CPP, bruger vi unionen afstand. Først tildeles en værdi til miles-elementet, og værdien skrives på skærmen. Derefter tildeles en værdi til meter-elementet hvorved værdien i miles overskrives: union afstand int miles; long meter; 4 81

81 C++ gaatur; gaatur.miles = 5; cout << "Manden gik en tur på " << gaatur.miles << " miles "<< endl; gaatur.meter = 10000; cout << "Manden gik en tur på " << gaatur.meter << " meter "<< endl; Det er vigtigt at huske at efter at programmet har tildelt en værdi til elementet meter, er værdien 5 som vi tildelte elementet miles blevet overskrevet. Anonyme unioner En anonym union er en union uden navn. En anonym union bruges til at spare plads i computerens hukommelse eller til at lave et alias til en variabel. Hvis f.eks. et program skal bruge to variabler: miles og meter, men kun én af variablerne skal bruges ad gangen: Så kunne man bruge afstand-unionen som før, men man ville være nødt til at bruge variablerne som afstand.miles og afstand.meter. Et alternativ er at erklære en anonym (navnløs) union: union int miles; long meter; ; Denne union har ikke noget navn, og man skal ikke erklære nogen variabel. Nu kan man adressere de to elementer uden at skulle bruge punktum-notationen. Det følgende program, ANONYM.CPP, erklærer en anonym union som indeholder elementerne miles og meter. Elementerne bliver betragtet som normale variabler, men når programmet tildeler en værdi til det ene element, bliver værdien af det andet overskrevet: union int miles; long meter; ; miles = 10000; cout << "Miles har værdien " << miles << endl; meter = ; cout << "Meter har værdien " << meter << endl; Ved at bruge en anonym union har vi nu en variabel som sparer plads i hukommelsen uden at gøre det nødvendigt at bruge et union-navn og punktum-operatoren til at adressere elementerne. Opsummering I denne øvelse har vi gennemgået hvordan en union erklæres. En union minder meget om en struktur bortset fra at data gemmes på forskellig måde. I øvelse 10 så vi at man skal overføre en pointer (memory-adresse) til en funktion for at den kan ændre værdien af en variabel. Siden da har vi også brugt pointere til arrays og strenge. I øvelse 20 skal vi se nærmere på pointeroperationer, men før vi går i gang med det, bør du sikre dig, at du har forstået følgende: Når en union-variabel erklæres, allokerer kompileren kun plads svarende til det største element i unionen. Definitionen af en union optager ikke plads i hukommelsen men er en type ud fra hvilken programmet kan erklære union-variabler. 82

82 3. Arrays og struktur-vairabler De enkelte elementer i en union adresseres ved hjælp af punktum-notationen. Når en værdi tildeles et nyt element bliver værdien af det aktive element overskrevet. En union kan således kun indeholde en værdi for ét af dens elementer ad gangen. En anonym union er en union uden navn. Elementerne i en anonym union kan som andre variabler bruges uden punktum-operatoren men stadig kun med ét aktivt element ad gangen. Øvelse 20 Pointere En variabel gemmes på en adresse i hukommelsen. En pointer peger på en adresse. I øvelse 10 så vi at man skal overføre en variabel til en funktion som adresse (pointer) for at de ændringer funktionen laver i variablen også skal være gældende efter at funktionen er afsluttet. Vi har allerede arbejdet med pointere flere gange, bl.a. i forbindelse med tekst-strenge og andre arrays. Pointere er velegnede til at manipulere de enkelte elementer i et array, og fordi så mange progammører bruger dem, er det vigtigt at forstå hvordan de virker. Derfor skal vi i denne øvelse kigge nærmere på pointere. Efter øvelsen vil vi have set på følgende: Mange programmer arbejder med strenge som pointere og manipulerer strengens indhold ved hjælp af pointer-operationer. Når et program inkrementerer en pointervariabel (altså en variabel som indeholder en adresse) sørger C++ automatisk for at forhøje adressen med den rigtige størrelse: 1 byte for char, 2 byte for int, fire byte for float, osv. Pointere kan også bruges i arrays af heltalsog decimaltals-værdier. Pointere er en central del af C++. Vær derfor omhyggelig i gennemgangen af øvelsen. Pointere og strenge En pointer indeholder en lager-adresse, og når et program overfører et array (f.eks. en streng) til en funktion er det i virkeligheden adressen på det første element i arrayet der bliver overført. Derfor er det naturligt at funktioner bruger pointere når de arbejder med tekst-strenge. En pointer til en streng erklæres ved at skrive en asterisk foran variabelnavnet i funktionen: void funktion(char *streng); Asterisken foran variabelnavnet fortæller C++ at variablen skal indeholde en adresse altså en pointer. I det følgende program, PTR_STR.CPP, bruger funktionen vis_streng en pointer til en streng til at skrive strengens indhold på skærmen, et tegn ad gangen: void vis_streng(char *streng) while (*streng!= \0 ) cout << *streng; streng++; vis_streng("programmering i C++"); Betingelsen i while-løkken (*streng!= '\0') tester om det tegn, pointeren streng peger på er forskelligt fra NULL (som jo markerer afslutningen på strengen). Hvis tegnet er forskelligt fra NULL, bliver det vist på skærmen med cout. Derefter inkrementeres pointeren streng så den peger på den næste adresse i strengen. 83

83 C++ Når løkken kommer til NULL er hele strengen skrevet, og løkken slutter. Lad os forestille os at den overførte strengs første element ligger på adressen 1000 i computerens hukommelse. Hver gang løkken inkrementerer pointeren streng, vil den pege på det næste tegn (på adresse 1001, 1002, 1003, osv) som det fremgår af figur streng++ Figur Gennemgang af streng med en pointer. Et eksempel mere Ved at bruge en pointer kan man altså gå igennem de enkelte tegn i en streng indtil NULL-tegnet mødes. Det følgende program, PTR_LEN.CPP, bruger i funktionen streng_laengde en streng-pointer til at beregne længden af den overførte tekst-streng: int streng_laengde(char *streng) int laengde = 0; while (*streng!= \0 ) laengde++; streng++; return(laengde); 4 char titel[] = "Programmering I C++"; cout << titel << " består af " << streng_laengde(titel) <<" tegn"; While-løkken kigger tegnene i strengen igennem enkeltvis, indtil den kommer til NULLtegnet. Fjern overflødige sætninger I de forrige programmer i denne øvelse brugte vi while-løkken på denne måde: while (*streng!= \0 ) Men da tegnet NULL jo også er ASCII-værdien 0, og 0 betyder falsk, kan vi reducere koden til dette: while (*streng) De to løkker har samme funktion, nemlig at udføre sætninger indtil pointeren streng peger på NULL. I øvelse 5 så vi at postfix inkrementerings-operatoren (når ++ står efter variabelnavnet) først inkrementerer værdien efter at variablen er blevet brugt i sætningen. Med den viden kan sætningerne i løkken skæres ned til en enkelt. De to følgende while-løkker fungerer fuldstændig ens: while (*streng) cout << *streng; streng++; while (*streng) cout << *streng++; Sætningen cout << *streng++; starter med at udskrive tegnet, som pointeren streng peger 84

84 3. Arrays og struktur-vairabler på, for herefter at inkrementere pointerens værdi så den peger på det næste tegn i tekststrengen. Ved at bruge de trimmede sætninger, kommer programmet med både vis_streng og streng_laengde-funktionerne, til at se sådan ud: void vis_streng(char *streng) while (*streng) cout << *streng++; int streng_laengde(char *streng) int laengde = 0; while (*streng++) laengde ++; return(laengde); char titel[] = "Programmering I C++"; vis_streng(titel); cout << " består af " << streng_laengde(titel)<< " tegn"; Programmer, der bruger pointere vil typisk bruge denne reducerede notation da den er lettere at skrive og fylder mindre i koden. Gennemlæsning af en tekst-streng Denne funktion, vis_store_bogstaver, bruger pointere til at konvertere en overført streng til store bogstaver: while (*streng) if ((*streng >= a ) && (*streng <= z )) *streng = *streng - a + A ; cout << *streng++; Funktionen kan så bruges i et program på denne måde: char tekst[] = "ole olsen i fedtefadet"; vis_store_bogstaver(tekst)<< endl; Husk at inkludere iostream.h i starten af programmet. Pointere til andre array-typer Pointere kan selvfølgelig bruges i forbindelse med andre array-typer end strenge. Det følgende program, PTRFLOAT.CPP, bruger en pointer til at udskrive værdierne i et array bestående af decimaltal: void vis_decimaltal(float *array, int antal_elementer) int i; for (i = 0; i < antal_elementer; i++) cout << *array++ << endl; 4 void vis_store_bogstaver(char *streng) 4 85

85 C++ float decimaltal[5] = 1.1, 2.2, 3.3, 4.4, 5.5; vis_decimaltal(decimaltal, 5); For-løkken starter med at vise den værdi, som pointer-variablen array peger på, og inkrementerer pointeren det antal gange, der er overført i variablen antal_elementer. I modsætning til hvis parameteren havde været en streng (og løkken kunne køre til den fandt NULL) kan funktionen ikke vide hvor mange elementer arrayet indeholder, og skal derfor have værdien overført, som her antal_elementer. C++ styrer pointerens spring Pointere kan bruges i forbindelse med arrays af hvilken som helst type. I denne øvelse har vi brugt pointere til strenge og decimaltal. Når en pointer bruges til at gå igennem et array skal C++ huske hvor mange byte den type, arrayet består af, fylder (f.eks. én byte til en char) for at kunne flytte sig med det rigtige interval. I et array af char skal pointeren flytte sig én byte for hver inkrementering, og i et array af int skal den flytte sig to byte. Som programmør skal man ikke holde styr på at pointeren flytter sig det korrekte antal byte, men kun sørge for at pointeren springer med det rigtige antal elementer. Opsummering Pointere bruges meget i C++, ikke mindst til at manipulere strenge med. I denne øvelse så vi nærmere på pointere. I næste øvelse går vi i gang med de objekt-orienterede elementer i C++ men før vi begynder på det, bør du sikre dig at du har forstået denne øvelse: En pointer-variabel indeholder en adresse. Når arrays overføres til funktioner er det adressen på arrayets første element, funktionen ser. Når en pointer inkrementeres, peger den på næste adresse i hukommelsen. En funktion der bruger pointere til at manipulere en streng, vil typisk gå igennem strengen, tegn for tegn, indtil den møder NULL-tegnet. Når en funktion får overført et array af en anden type end en streng, skal den også have arrayets størrelse (eller en fastlagt slutværdi) at vide. C++ sørger automatisk for at flytte en pointer det rigtige antal byte, når programmet inkrementerer pointeren. 86

86 4. Klasser Øvelse 21 Klasser i C++ En klasse er det centrale element i objekt-orienteret programmering i C++. I denne øvelse skal vi se at en klasse er en struktur, der er udvidet til, foruden data, også at kunne indeholde funktioner. En klasse bruges som skabelon til erklæring af objekter. Når man programmerer vil man opfatte et objekt som en konkret ting f.eks. en telefon. Objektet indeholder dels data som svarer til egenskaber (i telefonens tilfælde f.eks. farve og nummer) og dels funktioner der udfører en handling på, eller med, objektet (f.eks. ring_op, svar, osv). Ved at samle data og funktioner i én variabel, gøres programmerne enklere og lettere at genbruge. Vi vil i øvelsen komme ind på følgende: En klasse defineres ved at angive klassens navn, data-elementer og klassens funktioner (kaldet metoder) Definitionen af en klasse fungerer som skabelon, med hvilken et program kan erklære objekter. På samme måde som en struktur er en definition, man kan erklære variabler med. Data-elementerne i et objekt adresseres ved hjælp af punktum-operatoren. Et objekts funktioner kaldes ved hjælp af punktum-operatoren. Objekter og objekt-orienteret programmering Programmer, der ikke er objekt-orienterede, arbejder med variabler der indeholder informationer om forskellige ting f.eks. ansatte, bøger og filer og separate funktioner der manipulerer variablerne. I objekt-orienteret programmering fokuserer man på de ting (objekter), et system består af. Objektet indeholder altså både værdier og funktioner der kan manipulere værdierne. F.eks. kunne objektet være en fil: Foruden filens data indeholder objektet metoder til visning, udskrivning og sletning af filen. Når man så skal vise en fil, ved man at metoden til at gøre det, ligger i objektet. Målet er at indbygge så meget information om et objekt i klassen som muligt, hvilket også gør det muligt at tage et objekt fra et program og bruge det i et andet. En klasse er en kombination af dels data og dels funktioner der bruger klassens data. Almindeligvis omtales funktioner i en klasse som metoder. Ligesom en struktur skal klassen bestå af et unikt navn efterfulgt af ét eller flere elementer, skrevet mellem krøllede parenteser: class klasse_navn int data_element; // Data element void vis_data(void);// Funktions- // element // (metode) ; Når en klasse er defineret kan man erklære variabler af klassens type. Disse variabler kaldes objekter: klasse_navn objekt_et, objekt_to; Her defineres klassen ansat med data og metoder: class ansat public: char navn[64]; long id; float gage; void vis_ansat(void) 4 87

87 C++ ; cout << "Navn: " << navn <<endl; cout << "Id: " << id << endl; cout << "Gage: " << gage <<endl; ; Denne klasse indeholder tre data-elementer og et funktion-element. Læg mærke til ordet public: Som vi skal se nærmere på i øvelse 22, kan elementer i en klasse være enten private eller offentlige (public). Private elementer kan kun ses og ændres af objektets egne metoder mens offentlige elementer kan adresseres af alle funktioner, inden for objektets virkefelt. I eksemplet her er alle elementerne offentlige, hvilket gør det muligt at adressere dets elementer overalt i programmet ved hjælp af punktumoperatoren. Når en klasse er defineret, kan man erklære objekter af klassens type: ansat elev, chef, sekretaer; Det følgende program, KLASSE.CPP, erklærer to ansat-objekter. Ved hjælp af punktum-operatoren tildeles værdier til objektets data-elementer. Derefter kaldes objektets vis_ansat-metode som skriver informationen om den ansatte: #include <string.h> class ansat public: char navn[64]; long id; float gage; void vis_ansat(void) cout<< "Navn: " << navn <<endl; cout<< "Id: " << id << endl; cout<< "Gage: " << gage <<endl; ; ; 4 ansat chef, sekretaer; strcpy(chef.navn, "Ole Olsen"); chef.id = 12345; chef.gage = 25000; strcpy(sekretaer.navn,"ulf Borg"); sekretaer.id = 101; sekretaer.gage = ; chef.vis_ansat(); sekretaer.vis_ansat(); Programmet erklærer to objekter af klassen ansat og tildeler værdier til data-elementerne ved hjælp af punktum-operatoren og kalder til sidst objektets funktion vis_ansat. En klasses metoder kan skrives efter klasse-definitionen I det forrige eksempel skrev vi metoden vis_ansat inde i selve klassen. Men da lange metoder kan gøre det svært at overskue en klasse-definition, kan man skrive metoden efter klasse-definitionen og nøjes med at skrive funktionens prototype inde i klassen. Så kommer klassen til at se sådan ud: class ansat public: char navn[64]; long id; float gage; void vis_ansat(void); ; Nu er prototypen skrevet i klassen, og vi kan nu skrive selve metoden et andet sted i programmet. Men da flere klasser kan have metoder med samme navn, skal man angive hvilken klasse, metoden hører til. Det gøres ved at skrive klassenavn og metodenavn adskilt af ::- operatoren: 88

88 4. Klasser void ansat::vis_ansat(void) cout << "Navn: " << navn << endl; cout << "Id: " << id << endl; cout << "Gage: " << gage << endl; ; Lad os se hvordan hele programmet kommer til at se ud når definitionen af metoden bliver flyttet uden for klassen: #include <string.h> class ansat public: char navn[64]; long id; float gage; void vis_ansat(void); ; void ansat::vis_ansat(void) cout << "Navn: " << navn << endl; cout << "Id: " << id << endl; cout << "Gage: " << gage << endl; ; ansat chef, sekretaer; strcpy(chef.navn, "Ole Olsen"); chef.id = 12345; chef.gage = 25000; strcpy(sekretaer.navn,"ulf Borg"); sekretaer.id = 101; sekretaer.gage = ; chef.vis_ansat(); sekretaer.vis_ansat(); Et eksempel til Det følgende program, RACEHUND.CPP, definerer klassen hund, som foruden data-elementer, indeholder metoden vis_race. Selve programmet erklærer to objekter af klassen hund og viser information om hver hund: #include <string.h> class hund public: char race[64]; int gennemsnits_hoejde; //centim. int gennemsnits_vaegt; // kg-vægt void vis_hund(void); ; void hund:: vis_hund(void) cout << "Racens navn: " << race << endl; cout<< "Racens gennemsnitshøjde: " << gennemsnits_hoejde << endl; cout << "Racens gennemsnitsvægt: " << gennemsnits_vaegt << endl; hund prik, nuser; strcpy(prik.race, "Dalmatiner"); prik.gennemsnits_vaegt = 30; prik.gennemsnits_hoejde = 64; strcpy(nuser.race, "Boxer"); nuser.gennemsnits_vaegt = 22; nuser.gennemsnits_hoejde = 15; prik.vis_hund(); nuser.vis_hund(); 89

89 C++ Opsummering En klasse er en samling af data-elementer og metode-elementer. Klassen bruges som type ved erklæring af objekter. Klasser er fuldstændig som strukturer (som vi lærte om i øvelse 18) blot udvidet med funktioner, som er metoder, der arbejder med objektets data. Tit bruges betegnelserne klasse og objekt i flæng, hvilket ikke gør så meget da det drejer sig om det samme. Dog skal det slås fast, at en klasse er definitionen af et muligt objekt, mens et objekt er en erklæret (eksisterende) forekomst af en klasse. Et program arbejder således ikke med klasser, men med objekter som er skabt ud fra klassens anvisning. Klasser er kernen i objekt-orienteret programmering i C++, og i de kommende øvelser skal vi se nærmere på mulighederne i klasser. I øvelse 22 skal vi se på forskellen mellem offentlige og private data-elementer, men først bør du sikre dig, at du har forstået følgende: Et objekt er, sagt enkelt, en ting, som programmet kan udføre forskellige opgaver med. Et objekt erklæres på basis af en klasse. Ligesom en struktur, indeholder en klasse elementer. Elementerne består af data og funktioner (metoder) der manipulerer klassens data. Et programs klasser skal have unikke navne. Når en klasse er defineret kan den bruges til at erklære objekter af klassens type. Klassens elementer (data og metoder) bruges ved at skrive objektets navn og elementets navn adskilt af punktum-operatoren. En klasses metode kan skrives uden for klasse-definitionen. Det gøres ved at skrive prototypen i klassen og selve metoden udenfor, f.eks.: klasse::funktion. Øvelse 22 Private og offentlige elementer I øvelse 21 definerede vi vores første klasser. Vi brugte ordet public til at angive at klassens elementer kunne bruges af andre end objektet selv. I denne øvelse skal vi se at elementer i en klasse også kan være private og derfor ikke kan bruges af andre end objektet selv. Ved at kombinere offentlige (public) og private elementer i en klasse kan man kontrollere hvordan et program bruger et objekt. Efter øvelsen vil vi have gennemgået følgende: Rettigheden til at bruge elementer i et objekt kan styres med ordene private og public. Private elementer er elementer, som programmet, der bruger objektet ikke behøver at vide noget om. Klasser der indeholder private elementer kan også indeholde interface-funktioner som kan adressere de private elementer. Som omtalt i øvelse 21 bør man samle mest mulig information om et givet objekt i klassedefinitionen. Derved kommer klassen til at indeholde alt, hvad der skal til for at bruge objektet. Dette øger også muligheden for at genbruge klasse-definitioner fra program til program. Abstraktion En klasse indeholder data og metoder, og ideelt set behøver et program ikke at vide andet om objektet end navnene på (og formålet med) disse elementer: Programmet behøver ikke at vide hvad der sker inde i et objekts metoder men kun hvad metoden kan bruges til. Hvis et program f.eks. indeholder klassen fil, behøver programmet kun at vide at metoden fil.udprint printer filen ud, og at metoden fil.slet sletter filen men det er unødvendigt for programmet at vide hvordan metoderne gør. Objektet skal 90

90 4. Klasser ses som en sort kasse, hvor programmet ved hvilke metoder, det kan kalde, men ikke hvad der foregår inde i metoderne. Abstraktion betyder at et program kun kan se de få, klart definerede metoder, det skal kende for at kunne bruge objektet. De klasser, vi definerede i øvelse 21 bestod udelukkende af offentlige elementer som hele programmet kunne se, og derfor kunne vi skrive sætninger der ændrede objektets data direkte: class ansat // følgende elementer er offentlige public: char navn[64]; long id; float gage; void vis_ansat(void); De data-elementer, objektet bruger til mellemregninger og opbevaring af information, erklæres som private hvorved de ikke kan ses eller ændres af andre end objektet selv. Hvis man ikke bruger ordet public vil alle elementerne i en klasse være private, og de kan derfor ikke adresseres ved hjælp af punktumoperatoren. Derfor kan man adskille offentlige og private elementer på denne måde: class klasse public: int variabel1; void initialiser_private(int,float); void vis_data(void); private: int vaerdi1; float vaerdi2; I programmet kan vi nu bruge de offentlige elementer ved hjælp af punktum-operatoren: objekt.initialiser_private(20,1.23); objekt.vis_data() Hvis programmet prøver at adressere de private elementer vaerdi1 og vaerdi2, kommer kompileren med en syntaks-fejl. Det er god skik i objekt-orienteret programmering at skjule en klasses data-elementer, så de ikke kan manipuleres på tilfældige måder, fra tilfældige steder i programmet. Objektet kan kun ændres ved at bruge objektets egne metoder. Disse metoder kan så teste de overførte værdier og sikre at kun acceptable værdier bliver tildelt objektet. Forestil dig at Barsebäcks styrings-software har objektet reaktor med data-elementet temperatur som altid skal have en værdi mellem 40 og 75: Hvis temperatur er offentlig kan en programmør uden advarsel komme til at skrive en uheldig værdi: reaktor.temperatur = 110 Programmet er sikrere hvis temperatur derimod er privat, og klassen indeholder en metode der hedder saet_temperatur og som tester om den overførte værdi er acceptabel: int reaktor::saet_temperatur(int vaerdi) if ((vaerdi >= 40) && (vaerdi <= 75)) temperatur = vaerdi; return(0); // Temperatur OK else return(-1); // Temperatur ej OK Funktioner der styrer adgangen til en klasses data-elementer, kaldes interface-funktioner. Interface-funktioner beskytter et objekts dataelementer. klasse objekt; // Erklær et objekt objekt.variabel1 = 1001; 91

91 C++ Brug af offentlige og private elementer Det følgende program, GEMDATA.CPP, viser brugen af offentlige og private elementer. Først defineres klassen ansat: class ansat public: int init_vaerdier(char *, long, float); void vis_ansat(void); int skift_gage(float); long skaf_id(void); private: char navn[64]; long id; float gage; ; Klassen beskytter sine data-elementer ved at gøre dem private. Programmet skal bruge interface-funktionerne til at se og ændre data. Her følger programmet som bruger ansat-klassen: #include <string.h> class ansat public: int init_vaerdier(char*, long, float); void vis_ansat(void); int skift_gage(float); long skaf_id(void); private: char navn[64]; long id; float gage; ; int ansat::init_vaerdier(char *ansat_navn, long ansat_id, float ansat_gage) strcpy(navn, ansat_navn); 4 id = ansat_id; if (ansat_gage < ) gage = ansat_gage; return(0); // Transaktion OK else return(-1); // Ugyldig gage void ansat::vis_ansat(void) cout << "Den ansattes navn: " << navn << endl; cout << "Id: " << id << endl; cout << "Gage: " << gage << endl; int ansat::skift_gage(float ny_gage) if (ny_gage < ) gage = ny_gage; return(0); // Transaktion OK else return(-1); // Ugyldig gage long ansat::skaf_id(void) return(id); ansat pedel; if (pedel.init_vaerdier("kurt Olsen", 244, ) == 0) cout << "Værdier tildelt til ansat" << endl; pedel.vis_ansat(); 4 92

92 4. Klasser if (pedel.skift_gage( ) == 0) cout << "Ny gage tildelt" << endl; pedel.vis_ansat(); else cout <<"Ugyldig gage angivet.." << endl; Selvom det er et langt program, er det ikke kompliceret. Metoden init_vaerdier initialiserer objektets private data ved hjælp af en if-sætning for at sikre at den gage, der bliver tildelt, er korrekt. Metoden vis_ansat viser objektets private data, og metoderne skift_gage og skaf_id er også interface-funktioner som manipulerer private data-elementer. Hvis man prøver at adressere et af de private data-elementer direkte ved hjælp af punktum-operatoren, kommer kompileren med en syntaks-fejl. Adressering af klasse-elementer ved hjælp af den globale resolutions-operator Læg mærke til at parametrene i init_vaerdier begynder med ansat_, som vist her: int ansat::init_vaerdier(char *ansat_navn, long ansat_id, float ansat_gage) Når vi ikke nøjedes med at benævne parametrene navn, id og gage, var det for at undgå navne-konflikt med klassens data-elementer: Hvis en funktions-parameter har samme navn som et data-element, bruger programmet altid funktionens lokale variabel altså ikke klassens data-element men den overførte parameter. Hvis man alligevel ønsker at kalde parametrene det samme som klassens data-elementer, kan man skelne mellem dem ved hjælp af den globale resolutions-operator (::). I den følgende metode skriver vi klasse-navnet og :: foran data-elementets navn og viser dermed at der er tale om et data-element i klassen og ikke den overførte parameter: int ansat::init_vaerdier(char *navn, long id, float gage) strcpy(ansat::navn, navn); ansat::id = id; if (gage < ) ansat::gage = gage; return(0); // Transaktion OK else return(-1); // Ugyldig gage Ved at skrive data-elementets fulde navn (klassenavn::data-element) undgås navnekonflikter i klassens metoder. Metoder kan også være private Indtil nu er det kun data-elementer vi har set defineret som private, men metoder kan også være private. Hvis en offentlig metode i en klasse bruger en anden metode i klassen til en udregning, behøver udregningsmetoden ikke at være offentlig: Den skal kun bruges af en metode i klassen. I de situationer skrives funktionen (eller dens prototype) i gruppen af private elementer. Ligesom private data kan private metoder ikke bruges uden for objektet (ved hjælp af punktum-operatoren). Opsummering Ved at isolere elementer i en klasse så kun klassens egne metoder kan bruge elementet, reduceres risikoen for fejl. Elementer skjules for andre end klassen selv ved hjælp af ordet private. De fleste klasse-definitioner består af en kombination af offentlige og private elementer. En almindelig metode i en klasse er initialisering af objektet dvs at objektet får tildelt værdier til dets data-elementer. I øvelse 23 skal vi se, at der i C++ er en speciel metode, kaldet constructor, som automatisk bliver 93

93 C++ kaldt, når et objekt erklæres. Ved at bruge constructor-metoden er det let at initialisere objekter når de erklæres i programmet. Men før vi kaster os over constructor-metoden, bør du sikre dig, at du har forstået følgende: En klasses elementer kan være offentlige (public) eller private. Et objekts offentlige elementer kan bruges direkte af programmet, ved hjælp af punktum-operatoren mens private elementer kun kan bruges af objektets egne metoder. Hvis ikke andet er anført, betragter C++ alle elementer i en klasse som private. Private elementer i et objekt adresseres ved hjælp af interface-funktioner. Hvis en metode erklærer lokale variabler (f.eks. overførte parametre) og disse har samme navn som objektets data opstår der en navne-konflikt. Konflikten undgås ved at skrive klassens navn foran data-elementet, adskilt af ::, f.eks. ansat::navn. Øvelse 23 Constructor- og destructor-funktioner Constructor-funktioner er metoder som bruges til at initialisere et objekts data-elementer. Constructor-metoden hedder det samme som dens klasse. Constructor-funktioner returnerer aldrig værdier. Hver gang et program erklærer en variabel af en klasse bliver klassens constructor kaldt (hvis der er defineret en constructormetode i klassen). Mange objekter sætter plads af i hukommelsen til data. Når objektet slettes, kalder det destructor-funktionen som bl.a. kan bruges til at rydde op i hukommelsen efter objektet. Destructor-funktioner hedder det samme som deres klasser, bortset fra at der skal stå ~ foran navnet. Destructor-funktioner returnerer ikke nogen værdi. Man kan se på en constructor som en funktion, der hjælper med at konstruere et objekt. Ligeledes kan man se på en destructor som en funktion som bliver kaldt når et objekt destrueres. Destructor-funktioner bruges ikke så tit som constructor-funktioner: Typisk bruges de hvis objektet har brugt hukommelse som skal ryddes op når objektet slettes. Når man arbejder med objekt-orienteret programmering har man ofte brug for at initialisere data-elementerne i et objekt. Det kan f.eks. være private data som kun kan ændres ved at bruge en metode (som vi så i øvelse 22). For at gøre det lettere at initialisere et objekts data, findes der for hvert objekt en speciel constructor-funktion som bliver kaldt når objektet bliver erklæret. På samme måde er der en destructor-funktion som kaldes når objektet bliver nedlagt. I denne øvelse skal vi se på constructor- og destructor-metoderne, og når vi er igennem øvelsen har vi set på følgende: En enkel constructor-funktion En constructor er en metode i en klasse med samme navn som klassen. Hvis man har en klasse ved navn ansat, skal constructor-metoden også hedde ansat. Hvis der i en klasse findes en constructor-funktion, vil den blive kaldt, hver gang der erklæres et objekt af klassen. Det følgende program, CONSTRUC.CPP, definerer en klasse ved navn ansat. Klassen får også en constructor-funktion som tildeler start-værdier til objektets data-elementer. En constructor returnerer ikke en værdi, men det betyder ikke, at man skal skrive void foran constructor-navnet: 94

94 4. Klasser class ansat public: // Constructor-funktionen ansat(char *, long, float); void vis_ansat(void); int skift_gage(float); long skaf_id(void); private: char navn[64]; long id; float gage; ; Efter selve klasse-definitionen, indeholdende prototypen til constructor-funktionen, skrives constructor-metoden som en helt almindelig metode: ansat::ansat(char *navn, long id, float gage) strcpy(ansat::navn, navn); ansat::id = id; if (gage < ) ansat::gage = gage; else // Ugyldig gage ansat::gage = 0.0; Som vi så i øvelse 23 skrives klassens navn og :: foran data-elementerne for at skelne dem fra funktionens lokale variabler (i dette tilfælde parametrene). Nu vil vi indbygge klassen og dens constructor i et program, som vi kalder CONSTRUC.CPP: #include <string.h> class ansat public: // constructor-metoden ansat(char *, long, float); void vis_ansat(void); int skift_gage(float); long skaf_id(void); 4 private: char navn[64]; long id; float gage; ; ansat::ansat(char *navn, long id, float gage) strcpy(ansat::navn, navn); ansat::id = id; if (gage < ) ansat::gage = gage; else // Ugyldig gage ansat::gage = 0.0; void ansat::vis_ansat(void) cout << "Medarbejderens navn: " << navn << endl; cout << "Id: " << id << endl; cout << "Gage: " << gage << endl; ansat chef("alan Olfertsen", 144, ); chef.vis_ansat(); Læg mærke til at parentesen med objektets start-værdier står lige efter objektets navn i erklæringen, som var det et funktions-kald. Objekter, der indeholder en constructor erklæres samtidig med at start-værdierne overføres til objektet: ansat chef("ulf Borg", 14, ); Hvis programmet skulle erklære flere objekter af klassen ansat kunne erklæringen/initialiseringen se sådan ud: 95

95 C++ ansat chef("ulf Borg ",14, ); ansat pedel("ib Olsen",16, ); ansat vagt("ole Bjerre",17, ); Default parameter-værdier i constructor-funktioner Som vi så i øvelse 15, kan funktioner have default parameter-værdier. Hvis det kaldende program ikke bruger alle parametre, bliver default-værdien brugt for de manglende. Det samme gælder for constructor-funktioner. I det følgende eksempel har parameteren gage i constructor-funktionen ansat default-værdien Hvis programmet ikke angiver en gage-værdi, når objektet ansat erklæres, tildeles data-elementet gage default-værdien. Programmet skal dog altid angive den ansattes navn og id: ansat::ansat(char *navn, long id, float gage = ) strcpy(ansat::navn, navn); ansat::id = id; if (gage < ) ansat::gage = gage; else // Ugyldig gage ansat::gage = 0.0; Overload af constructor-funktioner I øvelse 13 lærte vi hvordan et program kan vælge mellem flere funktioner med samme navn men med forskellig parameter-antal eller type. Det kaldes overload og kan også bruges i forbindelse med constructor-funktioner. Det følgende program, CONSOVER.CPP, overloader constructor-funktionen ansat. Den første ansat-constructor kaldes ved at angive navn, id og gage for den ansatte. Den næste beder brugeren om at indtaste en gage hvis ikke programmet har angivet en i objekt-erklæringen: ansat::ansat(char *navn, long id) strcpy(ansat::navn, navn); ansat::id = id; do cout << "Skriv en gage for " << navn<<" under kr : "; cin >> ansat::gage; while (gage >= ); Klasse-definitionen skal indeholde prototyper for begge constructor-metoder: class ansat public: ansat(char *, long, float); ansat(char *, long); void vis_ansat(void); int skift_gage(float); long skaf_id(void); private: char navn[64]; long id; float gage; Det følgende program bruger den definerede klasse: #include <string.h> class ansat public: ansat(char *, long, float); ansat(char *, long); void vis_ansat(void); int skift_gage(float); long skaf_id(void); private: char navn[64]; long id; float gage; ; ansat::ansat(char *navn, long id, 96

96 4. Klasser float gage) 4 strcpy(ansat::navn, navn); ansat::id = id; if (gage < ) ansat::gage = gage; else // Ugyldig gage angivet ansat::gage = 0.0; ansat::ansat(char *navn, long id) strcpy(ansat::navn, navn); ansat::id = id; do cout << "Skriv en gage for " << navn<<" (max kr ): "; cin >> ansat::gage; while (gage >= ); void ansat::vis_ansat(void) cout << "Ansat: "<< navn << endl; cout << "Id: " << id << endl; cout << "Gage: " << gage << endl; ansat chef("ole Olsen", 10, ); ansat maskinmester("frode Hansen", 45); chef.vis_ansat(); maskinmester.vis_ansat(); Når programmet kompileres og køres, vil brugeren blive bedt om at indtaste gage for Frode Hansen da erklæringen af maskinmester-objektet ikke angav en gage-værdi. Efter indtastningen vil programmet fortsætte med at vise begge ansattes data. Destructor-funktioner En klasse kan også have en destructor-funktion som bliver kaldt når objektet bliver slettet. Nogle programmer kan have brug for at allokere store mængder hukommelse f.eks. til at rumme indholdet af en fil eller et billede. Modsat erklærede variabler (som bliver fjernet fra hukommelsen når funktionen, hvor de er erklæret, slutter) kan et program allokere (reservere) hukommelse som ikke frigives når funktionen slutter. I de tilfælde er det praktisk med destructor-funktionen som kan programmeres til at rydde op efter objektet. I de programmer, vi har lavet, bliver objekterne erklæret i starten af main-programmet. Når main slutter bliver objekterne slettet, og hvis objekterne indeholder en destructor-funktion vil den blive kaldt for hvert objekt. Destructorfunktioner hedder det samme som klassen (og constructoren) dog med en tilde (~) foran navnet: ~klasse_navn(void) // Funktionens sætninger Modsat constructor-funktionen kan man ikke overføre parametre til destructor-funktionen. Det følgende program, DESTRUCT.CPP, definerer en destructor-funktion for klassen ansat. Ligesom med constructor-funktioner returnerer en destructor aldrig en værdi, og derfor udelades retur-type (og void) foran navnet: ansat::~ansat(void) cout << "Nu slettes objektet som" cout << " indeholder " << navn << endl; Her viser destructor-funktionen en besked om at objektet bliver slettet. Funktionen kaldes automatisk for hvert objekt af typen ansat når programmet slutter. Her følger hele DESTRUCT.CPP programmet: 97

97 C++ #include <string.h> class ansat public: ansat(char *, long, float); ~ansat(void); void vis_ansat(void); int skift_gage(float); long skaf_id(void); private: char navn[64]; long id; float gage; ; // Constructor-funktionen ansat::ansat(char *navn, long id, float gage) strcpy(ansat::navn, navn); ansat::id = id; if (gage < ) ansat::gage = gage; else // Ugyldig gage angivet ansat::gage = 0.0; // Destructor-funktionen ansat::~ansat(void) cout << "Nu slettes objektet som" cout << " indeholder " << navn << endl; void ansat::vis_ansat(void) cout << "Ansat: " << navn << endl; cout << "Id: " << id << endl; cout << "Gage: " << gage << endl; 4 ansat chef("ulf Borg",10, ); chef.vis_ansat(); Når programmet kompileres og køres vil det skrive følgende på skærmen: C:\CPP\> DESTRUCT <ENTER> Ansat: Ulf Borg Id: 10 Gage: Nu slettes objektet som indeholder Ulf Borg Destructor-funktionen bliver udført uden at være kaldt fra programmet. Foreløbig har du sikkert ikke brug for at arbejde med destructor-funktioner, men når programmerne bliver mere omfattende vil destructor-funktionerne blive en nem måde at rydde op i hukommelsen efter et objekt. Opsummering Constructor- og destructor-funktioner er specielle metoder i en klasse. De kaldes automatisk når et objekt bliver erklæret og slettet. Typisk bruges constructor-funktioner til at initialisere data-elementerne i et objekt når det bliver erklæret, mens destructor-funktionerne ikke bruges så ofte i de små programmer vi arbejder med i øvelserne. I øvelse 24 skal vi lære hvordan man overloader operatorer. Man kan f.eks. omdefinere plus-operatoren (+) så den kan hægte en streng på en anden streng. En type (f.eks. char, float og int) definerer dels et sæt værdier, som en variabel kan indeholde og dels et sæt regler for hvad man kan gøre med variablen. Når man definerer en klasse, definerer man reelt en ny type. I C++ kan man specificere hvordan operatorerne bruges på objekter af den givne klasse. Før vi går i gang med det bør du sikre dig, at du har forstået følgende: En constructor er en speciel funktion som bliver kaldt når et objekt erklæres. Constructor-funktionen har samme navn som dens klasse. 98

98 4. Klasser Constructor-funktioner returnerer ikke en værdi. Men i stedet for at skrive void foran dem skal man undlade at skrive noget. Når et objekt erklæres, kan man overføre værdier til constructor-funktionen. Som andre funktioner kan også constructor-funktionen overloades og indeholde default-værdier. En destructor-funktion er en speciel funktion som bliver kaldt når et objekt bliver slettet. Den hedder det samme som klassen dog med en tilde (~) foran navnet. Øvelse 24 Overload af operatorer Et program overloader en operator ved at angive det reserverede ord operator. Overload defineres ved at angive en funktion som C++ kalder når et objekt af klassen bruger overload-operatoren. Den definerede operator vil så blive brugt i stedet for den almindelige. En operator bliver kun overloadet i objekter af den klasse hvor den er defineret. Overload påvirker ikke det øvrige program hvor operatorerne udfører de ting, de plejer. De fleste operatorer kan overloades. Der er dog fire som ikke kan (se tabel 24). Operator-overload kan gøre almindelige operationer enklere at bruge samtidig med at programmet bliver lettere at læse for programmøren. Som du sikkert vil se i eksemplerne, er det ikke så svært som det lyder. En type (f.eks. char, float og int) definerer dels de værdier, en variabel kan indeholde og dels de operationer, man kan udføre på variablen. F.eks. kan variabler af typen int lægges til, trækkes fra, ganges og divideres. Men man kan ikke samle to strenge ved hjælp af plustegnet. Når man definerer en klasse, definerer man reelt en ny type og derfor kan man også definere hvilke operationer, man kan udføre med den nye type. Når en klasse omdefinerer brugen af en eksisterende operator (f.eks. plus (+) som normalt bruges til addition) kaldes det operator overload. I denne øvelse definerer vi klassen streng som skal kunne overloade plus og minus-operatorerne. Når plus bruges på objekter af typen streng, vil den angivne tekst bliver tilføjet til streng-objektets indhold. Minus-operatoren skal fjerne enhver forekomst af et angivet tegn i objektet. Efter øvelsen vil vi have gennemgået følgende: Overload af operatorer bruges for at gøre programmet lettere at læse, og bør kun bruges når dette opnås. Overload af plus og minus Har man overloadet en operator i en klasse, har det kun indflydelse på objekter som bliver erklæret af den aktuelle klasse. Selvom plusoperatoren bliver ændret for streng-klassen, vil den fungere normalt når programmet f.eks. adderer to int-variabler. Kompileren vælger automatisk den rigtige operator for hver funktion og hver type. Nu vil vi definere klassen streng. Den indeholder et data-element som er selve strengen. Derudover har den forskellige metoder men foreløbig definerer vi ikke nye operatorer: class streng public: streng(char *); // Constructor void str_append(char *); void tegn_minus(char); void vis_streng(void); private: char strengdata[256]; ; 99

99 C++ Metoden str_append skal bruges til at tilføje (appende) tekst til data-elementet strengdata, og metoden tegn_minus skal fjerne angivne tegn fra streng-objektet. Det følgende program, STRKLAS.CPP, bruger klassen streng til at erklære to objekter: #include <string.h> class streng public: streng(char *); // Constructor void str_append(char *); void tegn_minus(char); void vis_streng(void); private: char strengdata[256]; ; streng::streng(char *str) strcpy(strengdata, str); void streng::str_append(char *str) strcat(strengdata, str); void streng::tegn_minus(char bogstav) char temp[256]; int i, j; for (i = 0, j = 0; strengdata[i]; i++) // skal bogstavet fjernes? if (strengdata[i]!= bogstav) temp[j++] = strengdata[i]; // hvis ikke så tilføj // det til temp 4 // Slut på strengen temp temp[j] = NULL; //Copy temps værdi til strengdata strcpy(strengdata, temp); void streng::vis_streng(void) cout << strengdata << endl; streng titel("jeg programmerer i C++!"); streng oevelse("overload af operatorer"); titel.vis_streng(); titel.str_append(" Det er jeg glad for!"); titel.vis_streng(); oevelse.vis_streng(); oevelse.tegn_minus( o ); oevelse.vis_streng(); Metoden str_append tilføjer tekst til strengobjektet, mens tegn_minus fjerner bestemte tegn. I program-eksemplet her kaldte vi funktioner som udførte operationerne, men ved hjælp af operator overload kan vi gøre det samme med plus og minus operatorerne. Når en operator skal overloades, bruges ordet operator i metodens prototype. Følgende klasse-definition tildeler plus-operatoren (+) til str_append-funktionen og minus-operatoren (-) til tegn_minus-funktionen. class streng public: streng(char *); // Constructor void operator +(char *); 4 100

100 4. Klasser void operator -(char); void vis_streng(void); private: char strengdata[256]; ; Når en funktion i en klasse overloader en operator, skal den indeholde en operation der svarer til den operator den overloader. Funktionen med den overloadede plus-operator kommer til at se sådan ud: void streng::operator +(char *str) strcat(strengdata, str); Som det fremgår, får funktionen ikke noget navn: I stedet skrives ordet operator efterfulgt af det aktuelle operator-symbol (her +). Men indholdet af funktionen er ikke ændret alt hvad der er ændret er at funktionens navn er blevet erstattet med et operator-symbol. Det følgende program, OPOVERLD.CPP, viser hvordan plus- og minus-operatorerne kan overloades: #include <string.h> class streng public: streng(char *); // Constructor void operator +(char *); void operator -(char); void vis_streng(void); private: char strengdata[256]; ; streng::streng(char *str) strcpy(strengdata, str); void streng::operator +(char *str) 4 strcat(strengdata, str); void streng::operator -(char bogstav) char temp[256]; int i, j; for (i = 0, j = 0; strengdata[i]; i++) if (strengdata[i]!= bogstav) temp[j++] = strengdata[i]; temp[j] = NULL; strcpy(strengdata, temp); void streng::vis_streng(void) cout << strengdata << endl; streng titel("jeg programmerer i C++!"); streng oevelse("overload af operatorer"); titel.vis_streng(); titel + " Det er jeg glad for!"; titel.vis_streng(); oevelse.vis_streng(); oevelse - o ; oevelse.vis_streng(); I programmet bliver de overloadede operatorer i objektet kaldt på denne måde: // Tilføj tekst titel + " Det er jeg glad for!"; 101

101 C++ // Fjern bogstavet o oevelse - o ; Denne operator-syntaks er lovlig, men den ser underlig ud. Normalt bruges operatorerne til at udføre en beregning og tildele resultatet til en variabel i en sætning hvor der også indgår en tildelings-operator (=): F.eks. sætningen streng = titel + "tekst"; Heldigvis er der mange muligheder for at styre hvordan operatorerne bruges. Det følgende program, STROVER.CPP, er ændret en smule, så de overloadede operatorer kan bruges på en mere naturlig måde: #include <string.h> class streng public: //Constructorfunktionens prototype streng(char *); char * operator +(char *); char * operator -(char); void vis_streng(void); private: char strengdata[256]; ; // Constructor-funktionen streng::streng(char *str) strcpy(strengdata, str); char * streng::operator +(char *str) return(strcat(strengdata, str)); char * streng::operator -(char bogstav) char temp[256]; int i, j; for (i = 0, j = 0; strengdata[i]; i++) if (strengdata[i]!= bogstav) temp[j++] = strengdata[i]; temp[j] = NULL; 4 return(strcpy(strengdata, temp)); void streng::vis_streng(void) cout << strengdata << endl; streng titel("jeg programmerer i C++!"); streng oevelse("overload af operatorer"); titel.vis_streng(); titel = titel + " Det er jeg glad for!"; titel.vis_streng(); oevelse.vis_streng(); oevelse = oevelse - o ; oevelse.vis_streng(); Ved at ændre de overloadede plus og minus operatorer så de returnerer en pointer til en streng, kan programmet nu bruge operatorerne på en mere tildelings-agtig måde: titel = titel + " Jeg er glad!"; oevelse = oevelse - o ; Et eksempel til Når man definerer egne datatyper ved hjælp af klasser vil man tit have brug for at teste om to objekter af en klasse har samme værdi. For at gøre det på en let måde kan man overloade de relationelle operatorer f.eks. == eller!=. Det følgende program, SAMLIGN.CPP, tilføjer en ny operator til klassen streng. Den nye operator tester om to objekter har samme indhold. Ved hjælp af den overloadede operator kan 102

102 4. Klasser programmet foretage sammenligningen på denne måde: if (streng1 == streng2) Indbygget i programmet kommer sammenligningen til at se sådan ud: #include <string.h> class streng public: streng(char *); char * operator +(char *); char * operator -(char); int operator ==(streng); void vis_streng(void); private: char strengdata[256]; ; streng::streng(char *str) strcpy(strengdata, str); char * streng::operator +(char *str) return(strcat(strengdata, str)); char * streng::operator -(char bogstav) char temp[256]; int i, j; for (i = 0, j = 0; strengdata[i]; i++) if (strengdata[i]!= bogstav) temp[j++] = strengdata[i]; temp[j] = NULL; int streng::operator ==(streng str) 4 int i; for (i = 0; strengdata[i] == str.strengdata[i]; i++) if ((strengdata[i] == NULL) && (str.strengdata[i] == NULL)) return(1); // Ens return(0); // Ikke ens void streng::vis_streng(void) cout << strengdata << endl; streng titel("jeg programmerer i C++!"); streng oevelse("overload af operatorer"); streng saetning("jeg programmerer i C++!"); if (titel == oevelse) cout << "titel og oevelse er ens" << endl; if (saetning == oevelse) cout << "saetning og oevelse er ens" << endl; if (titel == saetning) cout << "titel og saetning er ens" << endl; Ved at overloade operatorerne bliver programmerne lettere at læse. return(strcpy(strengdata, temp)); 103

103 C++ Ikke alle operatorer kan overloades Næsten alle C++'s operatorer kan overloades, men der er nogle undtagelser. Tabel 24 viser hvilke operatorer man ikke skal prøve at overloade. Operator Bruges til Eksempel. Element operator objekt.element.* Pointer til element objekt.*element :: Global resolution operator klasse::element?: Betingelsesudtryk c = (a > b)? a : b; Tabel 24. C++ operatorer som ikke kan overloades. Opsummering Muligheden for at tildele en operator en ny funktion i en klasse kaldes operator overload. Ved at bruge operator overload kan programmer gøres lettere at læse og ændre for programmøren. I øvelse 25 skal vi se hvordan man kan dele statiske elementer mellem flere objekter. Vi skal også se hvordan man kan bruge en klasses metode uden at der er erklæret nogen objekter af klassen. Men først bør du sikre dig at du har forstået følgende: En operator overloades for en bestemt klasse. Når en operator overloades, er operatoren kun ændret for objekter af den klasse hvor ændringen er defineret. I programmets andre objekter virker operatorerne normalt. For at overloade en operator skrives det reserverede ord operator. Følgende operatorer kan ikke overloades: element-operatoren (.), pointer-til-elementoperatoren (.*), den globale resolutions operator (::) og betingelses-udtryk-operatoren. Øvelse 25 Statiske funktioner og data Indtil nu har vores objekter haft hver deres data-elementer. Men sommetider kan det være en fordel, at flere objekter af samme klasse deler et data-element. Det kunne f.eks. være i forbindelse med et lønstyrings-program som holder øje med arbejdstimerne for 1000 ansatte. For at kunne styre skattebetalingen skal programmet kende hver medarbejders bopælskommune. Men hvis alle medarbejderne bor i samme kommune, kan hvert objekt bruge samme data-element. På den måde bruges mindre hukommelse da der spares 999 kopier af den samme information. Et data-element deles ved at erklære elementet som static. I denne øvelse ser vi hvordan flere objekter deler elementer. Efter øvelsen vil vi have gennemgået følgende: I C++ kan objekter have fælles data-elementer. Når en værdi tildeles et fælles element, kan den ses af alle objekterne af den givne klasse. Et fælles element defineres med det reserverede ord static. For hvert statisk element i en klasse skal der erklæres en tilsvarende, global variabel uden for klasse-definitionen. Metoder kan også være statiske og kan dermed kaldes selvom der ikke er erklæret objekter af den givne klasse. Fælles data-elementer Normalt har hvert objekt sit eget sæt af elementer: data og funktioner (metoder). Men det kan være nødvendigt for flere objekter af samme klasse at kunne være fælles om et eller flere elementer. I så fald skrives ordet static foran elementets navn i klasse-definitionen: 104

104 4. Klasser private: static int delt_vaerdi; Når klassen er defineret skal elementet også erklæres som en global variabel uden for klasse-definitionen: int klasse_navn::delt_vaerdi; Det følgende program, DELDATA.CPP, definerer klassen bog_serie som indeholder elementet sidetal som skal være det samme for alle objekter af klassen (alle bøger i vores serie er lige lange). Hvis programmet ændrer værdien af sidetal i et objekt, er ændringen med det samme synlig for de andre objekter af samme klasse: #include <string.h> class bog_serie public: // Constructor-prototype bog_serie(char *, char *, float); void vis_bog(void); void saet_sidetal(int); private: static int sidetal; char titel[64]; char forfatter[64]; float pris; ; int bog_serie::sidetal; void bog_serie::saet_sidetal(int sider) sidetal = sider; forfatter); bog_serie::pris = pris; void bog_serie::vis_bog(void) cout << "Titel: "<< titel<< endl; cout << "Forfatter: " << forfatter << endl; cout << "Pris: " << pris << endl; cout << "Sidetal: " << sidetal << endl; bog_serie programmering("c++", "Jamsa", ); bog_serie tekstbehandling("lær Word for Windows", "Hansen", ); tekstbehandling.saet_sidetal(256); programmering.vis_bog(); tekstbehandling.vis_bog(); cout << endl <<"Skifter sidetal. " << endl; programmering.saet_sidetal(512); programmering.vis_bog(); tekstbehandling.vis_bog(); Elementet sidetal er en statisk værdi af typen int. Lige efter klasse-definitionen følger den globale erklæring af sidetal, og når programmet ændrer sidetal i et objekt bliver værdien ændret i alle objekter af klassen bog_serie. // Constructor bog_serie::bog_serie(char *titel, char *forfatter, float pris) strcpy(bog_serie::titel, titel); strcpy(bog_serie::forfatter, 4 Brug af statiske elementer selvom der ikke er erklæret objekter af klassen Et statisk element er fælles for alle objekter af en given klasse. Men et program kan også ha- 105

105 C++ ve behov for at bruge det statiske element selvom der ikke er erklæret objekter af klassen. Elementet skal da erklæres både som offentligt (public) og statisk. Det følgende program, BRUGELE.CPP, bruger elementet sidetal fra klassen bog_serie, selvom der ikke er erklæret objekter af klassen: #include <string.h> class bog_serie public: static int sidetal; private: char titel[64]; char forfatter[64]; float pris; ; int bog_serie::sidetal; bog_serie::sidetal = 256; cout << "Sidetallet er nu " << bog_serie::sidetal << endl; Da klassen definerer sidetal som public, kan programmet læse og skrive til variablen sidetal selvom der ikke er erklæret noget objekt af klassen bog_serie. Statiske funktions-elementer Ligesom en klasses data-elementer kan defineres som statiske, kan klassens metoder også være statiske. En statisk metode kan kaldes selvom der ikke er erklæret objekter af klassen. Det kan være brugbart hvis f.eks. en klasse indeholder en metode som man gerne vil kunne bruge uden for objekter af klassen. I det følgende eksempel bruger klassen menu ANSIdriveren til at slette skærmen. Hvis din computer har installeret ANSI.SYS-driveren kan du bruge metoden slet_skaerm til at slette skærmen. Da metoden er defineret som statisk kan programmet bruge metoden selvom der ikke er erklæret noget objekt af typen menu. Det følgende program, CLR_SCR.CPP, bruger metoden slet_skaerm til at slette teksten på skærmen: class menu public: static void slet_skaerm(void); // Her ville de andre metoder stå private: int antal_menu_punkter; ; void menu::slet_skaerm(void) cout << \033 << "[2J"; menu::slet_skaerm(); Til trods for at der ikke er erklæret objekter kan programmet bruge metoden slet_skaerm fordi den er erklæret som statisk. Opsummering Hvis man skriver det reserverede ord static foran et data-element i en klasse, vil information være ens i alle objekter erklæret af den aktuelle type. Og hvis data-elementet er offentligt (public) kan programmet adressere det uden at der behøver være erklæret objekter af klassen. Skriver man static foran en klasse-metode, kan funktionen bruges også selvom der ikke skal indgå objekter i brugen. I øvelse 26 skal vi se hvordan man bruger arv til at definere en klasse på basis af én eller flere eksisterende klasser. Arv kan spare en mængde programmeringstid. Før vi går videre bør du checke at du har forstået følgende: 106

106 4. Klasser Når et element i en klasse defineres som statisk, kan alle objekter af den givne klasse bruge elementet. Når et element er defineret som statisk skal det også erklæres som en global variabel uden for klasse-definitionen. Hvis elementer defineres som både statiske og offentlige, kan programmet bruge elementet også selvom der ikke eksisterer objekter af den aktuelle klasse. For at kunne adressere elementet bruges den globale resolutions-operator (::) f.eks. klasse_navn::element_navn. Hvis en metode erklæres som offentlig og statisk kan programmet kalde funktionen på trods af, at der ikke er erklæret noget objekt af klassen. Ligesom med data-elementet skal man for metode-elementet angive den globale resolutions-operator (::) f.eks menu::slet_skaerm(). 107

107 5. Arv, konstanter og makroer Øvelse 26 Arv Et af formålene med objekt-orienteret programmering, er at kunne tage klasser fra et program og genbruge dem i et andet hvilket kan spare programmeringstid og øge sikkerheden i de programmer man laver. Når man definerer en ny klasse kan det være, at den foruden nogle nye egenskaber (data og metoder) kan bruge egenskaber fra en eksisterende klasse. I C++ kan en klasse arve elementerne fra en anden klasse (som så kaldes basis-klassen). En klasse der har arvet elementer fra en basisklasse, kaldes en afledt klasse. I denne øvelse ser vi på arv i C++ og efter øvelsen vil vi have gennemgået følgende: I programmer, der arbejder med arv, bruges en basis-klasse til at lave en afledt klasse. Den afledte klasse arver basis-klassens elementer. Objekter af en afledte klasse initialiseres ved at kalde basis-klassens og den afledte klasses constructor-metoder. Punktum-operatoren bruges til at adressere elementer i både basis-klassen og den afledte klasse. Foruden offentlige og private, kan elementer også være beskyttede hvilket betyder at de kun kan adresseres fra basis-klassen og den afledte klasse. For at undgå navne-konflikter mellem basis-klassen og den afledte klasse, bruges den globale resolutions operator sammen med navnet på den aktuelle klasse. Arv er et af de centrale begreber i objekt-orienteret programmering. Det er ikke svært at bruge arv i programmer, og det giver mulighed for at spare programmeringstid. Og selvom det hedder arv, kan der stadig erklæres objekter med basis-klassen: Den afledte klasse kan snarere siges at have kopieret basis-klassens elementer. Et eksempel på arv Arv er når en afledt klasse arver elementerne fra en eksisterende basis-klasse. Forestil dig et program som indeholder basis-klassen ansat: class ansat public: ansat(char *, char *, float); void vis_ansat(void); private: char navn[64]; char stilling[64]; float gage; ; Nu vil vi gerne definere en ny klasse der skal hedde chef, og som skal indeholde de samme elementer som klassen ansat, foruden disse tre nye elementer: float aarlig_bonus; char firma_bil[64]; int firma_aktier; Der er ikke noget i vejen for at erklære en helt ny klasse, men det er smartere at lade klassen chef arve elementerne fra klassen ansat. Jo flere elementer f.eks. store metoder der er i en klasse jo mere grund er der til at arve eksisterende elementer. Klassen chef bliver defineret på præcis samme måde som enhver anden klasse. Men for at vise at den arver elementerne fra klassen ansat, skrives basis-klassens navn efter den nye klasses navn, adskilt af kolon og ordet public: 108

108 5. Arv, konstanter og makroer class chef : public ansat // Her defineres nye elementer ; Ordet public foran klasse-navnet ansat angiver at offentlige elementer i basis-klassen (ansat) også er offentlige i den afledte klasse (chef). I praksis ser definitionen af den afledte klasse sådan ud: class chef : public ansat public: chef(char *, char *, char *, float, float, int); void vis_chef(void); private: float aarlig_bonus; char firma_bil[64]; int firma_aktier; ; Bemærk: Private elementer i basis-klassen er kun tilgængelige for den afledte klasse gennem basisklassens interface-funktioner. Den afledte klasse kan derfor ikke adressere private elementer i basisklassen ved hjælp af punktum-operatoren. Det følgende program, CHEF_ANS.CPP, viser brugen af arv ved at definere klasserne ansat og chef: #include <string.h> class ansat public: ansat(char *, char *, float); void vis_ansat(void); private: char navn[64]; char stilling[64]; float gage; ; ansat::ansat(char *navn, char *stilling, float gage) 4 strcpy(ansat::navn, navn); strcpy(ansat::stilling, stilling); ansat::gage = gage; void ansat::vis_ansat(void) cout << "Navn: " << navn << endl; cout << "Stilling: " << stilling << endl; cout << "Gage: kr. " << gage << endl; class chef : public ansat public: chef(char *, char *, char *, float, float, int); void vis_chef(void); private: float aarlig_bonus; char firma_bil[64]; int firma_aktier; ; chef::chef(char *navn, char *stilling, char *firma_bil, float gage, float bonus, int firma_aktier) : ansat(navn, stilling, gage) strcpy(chef::firma_bil, firma_bil); chef::aarlig_bonus = bonus; chef::firma_aktier = firma_aktier; void chef::vis_chef(void) vis_ansat(); cout << "Firma bil: " << firma_bil << endl; cout << "Årlig bonus: kr. " << aarlig_bonus << endl; cout << "Firma aktier: " 4 109

109 C++ << firma_aktier << endl; ansat medarbejder("mads Andersen", "Programmør", 35000); chef direktoer("hanne Hundslund", "Adm. Dir.", "Volvo 740", , 5000, 1000); medarbejder.vis_ansat(); direktoer.vis_chef(); Først defineres basis-klassen ansat og derefter defineres den afledte klasse chef. Læg især mærke til chef's constructor-funktion: Constructor-funktionen i en afledt klasse skal kalde basis-klassens constructor. Det gøres ved at skrive basis-klassens constructor efter den nye klasses constructor, adskilt af kolon: chef::chef(char *navn, char *stilling, char *firma_bil, float gage, float bonus, int firma_aktier) : ansat(navn, stilling, gage) strcpy(chef::firma_bil,firma_bil); chef::aarlig_bonus =bonus; chef::firma_aktier =firma_aktier; Læg også mærke til at metoden vis_chef kalder metoden vis_ansat som er et element i basisklassen ansat. Da chef er afledt af ansat, kan den adressere de offentlige elementer i ansat. Et eksempel mere Lad os forestille os et program, der indeholder klassen bog: class bog public: bog(char *, char *, int); void vis_bog(void); 4 private: char titel[64]; char forfatter[64]; int sidetal; ; Nu vil vi lave en klasse der hedder bogkort, og som skal bruges på et bibliotek til administration af bøger. Bogkort-klassen skal foruden elementerne i bog, også indeholde følgende elementer: char fagnr[64]; // biblioteks nummer int udlaant; // 1 hvis bogen er // udlånt, ellers 0 Programmet kan aflede klassen bogkort, fra klassen bog: class bogkort : public bog public: bogkort(char *, char *, int, char *, int); void vis_kort(void); private: char fagnr[64]; int udlaant; ; Det følgende program, BOGKORT.CPP, afleder klassen bogkort fra klassen bog: #include <string.h> class bog public: bog(char *, char *, int); void vis_bog(void); private: char titel[64]; char forfatter[64]; int sidetal; ; bog::bog(char *titel, char *forfatter, int sidetal) 110

110 5. Arv, konstanter og makroer 4 strcpy(bog::titel, titel); strcpy(bog::forfatter, forfatter); bog::sidetal = sidetal; void bog::vis_bog(void) cout << "Titel: "<< titel<< endl; cout << "Forfatter: " << forfatter << endl; cout << "Sidetal: " << sidetal << endl; class bogkort : public bog public: bogkort(char *, char *, int, char *, int); void vis_kort(void); private: char fagnr[64]; int udlaant; ; bogkort::bogkort(char *titel, char *forfatter, int sidetal, char *fagnr, int udlaant) : bog(titel, forfatter, sidetal) strcpy(bogkort::fagnr, fagnr); bogkort::udlaant = udlaant; void bogkort::vis_kort(void) vis_bog(); cout << "Fagnr: "<< fagnr<< endl; if (udlaant) cout << "Bogen er udlånt" << endl; else cout <<"Bogen er på bibliotek" << endl; 4 bogkort kort("programmering I C++", "Jamsa", 256, "32a", 1); kort.vis_kort(); Ligesom i programmet før, kalder den afledte klasses constructor-funktion basis-klassens constructor-funktion for at initialisere objekter af klassen bogkort. I metoden vis_kort kaldes basis-klassens metode vis_bog. Det kan objektet gøre uden at angive punktum-operatoren, da bogkort-klassen har arvet elementerne fra klassen bog. Beskyttede elementer Elementer i en basis-klasse kan være offentlige (public), private, eller beskyttede (protected). En afledt klasse kan ikke bruge private elementer i basis-klassen undtagen ved hjælp af interface-funktioner. Et beskyttet element i basisklassen er en mellemting mellem et privat og et offentligt element: Objekter af den afledte klasse, kan bruge beskyttede elementer direkte (som om de var offentlige). Men for resten af programmet er beskyttede elementer private og kan ikke bruges direkte. Det bliver nødt til at bruge interface-metoderne. Den følgende definition af bog bruger beskyttede elementer, som kan adresseres direkte af afledte klasser ved hjælp af punktum-operatoren: class bog public: bog(char *, char *, int); void vis_bog(void); protected: char titel[64]; char forfatter[64]; int sidetal; ; Hvis man definerer en klasse som man mener at kunne aflede nye klasser fra på et senere tidspunkt, bør man overveje om de afledte 111

111 C++ klasser skal have direkte adgang til elementerne i basis-klassen. I så fald skal elementerne defineres som beskyttede og ikke som private. Administration af element-navne Når en klasse afledes af en anden klasse, kan det ske at der er elementer i begge klasser med samme navn. I de tilfælde vil C++ altid bruge elementet i den afledte klasse. Hvis f.eks. klasserne bog og bogkort begge har et element ved navn pris: For bog-klassen er det bogens udsalgspris og for bogkort-klassen er det bibliotekets købspris (der kan være anderledes på grund af rabatter). Hvis man i den afledte klasses metoder ikke angiver andet end elementets navn, vil C++ altid arbejde med elementet fra den afledte klasse. Men hvis et bogkort skal bruge elementet pris fra basis-klassen bog, kan det skrive navnet på basis-klassen og resolutions-operatoren :: foran elementets navn: bog::pris. Hvis funktionen vis_kort skulle vise begge priser, ville koden komme til at se sådan ud: cout << "Pris for biblioteket: kr. " << pris << endl; cout << "Normal salgspris: kr." << bog::pris << endl; Opsummering I denne øvelse lærte vi, hvordan arv i C++ kan bruges til at definere (aflede) nye klasser fra allerede eksisterende klasser. Dette genbrug kan spare programmeringstid og fejlsøgning. I øvelse 27 skal vi se, at man kan aflede en klasse fra mere end én basis-klasse. Men før vi går i gang med det, bør du sikre dig at du har forstået følgende: En ny klasse kan afledes fra en allerede eksisterende klasse. Dette kaldes arv. Den nye klasse kaldes den afledte klasse, og den eksisterende klasse kaldes basis-klassen. Når en klasse afledes fra en anden, arver den afledte klasse elementerne fra basisklassen. Definitionen af en afledt klasse indeholder foruden navnet på den nye klasse også navnet på basis-klassen. De to navne adskilles med kolon, f.eks: class dalmatiner : hund. Den afledte klasse kan adressere offentlige elementer i basis-klassen som var de defineret i den afledte klasse selv. Private elementer kan kun adreseres ved hjælp af basis-klassens interface-funktioner. Constructor-funktionen i den afledte klasse skal kalde constructor-funktionen i basisklassen. Efter constructor-funktionens header, skrives kolon og navnet på basisklassens constructor-funktion og dens parametre. Hvis elementer i basis-klassen er defineret som beskyttede (protected), kan de adresseres fra afledte klasser som var de offentlige, uden at de kan ses fra andre steder i programmet (hvor de stadig vil være private). Hvis et element i den afledte klasse har samme navn som et element i basis-klassen, vil C++ bruge elementet fra den afledte klasse. Elementet i basis-klassen kan bruges ved foran dets navn at skrive basisklassens navn og ::, f.eks.: basisklasse::element. Øvelse 27 Arv fra flere klasser I øvelse 26 så vi, hvordan en ny klasse kunne arve egenskaberne fra en eksisterende klasse. Men en klasse kan arve elementer fra mere end én basis-klasse. Det kaldes multipel arv og i denne øvelse skal vi se hvordan det bruges. Efter øvelsen vil vi have gennemgået følgende: 112

112 5. Arv, konstanter og makroer Når en ny klasse arver elementer fra mere end én basis-klasse, kaldes det multipel arv. Constructor-funktionen i den afledte klasse skal kalde constructor-funktionerne i alle basis-klasserne. Når en klasse afledes fra en i forvejen afledt klasse, opbygges et objekt-hierarki. Multipel arv er en meget potent teknik i objekt-orienteret programmering som kan spare programmøren for en masse tid. Eksempel på multipel arv Lad os forestille os et program, som indeholder følgende definition af klassen computer_skaerm: class computer_skaerm public: computer_skaerm(char *, long, int, int); void vis_skaerm(void); private: char type[32]; long farver; int x_pixels; int y_pixels; ; I programmet defineres også klassen bundkort: class bundkort public: bundkort(int, int, int); void vis_bundkort(void); private: int processor; int clockfrekvens; int RAM; ; Ved hjælp af de to klasser, kan man aflede klassen computer: class computer : public computer_skaerm, public bundkort public: computer(char *, int, float, char *, long, int, int, int, int, int); void vis_computer(void); private: char navn[64]; int harddisk; float floppy; ; Fuldstændig som vi så i øvelse 26 angives basis-klassen efter navnet på den afledte klasse. Forskellen er blot at her angives flere basisklasser, adskilt af komma: class computer : public computer_skaerm, public bundkort Det følgende program, COMPUTER.CPP, afleder klassen computer fra basis-klasserne computer_skaerm og bundkort: #include <string.h> class computer_skaerm public: computer_skaerm(char *, long, int, int); void vis_skaerm(void); private: char type[32]; long farver; int x_pixels; int y_pixels; ; computer_skaerm::computer_skaerm( char *type, long farver, int x_res, int y_res) 4 113

113 C++ strcpy(computer_skaerm::type, type); computer_skaerm::farver = farver; computer_skaerm::x_pixels = x_res; computer_skaerm::y_pixels = y_res; void computer_skaerm::vis_skaerm(void) cout << "Skærm type: " << type << endl; cout << "Antal farver: " << farver << endl; cout << "Opløsning: " << x_pixels << " gange "<< y_pixels<< endl; class bundkort public: bundkort(int, int, int); void vis_bundkort(void); private: int processor; int clockfrekvens; int RAM; ; bundkort::bundkort(int processor, int clockfrekvens, int RAM) bundkort::processor = processor; bundkort::clockfrekvens = clockfrekvens; bundkort::ram = RAM; void bundkort::vis_bundkort(void) cout << "Processor: " << processor << endl; cout << "Clockfrekvens: " << clockfrekvens << " Mhz" << endl; cout << "RAM: " << RAM << " MB" << endl; 4 class computer : public computer_skaerm, public bundkort public: computer(char *, int, float, char *, long, int, int, int, int, int); void vis_computer(void); private: char navn[64]; int harddisk; float floppy; ; computer::computer(char *navn, int harddisk,float floppy, char *skaerm, long farver, int x_res, int y_res, int processor, int clockfrekvens, int RAM) : computer_skaerm(skaerm, farver, x_res, y_res), bundkort(processor, clockfrekvens, RAM) strcpy(computer::navn, navn); computer::harddisk = harddisk; computer::floppy = floppy; void computer::vis_computer(void) cout << "Computer type: " << navn << endl; cout << "Harddisk: " << harddisk << " MB" << endl; cout << "Floppy disk: " << floppy << " MB" << endl; vis_bundkort(); vis_skaerm(); computer min_pc("digital", 800, 1.44, "VGA", , 640, 480, 4 114

114 5. Arv, konstanter og makroer 486, 66, 8); min_pc.vis_computer(); I programmet kalder computer's constructorfunktion, constructor-funktionerne for både bundkort og computer_skaerm: computer::computer(char *navn, int harddisk, float floppy, char *skaerm, long farver, int x_res, int y_res, int processor, int clockfrekvens, int RAM) : computer_skaerm(skaerm, farver, x_res, y_res), bundkort(processor, clockfrekvens, RAM) Kæder af nedarvede klasser En klasse kan også arve elementer fra en basisklasse som selv er en afledt klasse for en helt tredje klasse. F.eks. kunne man bruge klassen computer til at definere en klasse ved navn workstation: class workstation : public computer public: workstation(char *operativsystem, char *navn, int harddisk, float floppy, char *skaerm, long farver, int x_res, int y_res, int processor, int clockfrekvens, int RAM); void vis_workstation(void); private: char operativsystem[64]; ; Constructor-funktionen i den afledte klasse workstation skal kalde constructor-funktionen i klassen computer (som så igen kalder constructor-funktionerne i dens basis-klasser computer_skaerm og bundkort): workstation::workstation(char *operativsystem, char *navn, int harddisk, float floppy, char *skaerm, long farver, int x_res, int y_res, int processor, int clockfrekvens, int RAM) : computer (navn, harddisk, floppy, skaerm, farver, x_res, y_res, processor, clockfrekvens, RAM) strcpy(workstation::operativsystem, operativsystem); Klassen computer (som er afledt af klasserne bundkort og computer_skaerm) bliver basis-klasse for workstation. På denne måde arver workstation alle elementer fra de tre klasser. Arven kan anskueliggøres som en kæde (se figur 27.1). skaerm bundkort computer Figur Kæde af nedarvede klasser. work_station Jo længere en kæde bliver, jo flere elementer arver de afledte klasser. Til gengæld kan kæden af constructor-funktioner også blive temmelig kompleks. Opsummering Når en klasse arver elementer fra flere basisklasser kaldes det multipel arv. Multipel arv er et centralt begreb i de omfattende objektorienterede muligheder i C++. Før vi går i gang med næste øvelse, bør du sikre dig, at du har forstået følgende: 115

115 C++ Når en klasse arver elementer fra flere basis-klasser kaldes det multipel arv. Basis-klasserne til en ny, afledt klasse, skrives adskilt af kommaer, efter navnet på den nye klasse, f.eks.: class tomoffel : tomat, kartoffel. Constructor-funktionen i den nye klasse skal kalde hver af basis-klassernes constructor-funktion med de korrekte parametre. Når afledte klasser bruges som basis-klasse for nye klasser, kaldes det en kæde af nedarvede klasser. Når constructor-funktionen kaldes, bliver basis-klassens constructorfunktion også kaldt og dermed alle constructor-funktioner hele vejen op igennem kæden. Når man begynder at skrive større programmer, skal de kunne gemme det arbejde brugeren har lavet, for senere at kunne indlæse det i programmet igen. I C++ er det let at arbejde med filer. Skrivning af data til en fil-strøm I samtlige øvelser i bogen har vi brugt outputstrømmen cout til at skrive data til skærmen. I virkeligheden er cout et objekt af klassen ostream som er defineret i header-filen iostream.h. Det er derfor den skal inkluderes i starten af programmet. I en anden header-fil, fstream.h, findes en klasse ved navn ofstream og ved hjælp af objekter af denne klasse, kan programmer skrive filer. Først skal man erklære et objekt af klassen ofstream hvor man angiver navnet på den fil, hvori informationerne skal skrives: ofstream fil_objekt("filnavn.ext"); Øvelse 28 Filer Typisk vil et program have brug for at kunne gemme data i filer, så det på et senere tidspunkt, kan indlæse dem igen. Hvis man har arbejdet med filer i C kan man bruge lignende teknikker i C++. Derudover indeholder C++ en samling af klasser til input og output ved hjælp af strømme, som gør det meget lettere at arbejde med filer. Efter øvelsen vil vi have gennemgået følgende: Ved at bruge fil-output-strømmen kan man skrive data til en fil med << operatoren. Data kan læses fra en fil ved hjælp af fil input-strømmen og >> operatoren. Metoderne i fil-klassen bruges til at åbne og lukke filer. Filer læses og skrives ved hjælp af << og >> operatorerne samt metoder fra fil-klassen. Når man angiver et filnavn i erklæringen af objektet, opretter C++ en ny fil med det angivne navn, på harddisken. Hvis der i samme bibliotek findes en fil med det samme navn, vil den blive overskrevet. Det følgende program, UDFIL.CPP, erklærer et objekt af typen ofstream hvorefter det skriver nogle tekstlinier til filen BOGINFO.DAT ved hjælp af <<-operatoren: #include <fstream.h> ofstream bog_fil("boginfo.dat"); bog_fil << "C++ af Kris Jamsa" << endl; bog_fil << "IDG Bøger" << endl; bog_fil << "Kr " << endl; Programmet åbner filen BOGINFO.DAT og skriver tre tekstlinier i filen. Kompilér og kør programmet. Hvis du er i DOS kan du bruge TYPE-kommandoen til at se indholdet af filen: 116

116 5. Arv, konstanter og makroer C:\CPP\> TYPE BOGINFO.DAT C++ af Kris Jamsa IDG Bøger Kr <ENTER> C:\CPP\> FILE_IN <ENTER> C++ af Kris Jamsa, Du kan også åbne filen i en tekst-editor, f.eks. den du bruger til at skrive programmerne med. Som du ser, er det ingen sag at skrive tekst til en fil. Læsning af data fra en input fil-strøm Ved hjælp af klassen ofstream kan et program skrive data til en fil. På samme måde kan indholdet af en fil læses ved at bruge objekter af klassen ifstream. Også her erklærer man objektet og overfører filnavnet som en parameter: ifstream input_fil("filnavn.ext"); Det følgende program, INDFIL.CPP, åbner filen BOGINFO.DAT, fra forrige program, og viser filens tre første elementer: #include <fstream.h> ifstream input_fil("boginfo.dat"); char et[64], to[64], tre[64]; input_fil >> et; input_fil >> to; input_fil >> tre; cout << et << endl; cout << to << endl; cout << tre << endl; Når du kompilerer og starter programmet, forventer du sikkert, at det indlæser og viser de tre første linier i filen. Men ligesom cin bruger fil-input strømmen mellemrumstegn som adskillelse mellem værdier. Derfor viser programmet følgende output: Læsning af en hel linie fra en fil For at indlæse en hel linie fra en fil kan man bruge metoden getline som findes i klassen ifstream. I det følgende program, FILLINIE.CPP, bruger vi getline til at indlæse alle tre linier fra filen BOGINFO.DAT: #include <fstream.h> ifstream input_fil("boginfo.dat"); char et[64], to[64], tre[64]; input_fil.getline(et,sizeof(et)); input_fil.getline(to,sizeof(to)); input_fil.getline(tre,sizeof(tre)); cout << et << endl; cout << to << endl; cout << tre << endl; Nu indlæser programmet alle linierne fra filen som det skal. Men kun fordi vi ved, at der er præcis tre tekstlinier i filen. Typisk vil et program ikke vide hvor mange linier der er i en fil. Derfor skal programmet selv kunne holde øje med om slutningen af filen er nået. Indlæsning til slutning af fil Det er ikke ualmindeligt at et program skal kunne indlæse indholdet af en fil fra start til slut. For løbende at kunne se om slutningen af filen er nået, kan man benytte fil-objektets eoffunktion (end of file). Hvis slutningen er nået, returnerer funktionen 1 (for sand: slutningen af filen er nået) ellers returnerer den 0 (falsk). Ved hjælp af en while-løkke kan programmet 117

117 C++ gentage de samme linier, så længe slutningen af filen ikke er nået: while (! input_fil.eof()) // Sætninger En løkke kører hvis testen er sand og ved at vende værdien af eof, (med NOT-operatoren!) fortsætter løkken så længe eof-funktionen returnerer falsk (0). Det følgende program, TESTEOF.CPP, indlæser linierne fra BOG- INFO.DAT indtil det kommer til slutningen af filen: #include <fstream.h> ifstream input_fil("boginfo.dat"); char linie[64]; while (! input_fil.eof()) input_fil.getline(linie, sizeof(linie)); cout << linie << endl; Ved hjælp af eof-funktionen kan man også indlæse hvert ord i en fil, indtil slutningen af filen mødes. Det prøver vi i programmet ORDEOF.CPP: #include <fstream.h> ifstream input_fil("boginfo.dat"); char ord[64]; while (! input_fil.eof()) input_fil >> ord; cout << ord << endl; Følgende program, TEGNEOF.CPP, indlæser en fil, ét tegn ad gangen ved hjælp af funktionen get, indtil det kommer til slutningen af filen: #include <fstream.h> ifstream input_fil("boginfo.dat"); char bogstav; while (! input_fil.eof()) bogstav = input_fil.get(); cout << bogstav; Fejl i fil-operationer Foreløbig har vi i vores programmer antaget, at der ikke opstår fejl under skrivning eller læsning af en fil. Men en gang imellem kan der opstå fejl. Når man åbner en fil for at indlæse den, bør programmet sikre sig at filen findes. Og når man har skrevet data til en fil, bør man sikre sig at operationen gik som den skulle: F.eks. kan en harddiskfejl forstyrre en skrivning. Man kan teste om en fil-operation er forløbet, som den skulle, ved hjælp af fil-objektets funktion fail. Hvis filoperationen er gået som den skal, vil fail returnere 0 (falsk) men hvis noget er mislykkedes vil den returnere 1 (sand). F.eks. bør man teste om der er opstået fejl ved åbningen af en fil: 118

118 5. Arv, konstanter og makroer ifstream input_fil("filnavn.dat"); if (input_fil.fail()) cerr << "Filen kunne ikke åbnes" << endl; exit(1); Programmet bør også teste om læsning og skrivning af data er lykkedes. Det følgende program, TESTALT.CPP, bruger fail-funktionen til at teste forskellige fejl-muligheder: #include <fstream.h> char linie[256]; ifstream input_fil("boginfo.dat"); if (input_fil.fail()) cerr << "Filen kunne ikke åbnes " << endl; else while ((! input_fil.eof()) && (! input_fil.fail())) input_fil.getline(linie, sizeof(linie)); if (! input_fil.fail()) cout << linie << endl; Filer der ikke bruges, bør lukkes Når et program slutter, bliver eventuelle åbne filer automatisk lukket af operativsystemet. Men et program bør selv lukke en fil, hvis det ikke længere skal arbejde med den. Det gøres med funktionen close: input_fil.close(); Når en fil lukkes, bliver de sidste data, man har skrevet til den overført og bibliotekets filliste bliver opdateret. Et program kan kontrollere åbning af filer Indtil nu har vores programmer læst og skrevet filer helt fra begyndelsen af filen. Men man kan også have brug for at skulle skrive data i slutningen af en fil som en forlængelse af de data der allerede er I filen. Derfor kan filer åbnes i append-mode ved at angive endnu en parameter i erklæringen: ifstream output_fil("filnavne.ext", ios::app); Her bruges parameteren ios::app til at angive at filen skal åbnes i append-mode. Ios kan bruges med flere værdier i forskellige situationer som det fremgår af tabel 34. Open Mode ios::app ios::ate ios::in ios::nocreate ios::noreplace ios::out ios::trunc Formål Åbner en fil i append-mode og stiller startpunktet ved filens slutning Stiller startpunktet ved filens slutning Angiver at filen skal åbnes til input Hvis den angivne fil ikke findes, skal den ikke oprettes, men der skal returneres en fejl Hvis filen findes skal der returneres en fejl hvis programmet prøver at åbne den Åbner fil for at skrive i den Overskriver indholdet af en eksisterende fil Tabel 34. Forskellige værdier, som filer kan åbnes med. I dette eksempel bliver en fil åbnet til output ved hjælp af ios::noreplace parameteren. Der- 119

119 C++ ved undgår man at en allerede eksisterende fil med det samme navn, bliver slettet: ifstream output_fil("filnavn.ext", ios::out ios::noreplace); Læsning og skrivning af data Foreløbig er det kun tekst-strenge vi har læst og skrevet fra og til filer. Men det er også nødvendigt at kunne læse og skrive arrays og strukturer. Det kan man gøre ved hjælp af funktionerne read og write. Når man bruger read og write, angiver man dels en data-buffer hvor de indlæste data bliver lagt, og dels bufferens størrelse, angivet i bytes: input_fil.read(buffer, sizeof(buffer)); output_fil.write(buffer, sizeof(buffer)); Det følgende program, STRUKUD.CPP, bruger funktionen write til at skrive indholdet af en struktur i filen ANSAT.DAT: #include <fstream.h> struct ansat char navn[64]; int alder; float gage; medarbejder = "Ole Olsen", 38, ; ofstream ansat_fil("ansat.dat"); ansat_fil.write((char *) &medarbejder, sizeof(ansat)); Normalt får write-funktionen overført en pointer som peger på en tekst-streng. Linien (char *) er en cast, som fortæller kompileren, at man overfører en pointer af en anden type. På samme måde indlæses en ansats data med readfunktionen i programmet STRUKIND.CPP: #include <fstream.h> struct ansat char navn[64]; int alder; float gage; medarbejder = "Ole Olsen", 33, ; ifstream ansat_fil("ansat.dat"); ansat_fil.read((char *) &medarbejder, sizeof(ansat)); cout << medarbejder.navn << endl; cout << medarbejder.alder << endl; cout << medarbejder.gage << endl; Opsummering Næsten alle større programmer skriver og læser data til og fra filer. Eksperimentér med programmerne i øvelsen så du bliver rutineret i brugen af filer. F.eks. kunne man ved hjælp af løkker lave et program der skrev og læste flere medarbejderes data. Før du går videre med næste øvelse, bør du sikre dig, at du har forstået følgende: I header-filen fstream.h defineres klasserne ifstream og ofstream som bruges til læsning og skrivning af data. Hvis et program skal læse eller skrive en fil, erklærer det et objekt af typen ifstream eller ofstream og overfører filnavnet til objektets constructor-funktion. 120

120 5. Arv, konstanter og makroer Når en fil er åbnet, kan data læses fra eller skrives til filen ved hjælp af >> og << operatorerne. Læsning og skrivning af enkelt-tegn kan udføres med funktionerne get og put. Med funktionen getline kan programmet indlæse en hel tekstlinie fra en fil. Hvis et program skal indlæse data til slutningen af en fil, kan det bruge eof-funktionen til at teste om filens slutning er nået. Når et program læser eller skriver filer, bør det bruge fail-funktionen til at teste at operationen forløber som det skal. Ved hjælp af metoderne read og write kan et program læse og skrive strukturer, arrays og objekter. Når programmet er færdig med at arbejde med en fil, bør filen lukkes. Det gøres med funktionen close. Øvelse 29 Kommandolinie-argumenter Mange kommandoer, f.eks. i DOS, kan bruges på forskellige måder ved at de kaldes med forskellige informationer: ved COPY angiver man f.eks. hvilken fil man vil kopiere og man kan også skrive hvor man vil have den kopieret hen. Ligeledes skal en kommandolinie-kompiler have at vide hvilken fil, den skal kompilere. I denne øvelse skal vi se, hvordan C++ kan bruge den information, brugeren af programmet skriver efter program-navnet ved DOSprompten. Vi skal i øvelsen gennemgå følgende: I C++ behandles argumenter på kommandolinien som parametre til funktionen main. Der bliver altid overført to parametre til main som i de fleste programmer hedder argc og argv. Parameteren argc indeholder antallet af kommandolinie-argumenter, et program bliver kaldt med. Parameteren argv er et array af pointere til tekst-strenge som hver især svarer til ét argument i kommandolinien. I nogle kompilere er der også en tredje parameter, kaldet env, som er et array af pointere til operativsystemet environment-værdier. Evnen til at kunne arbejde med bruger-overførte argumenter gør det muligt at bruge det samme program på flere måder. F.eks. kan man skrive sit eget TIME-program, som alt afhængig af de overførte argumenter viser tiden som klokkeslæt eller som klokkeslæt og dato osv. Argv og argc Når et program kaldes fra prompten betragtes hele linien som kommandolinien, f.eks.: C:\CPP\> COPY MINFIL.DOC NYFIL.DOC <Enter> I denne kommandolinie indgår kommandoen (COPY) og to argumenter (de to filnavne). I programmet kan man bruge kommandolinien ved at skrive main-funktionen med to parametre: void main(int argc, char *argv[]) Den første parameter, argc (argument count), indeholder antallet af elementer i argv-arrayet (argument values). I eksemplet med COPYkommandoen har argc værdien 3 (for kommandoen og to argumenter). Det følgende program, VISARGC.CPP, bruger argc-parameteren til at vise hvor mange argumenter, brugeren skrev på kommandolinien: 121

121 C++ void main(int argc, char *argv[]) cout<< "Antal argumenter i komman- dolinien: " << argc << endl; Når programmet kaldes vil det fortælle, hvor mange argumenter, det fandt i kommandolinien, f.eks.: C:\CPP\> VISARGC A B C <ENTER> Antal argumenter i kommandolinien: 4 Nogle kompilere opfatter tekst, skrevet i anførselstegn som et enkelt argument: C:\CPP\> VISARGC Dette er et enkelt argument <ENTER> Antal argumenter i kommandolinien: 2 Den anden parameter til main-funktionen er argv, som er et array af pointere til strenge som hver især indeholder et argument fra kommandolinien. Figur 36.1 viser hvordan argumenterne fra kommandolinien ligger i argvarrayet. argv [0] argv [1] argv [2] argv [3] argv C O P Y S O U R C E T A R G E T D O C D O C Figur Arrayet argv peger på argumenterne fra kommandolinien. Ved hjælp af en for-løkke viser det følgende program, VISARGV.CPP, elementerne i argvarrayet. Først vises arrayets første element (kommandoens navn) og derefter hver enkelt argument indtil løkken er slut (Når kontrol-variablen bliver større end argc): void main(int argc, char *argv[]) int i; for (i = 0; i < argc; i++) cout << "i argv[" << i << "] står der: " << argv[i] << endl; Når programmet kompileres og kaldes med argumenter, viser det f.eks. følgende på skærmen: C:\CPP\> VISARGV A B C <ENTER> I argv[0] står der: C:\CPP\VISARGV.EXE I argv[1] står der: A I argv[2] står der: B I argv[3] står der: C En løkke der kører indtil argv er NULL Som bekendt angives slutningen af en tekststreng med NULL-tegnet. På samme måde skriver C++ NULL efter det sidste element i argv-arrayet. I det følgende program, ARGV- NULL.CPP, ændrer vi det forrige programs for-løkke, så den kører indtil den møder NULL: void main(int argc, char *argv[]) int i; for (i = 0; argv[i]!= NULL; i++) cout << "I argv[" << i << "] står der: " << argv[i] << endl; Behandling af argv som en pointer Som vi tidligere har set, kan man i C++ adressere arrays med pointere. Det følgende pro- 122

122 5. Arv, konstanter og makroer gram, ARGVPTR.CPP, viser indholdet af kommandolinien ved at bruge argv som en pointer til en tekst-streng-pointer (altså en pointer til en pointer): void main(int argc, char **argv) int i = 0; while (*argv) cout << "I argv[" << i++ << "] står der " << *argv++ << endl; Lad os se på erklæringen af argv-parameteren i main-funktionen: void main(int argc, char **argv) Den første asterisk viser kompileren at argv er en pointer og den anden asterisk viser at argv er en pointer til en pointer i dette tilfælde en pointer til en pointer af typen char). Det er lettere at forstå, hvis man husker at argv er et array af pointere. Hver pointer peger altså på et array af typen char. Praktisk brug af kommandolinie-argumenter Vi vil nu skrive et program (VISFIL.CPP) som brugeren kan kalde med et filnavn som argument. Filen med det angivne navn bliver så udskrevet på skærmen. Hvis brugeren f.eks. vil se indholdet af C:\AUTOEXEC.BAT kommer kommandolinien til at se sådan ud: C:\> VISFIL C:\AUTOEXEC.BAT <Enter> Programmet starter med at teste værdien af argc for at sikre sig at brugeren har skrevet noget efter kommandoen. Hvis det er tilfældet er værdien af argc mindst 2. Derefter åbnes og vises den angivne fil med de teknikker vi gennemgik i øvelse 34. Hvis en fil ikke kan åbnes, vises en fejl-meddelelse, og programmet afsluttes: #include <fstream.h> #include <stdlib.h> void main(int argc, char *argv[]) char linie[256]; if (argc < 2) cerr << "Du skal angive et filnavn" << endl; exit(1); ifstream input_fil(argv[1]); if (input_fil.fail()) cerr << "Fejl under åbning af " << argv[1] << endl; else while ((! input_fil.eof()) && (! input_fil.fail())) input_fil.getline(linie, sizeof(linie)); if (! input_fil.fail()) cout << linie << endl; Brug af operativsystemets environment-indstillinger I de fleste operativsystemer er der systemvariabler som programmer kan bruge til at se forskellige indstillinger. I DOS skrives og læses system-variabler med SET-kommandoen. I nogle kompilere kan man bruge en tredje parameter i main, ved navn env. Env er en pointer til et array af tekst-strenge og er ligesom argv afsluttet med NULL-tegnet. Hvis din kompiler understøtter brugen af env-parameteren kan main-funktionen ændres til dette: 123

123 C++ void main(int argc, char *argv[], char *env[]) Det følgende program, VISENV.CPP, viser elementerne i env-arrayet som indeholder systemvariablerne: void main(int argc, char *argv[], char *env[]) while (*env) cout << *env++ << endl; While-løkken kører env-arrayet igennem indtil den møder NULL. Når programmet kompileres og køres, vil det vise system-værdierne, som f.eks. her: C:\CPP\> VISENV <ENTER> TEMP=C:\WINDOWS\TEMP PROMPT=$p$g COMSPEC=C:\WINDOWS\COMMAND.COM PATH=C:\WINDOWS;C:\DOS Opsummering Et C++-program kan læse de argumenter, brugeren skriver på kommandolinien hvilket giver mulighed for at gøre programmerne mere fleksible. I næste øvelse skal vi se, hvordan programmer kan skrives mere læsevenligt ved hjælp af makroer og konstanter. Men før vi går i gang med det, bør du sikre dig at du har forstået følgende: Når et program kaldes fra system-prompten er kommandolinien hele den linie, brugeren skriver. Ordene i kommandolinien kan bruges i programmet ved hjælp af parametrene argc og argv i main-funktionen. Parameteren argc indeholder antallet af kommandolinie-argumenter. Parameteren argv er et streng-array hvor hvert element er et ord fra kommandolinien. Nogle kompilere har en tredje kommandolinie-parameter, ved navn env. Env er et array som indeholder operativsystemets environment-værdier. 124

124 6. Avanceret C++ Øvelse 30 Konstanter og makroer Konstanter og makroer bruges til at gøre et program lettere at læse og ændre. Tal-værdier i koden kan erstattes med mere sigende konstant-navne f.eks. kan momssats være en konstant for værdien 0,25. Når, programmet bruger konstanten momssats, kan læseren lettere forstå hvad der sker. Med makroer kan man erstatte lange ligninger, som f.eks. resultat=(x*y-3) * (x*y-3) *(x*y-3); med en funktions-agtig makro f.eks. kaldet KUBUS, på denne måde: resultat= KUBUS(x*y-3); Denne makro gør ikke alene programmet mere læsbart, men mindsker samtidig risikoen for fejl på grund af lange beregninger. I denne øvelse skal vi se nærmere på konstanter og makroer, og efter øvelsen vil vi have gennemgået følgende: Programmer bliver lettere at læse og ændre hvis tal-værdier i koden erstattes med konstanter der har fornuftige navne. Formler kan erstattes med forståelige makro-navne. Før C++ kompilerer et program, bruger det et specielt program, kaldet preprocessoren, til at erstatte konstanter og makroer med de rigtige værdier og formler. Makroer bliver udført hurtigere end funktioner, men øger også størrelsen af den eksekverbare fil for hver gang makroen bliver anvendt. De fleste C++-kompilere indeholder fordefinerede konstanter og makroer, man kan bruge i programmet. Konstanter En konstant er et navn hvortil man knytter en værdi, som, i modsætning til en variabel, ikke kan ændres under program-afviklingen. Konstanter defineres med preprocessor-direktivet #define (en kommando til C++ preprocessoren). Denne sætning definerer konstanten MOMSPROCENT som svarende til værdien 0,25. #define MOMSPROCENT 0,25 Mange programmører skriver konstanter med store bogstaver for at kunne skelne dem fra variabler. I det følgende program, KON- STANT.CPP, defineres konstanten MOMS- PROCENT: // Den aktuelle momssats #define MOMSPROCENT 25 cout << "Momssatsen er på " << MOMSPROCENT << "%" << endl; Konstanten defineres med #define-direktivet øverst i programmet. Herefter kan konstanten bruges fuldstændig som den værdi, den svarer til. Bemærk: Konstant-definitioner slutter ikke med semikolon da preprocessoren i så fald ville opfatte semikolon-tegnet som del af konstantens værdi. Hvis man havde skrevet det ville MOMSPRO- CENT have svaret til 25; som ville have medført en kompilerings-fejl: 25; er ikke et tal. 125

125 C++ Preprocessor-direktiver Før et program blive kompileret, kalder kompileren et specielt program, kaldet preprocessoren. Preprocessoren kigger efter linier der begynder med #, f.eks. #include og #define. Hvis den f.eks. møder #include-direktivet, inkluderer preprocessoren den angivne fil i kildeteksten. Alle vores programmer har f.eks. inkluderet header-filen iostream.h ved hjælp af #include-direktivet. Møder preprocessoren #define-direktivet, laver den en konstant eller en makro. Når den i programmet møder brugen af enten konstanten eller makroen, erstattes navnet med den angivne værdi. Konstanter behøver ikke nødvendigvis at være tal-værdier: En konstant kan også være f.eks. en tekst-streng. Det følgende program, BOG- INFO.CPP, bruger #define-direktivet til at definere information om en bog: #define TITEL "C++!" #define OEVELSE 37 #define PRIS // Den aktuelle momssats #define MOMSPROCENT 25 cout << "Bogens titel: " << TITEL << endl; cout << "Aktuel øvelse: " << OEVELSE << endl; cout << "Pris: kr. " << PRIS << endl; cout << "Pris med moms: kr. " << PRIS + PRIS/100*MOMSPROCENT<<endl; Når programmet kompileres og køres, viser det følgende tekst på skærmen: C:\CPP\> BOGINFO <ENTER> Bogens titel: C++! Aktuel øvelse: 37 Pris: kr Pris med moms: kr. 228 Konstanter gør det lettere at ændre programmer Brugen af konstanter gør et program lettere at ændre. Hvis man f.eks. har brugt momsprocent-værdien 25 igennem et helt program, og momsen stiger til 30, skal man til at ændre værdierne alle stederne. Derimod skal et program med konstanter kun ændre konstantdefinitionen og kompileres igen. Det følgende program bruger tallet 50 (antallet af elever) mange steder: int test_resultat[50]; char karakter[50]; int elev; for (elev = 0; elev < 50; elev++) skaf_test_point(elev); for (elev = 0; elev < 50; elev++) beregn_karakter(elev); for (elev = 0; elev < 50; elev++) udskriv_karakter(elev); Men hvis programmet var skrevet med konstanter, ville det se sådan ud: #define ELEVTAL 50 int test_resultat[elevtal]; char karakter[elevtal]; int elev; for (elev = 0; elev < ELEVTAL; elev ++) 4 126

126 6. Avanceret C++ skaf_test_point(elev); for (elev = 0; elev < ELEVTAL; elev ++) beregn_karakter(elev); for (elev = 0; elev < ELEVTAL; elev ++) udskriv_karakter(elev); Hvis elevtallet stiger til 55 skal man kun ændre ét sted nemlig i konstant-definitionen: #define ELEVTAL 55 Makroer En computer er en fantastisk regnemaskine, og der vil tit være brug for at have komplekse beregninger i programmerne, f.eks.: resultat=(x*y-3) * (x*y-3) *(x*y-3); Denne beregning beregner udtrykket (x*y-3) i tredje potens. Jo flere gange en beregning skal udføres i programmet jo smartere er det at definere den som en makro. Det mindsker risikoen for skrivefejl i programmet. Hvis et program har defineret en makro ved navn KU- BUS som udfører beregningen, kan makroen bruges på denne måde: resultat = KUBUS(x*y-3); Ligesom med konstanterne er det almindeligt at makroer skrives med store bogstaver for at de ikke bliver forvekslet med funktioner. En makro defineres med preprocessor-direktivet #define. Her defineres f.eks. makroen KU- BUS: #define KUBUS(x) ((x)*(x)*(x)) Makroen bliver defineret til at gange parameteren x med sig selv, to gange. I det følgende program, VISKUBUS.CPP, bruger vi makroen til at vise kubus af værdierne fra 1 til 10: #define KUBUS(x) ((x)*(x)*(x)) for (int i = 1; i <= 10; i++) cout << "Kubus af " << i << " er " << KUBUS(i) << endl; Når programmet kompileres, bliver alle forekomster af makroen KUBUS erstattet med den tilsvarende makro-definition. Preprocessorens makro-erstatning laver denne kode om: #define KUBUS(x) ((x)*(x)*(x)) for (int i = 1; i <= 10; i++) cout << "Kubus af " << i <<" er " << KUBUS(i) << endl; så den kommer til at se sådan ud: #define KUBUS(x) ((x)*(x)*(x)) for (int i = 1; i <= 10; i++) cout << "Kubus af " << i << " er " << ((i)*(i)*(i))<<endl; Læg mærke til, at vi i makro-definitionen skrev parameteren x i parenteser ((x)*(x)*(x)) og ikke bare (x*x*x). Det bør gøres for at sikre at operator-prioriteten ikke laver om på beregningen i makroen. Hver operator har som vi så i øvelse 5 en prioritet ifølge hvilken en beregning vil blive udført. Forestil dig at KU- BUS-makroen bliver kaldt med værdien 3+5-2: 127

127 C++ resultat = KUBUS(3+5-2); Hvis makroens enkelte parametre står i parenteser vil preprocessoren lave følgende sætning: resultat = ((3+5-2) * (3+5-2) * (3+5-2)); Men hvis ikke parenteserne er der vil preprocessoren lave denne sætning: resultat = (3+5-2*3+5-2*3+5-2); De to beregninger giver forskellige resultater. Sådanne fejl undgås ved altid at skrive parametrene i parenteser. Forskelle mellem makroer og funktioner En makro-definition er ikke det samme som en funktion. En funktion findes kun ét sted i programmet og bliver så kaldt fra forskellige steder. Hver gang funktionen bliver kaldt, lægges parametrene på stakken, hvorefter funktionen startes. Når funktionen er slut, fjernes værdierne fra stakken og programmet springer tilbage og fortsætter med sætningen efter funktions-kaldet. Med makroen er det anderledes: Hver gang preprocessoren møder et makrokald, bliver det erstattet med den tilsvarende makro-definition. Hvis f.eks. et program havde defineret makroen KUBUS og kaldte den 200 steder i programmet, ville preprocessoren kopiere makroen ud til alle 200 steder, hvilket ville forøge programmets størrelse. En makro skal til gengæld ikke bruge tid på at kopiere værdier til og fra stakken: Koden ligger simpelthen hvor den bliver brugt. Makroer kan bruges til mange ting Et program kan bruge makroer på mange forskellige måder. Men husk at formålet med makroen er at gøre programmet lettere at programmere og læse. Det følgende program, MAKPAUSE.CPP, viser et andet eksempel på brug af makroer. Det kan sikkert også give et bedre billede af, hvordan preprocessoren erstatter et makro-kald med makro-definitionen: #define PAUSE(x) \ cout << "Venter " << x << endl; \ for(long int i=0; i < x; i++) \ ; \ PAUSE( ); PAUSE( ); PAUSE( ); Da makro-definitionen fylder flere tekst-linier, afsluttes hver linie med en bagvendt skråstreg hvis koden fortsætter på næste linie. Opsummering Formålet med makroer og konstanter er at gøre programmet lettere at programmere og læse. I øvelsen så vi hvordan man definerer og bruger makroer og konstanter. Makroer og konstanter erstatter tal og beregninger med forståelige navne som gør det lettere at læse programmet. Ved at bruge konstanter, kan man reducere den tid der skal bruges til at ændre værdierne i et helt program. Under kompileringen bruger C++ et program, kaldet preprocessoren, til at udskifte konstanter og makroer med deres definerede værdier og beregninger. Makroer udføres hurtigere end funktioner, men gør også den eksekverbare program-fil større. 128

128 6. Avanceret C++ En makro-definition kan skrives over flere linier, hvis man husker at afslutte de linier, der skal fortsættes, med en bagvendt skråstreg. Øvelse 31 Brug af free store Som vi tidligere så, allokerer C++ kompileren plads til elementerne når man i et program opretter et array. Men hvad nu hvis programmet på et tidspunkt skal arbejde med flere elementer end der er allokeret plads til? Så er der ikke plads til allesammen. Hvis man f.eks. har erklæret et array som skal indeholde 100 aktiekurser og senere får brug for at registrere flere end 100 aktie-kurser: Så er man nødt til at ændre i kildeteksten og kompilere programmet igen. Men i stedet for altid at allokere den samme plads til arrays med faste størrelser, kan programmer allokere den nødvendige plads dynamisk i takt med at behovet for pladsen opstår. Hvis et program skal registrere aktie-kurser kan det automatisk sætte plads af til 25 eller 200 kurser, alt efter hvad der er nødvendigt. Ved at allokere lagerplads dynamisk på denne måde tilpasser programmet sig brugerens behov uden yderligere programmering. Programmet angiver hvor meget lagerplads det skal bruge, og C++ returnerer en pointer til det afsatte hukommelses-område. Lageret som allokeres kommer fra et område i computerens hukommelse som kaldes free store. I denne øvelse ser vi på hvordan man får programmer til at allokere og frigive lagerplads dynamisk, mens programmet kører. Vi vil komme ind på følgende centrale ting: Til at allokere hukommelse under kørsel af et program bruges operatoren new. Med new angiver programmet hvor meget lager det har brug for. Hvis det lykkes new at allokere den ønskede lagerplads, returneres en pointer til starten af det allokerede område i lageret. Hvis new ikke kan allokere plads f.eks. hvis der ikke er mere ledig lagerplads returnerer den en NULL pointer. For at frigive lagerplads som er allokeret med new, bruger programmet operatoren delete. Det er en meget nyttig funktion at kunne allokere lagerplads dynamisk. I øvelsen vil du opdage at dynamisk plads-allokering faktisk er meget let. new operatoren I C++ gør operatoren new det muligt at allokere lagerplads mens programmet kører. Ved brug af new anføres hvor mange byte, programmet har brug for at få allokeret. Hvis programmet f.eks. skal oprette et array med plads til 50 byte kan new allokere plads i lageret på denne måde: char *buffer = new char[50]; Som nævnt returnerer new en pointer til starten af den allokerede plads hvis operationen er lykkedes. I eksemplet her tildeler programmet en pointer til en variabel som er defineret som en pointer til typen char. Hvis der ikke kan allokeres den ønskede plads, returnerer new en NULL pointer som indeholder værdien 0. Når et program allokerer plads med new operatoren bør det teste om den returnerede værdi er NULL. I følgende programeksempel, BRUG_NEW.CPP, bruger vi new operatoren til at allokere en pointer til et array på 100 tegn: char *pointer = new char[100]; 4 129

129 C++ if (pointer!= NULL) cout << "Lagerallokering lykkedes" << endl; else cout << "Fejl ved lagerallokering" << endl; Programmet tester værdien som new operatoren har tildelt til pointervariablen. Hvis pointeren har værdien NULL er det ikke lykkedes new at allokere den ønskede lagerplads. Hvis pointeren ikke indeholder NULL betyder det at det er lykkedes new at allokere plads, og pointeren indeholder nu adressen på begyndelsen af lagerområdet. I programmet allokerer vi plads til 100 byte i lageret. Da vi har skrevet præcis hvor meget lager vi ønsker, kan programmet ikke allokere andre størrelser ikke medmindre vi ændrer i koden. Fordelen ved dynamisk lagerallokering er netop at det gør det unødvendigt at omskrive og rekompilere programmet selv om lagerbehovet ændres. Det følgende program, SKAF_MEM.CPP, spørger brugeren om hvor mange bytes der skal allokeres hvorefter det gøres ved hjælp af new: int size; char *pointer; cout << "Angiv array-størrelse, under 30000: "; cin >> size; if (size <= 30000) pointer = new char[size]; if (pointer!= NULL) cout << "Lagerallokering lykkedes" << endl; else cout << "Fejl ved 4 lagerallokering" <<endl; Programmer der allokerer lagerplads dynamisk med new operatoren skal laves så de selv styrer hvor meget plads de allokerer. Et program der f.eks. skal indlæse data for en række ansatte fra en fil, skal indlæse en post for hver ansat og løbende allokere lagerplads indtil der ikke er flere personer i filen. Det følgende program, NOMEMORY.CPP, allokerer blokke på tegn ad gangen indtil new ikke kan finde mere plads i free store. Programmet bliver altså ved med at allokere plads indtil det har brugt hele den tilgængelige lagerplads. Så længe new kan allokere plads skrives en besked om at det er lykkedes, men når der ikke er mere plads kommer en fejlmeddelelse, og programmet afsluttes: char *pointer; do pointer = new char[10000]; if (pointer!= NULL) cout << "10,000 bytes allokeret" << endl; else cout << "Lagerallokering mislykkedes" << endl; while (pointer!= NULL); Bemærk: Hvis du arbejder i MS-DOS vil du opdage at lagerpladsen er opbrugt når der er allokeret mere end 64 Kb. Det er fordi de fleste C++ kompilere i MS-DOS arbejder med en mindre lager-model på kun 64 Kb. Den største enkeltblok som kan allokeres er således også 64 Kb. Den tilgængelige lager- 130

130 6. Avanceret C++ plads i free store varierer således efter hvilket miljø man arbejder i. Frigivelse af lagerplads efter brug Som vi har set, kan man med new operatoren under programafvikling dynamisk allokere lagerplads. Når programmet ikke mere har brug for den allokerede plads, bør den frigives hvilket gøres med delete operatoren. Delete operatoren bruges ved simpelthen at angive en pointer til lagerområdet på denne måde: delete pointer; I det følgende program, SLET_MEM.CPP, bruger vi delete operatoren til at frigive lagerplads som er allokeret med new operatoren: #include <string.h> char *pointer = new char[100]; strcpy(pointer, "Jeg programmerer i C++"); cout << pointer << endl; delete pointer; Et eksempel mere Det følgende program, ALLOKARR.CPP, allokerer lagerplads som skal indeholde et array på 1000 heltal. Programmet fylder derefter arrayet med værdierne 1 til 1000 og skriver dem på skærmen. Så frigives lagerpladsen, og programmet allokerer et array på 2000 decimaltal som fyldes med værdierne 1,0 til 2000,0: int *heltal_array = new int[1000]; float *decimaltal_array; int i; if (heltal_array!= NULL) for (i = 0; i < 1000; i++) heltal_array[i] = i + 1; for (i = 0; i < 1000; i++) cout<<heltal_array[i]<< ' '; delete heltal_array; decimaltal_array = new float[2000]; if (decimaltal_array!= NULL) for (i = 0; i < 2000; i++) decimaltal_array[i] = (i + 1) * 1.0; Almindeligvis sletter operativsystemet allokeret lagerplads ved slutningen af et program hvis programmet ikke selv gør det. Men hvis programmet selv sletter allokeret lagerplads lige så snart det er færdig med at bruge den, er pladsen fri og så kan programmet selv eller operativsystemet bruge den. for (i = 0; i < 2000; i++) cout << decimaltal_array[i] << ' '; delete decimaltal_array; Det er fornuftigt at programmet selv frigiver lagerpladsen efter at det er færdigt med at bruge den. 131

131 C++ Opsummering I denne øvelse så vi hvordan man ved hjælp af new operatoren dynamisk kan allokere lagerplads i et område af hukommelsen som kaldes free store. Det gør det muligt for et program at arbejde med forskellige datamængder uden at det skal skrives om og rekompileres. I næste øvelse skal vi se hvordan man kan styre new og allokeringen af lager i free store. Men før vi når så langt bør du sikre dig, at du har forstået følgende: Muligheden for at kunne allokere lagerplads efter behov mens programmet kører fjerner afhængigheden af arrays med faste størrelser. Hvis allokeringen af lager lykkes, returnerer new en pointer til starten af det afsatte lagerområde. Hvis det ikke lykkes new at allokere plads, returneres en NULL pointer som indeholder værdien 0. Hver gang new bruges, bør programmet teste værdien af den returnerede pointer for at se om den er NULL hvilket betyder at det ikke lykkedes new at allokere ny plads. Programmet kan adressere den allokerede lagerplads som en pointer eller array. New operatoren allokerer lagerplads fra et område af ikke-brugt hukommelse kaldet free store. Størrelsen af free store varierer fra operativsystem til operativsystem. I MS-DOS er free store ofte begrænset til 64Kb. Når programmet ikke har brug for den allokerede lagerplads mere, bør det frigive pladsen med delete operatoren. Øvelse 32 Styring af free store operationer I forrige øvelse så vi hvordan man med new operatoren dynamisk kunne allokere lagerplads fra free store mens et program kører: Hvis allokeringen lykkes returneres en pointer til lagerområdet, og hvis operationen ikke lykkes tildeles pointervariablen værdien NULL. Somme tider kan et program måske have brug for at udføre bestemte operationer hvis new ikke kan allokere lagerplads. I denne øvelse skal vi se, hvordan C++ kan programmeres til at kalde en bestemt funktion i programmet når new ikke kan allokere lagerplads. Vi vil i øvelsen komme ind på følgende: Man kan lave sin egen for-lidt-hukommelse-handler en funktion som C++ kalder hvis new ikke kan allokere den ønskede lagerplads. Det er muligt at skrive sin egen new operator som kan allokere og initialisere lagerplads. Man kan også skrive sin egen delete operator til at frigive lagerplads. Ved at lave sine egne new og delete operatorer bliver det lettere at styre hvordan programmet håndterer situationer med for lidt lagerplads. Vi laver en for-lidt-hukommelse-fejl handler Som vi så i forrige øvelse returnerer new værdien NULL til pointervariablen hvis det ikke lykkes at allokere den ønskede lagerplads i free store. Det følgende program, BRUG- FREE.CPP, kalder new operatoren som allokerer 1000 byte gentagne gange indtil pladsen i free store er opbrugt: 132

132 6. Avanceret C++ char *pointer; do pointer = new char[1000]; if (pointer!= NULL) 4 cout << "1000 bytes allokeret" << endl; else cout << "Free store er tom" << endl; while (pointer); #include <stdlib.h> // Exit proto- // type #include <new.h> // set_new_hand- // ler prototype void slut_program(void) 4 cout << "Lager-allokering mislykkedes " << endl; exit(1); char *pointer; Programmet kører i en løkke indtil new tildeler pointervariablen værdien NULL. Hvis man derimod vil have new til at gøre noget mere end bare at returnere NULL når der ikke er mere plads i free store, skal man først definere en funktion som programmet skal kalde når der ikke er plads nok til en allokering. For eksempel viser den følgende funktion, slut_program, en besked på skærmen hvorefter den afslutter programmet ved hjælp af funktionen exit fra run-time biblioteket: set_new_handler(slut_program); do pointer = new char[10000]; cout << "10000 bytes allokeret" << endl; while (1); void slut_program(void) cout << "Lager-allokering mislykkedes" << endl; exit(1); For at C++ kan kalde funktionen slut_program når det ikke lykkes for new at allokere lagerplads skal programmet kalde funktionen set_new_handler med funktionen slut_program som parameter: set_new_handler(slut_program); I det følgende program, SLUTFREE.CPP, kaldes funktionen slut_program hvis new ikke kan allokere den ønskede plads: I eksemplet afsluttes programmet ganske enkelt hvis new ikke kan allokere lagerplads i free store. Afhængigt af formålet med programmet, kan funktionen bruges til at allokere lagerplads fra et andet sted, som for eksempel computerens udvidede extendede hukommelse i MS-DOS. Ligeledes kunne programmet frigive lagerplads som det havde allokeret til et andet formål. Ved at gøre det muligt at styre for-lidt-lager handleren, giver C++ fuld kontrol over lager-allokerings processen. Vi skriver vores egen new og delete Som vi tidligere har set, kan man i C++ overloade operatorer. Det kan vi også bruge i forbindelse med new og delete operatorerne som vi kan modificere til at fungere på en anden måde. Et eksempel kunne være hvis et program allokerer 100 bytes som skal indehol- 133

133 C++ gram allokerer 100 bytes som skal indeholde firmaets tophemmelige data. Når programmet senere frigiver lagerpladsen med delete operatoren kunne en spion som også kan programmere med adgang til computeren, i teorien lokalisere de 100 byte og læse hemmelighederne. Men ved at overloade delete operatoren kan vi lave en delete som først overskriver dataene med nonsens før den frigiver lagerpladsen. Det følgende program, NYDELE- TE.CPP, overloader delete operatoren. Først overskrives de 100 byte som pointeren peger på hvorefter pladsen frigives med funktionen free fra run-time biblioteket: #include <stdlib.h> #include <string.h> static void operator delete(void *pointer) char *data = (char *) pointer; int i; for (i = 0; i < 100; i++) data[i] = 0; cout << "Dataene er slettet!" << endl; free(pointer); char *pointer = new char[100]; strcpy(pointer, "Firma hemmeligheder"); delete pointer; Når programmet starter, allokerer new hukommelse til et tegn-array. Derefter kopieres firmahemmeligheder til strengen. Senere bruger programmet den overloadede delete operator til at slette området. I delete funktionen er det denne sætning der tildeler en pointer til en tekststreng pointer: char *data = (char *) pointer; Ordene (char *) kaldes en cast og fortæller C++ kompileren at programmet godt ved, at det tildeler en pointer af typen void (se funktionens parameter) til en pointer af typen char. Hvis man ikke medtager en cast, kan programmet ikke kompileres. Efter dette skriver funktionen 0'er i de 100 byte i bufferen og frigiver lagerpladsen med run-time funktionen free. Det er vigtigt at huske at denne funktion kun virker ved hukommelses-størrelser på præcis 100 byte men det er i orden da programmet kun allokerer lagerplads en enkelt gang. Hvis programmet ændres til kun at allokere 10 byte i hukommelsen skal man også ændre delete funktionen som ellers vil overskrive 90 byte som programmet måske bruger et andet sted og derved fremkalde en fejl. Run-time biblioteket indeholder desuden en række funktioner der blandt andet kan fortælle hvor meget plads en given pointer peger på. Det næste program, NY_OVER.CPP, overloader også new operatoren. I dette eksempel skriver den overloadede funktionen teksten "Jeg programmerer i C++!" i starten af den allokerede hukommelse: #include <alloc.h> #include <string.h> static void *operator new(size_t size) char *pointer; pointer = (char *) malloc(size); if (size > strlen("jeg pro- grammerer i C++!")); strcpy(pointer, " Jeg pro- grammerer i C++!"); 134

134 6. Avanceret C++ return(pointer); char *str = new char[100]; cout << str << endl; Som det ses, bruger vi funktionen malloc fra runtime biblioteket til at allokere hukommelse, og hvis størrelsen af det allokerede område er stor nok til at kunne indeholde strengen "Jeg programmerer i C++!" bruges run-time funktionen strcpy til at kopiere den til hukommelsesområdet. Opsummering Efterhånden som programmerne bliver mere komplekse får de brug for at kunne allokere hukommelse mens programmet kører. Det gøres ved hjælp af new operatoren som vi nu har set hvordan man kan få til at arbejde på andre måder. Først ved at skrive en handler-funktion som programmet kalder når new ikke kan allokere den ønskede mængde hukommelse, og derefter ved at overloade selve new operatoren. Før vi fortsætter med næste øvelse skal vi lige opsummere hvad vi har gennemgået i denne øvelse: Hvis ikke new operatoren kan allokere den ønskede lagerplads, returnerer den værdien NULL til den angivne pointer. Hvis man ønsker bestemt kode udført hver gang new ikke kan efterkomme et hukommelses-ønske, kan man definere sin egen handler. Ved hjælp af funktionen set_new_handler kan programmet instruere new om at kalde en bestemt funktion når det ikke lykkes at allokere den ønskede plads. Man kan også overloade new og delete operatorerne, men før man gør det, bør man have en klar forståelse for hvad free store er samt de funktioner fra run-time biblioteket som kan bruges til at manipulere den. Øvelse 33 Polymorfisme En af de terminologier man ofte hører i forbindelse med C++ og objekt-orienteret programmering er polymorfisme. Polymorfisme kan defineres som et objekts evne til at skifte form. Ordet er sammensat af ordet poly som betyder mange og morfisme som har noget at gøre med form. I denne øvelse introduceres polymorfisme, og vi ser hvordan man kan bruge polymorfiske objekter i programmer til at simplificere koden. Efter øvelsen vil vi have gennemgået følgende: Polymorfisme er muligheden for at ændre et objekt mens programmet kører. I C++ er det let at lave polymorfiske objekter. For at lave polymorfiske objekter skal programmet bruge virtuelle funktioner. En virtuel funktion er en basis-klasse funktion hvor der står virtual foran funktionsnavnet. Enhver klasse som er afledt fra en basisklasse kan bruge eller overloade en virtuel funktion. Et polymorfisk objekt laves ved hjælp af en pointer til et basis-klasse objekt. Hvad er polymorfisme Et polymorfisk objekt kan ændre form i løbet af afviklingen af et program. Forestil dig at du arbejder som programmør for telefonselskabet, og du er blevet bedt om at skrive et program, der simulerer en telefon. Mens du tænker på hvordan almindelige mennesker bruger telefonen kan du hurtigt nævne almindelige handlinger såsom nummervalg, opringning, og optaget-melding. Med udgangspunkt i disse operationer kan man erklære følgende klasse: 135

135 C++ class telefon public: void drej(char *nummer) cout << "Drejer nummer" << nummer << endl; void svar(void) cout << "Venter på samtale" << endl; void afslut(void) 4 cout << "Samtale slut - lægger på" << endl; void ring(void) cout << "Ring, ring, ring" << endl; telefon(char *nummer) strcpy(telefon::nummer,nummer);; private: char nummer[13]; ; Det følgende program, TELEFON.CPP, bruger klassen telefon til at erklære en telefon-objekt: #include <string.h> class telefon public: void drej(char *nummer) cout << "Drejer nummer" << nummer << endl; void svar(void) cout << "Venter på samtale" << endl; void afslut(void) cout << "Samtale slut - lægger på" << endl; void ring(void) cout << "Ring, ring, ring" << endl; telefon(char *nummer) strcpy(telefon::nummer,nummer);; private: char nummer[13]; ; telefon mintelefon(" "); mintelefon.drej(" "); Men teleselskabet vil gerne have at programmet skal kunne kende forskel på drejeskive-telefoner og trykknap-telefoner, og det skal også kunne bruges til mønt-telefoner som forventer at brugeren smider penge i apparatet for at kunne ringe Da vi kender principperne for arv, beslutter vi os for at aflede klasserne trykknap og moent_telefon fra klassen telefon: class trykknap : telefon public: void drej(char *nummer) cout << "Bip Bip - Drejer " << nummer << endl; trykknap(char *nummer) : telefon(nummer) ; class moent_telefon : telefon public: void drej(char *nummer) cout << "Indkast " << beloeb << " kroner" << endl; cout << "Drejer " << nummer << endl; moent_telefon(char *nummer, int beloeb):telefon(nummer) moent_telefon::beloeb = beloeb; private: int beloeb; ; Som det fremgår erklærer klasserne trykknap og moent_telefon hver deres drej metode. Hvis man antager at drej metoden i telefon klassen er tænkt som en drejeskive-telefon er det unød- 136

136 6. Avanceret C++ vendigt at erklære en drejeskive-telefon klasse. Følgende program, NYTELE.CPP, erklærer objekter af de tre klasser: #include <string.h> moent_telefon::beloeb = beloeb; private: int beloeb; ; class telefon public: void drej(char *nummer) 4 cout << "Drejer nummer" << nummer << endl; void svar(void) cout << "Venter på samtale" << endl; void afslut(void) cout<< "Samtale slut - lægger på" <<endl; void ring(void) cout << "Ring, ring, ring" << endl; telefon(char *nummer) strcpy(telefon::nummer,nummer);; private: char nummer[13]; ; class trykknap : telefon public: void drej(char *nummer) cout << "Bip Bip - Drejer " << nummer << endl; trykknap(char *nummer) : telefon(nummer) ; class moent_telefon : telefon public: void drej(char *nummer) cout << "Indkast " << beloeb << " kroner" << endl; cout << "Drejer " << nummer << endl; moent_telefon(char *nummer, int beloeb) : telefon(nummer) telefon gammeldags(" "); 4 gammeldags.drej(" "); trykknap modernetelefon (" "); modernetelefon.drej(" "); moent_telefon telefonboks (" ", 2); telefonboks.drej(" "); Når programmet kompileres og startes, skriver det følgende output på skærmen: C:\> NYTELE <Enter> Drejer nummer Bip Bip - Drejer Indkast 2 kroner Drejer Som nævnt ændrer et polymorfisk objekt form mens programmet køres. I det forrige program brugte vi ikke polymorfisme. Der var med andre ord ikke nogen objekter, som ændrede form. Vi laver et polymorfisk telefon-objekt Teleselskabet er ikke helt tilfreds med telefonprogrammet: det skal kunne simulere drejeskive-, trykknap- og mønt-telefon efter behov: Ved én adressering skal objektet kunne være trykknap-telefon og næste adressering mønttelefon, og så videre. Objektet skal med andre ord kunne ændre form fra kald til kald. Den eneste funktion som er ens i de tre klasser er metoden drej. Objektet kan laves polymor- 137

137 C++ fisk ved at funktionen drej i basis-klassen gøres virtuel. Det gøres ved at skrive det reserverede ord virtual foran funktions-prototypen og headeren: class telefon public: virtual void drej(char *nummer) cout << "Drejer nummer" << nummer << endl; 4 void svar(void) cout << "Venter på samtale" << endl; void afslut(void) cout<< "Samtale slut - lægger på" <<endl; void ring(void) cout << "Ring, ring, ring" << endl; telefon(char *nummer) strcpy(telefon::nummer,nummer);; private: char nummer[13]; ; Herefter erklærer vi i programmet en pointer til et objekt af basis-klassen. I det aktuelle telefon-program erklærer vi en pointer til basisklassen telefon: telefon *poly_telefon; Objektets form ændres ved at tildele adressen på et objekt af en afledt klasse til pointeren: poly_telefon = (telefon *) &privat_telefon; (telefon *) som står efter tildelingsoperatoren er en cast. Den fortæller C++ kompileren at det er i orden at tildele adressen for en variabel af en type (privat_telefon) til en pointer til en variabel af en forskellig type (telefon). Og når programmet på den måde kan tildele forskellige objekters adresser til objektpointeren poly_telefon, så objektet ændrer form, kan det kaldes polymorfisk. Det følgende program, POLYMORF.CPP bruger teknikken til at lave et polymorfisk telefon-objekt. I løbet af programmet skifter objektet poly_telefon fra at være gammeldags telefon til at være trykknap-telefon og derefter til at være mønt-telefon: #include <string.h> class telefon public: 4 virtual void drej(char *nummer) cout << "Drejer nummer" << nummer << endl; void svar(void) cout << "Venter på samtale" << endl; void afslut(void) cout<< "Samtale slut - lægger på" <<endl; void ring(void) cout << "Ring, ring, ring" << endl; telefon(char *nummer) strcpy(telefon::nummer,nummer);; private: char nummer[13]; ; class trykknap : telefon public: void drej(char *nummer) cout << "Bip Bip - Drejer " << nummer << endl; trykknap(char *nummer) : telefon(nummer) ; class moent_telefon : telefon public: void drej(char *nummer) cout << "Indkast " << beloeb << " kroner" << endl; cout << "Drejer " << nummer << endl; 138

138 6. Avanceret C++ moent_telefon(char *nummer, int beloeb) : telefon(nummer) moent_telefon::beloeb = beloeb; private: int beloeb; ; 4 moent_telefon telefonboks (" ", 2); trykknap modernetelefon (" "); telefon gammeldags(" "); // Lav objektet til // gammeldags telefon telefon *poly_telefon = &gammeldags; poly_telefon->drej(" "); // Lav objektets form om til // modernetelefon poly_telefon = (telefon *) &modernetelefon; poly_telefon->drej(" "); // Lav objektets form om til // telefonboks poly_telefon = (telefon *) &telefonboks; poly_telefon->drej(" "); Når programmet kompileres og køres, viser det følgende output: C:\> POLYMORF <ENTER> Drejer Bip Bip - Drejer Indkast 2 kroner Drejer Objektet poly_telefon ændrer form i løbet af programmets kørsel og er derfor polymorfisk. Rent virtuelle funktioner Som vi har set skal man for at lave et polymorfisk objekt definere en eller flere af basis-klassens metoder som virtuelle funktioner. Afledte klasser kan så enten indeholde deres egne funktioner der udføres i stedet for basis-klassens virtuelle funktion eller udelade funktionen hvorved basis-klassens funktion bliver kaldt. Men i visse programmer er det ikke smart at de virtuelle funktioner er indeholdt i basis-klassen. Det kunne for eksempel være hvis de afledte klasser er så forskellige fra basis-klassen at ingen af dem vil gøre brug af basis-klassens metode. I stedet for at skrive sætninger i basis-klassens virtuelle funktion, kan man i de tilfælde skrive en rent virtuel funktion som ikke indeholder nogen sætninger. En rent virtuel funktion laves ved, at programmet nøjes med at angive funktionens prototype. I stedet for at indeholde sætninger tildeles funktionen værdien 0 på denne måde: class telefon public: virtual void drej(char *nummer) = 0; // Rent virtuel funktion void svar(void) cout << "Venter på samtale" << endl; void afslut(void) cout<< "Samtale slut - lægger på" <<endl; void ring(void) cout << "Ring, ring, ring" << endl; telefon(char *nummer) strcpy(telefon::nummer,nummer);; protected: char nummer[13]; ; For hver afledt klasse, som erklæres i programmet, skal der defineres en funktion som svarer til basis-klassens rent virtuelle funktion. Hvis en afledt klasse mangler en funktion for basis-klassens rent virtuelle funktion, kommer 139

139 C++ C++ kompileren med en syntaks-fejl meddelelse. Opsummering Polymorfisme er et objekts evne til at ændre form mens programmet kører. I denne øvelse så vi nærmere på, hvad der skal til for at lave polymorfiske objekter. Før vi fortsætter med næste øvelse bør du sikre dig at du har forstået følgende: Et polymorfisk objekt kan ændre form mens programmet kører. Polymorfiske objekter laves ved hjælp af klasser som afledes fra en eksisterende basis-klasse. En eller flere funktioner i basis-klassen erklæres som virtuelle funktioner. Generelt adskiller polymorfiske objekter sig fra hinanden på den måde, de bruger funktioner som er erklæret som virtuelle i basisklassen. Et polymorfisk objekt oprettes ved at lave en pointer til basis-klassens objekt. Et polymorfisk objekts form kan ændres ved at lade objektet pege på et andet objekt. Det gøres ved at tildele det nye objekts adresse til det polymorfiske objekts pointer. En funktion som i basis-klassen ikke indeholder funktions-sætninger (og tildeler funktionen værdien 0) kaldes en rent virtuel funktion. I en afledt klasse skal der være en funktion for hver rent virtuelle funktion i basis-klassen. Øvelse 34 Private elementer og friends Som vi tidligere har set, skal et program bruge et objekts interface-metoder for at kunne ændre private elementer i objektet. Ved at bruge private (eller beskyttede) elementer så meget som muligt, reduceres risikoen for at et program kommer til at ændre værdier i et objekt på en utilsigtet måde. Men sommetider kan et programs effektivitet forøges ved at give en klasse direkte adgang til private elementer i en anden klasse. På den måde undgås den ekstra processor-belastning som følger med at kalde objektets interface-funktioner. En klasse, som er defineret som en ven af en anden klasse, kan adressere elementerne i den anden klasse. I denne øvelse skal vi se hvordan en klasse defineres som en anden klasses ven. Efter øvelsen vil vi have gennemgået følgende: Ved hjælp af det reserverede ord friend kan en klasse adressere elementer i en anden klasse direkte. Friend-klasser bør kun bruges, hvis det er strengt nødvendigt at adressere private elementer i en anden klasse. Programmet kan begrænse en friend-klasses adgang til bestemte funktioner Private elementer er til for at beskytte klasser og reducere fejlmulighederne. Brugen af friend-klasser modarbejder den sikkerhed der ligger i private elementer og bør derfor ikke overdrives. Vi definerer en klasse som friend Hvis en klasse skal defineres som ven af en anden klasse, skrives ordet friend i definitionen af klassen. Efter friend skal stå navnet på den klasse, som friend-klassen skal have adgang til. I klassen, bog, defineres klassen bibliotekar som ven. Herefter kan objekter af typen bibliotekar adressere elementer i objekter af typen bog, ved hjælp af punktum-operatoren: 140

140 6. Avanceret C++ class bog public: bog(char *, char *, char *); void vis_bog(void); friend bibliotekar; private: char titel[64]; char forfatter[64]; char fagnr[64]; ; Andet skal der ikke til for at give objekter af en anden klasse adgang til private elementer. I det følgende program, SEBOG.CPP, defineres klassen bibliotekar som friend til klassen bog. Herefter kan programmet bruge metoden nyt_fagnr i klassen bibliotekar til at ændre en bogs fagnummer: #include <string.h> class bog public: bog(char *, char *, char *); void vis_bog(void); friend bibliotekar; private: char titel[64]; char forfatter[64]; char fagnr[64]; ; bog::bog(char *titel, char *forfatter, char *fagnr) strcpy(bog::titel, titel); strcpy(bog::forfatter, forfatter); strcpy(bog::fagnr, fagnr); void bog::vis_bog(void) cout << "Titel: " << titel << endl; cout << "Forfatter: " << forfatter << endl; cout << "Fag-nummer: " << fagnr << endl; class bibliotekar public: void nyt_fagnr(bog *, char *); char *skaf_fagnr(bog); ; void bibliotekar::nyt_fagnr(bog *denne_bog, char *nyt_fagnr) 4 strcpy(denne_bog->fagnr, nyt_fagnr); char *bibliotekar::skaf_fagnr(bog denne_bog) static char fagnr[64]; strcpy(fagnr, denne_bog.fagnr); return(fagnr); bog programmering("c++, "Jamsa", "P101"); bibliotekar bogsamling; programmering.show_book(); bogsamling.nyt_fagnr( &programmering, "EASY C++ 101"); programmering.vis_bog(); Programmet overfører objektet bog til funktionen nyt_fagnr i klassen bibliotekar som adresse. Da funktionen ændrer et element i klassen 141

141 C++ skal programmet overføre parameteren som adresse og bruger en pointer til at adressere elementet. Prøv at fjerne det reserverede ord friend fra definitionen af klassen bog. Da klassen bibliotekar nu ikke længere har adgang til de private elementer i klassen bog, vil C++ kompileren generere en fejlmeddelelse hver gang den møder en reference til private elementer i klassen bog. Forskellen på friends og beskyttede elementer I øvelse 26 så vi at man i C++ kan arbejde med beskyttede elementer som gør det muligt for afledte klasser at adressere private elementer i basis-klassen direkte med punktum-operatoren. Men det er kun afledte klasser, der kan adressere beskyttede elementer i basis-klassen, altså klasser der har arvet elementerne fra basis-klassen. Derimod er friend-klasser normalt ikke i familie (fordi der ikke er tale om arv). Den eneste måde en klasse kan adressere private elementer i en anden klasse, som den ikke er i familie med, er at den anden klasse fortæller kompileren at den første klasse er en ven. Begrænsning af en friends adgang med friend-funktioner Som vi lige har set kan en klasse der er erklæret som ven i en anden klasse få adgang til vennens private data. Men jo lettere adgangen bliver til private elementer i andre klasser, jo større bliver risikoen for fejl i programmeringen. Derfor kan man begrænse en friend-klasses adgang til kun at gælde bestemte funktioner i friend-klassen. Det kunne være praktisk hvis der for eksempel var flere funktioner i bibliotekar-klassen fra det forrige program og det kun er en bestemt funktion som vennen skal have lov at bruge. På samme måde kunne man lave det, så kun funktionerne nyt_fagnr og skaf_fagnr kan give friend-klasser adgang til private elementer. I definitionen af klassen bog begrænses adgangen til private elementer til kun at gælde de to funktioner på denne måde: class bog public: bog(char *, char *, char *); void vis_bog(void); friend char *bibliotekar::skaf_fagnr(bog); friend void bibliotekar::nyt_fagnr(bog *, char *); private: char titel[64]; char forfatter[64]; char fagnr[64]; ; Sætningerne med friend indeholder en fuld funktions-prototype for hver funktion i friendklassen som kan adressere de private elementer direkte. En friend-funktion erklæres altså ved at angive det reserverede ord friend foran en fuld prototype på denne måde: public: friend klasse_navn::funktion_navn (parametre typer); Kun de metoder som erklæres som friend kan få direkte adgang til private elementer med punktum-operatoren. Klasse-identifiers Et program hvor én klasse refererer til en anden kan løbe ind i syntaks-fejl hvis rækkefølgen af klasse-definitionerne ikke er korrekt. I dette tilfælde bruger definitionen af klassen bog funktionsprototyper fra klassen bibliotekar. Derfor skal definitionen af klassen bibliotekar stå før klassen bog. Men ved et nærmere syn på klassen bibliotekar ser man at den indeholder referencer til klassen bog: class bibliotekar public: void nyt_fagnr(bog *, char *); 142

142 6. Avanceret C++ ; char *skaf_fagnr(book); Og da man ikke kan skrive definitionen for klassen bog foran klassen bibliotekar samtidig med at klassen bibliotekar står foran klassen bog, kan man i C++ skrive en klasse-definition på en enkelt linie som fortæller kompileren at bog er en klasse som vil blive defineret senere i programmet: class bog; Denne sætning kaldes en klasse-identifier da den identificerer en kommende klasse for C++ kompileren. Det følgende program, LIMITFRI.CPP, bruger friend-funktioner til at begrænse klassen bibliotekar's adgang til private elementer i klassen bog. Bemærk rækkefølgen af klasse-definitionerne. #include <string.h> class bog; class bibliotekar public: void nyt_fagnr(bog *, char *); char *skaf_fagnr(bog); ; class bog public: bog(char *, char *, char *); void vis_bog(void); friend char *bibliotekar::skaf_fagnr(bog); friend void bibliotekar::nyt_fagnr(bog *, char *); private: char titel[64]; char forfatter[64]; char fagnr[64]; ; bog::bog(char *titel, char *forfatter, char *fagnr) strcpy(bog::titel, titel); strcpy(bog::forfatter, forfatter); strcpy(bog::fagnr, fagnr); void bog::vis_bog(void) cout << "Titel: " << titel << endl; cout << "Forfatter: " 4 << forfatter << endl; cout << "Fagnr: " << fagnr << endl; void bibliotekar::nyt_fagnr( bog *denne_bog, char *andet_fagnr) strcpy(denne_bog->fagnr, andet_fagnr); char *bibliotekar::skaf_fagnr( bog denne_bog) static char fagnr[64]; strcpy(fagnr, denne_bog.fagnr); return(fagnr); bog programmering("c++, "Jamsa", "P101"); bibliotekar bibliotek; programmering.vis_bog(); 143

143 C++ bibliotek.nyt_fagnr( &programmering, "P102"); programmering.vis_bog(); Programmet starter med at fortælle kompileren, at der senere vil komme en definition af klassen bog og at klassen bibliotekar nu kan indeholde referencer til klassen bog selvom den først bliver defineret senere. Opsummering I denne øvelse så vi hvordan man ved at bruge friend-klasser kan lade én klasse få direkte adgang til private elementer i en anden klasse ved hjælp af punktum-operatoren. Før du går videre med næste øvelse er det en god idé at tjekke at du har forstået denne øvelse: Ved at bruge ordet friend i C++ kan en klasse få direkte adgang til private elementer i en anden klasse. En klasse defineres som friend af en anden klasse ved at skrive dens navn efter ordet friend i definitionen af den anden klasse. Når en klasse er erklæret som friend i en anden klasse har alle funktioner i klassen adgang til private elementer i den anden klasse. Friend-metoders adgang til private data kan begrænses ved at skrive friend-funktioner. En friend-funktion erklæres ved at skrive det reserverede ord friend foran funktions-prototypen for den funktion, som skal have adgang til private elementer. Når man definerer friend-funktioner kan der opstå syntaks-fejl hvis rækkefølgen af klasse-definitionerne ikke er korrekt. Derfor kan man angive klassen over for kompileren ved kun at skrive dens navn og senere efter definitionen af de klasser, som refererer til klassen skrive hele definitionen. Det gøres med sætningen: class klasse_navn; Øvelse 35 Funktions-skabeloner Sommetider skal to funktioner udføre lignende opgaver, men arbejde med forskellige datatyper (én funktion kan f.eks. bruge parametre af typen int, en anden float). Som vi så i øvelse 13 kan funktioner overloades ved at give dem samme navn men forskellige parametertyper. Vil man derimod have to funktioner, der returnerer forskellige datatyper, skal de have hver sit navn. Hvis et program har en funktion der hedder max, og som returnerer det største af to overførte heltal, og man også vil have en lignende funktion som returnerer det største af to decimaltal, er man nødt til at give den nye funktion et andet navn, f.eks. fmax. I denne øvelse skal vi lære hvordan man let kan programmere funktioner der returnerer forskellige værdier. I øvelsen skal vi gennemgå følgende: Skabeloner (templates) definerer nogle sætninger som programmet senere kan bruge til at erklære flere funktioner. Funktions-skabeloner bruges ofte til hurtigt at definere to eller flere funktioner, som ligner hinanden, men som har forskellige parametre eller returnerer forskellige datatyper. Funktions-skabeloner har et specifikt navn som svarer til det funktions-navn, man vil bruge i programmet. Når en skabelon er defineret, kan programmet når som helst lave en reel funktion af skabelonen. Når programmet kompileres, laver C++ funktionerne i programmet på basis af skabelonen. Ved første øjekast kan syntaksen i skabeloner virke svær, men når man har skrevet og brugt en eller to skabeloner opdager man hvor lette de er at bruge. 144

144 6. Avanceret C++ En enkel funktions-skabelon En funktions-skabelon definerer en type-løs funktion, af hvilken programmet senere kan erklære en funktion med typer. Det følgende eksempel definerer en skabelon for funktionen max, som returnerer den største af to værdier: template<class T> T max(t a, T b) if (a > b) return(a); else return(b); I dette eksempel er bogstavet T skabelonens generelle ikke fastlagte type. Programmet kan nu erklære funktioner som prototyper. Her erklærer vi en funktion som arbejder med float-variabler og én som arbejder med intvariabler: float max(float, float); int max(int, int); Når kompileren møder disse funktions-prototyper, laver den den tilsvarende funktion ved at erstatte T i skabelonen med den angivne type: float max(float, float); template<class T> T max(t a, T b) if (a > b) return(a); else return(b); float max(float a, float b) if (a > b) return(a); else return(b); Det følgende program, MAXTEMP.CPP, bruger skabelonen max til at erklære funktioner af typerne int og float: template<class T> T max(t a, T b) if (a > b) return(a); 4 else return(b); float max(float, float); int max(int, int); cout << "Det største tal af 100 og 200 er " << max(100, 200) << endl; cout << "Det største tal af og er " << max(5.4321, ) << endl; Under kompileringen bliver der automatisk lavet en funktion, der bruger int-værdier og en anden funktion, som bruger float-værdier. C++ holder de to funktioner adskilt, selvom de hedder det samme. Derfor kan man have flere funktioner med samme navn men som returnerer værdier af forskellig type. Dette er ikke muligt ved hjælp af funktions-overload som vi så i øvelse 13. Brug af flere typer i skabeloner I den forrige skabelon til max brugte vi kun den generelle type, som vi kaldte T. Men en 145

145 C++ skabelon kan sagtens indeholde flere typer. Her er en skabelon, vis_array, som kan vise indholdet af et array. Typen T definerer arraytypen og T1 angiver typen for antallet: template<class T,class T1> void vis_array(t *array,t1 antal) T1 index; for (index = 0; index < antal; index++) cout << array[index] << ; cout << endl; Som før skal programmet erklære funktionsprototyper som svarer til de ønskede funktions-typer: void vis_array(int *, int); void vis_array(float *, unsigned); Det følgende program, VISARRAY.CPP, bruger skabelonen til at erklærer to funktioner, der kan vise indholdet af arrays af typen int og float: template<class T,class T1> void vis_array(t *array,t1 antal) T1 index; for (index = 0; index < antal; index++) cout << array[index] << ; cout << endl; int sider[] = 100, 200, 300, 400, 500 ; float priser[] = 10.05, 20.10, ; vis_array(sider, 5); vis_array(priser, 3); Opsummering Brug af funktions-skabeloner kan simplificere programmeringen ved at lade kompileren generere sætninger for funktioner som kun adskiller sig på typen af de værdier der returneres eller overføres som parametre. I næste øvelse skal vi se, hvordan man med skabeloner kan erklære typeløse eller generelle klasser. Først bør du sikre dig at du har forstået indholdet i denne øvelse: Funktions-skabeloner bruges til at definere typeløse (generelle) funktioner. Programmet bruger skabelonen ved at erklære en funktions-prototype med de rigtige datatyper. Når kompileren møder prototypen, laver den den rigtige funktion ved at erstatte de generelle typer i skabelonen med typerne i prototypen. Det er en god idé at definerer skabeloner for hyppigt brugte funktioner der arbejder med forskellige typer af data. Hvis en skabelon skal kunne bruges med forskellige typer, skal hver generel type i skabelonen have sit eget navn. Under kompileringen bliver de rigtige typer så tildelt skabelonen. void vis_array(int *, unsigned); void vis_array(float *, unsigned); 146

146 6. Avanceret C++ Øvelse 36 Klasse-skabeloner Ved at lave funktions-skabeloner kan man lave typeløse generelle funktioner. Det så vi i øvelse 35. Ved at definere skabelonen kunne kompileren senere lave en rigtig funktion som fik overført parametre (og returnerede data) af en bestemt type. På samme måde kan man definere generelle klasser klasse-skabeloner. I denne øvelse skal vi se hvordan klasseskabeloner defineres og bruges og vi vil gennemgå følgende: Ved at angive det reserverede ord template og type-symboler som f.eks. T, T1 og T2, kan man definere en klasse-skabelon. Typesymbolerne angiver data-elementer, funktions-elementer og parameter-typer, mm. For at oprette objekter på basis af klasseskabelonen, skrives skabelonens navn efterfulgt af de typer som kompileren skal indsætte i skabelonen for hvert symbol. Symbolerne skrives i spidse parenteser f.eks.: <int, float> efterfulgt af variabelnavnet. Hvis klasse-skabelonen indeholder en constructor-funktion, kaldes den f.eks. på denne måde når et objekt oprettes: class_name<int, float> værdier(200);. Når kompileren kommer til en objekt-erklæring, bruger den skabelonen til at lave en klasse med de angivne typer. Ligesom funktions-skabelonerne kan også klasse-skabeloner se svære ud ved første øjekast. Men når man har defineret sin første klasse-skabelon opdager man hvor let det er. Definition af en klasse-skabelon Som eksempel bruger vi et program, som skal indeholde en klasse med metoder til at beregne sum og gennemsnit af værdierne i et array. Hvis arrayet består af int-værdier kan klassen se sådan ud: class array public: array(int stoerrelse); long sum(void); int gennemsnit(void); void vis_array(void); int tilfoej_vaerdi(int); private: int *data; int stoerrelse; int index; ; Det følgende program, I_ARRAY.CPP, bruger klassen array til at arbejde med værdier af typen int. #include <stdlib.h> class array public: array(int stoerrelse); long sum(void); int gennemsnit(void); void vis_array(void); int tilfoej_vaerdi(int); private: int *data; int stoerrelse; int index; ; array::array(int stoerrelse) data = new int[stoerrelse]; if (data == NULL) cerr << "For lidt hukommelse-- slutter program" << endl; exit(1); 4 147

147 C++ array::stoerrelse = stoerrelse; array::index = 0; long array::sum(void) long sum = 0; int i; for (i = 0; i < index; i++) sum += data[i]; return(sum); int array::gennemsnit(void) long sum = 0; int i; for (i = 0; i < index; i++) sum += data[i]; return(sum / index); void array::vis_array(void) int i; for (i = 0; i < index; i++) cout << data[i] << ; cout << endl; int array::tilfoej_vaerdi(int vaerdi) if (index == stoerrelse) return(-1); // Arrayet er fuldt else data[index] = vaerdi; index++; return(0); // Funktionen // forløb OK 4 // erklær array objekt med // 100 elementer array tal(100); int i; for (i = 0; i < 50; i++) tal.tilfoej_vaerdi(i); tal.vis_array(); cout << "Summen af tallene er " << tal.sum() << endl; cout << "Gennemsnittet er " << tal.gennemsnit() << endl; Programmet allokerer plads til et objekt af typen array med 100 elementer hvorefter det tildeler værdier til de 50 første med metoden tilfoej_vaerdi. Klassen array bruger elementet index til at huske hvor mange elementer der til enhver tid er gemt i arrayet. Hvis programmet prøver at tilføje flere værdier til arrayet end der er plads til, viser metoden en fejl-meddelelse. Metoden gennemsnit bruger elementet index til at udregne arrayets gennemsnit. I programmet bruges new-operatoren til at allokere plads til arrayet. I øvelse 31 så vi nærmere på brugen af new. Nu vil vi gerne have at programmet foruden heltal også kan bruge et array af decimaltal. Det kan gøres ved at definere en ny klasse til decimaltal, men ved at bruge en klasse-skabelon behøver man kun én klasse. Her erklærer vi klasse-skabelonen til den generelle klasse array: template<class T, class T1> class array public: array(int size); 148

148 6. Avanceret C++ T1 sum(void); T gennemsnit(void); void vis_array(void); int tilfoej_vaerdi(t); private: T *data; int stoerrelse; int index; ; Skabelonen definerer symbolerne T og T1. Hvis man vil have et array af heltal, vil T blive erstattet med typen int og T1 vil blive erstattet med typen long. Hvis man vil have et decimaltals-array, bliver både T og T1 lavet til float. For hver funktion i klassen skal der defineres en tilsvarende skabelon. Efter klasse-navnet skrives klassens typer, f.eks. array<t, T1>::gennemsnit. Det følgende eksempel viser definitionen af metoden gennemsnit: template<class T, class T1> T array<t, T1>::gennemsnit(void) 4 T1 sum = 0; int i; for (i = 0; i < index; i++) sum += data[i]; return(sum / index); Når skabelonen med metoder er defineret, kan man erklære et objekt af skabelon-klassen ved at angive de ønskede typer i spidse parenteser: array<int, long> heltal(100); array<float, float> decimaltal(200); Det følgende program, GENARRAY.CPP, definerer en skabelon til klassen array. Af skabelonen erklæres to objekter: Et som bruges med int-værdier, og et som bruges med float-værdier: #include <stdlib.h> template<class T, class T1> class array public: array(int size); // Constructor- // prototype T1 sum(void); T gennemsnit(void); void vis_array(void); int tilfoej_vaerdi(t); private: T *data; int stoerrelse; int index; ; template<class T, class T1> array<t, T1>::array(int stoerrelse) // Constructor data = new T[stoerrelse]; if (data == NULL) cerr << "For lidt hukommelse - 4 afslutter program" << endl; exit(1); array::stoerrelse = stoerrelse; array::index = 0; template<class T, class T1> T1 array<t, T1>::sum(void) T1 sum = 0; int i; for (i = 0; i < index; i++) sum += data[i]; return(sum); template<class T, class T1> T array<t, T1>::gennemsnit(void) 149

149 C++ T1 sum = 0; int i; for (i = 0; i < index; i++) sum += data[i]; return(sum / index); template<class T, class T1> void array<t, T1>::vis_array(void) int i; for (i = 0; i < index; i++) cout << data[i] << ; cout << endl; template<class T, class T1> int array<t, T1>::tilfoej_vaerdi(T vaerdi) if (index == stoerrelse) 4 return(-1); // Array is full else data[index] = vaerdi; index++; return(0); // Successful // 100 element array array<int, long> heltal(100); // 200 element array array<float, float> decimaltal(200); int i; for (i = 0; i < 50; i++) heltal.tilfoej_vaerdi(i); heltal.vis_array(); cout << "Summen af tallene er " << heltal.sum() << endl; cout << "Gennemsnittet er " << heltal.gennemsnit() << endl; for (i = 0; i < 100; i++) decimaltal.tilfoej_vaerdi(i * 100); decimaltal.vis_array(); cout << "Summen af tallene er " << decimaltal.sum() << endl; cout << "Gennemsnittet er " << decimaltal.gennemsnit()<< endl; En god måde at forstå klasse-skabeloner på, er at printe to eksemplarer af programmet. På det første print kan man erstatte hver forekomst af T og T1 med int og long og på det andet print erstatter man T og T1 med float. Alt hvad man skal gøre for at oprette objekter med en klasse-skabelon er at angive navnet på klasse-skabelonen efterfulgt af hårde parenteser som indeholder de typer, man ønsker, at kompileren skal erstatte symbolerne T, T1, T2, osv, med. Herefter skrives objektets navn med de parameter-værdier, man ønsker at overføre til klassens constructor-funktion: klasse_skabelon_navn<type1, type2> objekt_navn(parameter1, parameter2); Når kompileren møder erklæringen, opretter den en klasse baseret på de angivne typer. Den følgende sætning bruger for eksempel klasseskabelonen array til at erklære et array af typen char som skal indeholde 100 elementer: array<char, int> smaa_tal(100); Opsummering I denne øvelse så vi hvordan man med klasseskabeloner kan gøre det unødvendigt at kopie- 150

150 6. Avanceret C++ Øvelse 37 Få mere ud af cin og cout Igennem hele bogen har vi brugt cout outputstrømmen til at vise information på skærmen. Vi har også i mange programmer brugt cin inputstrømmen til at skaffe information fra brugeren. I virkeligheden er cin og cout objekter som er oprettet ud fra klasser i header-filen iostream.h og som objekter indeholder de forskellige operatorer og muligheder. I denne øvelse skal vi se hvordan man kan øge mulighederne ved at bruge funktionerne i cin og cout klasserne. I øvelsen vil vi gennemgå følgende: re program-kode for at definere klasser der kun adskiller sig fra hinanden ved de typer, de bruger. Brugen af klasse-skabeloner kan dog godt blive svær at overskue. Derfor er det en god idé at definere skabelonen som om den var en helt almindelig klasse med almindelige typer for herefter at udskifte de typer der skal kunne varieres med symboler, for eksempel T, T1, T2 osv. Klasse-skabeloner overflødiggør gentaget kode som kun adskiller sig på typen af elementerne. En klasse-skabelon defineres ved at skrive ordet template foran klasse-definitionen og skrive klassens type-symboler som f.eks. T og T1. Desuden skal der foran hver funktion i klassen også stå template. Man skal også angive skabelonens typer mellem < og > mellem klasse-navnet og opløsnings-operatoren (::) f.eks. klasse_navn<t, T1>::funktions_navn. For at oprette et objekt på basis af klasseskabelonen skal man skrive klassens navn efterfulgt af < og > hvorimellem der skal stå de endelige typer som skal erstatte type-symbolerne i skabelonen, f.eks. klasse_navn<int, long> objekt. Header-filen iostream.h indeholder klassedefinitioner som man kan kigge på for at få en forståelse af I/O strømmene. Ved hjælp af metoden cout.width kan et program styre bredden af output. Ved at bruge metoden cout.fill kan et program udskifte mellemrumstegn i en output streng med et ønsket andet tegn. Et program kan også bestemme antallet af decimaler i et decimaltal med cout.setprecision metoden. Output til skærmen kan vises et tegn ad gangen, og input kan skaffes et tegn ad gangen med metoderne cout.put og cin.get. Ved at bruge metoden cin.getline kan et program indlæse en hel linie ad gangen. Næsten alle C++ programmer gør brug af cout og cin til I/O operationer. Derfor er det en god idé at eksperimentere med program-eksemplerne i denne øvelse. Et nærmere kig på iostream.h Lige siden øvelse 1 har vi i vores programmer inkluderet headerfilen iostream.h. I denne fil står der definitioner, som giver programmer mulighed for at bruge cout til output og cin til input. Helt præcist indeholder filen klasserne ostream og istream (for output stream og input stream). Cout og cin er objekt-forekomster af ostream og istream. Det er en god idé at lave et print af filen iostream.h som findes i kompilerens INCLUDE bibliotek. Definitionerne i filen er dog ret komplekse men hvis man går den langsomt igennem vil man opdage at de fleste definitioner bare er enkle klasser og konstanter. I filen står også erklæringen for objekterne cin og cout. Bedre brug af cout Som omtalt er cout et objekt af en klasse som indeholder forskellige metoder. Det følgende program viser nogle af de metoder, man kan bruge til at formatere output i sine programmer. Som vi så i øvelse 3 giver setw mulighed for at angive det mindste antal tegn, som den følgende værdi vil fylde: 151

151 C++ #include <iomanip.h> cout << "Mit yndlingstal er " << setw(3) << 1001 << endl; cout << "Mit yndlingstal er " << setw(4) << 1001 << endl; cout << "Mit yndlingstal er " << setw(5) << 1001 << endl; cout << "Mit yndlingstal er " << setw(6) << 1001 << endl; På samme måde med metoden cout.width som angiver et minimum antal tegn som cout skal bruge til at vise den følgende værdi. Det følgende program, COUTWIDT.CPP, bruger metoden cout.width til at udføre den samme funktion som setw i forrige program: #include <iomanip.h> 4 int i; for (i = 3; i < 7; i++) cout << "Mit yndlingstal er"; cout.width(i); cout << 1001 << endl; Når programmet kompileres og startes viser det følgende udskrift på skærmen: C:\> COUTWIDT <Enter> Mit yndlingstal er1001 Mit yndlingstal er1001 Mit yndlingstal er 1001 Mit yndlingstal er 1001 Ligesom med setw, virker den angivne bredde for cout.width også kun for det følgende tal som skrives. Tekst-padding Når man bruger setw eller cout.width til at styre bredden af output, sørger cout automatisk for at skrive det nødvendige antal tomme tegn før (eller efter hvis den er venstre-justeret) værdien som skal skrives. Men i visse programmer kan man have brug for at erstatte mellemrumstegnet med et andet tegn. Hvis for eksempel programmet skal udskrive en oversigt som denne: Indhold Firma profil Årsregnskab Bestyrelsesmedlemmer Her skal der være prikker foran sidenummeret. Funktionen cout.fill giver mulighed for at angive det tegn man ønsker at mellemrum skal fyldes med. Det følgende program, COUTFILL.CPP, udskriver en tabel som den ovenfor viste: #include <iomanip.h> 4 cout << "Indhold" << endl; cout.fill('.'); cout << "Firma profil" << setw(23) << 10 << endl; cout << "Årsregnskab" << setw(24) << 11 << endl; cout << " Bestyrelsesmedlemmer" << setw(15) << 13 << endl; Når programmet har ændret fyld-tegnet med cout.fill er ændringen i kraft indtil et nyt tegn bliver valgt med cout.fill. Styring af decimaler i decimaltal Almindeligvis kan man ikke være sikker på, hvor mange decimaler der bliver skrevet når man bruger cout til at udskrive et decimaltal. Men med setprecision manipulatoren kan man styre det nøjagtigt. Det følgende program, SETPREC.CPP, bruger setprecision manipulatoren til at bestemme antallet af decimaler i et udskrevet tal: 152

152 6. Avanceret C++ #include <iomanip.h> float vaerdi = ; int i; for (i = 1; i < 6; i++) cout << setprecision(i) << vaerdi << endl; Når programmet kompileres og startes, vil det vise følgende output på skærmen: C:\> SETPREC <ENTER> Når et program har brugt setprecision manipulatoren til at ændre antallet af decimaler bliver programmet ved med at udskrive tal med det angivne antal decimaler indtil programmet bruger setprecision igen. Indlæsning og udskrivning et tegn ad gangen Man kan komme ud for at det er nødvendigt at et program skal udskrive eller indlæse et tegn ad gangen. Skal man skrive et tegn ad gangen kan man bruge funktionen cout.put. Det følgende program, COUTPUT.CPP, bruger denne funktion til at udskrive en besked til skærmen et tegn ad gangen: char streng[] = "Programmering i C++!"; int i; I run-time biblioteket er der også en funktion, som hedder toupper, som laver små bogstaver om til store bogstaver. Det følgende program, COUTSTOR.CPP, bruger funktionen toupper til at ændre små bogstaver til store bogstaver og udskriver dem med cout.put: #include <ctype.h> // indeholder toupper prototypen char streng[] = "Programmering i C++!"; 4 int i; for (i = 0; streng[i]; i++) cout.put(toupper(streng[i])); cout << endl << "Slut på streng: " << streng << endl; Når programmet kompileres og startes vil det udskrive følgende på skærmen: C:\> COUTUPPR <ENTER> REDDET AF C++! Slut på streng: Programmering i C++! Indlæsning fra tastatur et tegn ad gangen Ligesom cout indeholder metoden cout.put som udskriver tekst et tegn ad gangen har cin metoden cin.get som bruges til at indlæse et enkelt tegn. Cin.get bruges ved at tildele det tegn, som funktionen returnerer til en variabel: bogstav = cin.get(); Det følgende program, CIN_GET.CPP, beder brugeren om at skrive J eller N. Løkken, som spørger brugeren om et bogstav, gentages indtil brugeren har skrevet J eller N: for (i = 0; streng[i]; i++) cout.put(streng[i]); #include <ctype.h> 153

153 C++ char bogstav; cout<< "Vil du fortsætte?(j/n): "; do bogstav = cin.get(); // Lav om til stort bogstav bogstav = toupper(bogstav); while ((bogstav!= 'J') && (bogstav!= 'N')); cout << endl << "Du skrev bogstavet "<< bogstav<< endl; Indlæsning af hele linier Som vi har set bruger cin mellemrumstegn mellemrum, tabulator, enter til at afgøre hvor én værdi slutter og en anden begynder. Ofte er det imidlertid nødvendigt at kunne indlæse en hel linie ad gangen. Til det formål kan man bruge funktionen cin.getline. Man angiver en tekst-streng hvori den indlæste tekst skal stå og hvor lang tekst, der kan skrives: cin.getline(streng, 64); Cin.getline indlæser tekst fra tastaturet men ikke flere tegn end strengen kan indeholde. En almindelig måde at angive størrelsen af strengen på er at bruge C++'s sizeof operator på denne måde: cin.getline(streng, sizeof(streng)); Hvis man senere ændrer størrelse af strengen er man fri for at skulle lede efter hver enkelt cin.getline sætning og ændre størrelsen. Sizeof sørger for at funktionen får en rigtig størrelse på strengen. Det følgende program, GETLI- NE.CPP, bruger funktionen cin.getline til at indlæse en sætning fra tastaturet: cout << "Skriv noget tekst og tryk ENTER" << endl; cin.getline(streng, sizeof(streng)); cout << "Du skrev: " << streng << endl; Man kan også få brug for at programmet indlæser tekst indtil det møder et bestemt tegn. Når tegnet er læst ind afsluttes input-operationen. Det kan gøres ved at angive det ønskede tegn i cin.getline. Dette funktionskald instruerer cin.getline om at indlæse en tekststreng indtil brugeren har skrevet 64 tegn, eller trykker ENTER eller skriver tegnet Z: cin.getline(streng, 64, 'Z'); Det følgende program, INDTIL_Z.CPP, bruger cin.getline til at indlæse tekst til og med tegnet Z (dog max 128 tegn): char streng[128]; cout << "Skriv en tekstlinie og tryk ENTER" << endl; cin.getline(streng, sizeof(streng), 'Z'); cout << "Du skrev: " << streng << endl; Kompilér og kør programmet. Prøv at skrive forskellige tekstliner, for eksempel en der starter med bogstavet Z og nogle der slutter med Z og nogle der slet ikke indeholder Z. char streng[128]; 154

154 6. Avanceret C++ Opsummering Stort set alle C++ programmer bruger cin og cout til at udføre input og output operationer. I denne øvelse så vi på en række I/O manipulatorer og funktioner som man kan bruge sammen med cin og cout. Lad os lige opsummere inden du går videre med næste øvelse. cin og cout er objekt-variabler af klasserne istream og ostream som findes i headerfilen iostream.h. De indeholder funktioner, man kan bruge til at udføre bestemte opgaver med. Funktionen cout.width angiver hvor mange tegn det følgende output mindst skal fylde. Funktionen cout.fill bruges til at angive det tegn, som cout skal bruge til mellemrum med funktionerne cout.width og setw. Manipulatoren setprecision bestemmer hvor mange decimaler der vil blive udskrevet til højre for kommaet i et decimaltal. Med funktionerne cin.get og cout.put kan et program indlæse og udskrive et tegn ad gangen. Funktionen cin.getline giver programmet mulighed for at indlæse en hel tekstlinie fra tastaturet. 155

155 Stikordsregister A abstraktion 91 adresse-operator 54 alias 64 anonym union 82 API funktioner 58 argv 122 aritmetiske operatorer 24 array 69 arv 108 B begrænsning af friends adgang 142 beskyttet element 111, 141 blok 34 C cerr 18 char 20, 73 cin 30 constructor 94 cout 13 cout.width 151 D default parameter-værdier 66 dekrementerings-operator 27 delete 133 delete operator 132 destructor 94 do while 44 double 20 E else 35 end og file 117 endl 16 eof 117 F falsk 37 fejl i fil-operationer 118 float 20 for-løkke 41 free store 129 friend 140 funktion 46 funktions-prototyper 51 funktions-skabeloner 144 G gentage sætning 40 global variabel 60 H headerfiler 12 I I/O strøm 151 if 34 #include 11 index i array 70 index-variabel 70 initialisering af arrays 71 inkrementering 26 input-strøm 30 int 20 iostream 151 iteration 40 K klasse 87 klasse-definition 88 klasse-skabelon 147 kommandolinie-argumenter 121 kommentarer 23 konstant 125 kontrolstruktur, 36 L lokal variabel 59 long 20 læsning fra fil 117 løkke 41 M makro 125 maskinsprog 8 metode 88 metoden str_append 100 multipel arv 113 N 156

156 6. Avanceret C++ navnekonflikter 59 new 133 new operator 129 NOT operator 38 NULL 73 O objekt 87 offentlige elementer 88 << operator 14 operator-prioritet 28, 29 output modifier 17 output til skærmen 13 output-bredde 18 output-strømmen 13 output-tegn 16 overflow 22 overfør data til funktioner 48 overload 62 overload af constructor-funktioner 96 overload af operatorer 99 P parameter-værdi, default 66 pointer 54, 83, 86 polymorfisme 135 postfix 26 prefix 26 preprocessor 125 preprocessor-direktiv 126 private element 140 private elementer 88 private metoder 93 prototype 88 præcision 23 public elementer 88 R reference 66 referencer i C++ 64 regnefunktioner 24 regne-operationer 25 relationelle operatorer 34 reserverede ord 21 retur-type 50 run-time biblioteket 57 S sammensatte betingelser 36 sand 37 scope 59, 61 skabeloner 144 skrivning til fil 116 standard error enhed 18 startværdi til variabel 21 statiske funktioner 104 str_append 100 streng 83 streng-konstant 74 struktur-variabler 77 switch-sætning 39 syntaks 9 syntaksfejl 9, 10 sætnings-blok 34 T teksteditor 7, 8 tekst-padding 152 tekst-streng 72 templates 144 tildeling af værdi 21 U uendelige løkker 43 union 80 union anonym 82 unsigned 20 V variabel global 60 variabel lokal 59 variabel-type 19 void 12 while

Kapitel 4 Løkker i C#

Kapitel 4 Løkker i C# Kapitel 4 Løkker i C# Løkker en vigtig del af alle programmeringssprog, og C# er ikke andeles. En løkke er en måde at udføre en del af koden gentagne gange. Ideen er at du fortsætter med at udføre en opgave

Læs mere

Programmering i C. Lektion 4. 5. december 2008

Programmering i C. Lektion 4. 5. december 2008 Programmering i C Lektion 4 5. december 2008 Funktioner Eksempel Fra sidst 1 Funktioner 2 Eksempel Funktioner Eksempel Eksempel: 1 / f u n k t i o n s p r o t o t y p e r / i n t i n d l a e s ( void )

Læs mere

Klasse 1.4 Michael Jokil 03-05-2010

Klasse 1.4 Michael Jokil 03-05-2010 HTX I ROSKILDE Afsluttende opgave Kommunikation og IT Klasse 1.4 Michael Jokil 03-05-2010 Indholdsfortegnelse Indledning... 3 Formål... 3 Planlægning... 4 Kommunikationsplan... 4 Kanylemodellen... 4 Teknisk

Læs mere

Programmering C RTG - 3.3 09-02-2015

Programmering C RTG - 3.3 09-02-2015 Indholdsfortegnelse Formål... 2 Opgave formulering... 2 Krav til dokumentation af programmer... 3 ASCII tabel... 4 Værktøjer... 5 Versioner af ASCII tabel... 6 v1.9... 6 Problemer og mangler... 6 v2.1...

Læs mere

Programmering for begyndere Lektion 2. Opsamling mm

Programmering for begyndere Lektion 2. Opsamling mm Lektion 2 Opsamling mm God tone Der er indlagt spørge sessioner Lektion 2 - Agenda Programmering for Lidt ændringer til teknikken, herunder hvordan du genser en lektion Lidt generelle tilbagemeldinger

Læs mere

Start på Arduino og programmering

Start på Arduino og programmering Programmering for begyndere Brug af Arduino Start på Arduino og programmering EDR Hillerød Knud Krogsgaard Jensen / OZ1QK 1 Start på Arduino og programmering Sidste gang (Introduktion) Programmeringssproget

Læs mere

Python programmering. Per Tøfting. MacFest

Python programmering. Per Tøfting. MacFest Python programmering MacFest 2005 Per Tøfting http://pertoefting.dk/macfest/ Indhold Måder at afvikle Python program på Variabler Data typer Tal Sekvenser Strenge Tupler Lister Dictionaries Kontrolstrukturer

Læs mere

Indhold. Maskinstruktur... 3. Kapitel 1. Assemblersprog...3. 1.1 Indledning...3 1.2 Hop-instruktioner... 7 1.3 Input og output...

Indhold. Maskinstruktur... 3. Kapitel 1. Assemblersprog...3. 1.1 Indledning...3 1.2 Hop-instruktioner... 7 1.3 Input og output... Indhold Maskinstruktur... 3 Kapitel 1. Assemblersprog...3 1.1 Indledning...3 1.2 Hop-instruktioner... 7 1.3 Input og output... 9 Kapitel 2. Maskinkode... 13 2.1 Den fysiske maskine... 13 2.2 Assemblerens

Læs mere

Programmering i C Intro og grundlæggende C 5. marts 2007

Programmering i C Intro og grundlæggende C 5. marts 2007 Programmering i C Intro og grundlæggende C 5. marts 2007 Mads Pedersen, OZ6HR [email protected] Plan for kurset Ma. 5/3: Ma. 19/3: Ma. 2/4: To. 12/4: Formål, intro, grundlæggende Videre, sprogkonstruktioner

Læs mere

Sproget Six. Til brug i rapportopgaven på kurset Oversættere. Vinter 2006. Abstract

Sproget Six. Til brug i rapportopgaven på kurset Oversættere. Vinter 2006. Abstract Sproget Six Til brug i rapportopgaven på kurset Oversættere Vinter 2006 Abstract Six er baseret på det sprog, der vises i figur 6.2 og 6.4 i Basics of Compiler Design. Den herværende tekst beskriver basissproget

Læs mere

Dokumentation af programmering i Python 2.75

Dokumentation af programmering i Python 2.75 Dokumentation af programmering i Python 2.75 Af: Alexander Bergendorff Jeg vil i dette dokument, dokumentere det arbejde jeg har lavet i løbet opstarts forløbet i Programmering C. Jeg vil forsøge, så vidt

Læs mere

//Udskriver System.out.println("Hej " + ditfornavn + " " + ditefternavn + "."); System.out.println("Du er " + dinalder + " aar gammel!

//Udskriver System.out.println(Hej  + ditfornavn +   + ditefternavn + .); System.out.println(Du er  + dinalder +  aar gammel! Denne guide er oprindeligt udgivet på Eksperten.dk Brugerinput i Java Denne her artikel gennemgår diverse ting ved brug af brugerinput i Java. Den starter med det simple og fortæller derefter skridt for

Læs mere

Arduino Programmering

Arduino Programmering Microcontroller, Arduino I teknologi skal vi lære at lave programmer til uc for at have muligheden til eksamen at kunne lave intelligente el-produkter. I hvert fald skal vi have set mulighederne, og forstået

Læs mere

Kursusarbejde 3 Grundlæggende Programmering

Kursusarbejde 3 Grundlæggende Programmering Kursusarbejde 3 Grundlæggende Programmering Arne Jørgensen, 300473-2919 klasse dm032-1a 21. november 2003 Indhold 1. Kode 2 1.1. forestillinger.h............................................. 2 1.2. forestillinger.cc.............................................

Læs mere

Lær Python dag 1 - modul 1

Lær Python dag 1 - modul 1 Lær Python dag 1 - modul 1 Introduktion, basis python Steffen Berg Klenow Jonas Bamse Andersen Syddansk Universitet Indhold 1. Velkommen 2. Programmering i python 3. Typer, variabler og udtryk 1 Velkommen

Læs mere

I denne manual kan du finde en hurtig introduktion til hvordan du:

I denne manual kan du finde en hurtig introduktion til hvordan du: VORES NORDSJÆLLAND HURTIGT I GANG MANUAL 01: Bruger HVAD INDEHOLDER DENNE MANUAL? I denne manual kan du finde en hurtig introduktion til hvordan du: 1. Finder Vores Nordsjælland hjemmesiden 2. Opretter

Læs mere

Indholdsfortegnelse Forord...8 Makroer samt aktivere Udvikler-fanen...10 Makrosikkerhed (Sikkerhedsindstillinger)...13

Indholdsfortegnelse Forord...8 Makroer samt aktivere Udvikler-fanen...10 Makrosikkerhed (Sikkerhedsindstillinger)...13 1 Indholdsfortegnelse Forord...8 Makroer samt aktivere Udvikler-fanen...10 Hvad er en makro... 10 Hvad kan du bruge en makro til... 10 Hvad en makro er (Visual Basic for Applications)... 11 Det hele sker

Læs mere

Kursusarbejde 2 Grundlæggende Programmering

Kursusarbejde 2 Grundlæggende Programmering Kursusarbejde 2 Grundlæggende Programmering Arne Jørgensen, 300473-2919 klasse dm032-1a 31. oktober 2003 Indhold 1. Kode 2 1.1. hotel.h.................................................... 2 1.2. hotel.cc...................................................

Læs mere

Programmering I Java/C#

Programmering I Java/C# Programmering I Java/C# Dit første projekt Datatekniker Intro to C# C# (C Sharp) Et enkelt, moderne, generelt anvendeligt, objektorienteret programmeringssprog Udviklet af Microsoft, ledet af danskeren

Læs mere

SWC eksamens-spørgsmål. Oversigt

SWC eksamens-spørgsmål. Oversigt SWC eksamens-spørgsmål Oversigt #1 Typer og variable #2 Aritmetik og logik #3 Klasser (definition, objekter) #4 Klasser (metoder) #5 Klasser (nedarvning, polymorfi) #6 Conditional statements #7 Repetition

Læs mere

Indledning. Hvorfor det forholder sig sådan har jeg en masse idéer om, men det bliver for meget at komme ind på her. God fornøjelse med læsningen.

Indledning. Hvorfor det forholder sig sådan har jeg en masse idéer om, men det bliver for meget at komme ind på her. God fornøjelse med læsningen. Indledning...2 Variabler...13 Eksempel: 1...13 Eksempel 2:...13 Eksempel 3:...15 Eksempel 4:...16 Metoder...17 Metode (intet ind og intet ud)...17 Metode (tekst ind)...18 Metode (tekst ind og tekst ud)...19

Læs mere

Videregående Programmering for Diplom-E Noter

Videregående Programmering for Diplom-E Noter Videregående Programmering for Diplom-E Noter 1. Uddelegering Ét af de væsentlige principper i objektorienteret programmering er, at enhver klasse selv skal kunne "klare ærterne". Enhver klasse skal altså

Læs mere

University of Southern Denmark Syddansk Universitet. DM502 Forelæsning 3

University of Southern Denmark Syddansk Universitet. DM502 Forelæsning 3 DM502 Forelæsning 3 Indlæsning fra tastatur Udskrift til skærm Repetition Beregning af middelværdi Gentagelse med stop-betingelse (while) Heltalsdivision Division med nul Type-casting ( (double) ) Betinget

Læs mere

vil jeg blive mindet om det af VBA allerede mens jeg skriver koden, da der er tale om en såkaldt kompileringsfejl:

vil jeg blive mindet om det af VBA allerede mens jeg skriver koden, da der er tale om en såkaldt kompileringsfejl: Fejlhåndtering Selv de bedste programmører laver af og til fejl! Dette kommer sikkert som en overraskelse for de fleste, bortset fra de, der har arbejdet med et hvilket som helst større program. Fejl kan

Læs mere

poedit og oversættelse af sprogfiler

poedit og oversættelse af sprogfiler poedit og oversættelse af sprogfiler af Georg S. Adamsen WordPress.Blogos.dk 2009 http://kortlink.dk/wordpressblogosdk/6g38 1 af 11 14-04-2009 14:55 Jeg får af og til spørgsmål om, hvordan man bruger poedit,

Læs mere

Kapitel 3 Betinget logik i C#

Kapitel 3 Betinget logik i C# Kapitel 3 i C# er udelukkende et spørgsmål om ordet IF. Det er faktisk umuligt at programmere effektivt uden at gøre brug af IF. Du kan skrive små simple programmer. Men når det bliver mere kompliceret

Læs mere

Bogfunktionen eller Slægtsbogen i FTM

Bogfunktionen eller Slægtsbogen i FTM Bogfunktionen eller Slægtsbogen i FTM En blandt mange af Family Tree Maker s styrker er evnen til at præsentere data på mange forskellige måder, og i dette skrift vil bogfunktionen blive gennemgået. Funktionen

Læs mere

Andreas Lauge V. Hansen klasse 3.3t Roskilde HTX

Andreas Lauge V. Hansen klasse 3.3t Roskilde HTX IT -Eksamen Andreas Lauge V. Hansen klasse 3.3t Roskilde HTX [Vælg en dato] Indhold Indledning... 2 Teori... 3 Hvorfor dette design... 4 Produktet... 4 Test og afprøvning... 9 Konklusion... 10 Indledning

Læs mere

APPENDIX A INTRODUKTION TIL DERIVE

APPENDIX A INTRODUKTION TIL DERIVE APPENDIX A INTRODUKTION TIL DERIVE z x y z=exp( x^2 0.5y^2) CAS er en fællesbetegnelse for matematikprogrammer, som foruden numeriske beregninger også kan regne med symboler og formler. Det betyder: Computer

Læs mere

Undtagelseshåndtering i C#

Undtagelseshåndtering i C# Denne guide er oprindeligt udgivet på Eksperten.dk Undtagelseshåndtering i C# I modsætning til C++ kan man i C# ikke skrive et program uden undtagelseshåndtering, så derfor har jeg skrevet denne guide

Læs mere

EXCEL 2011 TIL MAC GODT I GANG MED PETER JENSEN GUIDE VISUEL

EXCEL 2011 TIL MAC GODT I GANG MED PETER JENSEN GUIDE VISUEL PETER JENSEN EXCEL 2011 TIL MAC GODT I GANG MED EXCEL 2011 TIL MAC VISUEL GUIDE 59 guides der får dig videre med Excel En instruktion på hver side - nemt og overskueligt Opslagsværk med letforståelig gennemgang

Læs mere

matematik Demo excel trin 2 bernitt-matematik.dk 1 excel 2 2007 by bernitt-matematik.dk

matematik Demo excel trin 2 bernitt-matematik.dk 1 excel 2 2007 by bernitt-matematik.dk matematik excel trin 2 bernitt-matematik.dk 1 excel 2 2007 by bernitt-matematik.dk matematik excel 2 1. udgave som E-bog 2007 by bernitt-matematik.dk Kopiering af denne bog er kun tilladt efter aftale

Læs mere

Introduktion til funktioner, moduler og scopes i Python

Introduktion til funktioner, moduler og scopes i Python Denne guide er oprindeligt udgivet på Eksperten.dk Introduktion til funktioner, moduler og scopes i Python Denne artikel er fortsættelsen af "I gang med Python", som blevet publiceret her på sitet for

Læs mere

DM507 Algoritmer og datastrukturer

DM507 Algoritmer og datastrukturer DM507 Algoritmer og datastrukturer Forår 2018 Projekt, del II Institut for matematik og datalogi Syddansk Universitet 13. marts, 2018 Dette projekt udleveres i tre dele. Hver del har sin deadline, således

Læs mere

SIGIL Sådan opretter du en e- bog Step by Step

SIGIL Sådan opretter du en e- bog Step by Step SIGIL Sådan opretter du en e- bog Step by Step Af Gitte Winter Graugaard Nov. 2013, Sigil version 0.7.2 1 Her følger en intro skridt for skridt til at oprette en e- bog i SIGIL og publicere den på SAXO

Læs mere

How to do in rows and columns 8

How to do in rows and columns 8 INTRODUKTION TIL REGNEARK Denne artikel handler generelt om, hvad regneark egentlig er, og hvordan det bruges på et principielt plan. Indholdet bør derfor kunne anvendes uden hensyn til, hvilken version

Læs mere

Noter til C# Programmering Selektion

Noter til C# Programmering Selektion Noter til C# Programmering Selektion Sætninger Alle sætninger i C# slutter med et semikolon. En sætning kontrollerer sekvensen i programafviklingen, evaluerer et udtryk eller gør ingenting Blanktegn Mellemrum,

Læs mere

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { int wmid, wmevent; programmering med

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { int wmid, wmevent; programmering med LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) int wmid, wmevent; PAINTSTRUCT Introduktion ps; til HDC hdc; programmering med switch (message) case WM_COMMAND: wmid = LOWORD(wParam);

Læs mere

Java Programmering. En bog for begyndere. Skrevet af Henrik Kressner

Java Programmering. En bog for begyndere. Skrevet af Henrik Kressner Java Programmering En bog for begyndere Skrevet af Henrik Kressner Indholdsfortegnelse Introduktion...3 1 Introduktion til Java...4 1.1 Javakoden...4 1.2 Det første program...6 1.2 Skriv til skærmen...6

Læs mere

Python 3 kursus lektion 1:

Python 3 kursus lektion 1: Python 3 kursus lektion 1: Her laves et nyt program Her køre programmet! Her skrives koden: Gem (CTRL-s) Tryk F5 (for at køre) www.madsmatik.dk d.14-01-2016 1/5 At skrive til skærmen: Hello World Man kan

Læs mere

DM507 Algoritmer og datastrukturer

DM507 Algoritmer og datastrukturer DM507 Algoritmer og datastrukturer Forår 2018 Projekt, del II Institut for matematik og datalogi Syddansk Universitet 20. marts, 2019 Dette projekt udleveres i tre dele. Hver del har sin deadline, således

Læs mere

METODER ARV KLASSER. Grundlæggende programmering Lektion 5

METODER ARV KLASSER. Grundlæggende programmering Lektion 5 METODER KLASSER ARV Grundlæggende programmering Lektion 5 1 METODER Sekvenser af kode om samme emne 2 REPETITION Række af statements der udfører en handling Mindst én metode der hedder main Forskellen

Læs mere

E-MAIL WINDOWS LIVE MAIL

E-MAIL WINDOWS LIVE MAIL E-MAIL WINDOWS LIVE MAIL Erik Thorsager, Esbjerg. 3. udgave: Live Mail Side 1 Windows Live Mail Hvordan skriver og sender jeg en e-mail? Det engelske ord mail betyder post. E står for elektronisk. E-mail

Læs mere

Opstilling af model ved hjælp af differentialkvotient

Opstilling af model ved hjælp af differentialkvotient Opstilling af model ved hjælp af differentialkvotient N 0,35N 0, 76t 2010 Karsten Juul Til eleven Dette hæfte giver dig mulighed for at arbejde sådan med nogle begreber at der er god mulighed for at der

Læs mere

Test af It-komponent

Test af It-komponent Test af It-komponent I programmeringssproget Java Programmet Login service Elev: Mads Funch Klasse 2.4 Mat, It, Programmering Skole: Roskilde Tekniske Gymnasium HTX Underviser: Karl Dato: 31-08-2016 Side

Læs mere

02101 Indledende Programmering Introduktion til Eclipse

02101 Indledende Programmering Introduktion til Eclipse 02101 Indledende Programmering Introduktion til Eclipse Version 2018 1 Introduktion I dette kursus lægger vi op til at man bruger det integrerede udviklingsmiljø Eclipse. Basalt set er et integreret udviklingsmiljø

Læs mere

E-MAIL G-MAIL (GOOGLE)

E-MAIL G-MAIL (GOOGLE) E-MAIL G-MAIL (GOOGLE) Erik Thorsager, Esbjerg. 3. udgave: G-mail Side 1 G-mail E-mail: Det engelske ord mail betyder post. E står for elektronisk. E-mail betyder altså elektronisk post. Elektronisk post

Læs mere

Kort introduktion til Google.

Kort introduktion til Google. Google Side 1 af 10 Kort introduktion til Google.... 2 Tilpas din søgning... 2 Generelle Tips... 2 Udelukkelse af ord... 2 Brug af *... 3 Sætningssøgninger... 3 Jeg Føler Mig Heldig... 3 Avanceret søgning...

Læs mere

Maple. Skærmbilledet. Vi starter med at se lidt nærmere på opstartsbilledet i Maple. Værktøjslinje til indtastningsområdet. Menulinje.

Maple. Skærmbilledet. Vi starter med at se lidt nærmere på opstartsbilledet i Maple. Værktøjslinje til indtastningsområdet. Menulinje. Maple Dette kapitel giver en kort introduktion til hvordan Maple 12 kan benyttes til at løse mange af de opgaver, som man bliver mødt med i matematiktimerne på HHX. Skærmbilledet Vi starter med at se lidt

Læs mere

ALMINDELIGT ANVENDTE FUNKTIONER

ALMINDELIGT ANVENDTE FUNKTIONER ALMINDELIGT ANVENDTE FUNKTIONER I dette kapitel gennemgås de almindelige regnefunktioner, samt en række af de mest nødvendige redigerings- og formateringsfunktioner. De øvrige redigerings- og formateringsfunktioner

Læs mere

Kapitel 1 I gang med C#

Kapitel 1 I gang med C# Kapitel 1 I gang med C# Vi skal starte med at oprette et meget simpelt program, så du kan se hvad der ligger bag et C# projekt. Når du er færdig med dette kapitel vil du have lært: Hvordan du opretter

Læs mere

DM507 Algoritmer og datastrukturer

DM507 Algoritmer og datastrukturer DM507 Algoritmer og datastrukturer Forår 2016 Projekt, del I Institut for matematik og datalogi Syddansk Universitet 29. februar, 2016 Dette projekt udleveres i tre dele. Hver del har sin deadline, således

Læs mere

C++ Programmering V. 0.99

C++ Programmering V. 0.99 Indholdsfortegnelse 1. Indledning...3 1.2 Forudsætninger:...3 1.3 Udeståender...4 6 Start med C++...5 6.1 Det første C++ program...5 6.2 Formatering af output...8 6.3 Kommentarer...9 6.4 Funktions prototyper...9

Læs mere

I Windows fil struktur er der følgende ting Drev, Mapper, Filer og Genveje.

I Windows fil struktur er der følgende ting Drev, Mapper, Filer og Genveje. Windows Fil Struktur I Windows fil struktur er der følgende ting Drev, Mapper, Filer og Genveje. Hvad er et drev Et drev, er en afgrænsning af fil strukturen. Når du går ind på et drev vil du stå i roden

Læs mere

Grundlæggende Programmering ITU, Efterår 1999. Skriftlig eksamen i Grundlæggende Programmering

Grundlæggende Programmering ITU, Efterår 1999. Skriftlig eksamen i Grundlæggende Programmering Skriftlig eksamen i Grundlæggende Programmering ITU, 20. januar 2000 Alle hjælpemidler tilladt, dog ikke datamat. Eksamen er skriftlig, fire timer, og bedømmes efter 13-skalaen. Opgavesættet består af

Læs mere

Mathcad Survival Guide

Mathcad Survival Guide Mathcad Survival Guide Mathcad er en blanding mellem et tekstbehandlingsprogram (Word), et regneark (Ecel) og en grafisk CAS-lommeregner. Programmet er velegnet til matematikopgaver, fysikrapporter og

Læs mere

2) Det er let at være produktiv med Python, da Python som regel kun har mellem 67 og 80% færre linier end tilsvarende C eller Java kode.

2) Det er let at være produktiv med Python, da Python som regel kun har mellem 67 og 80% færre linier end tilsvarende C eller Java kode. Denne guide er oprindeligt udgivet på Eksperten.dk I gang med Python I denne artikel vil jeg forsøge at give et kort og hurtigt indblik i programmeringssproget Python, der desværre er alt for overset.

Læs mere

Hvad er Objekter - Programmering

Hvad er Objekter - Programmering Denne guide er oprindeligt udgivet på Eksperten.dk Hvad er Objekter - Programmering En rigtig god gennemgang af hvad objekter er! Hvordan de oprettes og anvendes! Det er helt klart til nybegyndere, som

Læs mere

Vistemmernu. Et webbaseret værktøj udviklet af Programdatateket i Skive. E-mail: [email protected] Web: http://www.programdatateket.

Vistemmernu. Et webbaseret værktøj udviklet af Programdatateket i Skive. E-mail: programdatateket@viauc.dk Web: http://www.programdatateket. Vistemmernu Et webbaseret værktøj udviklet af Programdatateket i Skive E-mail: [email protected] Web: http://www.programdatateket.dk Kolofon HVAL-vejledning Vistemmernu på HVAL.DK Forfatter: Susanne

Læs mere

Sammenlign og byt. Et eksempel på dokumentering af et program

Sammenlign og byt. Et eksempel på dokumentering af et program Sammenlign og byt Et eksempel på dokumentering af et program Sammenlign og byt Jeg har valgt, som et eksempel, at dokumentere et meget enkelt program som indlæser to tal, sammenligner dem og udskriver

Læs mere

Microcontroller, Arduino

Microcontroller, Arduino Microcontroller, Arduino Programmerbar elektronik. uc Vi skal lære at lave programmer til uc for at kunne lave el-produkter. Forstå princippet i programmering af en uc og se mulighederne. Programmeringen

Læs mere

Kom godt i gang med I-bogen

Kom godt i gang med I-bogen Kom godt i gang med I-bogen At åbne bogen Det allerførste, du skal gøre, for at kunne arbejde med i-bogen, er at aktivere den. Det gøres ved at oprette en konto på systime.dk og derefter aktivere bogen

Læs mere

Bits, bit operationer, integers og floating point

Bits, bit operationer, integers og floating point Denne guide er oprindeligt udgivet på Eksperten.dk Bits, bit operationer, integers og floating point Denne artikel beskriver hvordan data gemmes som bits og hvordan man kan manipulere med bits. Den forudsætter

Læs mere

E-MAIL MICROSOFT OUTLOOK 2010

E-MAIL MICROSOFT OUTLOOK 2010 E-MAIL MICROSOFT OUTLOOK 2010 Erik Thorsager, Esbjerg. 3. udgave: Outlook Side 1 Microsoft Outlook 2010 Hvordan skriver og sender jeg en e-mail? Det engelske ord mail betyder post. E står for elektronisk.

Læs mere

Programmering. Det rent og skært nødvendige, det elementært nødvendige! Morten Dam Jørgensen

Programmering. Det rent og skært nødvendige, det elementært nødvendige! Morten Dam Jørgensen Programmering Det rent og skært nødvendige, det elementært nødvendige! Morten Dam Jørgensen Oversigt Undervisningen Hvad er programmering Hvordan er et program organiseret? Programmering og fysik Nobelprisen

Læs mere

Integer.parseInt(args[0]) konverterer tegnstreng (f.eks. "10") til heltal (10). if (udtryk) else

Integer.parseInt(args[0]) konverterer tegnstreng (f.eks. 10) til heltal (10). if (udtryk) else Programmering 1999 Forelæsning 2, fredag 3. september 1999 Betingede ordrer: if-, if Indlejrede betingede ordrer Løkker med begrænset iteration: for Løkker med ubegrænset iteration: while Betingede ordrer,

Læs mere

Lav din egen forside i webtrees

Lav din egen forside i webtrees Lav din egen forside i webtrees Du behøver ikke at kunne kode eller gøre noget advanceret for at designe din helt egen forside i webtrees. Alt du skal gøre er bare at gøre brug af den indbygget editor.

Læs mere

Start på programmering (IT-hæfter fra Libris)

Start på programmering (IT-hæfter fra Libris) Start på programmering (IT-hæfter fra Libris) Greg Perry Pris Bøger: Start på programmering (IT-hæfter fra Libris) pdf - (KR 0.00); Start på programmering (IT-hæfter fra Libris) fb2 - (KR 0.00); Start

Læs mere

DIVISIONSMATCHBEREGNING VERSION 1.07

DIVISIONSMATCHBEREGNING VERSION 1.07 DIVISIONSMATCHBEREGNING VERSION 1.07 ANDERS KLINTING FIF HILLERØD ORIENTERING 2. MAJ 2013 1 INDHOLD Divisionsmatchberegning... 3 Historik... 3 Løbsdata... 3 løbsdata fra OE2003... 3 Løbsdata andre programmer...

Læs mere

Start af nyt schematic projekt i Quartus II

Start af nyt schematic projekt i Quartus II Start af nyt schematic projekt i Quartus II Det følgende er ikke fremstillet som en brugsanvisning der gennemgår alle de muligheder der er omkring oprettelse af et Schematic projekt i Quartus II men kun

Læs mere

Anvendelse af metoder - Programmering

Anvendelse af metoder - Programmering Denne guide er oprindeligt udgivet på Eksperten.dk Anvendelse af metoder - Programmering En forhåbentlig rigtig god forklaring på hvad metoder er og hvordan de anvendes. Lidt om private og public, retur

Læs mere

matematik Demo excel trin 1 preben bernitt bernitt-matematik.dk 1 excel 1 2007 by bernitt-matematik.dk

matematik Demo excel trin 1 preben bernitt bernitt-matematik.dk 1 excel 1 2007 by bernitt-matematik.dk matematik excel trin 1 preben bernitt bernitt-matematik.dk 1 excel 1 2007 by bernitt-matematik.dk matematik excel 1 1. udgave som E-bog 2007 by bernitt-matematik.dk Kopiering af denne bog er kun tilladt

Læs mere

En lille vejledning til lærere og elever i at bruge matematikprogrammet WordMat (begynderniveau)

En lille vejledning til lærere og elever i at bruge matematikprogrammet WordMat (begynderniveau) Matematik i WordMat En lille vejledning til lærere og elever i at bruge matematikprogrammet WordMat (begynderniveau) Indholdsfortegnelse 1. Introduktion... 3 2. Beregning... 4 3. Beregning med brøker...

Læs mere

Computerarkitektur. - en introduktion til computerarkitektur med LINDA

Computerarkitektur. - en introduktion til computerarkitektur med LINDA Computerarkitektur - en introduktion til computerarkitektur med LINDA [email protected] Faraz Butt [email protected] Mads Danquah [email protected] Ulf Holm Nielsen Roskilde Universitetscenter Naturvidenskabelig

Læs mere

Øvelser rundt på computeren

Øvelser rundt på computeren Øvelser rundt på computeren Kursister med it-færdigheder bør læse øvelserne igennem. Hvis der er elementer, som er ukendte, bør du udføre øvelserne. Hvis øvelserne derimod er kendt information kan øvelserne

Læs mere

DM507 Algoritmer og datastrukturer

DM507 Algoritmer og datastrukturer DM507 Algoritmer og datastrukturer Forår 2019 Projekt, del I Institut for matematik og datalogi Syddansk Universitet 27. februar, 2019 Dette projekt udleveres i tre dele. Hver del har sin deadline, således

Læs mere

C Programmering V1.37

C Programmering V1.37 Indholdsfortegnelse Indledning...3 1 I gang med C...5 1.2 Variabler...12 1.3 Intelligens...17 1.6 Afrunding...38 1.7 Opgaver...41 2 Grundbegreber...42 2.1 Blokke...42 2.2 Datatyper...47 2.3 Typekonvertering...49

Læs mere

Få flot tekst i din slægtsbog med få klik (Af Henning Karlby)

Få flot tekst i din slægtsbog med få klik (Af Henning Karlby) Få flot tekst i din slægtsbog med få klik (Af Henning Karlby) Når man vil til at skrive sin slægtshistorie ind i et tekstbehandlingsprogram, vil man gerne give sin tekst sit eget udseende. Med det mener

Læs mere

Brug af Word til matematik

Brug af Word til matematik Flex på KVUC, matematik C Brug af Word til matematik Word er et af de gængse tekstbehandlingssystemer der slipper bedst fra det at skrive matematiske formler. Selvfølgelig findes der andre systemer der

Læs mere

//--------------------------------- Definition af porte og funktioner -------------------------

//--------------------------------- Definition af porte og funktioner ------------------------- Temeraturmåler (C-program).txt // Initialiserings-sekvens #include #pragma model=medium #pragma code=0x0000 #pragma xdata=0x4000 #pragma asm=on #pragma parameters=register //#define display P4

Læs mere