C/Inn- og utdata
Til nå har vi bare sett på teoretiske ting, som definering av variabler. I dette kapittelet, skal vi se nærmere på hvordan vi viser noe på skjermen (utdata), og hvordan vi mottar data fra brukeren (inndata) -- vi skal altså begynne å lage noen skikkelige programmer.
Eksterne bibliotek
redigerFør vi kan begynne å se på inn- og utdata, må vi avklare en del ting. Mange språk har en rekke innebygde metoder for å motta data fra tastaturet, eller vise data på skjermen. C derimot, har ikke dette innebygd i språket. I stedet, har de aller fleste C-kompilatorer en rekke medfølgende biblioteker med slike funksjoner. Bibliotekenes funksjoner er også programmert i C, men er ferdiglagde, slik at vi bare trenger å inkludere dem i koden vår for å ta dem i bruk.
Du husker kanskje denne linja i "Hallo, verden!"-programmet:
#include <stdio.h>
#include forteller kompilatoren at vi vil inkludere en fil i koden vår. Alle filer med .h-endelse, er headerfiler som inneholder informasjon om funksjonene til biblioteket vi inkluderer.
"stdio.h", er altså headerfila til biblioteket stdio. Men stdio er ikke et hvilket som helst bibliotek; det er et standardbibliotek. Et standardbibliotek, er et bibliotek med funksjoner, som er en del av C-standarden, og som følger med alle C-kompilatorer. Standardbibliotekene har de mest nødvendige og grunnleggende funksjonene, blant dem er inn- og utdata-funksjonene som vi skal bruke og har brukt, slik som printf. Funksjonene i standardbibliotekene skal, av standard, fungere likt på alle operativsystemer og kompilatorer.
Utskrift på skjermen: printf
redigerprintf så du allerede i bruk i første programeksempelet. printf er en funksjon som er definert i biblioteket stdio (standard input output). printf tar minst ett argument. Et argument til en funksjon, er kort sagt det funksjonen får inn og skal bruke i koden sin. Får en funksjon flere argumenter, skilles de fra hverandre med komma. Jeg skal ikke gå nærmere innpå funksjoner rent syntaksmessig, det kommer senere.
Som alle funksjoner i C, blir argumentene gitt etter funksjonsnavnet, omgitt av paranteser. Det første argumentet til printf, er alltid teksten som skal vises på skjermen. Dette argumentet kalles formatstrengen til printf. Hvorfor, vil du forstå om litt. Om vi vil vise "Heisann" på skjermen, gir vi "Heisann" som formatstreng, altså første argument til printf. Husk hermetegn for å definere "Heisann" som en streng:
#include <stdio.h> int main(void) { printf("Heisann \n"); return 0; }
I konsollvinduet vil du da få "Heisann" når du kjører programmet. Men hva om vi vil skrive noe på en linje og så noe annet på neste? Denne kodesnutten kan se rett ut:
printf("Her er noe på en linje ..."); printf("Og her er noe på en annen");
Men dette vil resultere i følgende utskrift:
Her er noe på en linje ...Og her er noe på en annen
Løsningen er å bruke et spesialtegn, linjeskiftstegnet. Vi kan ikke skrive linjeskiftstegnet i strengen vår, da teksteditoren ville hoppet til nesten linje, men vi kan skrive en slags kode for det i strengen vår, som kompilatoren omgjør til linjeskift. "\n" er koden for linjeskift ("n" for newline). Backslashen foran, signaliserer at vi ikke vil skrive bokstaven "n", men linjeskiftstegnet.
Den rette koden blir, ved hjelp av linjeskiftstegnet:
printf("Her er noe på en line ...\nOg her er noe på en annen");
Om du vil, kan du selvsagt også bruke to printf-uttrykk, men du må fortsatt huske linjeskift:
printf("Her er noe på en line ...\n"); printf("Og her er noe på en annen");
Siden backslash betyr at neste tegn skal være et spesialtegn, byr dette på problemer dersom du vil skrive en backslash på skjermen. Løsningen er å skrive to backslasher:
printf("Dette er en backslash: \\");
Dette gir følgende utskrift:
Dette er en backslash: \
Utskrift av variabler
redigerprintf kan selvsagt også vise innholdet av variabler på skjermen. For å gjøre dette, må vi markere dette i formatstrengen, og så oppgi variabelen som neste argument. Markeringen av dette, bruker vi %x til, der x er følgende:
c Bokstav (char) d/i Heltall f Desimaltall (float, double) g Kortversjon av desimaltall (1.5000000 blir 1.5) s Streng
Dersom vi vil vise en variabel av typen int, altså et heltall, oppgir vi dette ved å skrive %i eller %d i formatstrengen til printf, og oppgi variabelnavnet i neste argument. Vi kan da si at definisjonen av printf ser slik ut:
printf("formatstreng", variabel1, variabel2, ..., variabelX)
printf tar så mange argumenter som det er oppgitt i formatstrengen.
Se på følgende eksempel:
#include <stdio.h> int main(void) { int alder = 99; printf("Alder: %i\n", alder); return 0; }
Her definerer vi en variabel, alder, og gir den verdien 99. Så viser vi denne på skjermen med printf, ved å oppgi "%i" i formatstrengen, og deretter variabelnavnet som neste argument. "%i" blir da erstattet med innholdet i alder. Vi får følgende utskrift:
Alder: 99
Dersom vi markerer noe i formatstrengen til printf med "%", men ikke oppgir noe variabelnavn som neste argument, får vi en feilmelding!
La oss se på et annet eksempel:
#include <stdio.h> int main(void) { int alder = 99; char navn[] = "Lars"; printf("Navn: %s, alder: %i\n", navn, alder); return 0; }
Her definerer vi også en variabel, navn, med strengen "Lars". Siden dette er en streng, markerer vi dette i formatstrengen til printf med "%s", og siden alder er et heltall, markerer vi dette med "%i". Og siden strengen kommer først, og deretter heltallet, må vi oppgi alder og navn i den rekkefølgen "%s" og "%i" er oppgitt i formatstrengen, altså navn først og deretter alder. Vi får følgende utskrift på skjermen:
Navn: Lars, alder: 99
Data fra tastaturet: scanf
redigerFor å gjøre programmene våre interaktive, må vi ha en måte å få inndata fra brukeren. C har en standardfunksjon for å hente data fra tastaturet: scanf. Den tar, i likhet med printf, en formatstreng. Men i scanf kan formatstrengen kun inneholde formatmarkeringer som "%i" og "%s". I formatstrengen til scanf oppgir vi hvilken type data den skal lese inn, og formatmarkeringene er de samme som i printf. Men i stedet for at de påfølgende argumentene (like mange som det er formatmarkeringer i formatstrengen) er kildene til det som skal vises på skjermen, er de målvariablene for innlesningene -- det vil si der det som tastes inn skal lagres. scanf vil lese hvert tegn som blir tastet inn, til det blir tastet mellomrom, tab eller enter. Her er et enkelt eksempel på scanf:
#include <stdio.h> int main(void) { char navn[20]; printf("Navnet ditt: "); scanf("%s", navn); printf("Hei, %s!\n", navn); return 0; }
Her definerer vi først en strengvariabel, navn, for å holde på navnet som tastes inn. Denne definerer vi til å holde 20 tegn. Deretter ber vi brukeren skrive navnet, og bruker scanf til å hente det som tastes inn. Inndataene lagres da i navn, fordi den er oppgitt som målvariabel, og printes så ut på skjermen til slutt. Det er dog en ting som er viktig å merke seg: scanf vet ikke hvor mange tegn navn har plass til. Det betyr at om vi taster inn mer enn 20 tegn, vil scanf prøve å lagre dette i minnet etter navns minne, noe som fører til at programmet mest sannsynlig terminerer med en feilmelding. Dette kan forhindres, noe du kan lese om snart.
Når vi kjører programmet over, vil vi få omtrent følgende utskrift:
Navnet ditt: Johan Hei, Johan!
Vi kan også spørre etter flere ting som da må skrives inn i en spesiell rekkefølge av brukeren, alt etter hvordan formatstrengen ser ut. Vi kan utvide programmet vårt til å spørre om etternavn også, i samme scanf-uttrykk:
#include <stdio.h> int main() { char navn[20]; char etternavn[20]; printf("Navn ditt (navn og etternavn): "); scanf("%s %s", navn, etternavn); printf("%s, %s %s.\n", etternavn, navn, etternavn); return 0; }
En kjøring av dette programmet vil se omtrent slik ut:
Navnet ditt: James Bond Bond, James Bond.
La oss skrive litt om på programmet og f.eks. spørre brukeren om alder også:
#include <stdio.h> int main(void) { char navn[20]; char etternavn[20]; int alder; printf("Navnet ditt (navn og etternavn): "); scanf("%s %s", navn, etternavn); printf("Alderen din: "); scanf("%i", &alder); printf("Hei, du heter %s %s og er %i år gammel.\n", navn, etternavn, alder); return 0; }
Her har vi lagt til en ny variabel, alder, av typen int. Deretter spør vi om alderen til brukeren og gir alder som argument til scanf -- dermed vil alderen som tastes inn lagres i alder. Merk deg &-tegnet foran alder. Dette forteller scanf hvor i minnet variabelen alder befinner seg, slik at den vet hvor det innleste tallet skal legges. Vi kommer nærmere innpå dette i kapittelet om pekere. For nå må du bare huske at alle variabler av alle datatyper utenom strenger (char[], og senere andre tabeller) må ha & foran når de benyttes med scanf.
En ting du bør merke deg med scanf er hva som skjer dersom vi taster inn flere ord enn det er formatmarkeringer i formatstrengen. Når man taster noe på tastaturet, blir dette sendt til programmet i en såkalt strøm av tegn. Det samme gjelder når vi sender data som skal vises på skjermen med f.eks. printf. De to strømmene for inn- og utdata kaller vi stdin og stdout. scanf vil alltid bare ta opp det den trenger fra stdin, dvs. så mye som det står i formatstrengen. Resten ignorerer den, og det ligger da igjen til neste scanf, som vil ta det opp i stedet for å vente på nye inndata.
#include <stdio.h> int main(void) { char navn[20]; char etternavn[20]; printf("Navnet ditt: "); scanf("%s", navn); printf("Etternavnet ditt: "); scanf("%s", etternavn); printf("Hei, %s %s.\n", navn, etternavn); return 0; }
Sett at vi kjører dette programmet slik:
Navnet ditt: James Bond Etternavnet ditt: Hei, James Bond.
Her taster vi inn to ord der scanf forventer ett. Som du ser vil den siste scanf-en ta det som ble til overs i den første, i stedet for å vente på inndata fra tastaturet. Dette er som regel aldri det vi vil. Hvordan kan vi forhindre det? C har en funksjon for det også; fflush. Den tar en strøm som argument, og tømmer det som ligger lagret der fra tidligere scanf-uttrykk. Dersom vi gir fflush stdin som argument, vil det som ligger i der fra før altså fjernes:
#include <stdio.h> int main(void) { char navn[20]; char etternavn[20]; printf("Navnet ditt: "); scanf("%s", navn); fflush(stdin); /* fjern det som ligger i bufferet */ printf("Etternavnet ditt: "); scanf("%s", etternavn); printf("Hei, %s %s.\n", navn, etternavn); return 0; }
Nå vil programmere fungere som det skal, alle andre ord enn det første som blir tastet inn vil bli slettet, og ikke ført videre til neste scanf.
Når vi vil motta hele setninger: fgets
redigerAv og til har vi bruk for å hente hele setninger fra tastaturet. Dette kan ikke scanf gjøre fordi den stopper ved mellomrom. I stdio.h er det deklarert en funksjon som kan ta seg av dette, nemlig fgets. I stedet for en formatstreng og påfølgende argumenter, tar fgets tre argumenter: målvariabel, antall tegn og en strøm. Målvariabelen er selvsagt der vi vil ha setningen som tastes inn, antall tegn er lengden på målvariabelen, og strømmen er stdin. fgets henter alle tegn til den har lest maks. antall tegn, eller kommer til \n (som blir inkludert i strengen.) Merk at siden vi kan oppgi hvor stor målvariabelen er, slipper vi at programmet kan krasje fordi det skrives inn flere tegn enn målvariabelen kan holde. Vi kan skrive om programmet vårt som spør om navn og etternavn med en scanf med to formatmarkeringer, til å bruke én fgets i stedet:
#include <stdio.h> int main(void) { char navn[20]; printf("Navnet ditt: "); fgets(navn, 20, stdin); printf("Hei, %s", navn); return 0; }
I dette programmet kan vi skrive så mange ord vi vil, men alle tegn over de 20 første blir ignorert, siden det var 20 vi oppgav som argument. Dersom vi ikke vil huske på hvor lang en strengvariabel er, eller har planer om å endre den, kan vi bruke en generell funksjon som finner lengden uavhengig av hva vi har definert den til: sizeof. sizeof tar en variabel som argument og returnerer størrelsen på den. sizeof(int) vil returnere 4, og sizeof(navn) vil returnere 20. Vi kan skrive om programmet vårt til å bruke sizeof i stedet for å skrive 20 selv:
#include <stdio.h> int main(void) { char navn[20]; printf("Navnet ditt: "); fgets(navn, sizeof(navn), stdin); printf("Hei, %s", navn); return 0; }
Nå kan vi endre definisjonen av navn til char navn[120] om vi vil, uten å måtte endre andre steder som referer til størrelsen av navn.