Föreläsning 10: In- och utmatning speciellt filhantering

Relevanta dokument
Föreläsning 13. In- och utmatning

Programmeringsteknik med C och Matlab

Deklarera en struct som kan användas för att representera en rät linje

Programmering, grundkurs, 8.0 hp HI1024, HI1900 etc., Tentamen TEN1. Måndagen den 10 januari 2011,

Strängar. Strängar (forts.)

Filer och structer Del 2

Föreläsning 7 Strängar

Dagens föreläsning. Specialtecken. Mer om printf. Formateringssträngar. Mer om scanf. Programmeringsteknik för Ingenjörer VT05

Programmering, grundkurs, 8.0 hp HI1024, omtentamen, TEN1. Tisdagen den 7 juni 2011,

Programmering, grundkurs, 8.0 hp, Elektro, KTH, hösten Programmering: att instruera en maskin att utföra en uppgift, kräver olika språk:

Planering Programmering grundkurs HI1024 HT TIDAA

Johan Karlsson Datavetenskap för teknisk kemi, 10p, moment 1 Datavetenskap Umeå Universitet. Tentamen

Lärare och handledare. Systemnära programmering. I dag. Föreläsningar & Gruppövningar & Handledning. Marcus Karlsson

KTH STH TENTAMEN. HI1024:TEN2 - Praktisk tentamen Tid: 8-13, den 18 februari 2012

Språket Python - Del 2 Grundkurs i programmering med Python

Planering Programmering grundkurs HI1024 HT data

HI1024, Programmering, grundkurs, 8hp KTH STH TENTAMEN. HI1024:TEN1 - Teoretisk tentamen Tid: Torsdagen den 20 oktober 2011,

Föreläsning 6 pekare och pekare tillsammans med arrayer

Övning från förra gången: readword

Exempelsamling Assemblerprogrammering

Tentamen i Programmering grundkurs och Programmering C

Programmering i C++ EDA623 Strömmar och filer. EDA623 (Föreläsning 9) HT / 19

Enklast att skriva variabelnamn utan ; innehåll och variabelnamn skrivs ut

Repetition av filer Grundkurs i programmering med Python

En kort text om programmering i C.

Enkla datatyper minne

HI1024, Programmering, grundkurs, 8hp KTH STH TENTAMEN. HI1024:TEN2 - Praktisk tentamen Tid: Fredagen den 21 oktober 2011,

Att skriva till och läsa från terminalfönstret

Möte 7: Uppföljning av föreläsningen med Peer Instruction - (PI)

Agenda. Arrayer deklaration, åtkomst Makron Flerdimensionella arrayer Initiering Strängar Funktioner och arrayer. Övningar nu och då

Programmeringsteknik med C och Matlab

Programmering, grundkurs, 8.0 hp HI1024, TEN1. Fredagen den 2 mars 2012

Tecken & Strängar. Kapitel 7

Tentamen i. Programmering i språket C

Uppgifter till praktiska tentan, del A. (7 / 27)

Dynamiskt minne. Vad är dynamiskt minne Motivering Hur gör man i C Övningar

Tentamen *:58/ID100V Programmering i C Exempel 3

*Pekarvärden *Pekarvariabler & *

Tentamen i. för D1 m fl, även distanskursen. lördag 19 januari 2013

Hantering av textsträngar och talsträngar. William Sandqvist

TDIU01 - Programmering i C++, grundkurs

Introduktion till programmering, hösten 2011

Tentamen i Programmering grundkurs och Programmering C

Uttryck, satser och strömhantering

Föreläsning 11. Strängar

Laboration 3 HI1024, Programmering, grundkurs, 8.0 hp

5 Grundläggande in- och utmatning

Tentamen i Programmering grundkurs och Programmering C

Programmeringsteknisk översiktskurs för yrkeshögskoleprogram

2 Pekare och dynamiska variabler.

Tentamen i Programmering grundkurs och Programmering C

Sammanfattning. Listor. List-manipulering. Matris. /home/lindahlm/activity-phd/teaching/11dd1310/exercise3/exercise3.py September 13, 20111

Skriv i mån av plats dina lösningar direkt i tentamen. Skriv ditt kodnummer längst upp på varje blad.

Lösningar till uppgifterna sätts ut på kurssidan på nätet idag kl Omtentamen i Programmering C, 5p, A1, D1, E1, Fri, Pr1, Te/Ek1,

GU / Chalmers Campus Lindholmen Tentamen Programutveckling LEU 482 / TIG167

Laboration 1 Introduktion till Visual Basic 6.0

Laboration 3 HI1024, Programmering, grundkurs, 8.0 hp

Planering Programmering grundkurs HI1024 HT 2014

Lite mer om Javas stöd för fält. Programmering. Exempel: vad är det största talet? hh.se/db2004. Fält samt Input/Output

Kompilering och exekvering. Föreläsning 1 Objektorienterad programmering DD1332. En kompilerbar och körbar java-kod. Kompilering och exekvering

Programmera i C Varför programmera i C när det finns språk som Simula och Pascal??

TDP002 - Imperativ programmering

Lösningar till tentauppgifterna sätts ut på kurssidan på nätet idag kl 19. Omtentamen i Programmering C, 5p, fristående, kväll,

Föreläsning 10. Pekare (Pointers)

Instruktioner för att kunna programmera på skolans datorer

Övningsuppgifter till föreläsning 2 Variabler och uttryck

Tentamen i Programmering grundkurs och Programmering C

Guido van Robot och olika UNIX-kommandon

Pekare och arrayer. Indexering och avreferering

kl Tentaupplägg

Lösningar till uppgifterna sätts ut på kurssidan på nätet i dag kl Omtentamen i Programmering C, Fri, Kväll,

Grundkurs i programmering, 6 hp (725G61) Dugga 2 tillfälle 2

Filer. DA2001 (Föreläsning 16) Datalogi 1 Hösten / 19

Omkoppling av in- och utmatning. In- och utmatning i Unix. Kommando exempel, ls, pipe forts. Kommando exempel, ls, pipe

Laboration 3 HI1024, Programmering, grundkurs, 8.0 hp

Tentamen ges för: Tentamensdatum: Tid:

#include <pthread.h> #include <stdio.h> int p1[2]; void* char_print (void* parameter) { int fds; fds = *((int *)parameter); int i;

TDIU01 - Programmering i C++, grundkurs

4 Sammansatta datatyper

C-programmering, föreläsning 1 Jesper Wilhelmsson

Tentamen i Programmering grundkurs och Programmering C

Lösningar till uppgifterna sätts ut på kurssidan på nätet i dag kl Tentamen i Programmering C, Fri, Kväll,

PC-teknik, 5 p LABORATION FILHANTERING

Programmering I Tobias Wrigstad fredag, 2009 augusti 28

Textsträngar från/till skärm eller fil

HI1024 Programmering, grundkurs TEN

Tentamen i Programmering grundkurs och Programmering C

Laboration: Whitebox- och blackboxtesting

Några inbyggda funktioner (med resultat!) Introduktion till programmering D0009E. Föreläsning 4: Villkor och rekursion. Modulus-operatorn.

tentaplugg.nu av studenter för studenter

Numeriska Metoder och Grundläggande Programmering för P1, VT2014

729G04 Programmering och diskret matematik. Python 2: Villkorssatser, sanningsvärden och logiska operatorer

Introduktion till programmering SMD180. Föreläsning 4: Villkor och rekursion

Objektorienterad programmering i Java I. Uppgifter: 2 Beräknad tid: 5-8 timmar (OBS! Endast ett labbtillfälle) Att läsa: kapitel 5 6

Kapitel 5. Strömmar. Utmatning

Föreläsning 3. Programmering, C och programmeringsmiljö

Outline. I Vi kan lätt göra samma sak för fyra variabler... I Hur gör vi för 400 inlästa värden? I Ofta behöver man flera likadana variabler

Om pekare och minneshantering i C, relaterat till operativsystem och särskilt konstruktionen fork() execvp().

kl Tentaupplägg. TIPS 4: Du kan kompilera din kod med följande kommandon g++11, w++11 (för mer varningar)

Operativsystem ID1200/06 (ID2200/06 6hp) Tentamen :00-18:00

Transkript:

Föreläsning 10: In- och utmatning speciellt filhantering Vi har genom hela kursen arbetat med in- och utmatning i våra program. Varje gång vi anropar printf(), scanf(), gets() osv har vi gjort in- eller utmatning, eller IO som det ofta förkortas inom datortekniken. Det är givetvis oerhört väsentligt med IO, utan det kan vi inte få ut något resultat av våra program eller mata in några indata till våra program. 22.1 Strömmar Följande är en föreställning om vad en fil på en hårddisk är: en följd av lagringsutrymmen som håller kvar sitt innehåll då datorn är avstängd. Men för att kommunicera med filer, alltså antingen läsa från dem eller skriva till dem (IO) inför vi ett mellanled, en ström. En ström är den punkt som ett program använder för att kommunicera med till exempel en fil på en hårddisk, man säger att man öppnar strömmen och associerar den till en fil. Men vi brukar faktiskt uttrycka oss lite kortare och referera till en ström som om det vore en fil, då säger vi att vi öppnar filen. Filpekare Vi måste alltså alltid ha en ström för att komma åt en fil. En ström kommer vi åt via något som kallas en filpekare (vilket återigen visar hur tätt kopplade begreppen fil och ström är). I ett program som ska arbeta med filer behövs filpekare, vi deklarerar dem så här FILE *fp1, *fp2; och här har vi exempel på hur vi har två strömmar som vi kan använda. Dock innan vi kan använda dem måste de öppnas, mer om det senare. Omdirigering: läs jsälva Textfiler och binärfiler Textfiler kan människor läsa, binärfiler är det ofta mycket svårt för en människa att läsa utan speciella program. Källkoden för ett C-program är lagrat i en textfil, däremot är den körbara koden hörande till programmet lagrat i en binärfil. Den precisa tekniska betydelsen av textfil är att de är indelade i rader och kan ha ett filslutstecken. (Men det betyder egentligen inte så mycket.) Man kan läsa en textfil rad för rad. Alla filer (både binära och textfiler) är en lång följd av bytes. Men i en textfil återkommer radbytestecknet då och då för att markera radslut. (I UNIX är radslutstecknet nr 10, i Windows, nr 13 följt av nr 10, alltså två tecken). Binärfiler har inte denna uppdelning i rader. Observera att skillnaden mellan text- och binärfil är främst ett val av perspektiv: samma fil kan öppnas och behandlas som textfil och senare som binärfil, men det är ofta inte meningsfullt att se en fil som hörande till båda slagen. Om vi till exempel, med en texteditor, som Emacs, öppnar filen som innehåller ett körbart program så går det att göra, men det vi ser är inte meningsfullt. Textfiler anses bestå av följder av tecken, som till exempel källkoden till ett C-program. Om vi ska lagra talet 32767 i en textfil så blir det i form av fem tecken, '3', '2', '7', '6', '7'. Det upptar fem bytes och det är en textmässig representation som lagras, alltså tecknena, som då i sin tur baseras på ASCII-tabellen, till exempel tecknet '3' har asciikoden 51 som har bitmönstret 00110011. Skillnaden om man lagrar det hela binärt är att bitmönstret för talet 32767 lagras. Det är 01111111 11111111. Det upptar bara 2 bytes. Det är dessutom lagrat i den form som datorn använder internt då den räknar, vi kan alltså utföra räkneoperationer på binär data. Om vi ska utföra räkneoperationer på data lagrat i en textfil måste vi först omvandla till binär form. Man kan säga att en binärfil innehåller en exakt kopia av data såsom datorn representerar det och när man läser in en binärfil i datorns minne behövs därför inga omvandlingar som i fallet med data lagrat i textfiler. 22.2 Operationer på filer Det första vi måste göra för att komma åt innehållet i en fil är att öppna den. Det gör vi med hjälp av en operation på en ström med systemanropet fopen(). Vi studerar ett utdrag ur en manualsida, johnnyp@kth.se Sidan 1 av 11

man fopen: NAME HI1024 Programmering, grundkurs, 8.0 hp, KTH STH, hösten 2012 fopen, fdopen, freopen - stream open functions SYNOPSIS FILE *fopen(const char *path, const char *mode); FILE *fdopen(int fd, const char *mode); FILE *freopen(const char *path, const char *mode, FILE *stream); Feature Test Macro Requirements for glibc (see feature_test_macros(7)): fdopen(): _POSIX_C_SOURCE >= 1 _XOPEN_SOURCE _POSIX_SOURCE DESCRIPTION The fopen() function opens the file whose name is the string pointed to by path and associates a stream with it. The argument mode points to a string beginning with one of the following sequences (Additional characters may follow these sequences.): r Open text file for reading. The stream is positioned at the beginning of the file. r+ Open for reading and writing. The stream is positioned at the beginning of the file. w Truncate file to zero length or create text file for writing. The stream is positioned at the beginning of the file. w+ Open for reading and writing. The file is created if it does not exist, otherwise it is truncated. The stream is positioned at the beginning of the file. a Open for appending (writing at end of file). The file is created if it does not exist. The stream is positioned at the end of the file. a+ Open for reading and appending (writing at end of file). The file is created if it does not exist. The initial file position for reading is at the beginning of the file, but output is always appended to the end of the file. The mode string can also include the letter 'b' either as a last character or as a character between the characters in any of the two-character strings described above. This is strictly for compatibility with C89 and has no effect; the 'b' is ignored on all POSIX conforming systems, including Linux. (Other systems may treat text files and binary files differently, and adding the 'b' may be a good idea if you do I/O to a binary file and expect that your program may be ported to non-unix environments.) Ett exempel på ett anrop kan vara fp1 = fopen( mytextfile.txt, r ); vilket öppnar en textfil för läsning. Vi kan öppna en textfil för att lägga till saker genom att skriva fp1 = fopen( mytextfile.txt, a ); eller skapa en helt ny fil genom att skriva w sist. johnnyp@kth.se Sidan 2 av 11

Faktiskt så är det så att vi också kan hantera binärfiler på samma sätt vi behöver inte lägga till ett b i anropet i POSIX/UNIX, men om vi ska skriva program för Windows måste vi göra det om vi ska hantera binärfiler. Vi kommer dock i denna kurs inte att göra så mycket med filer, vi kommer att klara oss med r och w. Det är inte så fasligt avancerat att använda sig av andra sätt att öppna filer (det här med +, och a ) men det lämnar vi utanför denna kurs, vi försöker begränsa oss så mycket vi kan i denna kurs utan att förlora det vi verkligen behöver för att kunna programmera ordentligt. Då en fil öppnas kontrollerar operativsystemet om filen finns och man har rättigheter att öppna filen på det sätt man begär. Om man har läsrättigheter på en fil så kommer anropet till fopen() med r att lyckas, men w eller a kommer inte att lyckas. Vi får inget felmeddelande om detta, vi måste kolla om returvärdet från fopen() är en NULL-pekare, det betyder att anropet inte har lyckats. Vi ser ofta konstruktioner av typen fp = fopen( afile, w ); if(fp == NULL) printf( Cannot open file.\n ); exit(exit_failure); som alltså avslutar programmet om programmet inte lyckas öppna en viss fil. Möjligtvis kanske man inte behöver avsluta ett program, man kan ge användaren en chans till att ge en annan fil, men ovanstående är ändå ganska standardmässigt. När vi öppnat en fil och arbetat med den (skrivit eller läst eller vad vi nu ska göra) måste strömmen som är associerad med filen stängas. Det är först när filen är stängd som den säkert innehåller det som vårt program vill. Om vi öppnat en fil för skrivning, skrivit en hel del till den ström som är associerad med filen, så skrivs kanske inte dessa ändringar till disken, alltså till den fysiska filen, förrän strömmen stängs. Anledningen är att data mellanlagras i strömmen i buffrar vars innehåll skrivs ut då filen stängs. En möjlighet är också att explicit begära att buffrarna töms med ett anrop som heter fflush() men vi ska inte gå in på det. Vi satsar på att stänga strömmar/filer istället. Det görs med kommandot fclose() och man skickar in filpekaren som returnerats från fopen(). Från och med nu kommer vi också att säga, väldigt ofta, att vi stänger filen snarare än strömmen och vi menar då att vi stänger den ström som har den avsedda filen associerad till sig. Ni kan själva läsa om temporärfiler och buffring, det är inte nödvändigt för att klara kursen, men om ni vill läsa om det går det förstås utmärkt. Avsnittet om att ta in filnamn från kommandoraden har vi redan gått igenom i och med att vi vet från kapitlet om strängar hur man tar in strängar från kommandoraden. 22.3 Formatterad IO Vi har redan använt formaterad IO väldigt mycket, f:et i namnen på printf() och scanf() står just för formattering. Namnet på printf() ska utläsas, print a formatted string, alltså skriv ut en formaterad sträng. Det innebär att vi egentligen redan känner till det centrala när det gäller formatterad IO till och från strömmar: det centrala är just formatsträngen som vid anrop med till exempel printf() omvandlas enligt de omvandlingsspecifikationer vi angett, tex %d och liknande. Vi tar ett par exempel på en gång. Vi känner som sagt redan till formaterad IO, vi har gjort formatterad utmatning till skärmen med printf() och formaterad inmatning från tangentbordet med scanf(). Strömmarna stdin och stdout är två fördefinerade strömmar som är kopplade till skärm och tangentbord som förval och vi ska nu se hur vi kopplar strömmar till filer på disk. Vi johnnyp@kth.se Sidan 3 av 11

studerar ett redigerat utdrag ur manualsidan till printf(), vi ger kommandot man printf vid en prompt: NAME printf, fprintf, sprintf, snprintf - formatted output conversion SYNOPSIS int printf(const char *format,...); int fprintf(file *stream, const char *format,...); int sprintf(char *str, const char *format,...); int snprintf(char *str, size_t size, const char *format,...); DESCRIPTION The functions in the printf() family produce output according to a format as described below. The functions printf() writes output to stdout, the standard output stream; fprintf() writes output to the given output stream; sprintf(), snprintf() write to the character string str. The function snprintf() writes at most size bytes (including the trailing null byte ('\0')) to str. These functions write the output under the control of a format string that specifies how subsequent arguments (or arguments accessed via the variable-length argument facilities of stdarg(3)) are converted for output. C99 and POSIX.1-2001 specify that the results are undefined if a call to sprintf(), snprintf() would cause copying to take place between objects that overlap (e.g., if the target string array and one of the supplied input arguments refer to the same buffer). See NOTES. Return value Upon successful return, these functions return the number of characters printed (not including the trailing '\0' used to end output to strings). Ovan har vi tagit bort beskrivningen av en kategori av funktioner som börjar med v, tex vprintf(), vi väljer att göra det för att begränsa innehållet i kursen. Om ni studerar en manualsida till printf() kommer ni att hitta information om dessa funktioner, ni kan då bortse från den informationen om ni vill. Argumentlistan till printf() har utseendet const char *format,.... Här ser vi tre punkter i rad,..., det betyder att funktionen printf() accepterar flera argument men man behöver inte på förhand ange exakt hur många argument vi skrickar till printf() - det kallas för en ellips (engelska ellipsis) och indikerar ett variabelt antal element. Vi är ju vana vid att printf() kan ta flera argument, vi skriver till exempel printf( Tal 1: %d.\n, x); och anropar då printf() med två argument, en formatsträng och ett heltal, men vi kan ju också göra printf( Tal 1: %d Tal 2: %d.\n, x, y); och då anropar vi samma funktion, printf(), fast med tre argument, en formatsträng och två heltal. Det gemensamma för båda anropen är att formatsträngen måste vara där och det är alltså den som anges i parameterlistan i funktionsprototypen till printf() som vi såg ovan, alltså const char *format,.... Funktionen printf() används för utmatning till strömmen stdout, som normalt är skärmen, men när vi nu ska gå vidare och studera funktioner som gör utmatning till andra strömmar som kanske ska associeras med filer på disken studerar vi funktioner som är besläktade med printf() johnnyp@kth.se Sidan 4 av 11

men som har en extra parameter som anger vilken ström som avses. Vi ska börja med att studera den enklaste, fprintf(). Vi tittar på dess funktionsprototyp: int fprintf(file *stream, const char *format,...); Vi ser att vi, liksom printf(), har parametrarna const char *format,... men att det först ska anges en parameter till den ström som avses, FILE *stream. Det är bra att vi studerar manualsidan på detta sätt, de ger oss upplysningar om hur funktionerna ska anropas. Vi kan nu konstruera ett enkelt exempel baserat på denna dokumentation: main() FILE * fp1; int number_of_cows = 5; double temperature = 25.5; printf("a string: %s.\na floating point number: %.1f.\nAn integer: %d.\n", "Charles", temperature, number_of_cows); fp1 = fopen ("newtextfile.txt", "w"); fprintf(fp1, "A string: %s.\na floating point number: %.1f.\nAn integer: %d.\n", "Charles", temperature, number_of_cows); fclose(fp1); Om vi kör ovanstående program så får vi utskriften: A string: Charles. A floating point number: 25.5. An integer: 5. och det kommer av anropet till printf() som vi ser i programmet och vi är såpass bekanta med printf() att vi kan se att den utskrift vi har verkligen härör från anropet i programmet. Vad vi inte ser i utskriften är att precis denna text, som skrivs ut på skärmen, också skrivs in i en ny fil som skapas av programmet, vi börjar med att öppna strömmen fp1 och associera filen newtextfile.txt med den, eller kortare uttryckt, vi öppnar filen newtextfile.txt för skrivning. Därefter gör vi anropet till fprintf() som då alltså skriver till strömmen fp1, på precis samma sätt som printf() skrev till skärmen den text som vi såg. När vi till sist stänger strömmen fp1, med fclose(fp1) så skrivs innehållet till textfilen på hårddisken. Vi kan då göra cat newtextfile.txt och får precis samma utskrift som ovan vilket bekräftar att filen skapats med det innehåll vi ville. Vi åstadkommer alltså detta genom att bara addera parametern fp1, i anropet till fprintf(). Och förstås genom att öppna innan för skrivning och sedan stänga. När det gäller strömmar så är fp1, i ovanstående exempel associerad med filen som vi skapar. Men vi kan faktiskt också skriva till andra strömmar. Strömmen stdout är normalt skärmen och om vi skriver fprintf(stdout, Test ); så är det samma sak som att skriva printf( Test );. Det betyder att om vi i anropet till fprintf() i programmet ovan hade struntat i strömmen fp1 och skrivit fprintf(stdout, "A string: %s.\na floating point number: %.1f.\nAn integer: %d.\n", "Charles", temperature, number_of_cows); så hade resultatet blivit detsamma som den tidigare utskriften. Filen som associerades till strömmen fp1 hade inte fått något innehåll utan programmet skulle bara gjort likadana utskrifter efter varandra. (Om vi hade haft kvar fopen() och fclose() hade filen bara skapats men blivit tom.) johnnyp@kth.se Sidan 5 av 11

De goda nyheterna är att svårare än så här är inte formaterad utmatning. Nästa funktion vi kan studera är sprintf(), men den är ännu enklare, den fungerar precis som fprintf() men den skriver till en sträng istället för en ström. Då behöver vi inte öppna och stänga, det enda som behövs är att strängen som vi skriver till är deklarerad på ett bra sätt. Den här funktionen är användbar om vi vill skapa en sträng och returnera den men inte nödvändigtvis skriva ut den på skärmen. Vi vill låta programmeraren avgöra hur strängen ska hanteras, antingen skrivas ut, eller kanske slås ihop med andra strängar och sedan skrivs det ut. Det ger oss flexibilitet. Det är inte så ofta sprintf() behövs men när den behövs är den oslagbar. Funktionen snprintf() är en säkrare variant som ni kan studera själva. Formaterad inmatning gör vi med en släkting till scanf() som (analogt med fprintf()) heter fscanf(), och den fungerar i princip på precis samma sätt som som scanf() men man kan alltså ställa in den så att den behandlar en annan ström än stdin. Om vi skriver ett program som är motsvarigheten till programmet ovan, som öppnar filen newtextfile.txt för läsning och läser in data och skriver ut på skärmen så kan det se ut så här: main() FILE * fp1; int number_of_cows; double temperature; char str[40]; fp1 = fopen ("newtextfile.txt", "r"); fscanf(fp1, "A string: %s.\na floating point number: %lf.\nan integer: %d.\n", str, &temperature, &number_of_cows); fclose(fp1); printf("string: %s\n", str); printf("floating pt nr: %.1f.\n", temperature); printf("an integer: %d.\n", number_of_cows); Alltså en enkel inläsning av de data vi skrivit in i filen. Tyvärr fungerar inte det här programmet som det är tänkt, om vi kör det får vi följande resultat: String: Charles. Floating pt nr: 0.0. An integer: 0. Vi lyckas alltså inte få in de två talen på rätt plats. Rent teoretiskt kan man använda fscanf() på det här sättet och bilda olika formatsträngar med omvandlingsspecifikationer, men det är mer snärjigt än att bara ta samma formatsträng som fprintf() använde vid skapandet av filen. För att få saker att fungera på ett solitt sätt är det först och främst bättre att använda binärfiler, men om vi vill använda textfiler (som här) är det bättre att läsa från filen i små portioner, först läsa rad 1, sedan rad 2 osv. Omvandlingsspecifikationen %s är också särskilt lömsk, den läser ända till radslut, mellanslag, eller tabulatorsteg, vi ser utskriften String: Charles. i testkörningen, men detta ges i respons till anropet printf("string: %s\n", str);. Problemet här är att det inte finns någon punkt (.) i detta anrop. Men vi ser en punkt i resultatet... vad händer? Det som hänt är att punkten från textfilen lästs in tillsammans med texten Charles och det som händer då är att fscanf() har kvar att matcha.\na floating point number: %lf.\nan integer: %d.\n" mot innehållet som är kvar i filen som är johnnyp@kth.se Sidan 6 av 11

A floating point number: 25.5. An integer: 5. Det finns ingen punkt här och där brakar läsningen samman, vi kommer i otakt. Det är därför vi får nollor som värden till temperature och number_of_cows. Det går att skriva formatsträngar så att vi klarar oss bra, att tänka och vända och vrida på det, men vi avstår från det. Det är bättre att använda binärfiler som sagt, och det ska vi göra snart, men om vi väldigt gärna vill arbeta med textfiler är det bättre för oss om vi hanterar dem rad för rad istället. Vi kan lösa detta problem bättre när vi hanterar inmatning radvis, vi kommer att studera det lite senare, så vi släpper just det här problemet nu, det är normalt inte så här man gör heller med textfiler så vi ska inte slösa tid på att studera detta. Vi kan bara översiktligt säga att det skulle vara mycket lättare om vi inte skulle blanda tal och text utan använde ett enklare filformat där heltalet 5 och flyttalet 25.5 förekom på egna rader. Vi går vidare till något mer produktivt istället. Då det gäller formaterad in- och utmatning finns det väldigt många omvandlingsspecifikationer. Vi ska inte lära oss alla dessa, det är bättre om vi vet var vi kan slå upp dem någonstans. Vi ska bara nämna definitionen av något som kallas white space. Vi har stött på det tidigare, så vi läser med fscanf() och har %s som omvandlingsspecifikation så läser fscanf() tills nästa mellanslag, radbytestecken eller tabulatorsteg. Dessa tre avgränsare kallas med ett gemensamt namn för white space. Det var anledningen till att %s ovan innebar att punkten lästes in från filen och slogs samman med Charles och bildade den sträng som fscanf() läste in, vi läste fram till nästa white space och fick då innehållet Charles. i strängen. Vi måste se upp med %s vid inmatning. 22.4 IO tecken för tecken och 22.5 Radvis IO Vi ska studera funktioner som skriver och läser enskilda tecken eller hela rader av tecken till och från strömmar. Namnen på dessa funktioner är fputc(), fputs(), putc(), putchar() och puts()för utmatning av tecken (de med c i namnet) och rader (de med s i namnet). Vidare har vi fgetc(), fgets(), getc(), getchar() och gets() för inmatning. Hit hör också en specialfunktion som heter ungetc() som kan användas för att temporärt stoppa tillbaka ett tecken man redan läst. Om vi studerar manualsidorna för dessa funktioner så får vi en sammanfattning av vad de gör. De med c i namnet läser respektive skriver till en öppen ström ett tecken i taget och det finns egentligen inte så mycket att säga om det. Det finns bara några enskilda saker som är värda att notera: fgetc(), getc() och getchar() returnerar heltal, alltså int och inte char. Det värde som returneras är antingen en angivelse av att filen är slut, då blir det 1 som är asciikoden för EOF, End Of File eller så ges ett värde mellan 0 och 255 som är ett så kallat unsigned char, ett tecken med asciikoder i intervallet 0 till 255. getc() är ibland skapat som ett så kallat makro. Det betyder att om vi skriver ch=getc(fp1()); för att läsa från en ström som returneras av en funktion, fp1() så kan det hända att denna funktion anropas två gånger. Vi ska vara försiktiga med detta. För att vara på säkra sidan så kan man se till att aldrig göra funktionsanrop i argumenten till getc() eller använda fgetc() som inte är ett makro. Beträffande putc() och fputc() så gäller samma som för fgetc() och getc(), nämligen att är putc() ett makro och det är då inte bra med funktionsanrop i argumenten till putc(). För att göra saker enkla, använd helt enkelt bara fputc() istället. Studera mycket gärna exemplet Copying a File på sidan 568. johnnyp@kth.se Sidan 7 av 11

Då det gäller in- och utmatning av hela rader används funktionerna med s i sitt namn, fgets(), gets(), fputs() och puts(). Funktionen puts() skriver en sträng till skärmen följt av ett nyradstecken (som man alltså får på köpet) och fputs() kan ställas in så att strängen skrivs till en ström istället, men då skrivs inget nyradstecken och inte heller en avslutande nolla. Det är intressantare att diskutera inmatningsfunktionerna, fgets() och gets(). Vi har troligtvis använt många gånger i olika program. Den läser, som bekant, från stdin, och lägger in resultatet i den sträng som den opererar på. Problemet med den är att det inte finns någon begränsning på hur många tecken den läser in, vi har också troligtvis sett många gånger en varning om att funktionen gets() är osäker och vi kan, från och med nu, rätta till detta. Tidigare har vi skrivit gets(str) då vi velat mata in en sträng i str, men nu kan vi skriva så här istället: fgets(str, sizeof(str), stdin); då är vi säkra på att en begränsning införs, den andra parametern, sizeof(str), är denna övre gräns. Givetvis kan användas för att läsa från andra strömmar än stdin, vi går tillbaka till vårt ursprungliga problem att läsa i filen med detta innehåll: A string: Charles. A floating point number: 25.5. An integer: 5. Ett program som verkligen klarar av att göra detta visas nedan: main() FILE * fp1; char str_from_file[100]; int number_of_cows; double temperature; char str[40]; fp1 = fopen ("newtextfile.txt", "r"); fgets(str_from_file,sizeof(str_from_file),fp1); printf("line 1: %s", str_from_file); sscanf(str_from_file, "A string: %s", str); fgets(str_from_file,sizeof(str_from_file),fp1); printf("line 2: %s", str_from_file); sscanf(str_from_file, "A floating point number: %lf.", &temperature); fgets(str_from_file,sizeof(str_from_file),fp1); printf("line 3: %s", str_from_file); sscanf(str_from_file, "An integer: %d.\n", &number_of_cows); fclose(fp1); printf("string: %s\n", str); printf("floating pt nr: %.1f.\n", temperature); printf("an integer: %d.\n", number_of_cows); Det här kanske verkar vara ett stort program, men är ändå mer systematiskt än det tidigare johnnyp@kth.se Sidan 8 av 11

programmet som inte fungerade som vi ville. Här använder vi fgets() för att först säkert få in hela raden från textfilen följt av sscanf() för att sedan läsa detaljerna på varje rad och på så sätt extrahera ut den information vi vill ha, då är det lätt att få in rätt data i rätt variabel. Provkörningen ser ut så här: Line 1: A string: Charles. Line 2: A floating point number: 25.5. Line 3: An integer: 5. String: Charles. Floating pt nr: 25.5. An integer: 5. Nu får vi verkligen in 25.5 i en flyttalsvariabel och 5 i heltalsvariabeln. 22.6 Blockvis IO Vid alla former av in- och utmatning vi hittills stött på har vi ofta gått igenom de olika datastrukturerna som ska med någon form av loop och läst eller skrivit till den ström som varit associerad med den fil där vi lagrar data. Vid användning av fprintf() och fputs() har vi hittills dessutom skapat en textorienterad representation av data som, när vi läser in den igen, behöver omvandlas tillbaka till binär form. (Som datorn kan räkna med.) Nu ska vi se på en helt annan form av in- och utmatning som behåller datorns binära representationssätt också i den fil som skapar: så kallad blockvis IO. Vi åstadkommer blockvis IO med de båda funktionerna fread() och fwrite(). Dessa funktioner är dessutom konstruerade så att man ofta slipper anropa dem med hjälp av en loop. Vi ser på ett exempel på en gång. Följande program skriver innehållet i en array av 10 heltal till en fil som heter numbers: main() FILE *fp; int a[] = 1, 2, 3, 4, 5, 6, 7, 8, 9, 10; fp = fopen("numbers", "wb"); fwrite(a, sizeof(a[0]), sizeof(a) / sizeof(a[0]), fp); fclose(fp); En provkörning ger ingen utskrift eftersom vi inte skriver någonting till skärmen, vi skriver till filen som heter numbers. Skrivningen sker i och med anropet till fwrite() och argumenten är a, sizeof(a[0]), sizeof(a) / sizeof(a[0]) och fp. Då vi har arrayen a med 10 heltal så antar dessa parametrar värdena: * adressen till a[0], alltså startpunkten för utmatningen, * 4 (eftersom int har storleken 4), * 40/4 som är 10, vilket anger antalet element som ska matas ut och * fp är förstås pekaren till strömmen. Om vi ändrar i programmet så att vi istället läser ifrån filen (via strömmen) till en tom array så är det mycket lätt att göra, fread() har nämligen samma parametrar som fwrite(), vi anger alltså först vart startpunkten för det som ska läsas in ska vara (arrayen a), sedan 4 (i form av sizeof(a[0]), följt av 10 (i form av sizeof(a) / sizeof(a[0])) och sist pekaren till den ström vi läser från. Hela programmet ser då ut nästan som det program som skrev filen, så här: johnnyp@kth.se Sidan 9 av 11

HI1024 Programmering, grundkurs, 8.0 hp, KTH STH, hösten 2012 main() FILE *fp; int i, a[10]; fp = fopen("numbers", "rb"); fread(a, sizeof(a[0]), sizeof(a) / sizeof(a[0]), fp); fclose(fp); printf("numbers: "); for(i=0;i<10;i++)printf("%d ", a[i]); printf("\n"); Körningen ser ut så här: Numbers: 1 2 3 4 5 6 7 8 9 10 Vi ser här då återigen en utmatning av de tal som det förra programmet skrev ut till filen. Och allt detta har vi åstadkommit utan att använda loopar, både fread() och fwrite() har alltså loopfunktionaliteten inbyggd. Det här fungerar alldeles utmärkt med en array av structar också och är grunden för hur vi kan dumpa ut innehållet i en jättearray av structar i en fil och snabbt och enkelt läsa in innehållet igen. Programmeringsprojekt nr 8 är utmärkt för att exemplifiera detta. Vi rekommenderar verkligen att ni gör det projektet. Det är inte alls svårt, ni kan bygga på exemplet ovan. 22.7 Positionering i filer Då vi läser eller skriver i en fil finns det någonting som kallas filposition. Den hör till strömmen som är öppnad för filen och gör det möjligt för oss att behandla olika delar av filen, vi kan gå fram och tillbaka i filen och läsa eller skriva. Normalt är detta endast för binärfiler, det går för textfiler också men kräver en del specialmanövrar som vi inte ska studera i denna kurs. Det är rent av så att vi inte alls kommer att behöva ändra positionerna i några filer, det kommer troligen att räcka med att bara dumpa ut data (från en jättearray) i en fil i slutet av programmet och sedan stänga den respektive läsa in ett helt filinnehåll i en jättearray i början av programmet. Men vi nämner ändå möjligheten att ändra filpositionen för fullständighetens skull. Det finns fem funktioner för att hantera detta med filpositioner, vi hittar dem om vi skriver man fseek, eller man ftell eller man på någon annan av dem: int fseek(file *stream, long offset, int whence); long ftell(file *stream); void rewind(file *stream); int fgetpos(file *stream, fpos_t *pos); int fsetpos(file *stream, fpos_t *pos); Funktionen ftell() anger nuvarande filposition i form av ett heltal, rewind() sätter filpositionen till början av filen, rewind betyder spola tillbaka. Funktionerna getpos() och setpos() ska vi inte studera i denna kurs, de är för filer som är ohyggligt stora, om ni kommer att använda ohyggligt stora filer någon gång så kan ni gå tillbaka till dem. Det som är kvar att beskriva är då alltså funktionen fseek(). Vi kommer då att studera ett exempel som också kommer att illustrera själva begreppet position i en fil. johnnyp@kth.se Sidan 10 av 11

Vi studerar följande exempel: int main(void) FILE *infil, *utfil; int t1,t2,t3,t4,k,s; int skriv_detta; s=sizeof(int); HI1024 Programmering, grundkurs, 8.0 hp, KTH STH, hösten 2012 utfil=fopen("tal1.dat", "wb"); for(skriv_detta=1;skriv_detta<=100;skriv_detta++) fwrite(&skriv_detta,4,1,utfil); fclose(utfil); infil=fopen("tal1.dat","rb"); fread(&t1,s,1,infil); fseek(infil,10*s,seek_set); fread(&t2,s,1,infil); fseek(infil,-20*s,seek_end); fread(&t3,s,1,infil); fseek(infil,2*s,seek_cur); fread(&t4,s,1,infil); fclose(infil); printf("%d %d %d %d\n",t1,t2,t3,t4); En körning: 1 11 81 84 Innehållet i filen är talen 1 till och med 100. I den här diskussionen ska vi låtsas som om en filposition innehåller ett heltal, men i själva verket krävs fyra positioner, fyra bytes, för att rymma ett heltal. Heltalsvariabeln skriv_detta går från 1 till 100 och vi skriver värdet av den in i den ström som är kopplad till den fil vi arbetar med. Första läsningen ger, som vi ser i utskriften, en etta. Det som händer sen är att vi går framåt (med fseek(infil,10*s,seek_set);) och läser ytterligare ett tal, denna läsning sker 10 steg (egentligen 40 steg) framåt och om man går 10 steg framåt hamnar vi på position nr 11 (egentligen position 44), där det står en 11:a. Därav 11:an i utskriften. Då vi i nästa anrop gör fseek(infil,-20*s,seek_end); ställer vi oss alltså 20 steg till vänster (om vi antar att filen börjar till vänster) om slutet, slutpositionen får då uppfattar som 101, eftersom filen har 100 positioner. 20 steg tillbaka ger oss position 81 och det är vad vi ser i nästa utskrift. Då vi läst detta har filpositionen ökats på med 1 (egentligen 4), och då är vi i position 82. När vi då gör fseek(infil,2*s,seek_cur); går vi alltså framåt två steg och hamnar på position 84 som vi ser i sista utskriften. 22.8 Formaterad IO med strängar: Läs själva johnnyp@kth.se Sidan 11 av 11