Kapitel 13
Input/Output og Filer

Kurt Nørmark
Institut for Datalogi, Aalborg Universitet


Sammendrag
Forrige lektion
Stikord Referencer Indhold
I denne lektion beskriver vi filbegrebet og de forskellige operationer på filer. Vi slutter af med en diskussion af hvordan structures can udskrives på filer og indlæses fra filer.


Introduktion til filer

Oversigt over filbegreber
Slide Indhold Stikord
Referencer 

En fil er en samling af data, typisk lagret på et eksternt og permanent lagermedium

  • Datatilgang:

    • Sekventiel

    • Random access

  • Dataindhold:

    • Tegn

      • Tekstfiler der indeholder bytes der tolkes som tegn fra et alfabetet (f.eks. ASCII alfabetet).

    • Andre data enheder

      • Binære filer der indeholder bitmønstre fra andre datatyper (eksempelvis structs)

Filer er ressourcer som administreres af et operativsystem

Fra et programmeringssprog er det muligt at knytte en forbindelse til en fil i operativsystemet

Sekventielle filer
Slide Indhold Stikord
Referencer 

Sekventielle filer er modelleret efter egenskaberne af strengt sekventielle medier, såsom magnetbånd

Begrebet sekventiel fil: En sekventiel fil læses altid i den rækkefølge den er skrevet. Rækkefølgen af enheder i en sekventiel fil afspejler direkte rækkefølgen af de udførte skriveoperationer på filen.

  • Mode of operation

    • En sekventiel fil tilgås enten i læse mode eller skrive mode

    • I det simple tilfælde kan man ikke både læse fra og skrive til en fil

  • Ydre enheder som sekventielle filer

    • Tastaturet modelleres typisk som en sekventiel fil i læse mode

      • Standard input

    • Skærmen modelleres typisk som en sekventiel fil i skrive mode

      • Standard output

Random access filer
Slide Indhold Stikord
Referencer 

Det er unaturligt at insistere på, at filer på en harddisk er sekventielle filer

Det er muligt og naturligt at kunne skrive og læse i en mere vilkårlig rækkefølge

På trods af dette tilgår vi typisk filer på harddiske sekventielt


Filer i C

Strukturen FILE
Slide Indhold Stikord
Referencer 

Strukturen FILE beskriver egenskaberne af en fil, herunder et afsat bufferområde

  • Egenskaberne (felterne) i FILE strukturen

    • Den nuværende filposition

    • Detaljer om filbufferen

    • Andre interne aspekter

Programmøren udnytter normalt ikke kendskab til detaljerne i FILE

FILE og de tilknyttede operationer er et eksempel på en abstrakt datatype

Sekventielle filer i C
Slide Indhold Stikord
Referencer 

Filer i C håndteres via biblioteket stdio.h

Sekventielle filer i C kan ses som abstraktioner over filerne i operativsystemet

Sådanne abstraktioner omtales ofte som streams

  • Åbning af en fil

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

    • Knytter forbindelsen til en fil i operativsystemets hierarki af filer

    • Angivelse af opening mode

    • Etablerer og returnerer en pointer til en FILE struct

  • Arbejde på filen

    • Sker ved brug af operationer fra stdio.h

    • Filen husker sin nuværende position

    • Mange operationer på filen foregår relativt til den nuværende position

  • Lukning af filen

    • int fclose(FILE *stream)

    • Tømmer buffere og frigiver interne ressourcer

    • Bryder forbindelsen til filen i operativsystemet

Filbuffering
Slide Indhold Stikord
Referencer 

En buffer er et lagerområde mellem programmet og det fysiske lager

Figur. En illustration af filbuffere mellem den ydre enhed og programmet

Funktionen fflush tømmer en output buffer

Simpel skrivning og læsning af en fil
Slide Indhold Stikord
Referencer 

Vi viser her elementære eksempler på skrivning og læsning af tegn fra tekstfiler i C

Program: Skrivning af tegn fra en tekststreng på en tekstfil.
#include <stdio.h>
#include <stdlib.h>

int main(void) {

  FILE *output_file_pointer;
  char *str = "0123456789abcdefghijklmnopqrstuvw";

  output_file_pointer = fopen("first-file", "w");

  if (output_file_pointer != NULL){      /* File could be opened */
    while (*str != '\0'){
      fputc(*str, output_file_pointer);
      str++;
    }
  
    fclose(output_file_pointer);
  }
  else{
    printf("Could not open output file. Bye.");
    exit(EXIT_FAILURE);
  }

  return 0;
}

Program: Læsning af tegn fra en tekstfil til en tekststreng.
#include <stdio.h>
#include <stdlib.h>
#define MAX_STR_LEN 100

int main(void) {

  FILE *input_file_pointer;
  char str[MAX_STR_LEN];
  int ch;
  int i = 0;

  input_file_pointer = fopen("first-file", "r");

  if (input_file_pointer != NULL){        /* File could be opened */
    while ((ch = fgetc(input_file_pointer)) != EOF){
      str[i] = ch;
      i++;
    }
    str[i] = '\0';

    printf("Read from file: %s\n", str);
 
    fclose(input_file_pointer);
  }
  else{
    printf("Could not open input file. Bye.");
    exit(EXIT_FAILURE);
  }

  return 0;
}

Henvisning

Program: Et program der laver dobbelt linieafstand i en tekstfil - input og output filer via parametre til main.
/* Adapted from the book 'C by Dissection' by Kelly and Pohl */
#include <stdio.h>
#include <stdlib.h>

void   double_space(FILE *ifp, FILE *ofp);
void   prn_info(char *pgm_name);

int main(int argc, char **argv)
{
   FILE   *ifp, *ofp;

   if (argc != 3) {
      prn_info(argv[0]);
      exit(1);
   }
   ifp = fopen(argv[1], "r");             /* open for reading */
   ofp = fopen(argv[2], "w");             /* open for writing */
   double_space(ifp, ofp);
   fclose(ifp);
   fclose(ofp);
   return 0;
}

void  double_space(FILE *ifp, FILE *ofp)
{
   int  c;

   while ((c = fgetc(ifp)) != EOF) {
      fputc(c, ofp);
      if (c == '\n') fputc('\n', ofp);    /* duplicate newline */
   }
}  

void prn_info(char *pgm_name)
{
   printf("\n%s%s%s\n\n%s%s\n\n",
      "Usage:  ", pgm_name, "  infile  outfile",
      "The contents of infile will be double-spaced ",
      "and written to outfile.");
}

Når en fil er læst til ende vil fgetc og andre tilsvarende funktioner returnere EOF værdien

putc og getc svarer til hhv. fputc og fgetc, men er typisk implementeret som makroer i stedet for funktioner

EOF repræsenteres typisk af heltallet -1

Standard input, output og error
Slide Indhold Stikord
Referencer 

De tre filer stdin, stdout, og stderr åbnes automatisk ved program start

Hver af stdin, stdout, og stderr er pointere til FILE

  • Standard input - stdin

    • Default knyttet til tastaturet

    • Kan omdirigeres til en fil med file redirection

  • Standard output - stdout

    • Default knyttet til skærmen

    • Kan omdirigeres til en fil med file redirection

  • Standard error - stderr

    • Default knyttet til skærmen

    • Benyttes f.eks. hvis stdout ikke tåler output af fejlmeddelelser

Henvisning

Der findes en række behændige filfunktioner, som implicit arbejder på stdin og stdout i stedet for at få overført en pointer til en FILE structure

Eksempelvis svarer getchar() til getc(stdin) og putchar(ch) svarer til putc(ch,stdout)

Fil opening modes
Slide Indhold Stikord
Referencer 

Foruden læse- og skrivemodes tilbydes yderligere et antal andre opening modes

Tabel. En tabel der beskriver betydningen af de forskellige opening modes af filer.
rÅbne eksisterende fil for input
wSkabe ny fil for output
aSkabe ny fil eller tilføje til eksisterende fil for output
r+Åbne eksisterende fil for både læsning og skrivning. Start ved begyndelse af fil.
w+Skabe en ny fil for både læsning og skrivning
a+Skabe en ny fil eller tilføje til eksisterende fil for læsning og skrivning
 

Henvisning

Program: Tilføjelse af tegn til en tekstfil.
#include <stdio.h>
#include <stdlib.h>

int main(void) {

  FILE *output_file_pointer;
  char *str = "Another string";
  int i;

  output_file_pointer = fopen("first-file", "a");

  if (output_file_pointer != NULL) {
    while (*str != '\0'){
      fputc(*str, output_file_pointer);
      str++;
    }

    fclose(output_file_pointer);
  }
  else{
    printf("Could not open file. Bye.");
    exit(EXIT_FAILURE);
  }

  return 0;
}

Program: Læsning og skrivning af fil med r+ - hvert andet tegn i filen erstattes med 'X' - virker ikke...
/* A variant of the program that does not work */

#include <stdio.h>
#include <stdlib.h>
#define MAX_STR_LEN 100

int main(void) {

  FILE *file_pointer;
  char str[MAX_STR_LEN], ch;
  int i = 0;

  file_pointer = fopen("first-file", "r+");

  if (file_pointer != NULL){
    while ((ch = fgetc(file_pointer)) != EOF){
      str[i] = ch;
      fputc('X', file_pointer);             /* Does not work ... */
      i++;
    }
    str[i] = '\0';

    printf("Read from file: %s\n", str);

    fclose(file_pointer);
  }
  else{
    printf("Could not open file. Bye.");
    exit(EXIT_FAILURE);
  }

  return 0;
}

Program: Læsning og skrivning af fil med r+ - hvert andet tegn i filen erstattes med 'X' - virker!.
/* This version works. Unfortunately, it is a bit weird... */
#include <stdio.h>
#include <stdlib.h>
#define MAX_STR_LEN 100

int main(void) {

  FILE *file_pointer;
  char str[MAX_STR_LEN], ch;
  int i = 0;

  file_pointer = fopen("first-file", "r+");

  if (file_pointer != NULL){
    while ((ch = fgetc(file_pointer)) != EOF){
      str[i] = ch;

      fseek(file_pointer, 0, SEEK_CUR);     /* Weird - but necessary in MingW.        */
      fputc('X', file_pointer);             /* fflush(file_pointer) also OK in Cygwin */
      fseek(file_pointer, 0, SEEK_CUR); 

      i++;
    }
    str[i] = '\0';

    printf("Read from file: %s\n", str);

    fclose(file_pointer);
  }
  else{
    printf("Could not open output file. Bye.");
    exit(EXIT_FAILURE);
  }

  return 0;
}

Program: first-file efter at ovenstående program er kørt.
0X2X4X6X8XaXcXeXgXiXkXmXoXqXsXuXwXnXtXeX XtXiXgX

Program: Standard output fra ovenstående program.
Read from file: 02468acegikmoqsuwnte tig

C muliggør åbning af binære filer ved at tilføje et b til opening mode

I C på Unix er der ikke forskel på binære filer og tekstfiler

Detaljer om fil opening modes
Slide Indhold Stikord
Referencer 

En mere detaljeret og nuanceret forståelse af forskelle og ligheder mellem fil opening modes

Tabel. En tabel der i størrel detalje beskriver betydningen af de forskellige opening modes af filer.
rwar+w+a+
Filen skal eksistere på forhåndja nejnejja nejnej
Filindhold tabes - slettesnejja nejnejja nej
Læsning tilladtja nejnejja ja ja
Skrivning tilladtnejja ja ja ja ja
Skrivning i bagende af filnejnejja nejnejja
 

Tabellen er reproduceret fra 'C - A Reference Manual' af Harbison og Steele

Funktioner der arbejder på sekventielle filer
Slide Indhold Stikord
Referencer 

Vi viser en oversigt over udvalgte funktioner, som processerer filer sekventielt

  • int fgetc(FILE *stream)

    • Læser og returnerer næste tegn fra stream. Returnerer EOF hvis der er end of file. EOF er et negativt heltal.

  • int fputc(int c, FILE *stream)

    • Skriver tegnet c til stream. Returnerer c, eller EOF.

  • int ungetc(int c, FILE *stream)

    • Omgør læsningen af c. Tegnet c puttes altså tilbage i bufferen for stream så det kan læses endnu en gang. Kun én pushback understøttes generelt.

  • char *fgets(char *line, int size, FILE *stream)

    • Læser en linie ind i line, dog højst size - 1 tegn. Stopper ved både newline og EOF. Newline læses også. Afslutter line med '\0' tegnet.

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

    • Skriver strengen s til stream, uden det afsluttende '\0' tegn.

  • int feof(FILE *stream)

    • Returnerer hvorvidt en tidligere kaldt funktion har signaleret en EOF betingelse.

Opgave 13.3. Læsning af k ord fra fil

Skriv en funktion

  int getwords(FILE *ifp, int k, char *words)

der, om muligt, læser k ord fra tekstfilen der er udpeget af pointeren ifp. Ordene skal læses over i tekststrengen words (som allokeres af den funktion, som kalder getwords). Hvis det ønskes kan ordene organiseres linievis i words, eller blot med 'white space' imellem ordene.

Den programmerede funktion skal returnere det faktiske antal af læste ord (som kan være mindre end k).

Opgave 13.3. En simpel grep funktion

grep er en Unix kommando der søger efter linjer i en tekstfil, som matcher en søgestreng. I denne opgave vil vi programmere en meget simpel variant af grep, som udskriver de tekstlinjer, i hvilke et bestemt søgeord forekommer.

Vi ønsker at anvende det udviklede program således fra kommandolinjen:

  search programmering my-file.txt

eller måske snarere:

  ./search programmering my-file.txt

Dette skal udskrive alle de linjer i my-file.txt som indholder strengen "programmering".

Programmet skal anvende programparametre, som forklaret på denne slide.


Directories i C

Directories i C
Slide Indhold Stikord
Referencer 

Directories (kataloger/mapper) kan håndteres via brug af funktioner fra dirent.h biblioteket

dirent.h er et psuedo-standard bibliotek for C

  • Udvalgte funktioner fra dirent.h

    • Typerne DIR og struct dirent

    • DIR *opendir(const char *dirname)

    • struct dirent* readdir(DIR *dirp)

    • void rewinddir(DIR *dirp)

    • int closedir(DIR *dirp)

Program: Et list directory program - lånt fra Wikepedia.
/**************************************************************
 * A simpler and shorter implementation of ls.
 * Adapted from Wikipedia.
 **************************************************************/

#include <stdio.h>
#include <dirent.h>
 
int listdir(const char *path) {
  struct dirent *entry;
  DIR *dp;
 
  dp = opendir(path);
  if (dp == NULL) {
    perror("opendir");
    return -1;
  }
 
  while((entry = readdir(dp)))
    puts(entry->d_name);
 
  closedir(dp);
  return 0;
}
 
int main(int argc, char **argv) {
  int counter = 1;
 
  if (argc == 1)
    listdir(".");
  else if (argc == 2)
    listdir(argv[1]);
  else printf("Usages: ls    or   ls dirPath\n");
 
  return 0;
}

Program: Mulig oversættelse og kørsel af programmet på ls.c.
$ gcc ls.c -o my-ls
$ my-ls.exe
.
..
ls.c
my-ls.exe

Funktioner på random access filer
Slide Indhold Stikord
Referencer 

Begrebsligt kan man tilgå en random access fil på samme måde som et array

  • Random access funktioner:

    • int fseek(FILE *fp, long offset, int place)

      • Sætter filpositionen for næste læse- eller skriveoperation

      • offset er en relativ angivelse i forhold til place

      • place er SEEK_SET, SEEK_CUR, SEEK_END svarende til filstart, nuværende position, og filafslutning

    • long ftell(FILE *fp)

      • Returnerer den nuværende værdi af filpositionen relativ til filstart

    • void rewind(FILE *fp)

      • Ækvivalent til fseek(fp, 0L, SEEK_SET)

Program: Et program der læser en fil baglæns.
/* Echoes a file, in reverse, to standard output */

#include <stdio.h>

#define   MAXSTRING   100

int main(void){
   char   filename[MAXSTRING];
   int    c;
   FILE   *ifp;

   // Prompt for file name:
   fprintf(stderr, "\nInput a filename:  ");
   scanf("%s", filename);

   // Read chars from file in reverse order:
   ifp = fopen(filename, "rb");    // MingW: b (for binary) seems necessary.
   fseek(ifp, 0, SEEK_END); 
   fseek(ifp, -1, SEEK_CUR);       // Now ready to read last char
   while (ftell(ifp) > 0) {        
      c = getc(ifp);        
      putchar(c);                  // Echo on standard output
      fseek(ifp, -2, SEEK_CUR);    
   }

   // The only character that has not been printed
   // is the very first character in the file. Handle it now:
   fseek(ifp, 0, SEEK_SET);
   c = getc(ifp);
   putchar(c); putchar('\n');

   // Close file:
   fclose(ifp);
   return 0;
}


Formateret output og input

Formateret output - printf familien (1)
Slide Indhold Stikord
Referencer 

Familien af printf funktioner tilbyder tekstuel output af værdier i forskellige typer, og god kontrol af output formatet

Familien omfatter funktioner der skriver på standard output, i en fil, og i en tekststreng

  • Formålet med printf

    • Pretty printing - formateret udskrift

    • Konvertering af værdier fra forskellige typer til tekst

    • Kontrol af det tekstuelle format - via en kontrolstreng

  • Tre varianter i printf familien:

    • printf(kontrolstreng, ...)

    • fprintf(fil, kontrolstreng, ...)

    • sprintf(streng, kontrolstreng, ...)

Henvisning

Formateret output - printf familien (2)
Slide Indhold Stikord
Referencer 

Vi ser her på fortolkningen af en kontrolstreng via et eksempel på en placeholder

Figur. Nedbrydning af kontrolstrengen for %-#08.3hd. Denne figur er inspireret fra 'A C Reference Manual' af Harbison & Steele

  • %-#08.3hd

    • Conversion letter: d for heltalsformatering

    • Flags: - left justify, # use variant (ikke anvendelig her), 0 zero padding

    • Min. field width: 8. Mindste plads som afsættes

    • Precision: 3. Mindste antal cifre der udskrives - bruger 0 paddings om nødvendig

    • Size modifier: h short

Formateret output - printf familien (3)
Slide Indhold Stikord
Referencer 

Lærebogen - og referencebøger om C - har mange detaljer om printf

  • Udvalgte bemærkninger om printf funktionerne

    • Returnerer antallet af udskrevne tegn

    • Man kan kontrollere højre og venstre justering i felt

      • Et - flag betyder venstrejustering

    • Minimum field width og precision kan overføres som parametre til printf

      • Angivelse af minimum field width og precision med *

Program: Udskrivning af heltal: Illustration af *.* field width og precision .
#include <stdio.h>

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

  int int_number, n, m, printf_res;

  while (1){
   /* Prompt for an integer, minimum field with, and precision */
   printf("Enter an integer, min. field width, and precision\n");
   scanf(" %d %d %d", &int_number, &n, &m);

   printf_res = printf("%*.*d\n", n, m, int_number);

   printf("printf returned: %d\n", printf_res);
  }

}

Program: Udskrivning af doubles: Illustration af *.* field width og precision .
#include <stdio.h>

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

  int n, m, printf_res;
  double double_number;

  while (1){
   /* Prompt for a double, minimum field with, and precision */
   printf("Enter a double, min. field width, and precision\n");
   scanf(" %lf %d %d", &double_number, &n, &m);

   printf_res = printf("%*.*f\n", n, m, double_number);

   printf("printf returned: %d\n", printf_res);

  }

}

Viden om printf tilegnes efter behov

Formateret input - scanf familien (1)
Slide Indhold Stikord
Referencer 

Familien af scanf funktioner tilbyder tekstuel input (parsing, scanning) af værdier i forskellige typer

Familien omfatter funktioner der læser fra standard input, fra en bestemt fil, og fra en tekststreng

  • Formålet med scanf

    • Parsing af tekst - Fortolkning af tegn

    • Konvertering af tekstudsnit til værdier i forskellige typer

    • Assignment af værdier til parametre overført via pointere - call by reference parametre

  • Tre varianter i scanf familien:

    • scanf(kontrolstreng, ...)

    • fscanf(fil, kontrolstreng, ...)

    • sscanf(streng, kontrolstreng, ...)

Parsing er vanskeligere end pretty printing

Formateret input - scanf familien (2)
Slide Indhold Stikord
Referencer 

Begreber som knytter sig til formateret input med scanf

  • Kontrolstreng med et antal direktiver

  • Tre slags direktiver

    • Almindelige tegn: Matcher de angivne tegn

    • White space: Matcher så mange mellemrum, tabs, og linieskift som mulig

    • Konverteringsspecifikation: %...c:
      Matcher input efter givne regler og konverteres til en værdi som angivet af c

  • Scan field:

    • Et sammenhængende område i inputtet som forsøges konverteret med en given konverteringsspecifikation.

    • Scan width: En angivelse i konverteringsspecifikationen af den maksimale længde af et scan field

    • Består af et område uden white space (pånær for %c)

Program: Illustration af directives med almindelige tegn.
/* Illustrates match of 'ordinary' characters in the control string */

#include <stdio.h>

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

  int scan_res;
  double double_number = 0;

  printf("Enter a dollar char followed by a double\n");
  scan_res = scanf("$%lf", &double_number);
  printf("Number read: %f\n", double_number);
  printf("scanf returned: %d\n", scan_res);
}

Program: Illustration af brugen af scan width ved læsning af en streng.
/* Illustrates use of scan width - reading a text string */

#include <stdio.h>

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

  int scan_res_1, scan_res_2;
  char str[20];

  printf("Enter a test string - at most 6 chars will be read\n");
  scan_res_1 = scanf("%6s", str);
  printf("This string has been read: %s\n", str);
  printf("scanf returned: %d\n", scan_res_1);

  /* Now reading more of the string */
  scan_res_2 = scanf("%s", str);
  printf("Next scanf read: %s\n", str);
  printf("scanf returned: %d\n", scan_res_2);
}

Program: Illustration af brugen af scan width ved læsning af en double.
/* Illustrates use of scan width - reading a double */

#include <stdio.h>

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

  int scan_res_1, scan_res_2;
  double d;

  printf("Enter a double - at most 6 chars will be read\n");
  scan_res_1 = scanf(" %6lf", &d);
  printf("This double has been read: %f\n", d);
  printf("scanf returned: %d\n", scan_res_1);

  /* Now reading yet another double */
  scan_res_2 = scanf(" %lf", &d);
  printf("This double has been read: %f\n", d);
  printf("scanf returned: %d\n", scan_res_2);

}

Formateret input - scanf familien (3)
Slide Indhold Stikord
Referencer 

Nogle væsentlige bemærkninger om scanf funktionerne

  • Returværdi

    • EOF hvis input 'løber tør' - input failure

    • Antallet af gennemførte konverteringer (indtil evt. matching failure)

  • Assignment suppression *

    • Matching - men ingen assignment til pointervariable

    • Tæller ikke som en konvertering

    • Angives lige efter % tegnet

  • Scan set af formen [cde]

    • Indlæsning af en streng fra alfabetet bestående af 'c', 'd' og 'e'.

  • Scan set af formen [^cde]

    • Indlæsning af en streng afsluttet af et tegn fra alfabetet bestående af 'c', 'd' og 'e'.

Formateret input - scanf familien (4)
Slide Indhold Stikord
Referencer 

Vi viser et antal eksempler på brug af scanf og scan sets

Program: Et program der læser ikke-blanke liner fra en fil og udskriver disse på standard output.
#include <stdio.h>
#define LINE_MAX 300

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

  FILE *ifp = fopen(argv[1],"r"); 
  int i = 0;

  char line[LINE_MAX];

  while (fscanf(ifp," %[^\n]", line) == 1){    
                                               /* Space: Skip white space.                      */
                                               /* The space is very important for this example. */
                                               /* Then scan until (but not including) new line. */
    printf("%s\n", line);
    i++;
  }

  printf("\n i: %d\n", i);

  fclose(ifp);
  return 0;
}

Program: Et andet program der adskiller alfabetiske og numeriske afsnit i en tekst - læser fra stdin.
#include <stdio.h>

int main(void) {

  char alph[25], numb[25];
  int numres;

  printf("Enter input, such as \"aaa111bbb222\"\n");

  while (scanf("%[abcdefghijklmnopqrstuvwxyz]", alph) == 1){
    numres = scanf("%[0123456789]", numb);

    if (numres != 1) 
       printf("MISMATCH");
    else
       printf("Alphabetic: %s\n"
              "Numeric: %s\n\n",
              alph, numb);
  }
  
  return 0;
}

Program: Et andet program der adskiller alfabetiske og numeriske afsnit i en tekst - læser fra en streng - virker ikke.
/* Does NOT work!  Reports repeated MISMATCHes */

#include <stdio.h>

int main(void) {

  char *str = "abc135def24681ghi3579";
  char alph[25], numb[25];
  int numres;

  while (sscanf(str,"%[abcdefghijklmnopqrstuvwxyz]", alph) == 1){
    numres = sscanf(str,"%[0123456789]", numb);

    if (numres != 1) 
       printf("MISMATCH");
    else
       printf("Alphabetic: %s\n"
              "Numeric: %s\n\n",
              alph, numb);
  }
  
  return 0;
}

Program: Et andet program der adskiller alfabetiske og numeriske afsnit i en tekst - læser fra en streng - virker.
/* Works as intended */

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

int main(void) {

  char *str = "abc135def24681ghi3579";
  char alph[25], numb[25];
  int numres;

  while (sscanf(str,"%[abcdefghijklmnopqrstuvwxyz]", alph) == 1){
    str += strlen(alph);

    numres = sscanf(str,"%[0123456789]", numb);
    str += strlen(numb);

    if (numres != 1) 
       printf("MISMATCH");
    else
       printf("Alphabetic: %s\n"
              "Numeric: %s\n\n",
              alph, numb);
  }
  
  return 0;
}

Program: Millies product codes - afleveringsopgave - PSPD8 opgave 1 side 490 - med sscanf.
#include <stdio.h>
#define PART_MAX 20

void separate_product_code(const char *code, char warehouse[], char productid[], char qualifier[]);

int main(void) {
  char *code = "ATL1203S14",
       warehouse[PART_MAX], productid[PART_MAX], qualifier[PART_MAX];

  separate_product_code(code, warehouse, productid, qualifier);

  printf("Product code: %s\n", code);
  printf("Parts: %s, %s, %s\n", warehouse, productid, qualifier);
  return 0;
}

void separate_product_code(const char *code, char warehouse[], char productid[], char qualifier[]){
  sscanf(code, "%[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ]" 
               "%[0123456789]" 
               "%s", 
               warehouse, productid, qualifier);
}

Program: Millies product codes - alternativ implementation.
#include <stdio.h>
#define PART_MAX 20

void separate_product_code(const char *code, char warehouse[], char productid[], char qualifier[]);

int main(void) {
  char *code = "ATL1203S14",
       warehouse[PART_MAX], productid[PART_MAX], qualifier[PART_MAX];

  separate_product_code(code, warehouse, productid, qualifier);

  printf("Product code: %s\n", code);
  printf("Parts: %s, %s, %s\n", warehouse, productid, qualifier);
  return 0;
}

void separate_product_code(const char *code, char warehouse[], char productid[], char qualifier[]){
  sscanf(code, "%[^0123456789]"
               "%[0123456789]" 
               "%s", 
               warehouse, productid, qualifier);
}

Program: Program output.
Product code: ATL1203S14
Parts: ATL, 1203, S14

Formateret input - scanf familien (5)
Slide Indhold Stikord
Referencer 

Vi studerer to eksempler på læsning af linje-orienteret input med fscanf

Køreplan og TV-oversigt

Program: Linje-opdelt input fil: Busplan.
Linje 12 17:20 18:30 19:40
Linje 2   7:20  8:30  9:40

Program: Et program der læser to linjer fra en simpel busplan.
#include <stdio.h>

int read_bus_line(FILE *ifp);

int main(void) {
  FILE *ifp = fopen("busplan", "r");  
  int res;

  res = read_bus_line(ifp);
  res = read_bus_line(ifp);

  fclose(ifp);
  
  return 0;
}

int read_bus_line(FILE *ifp){
  int res, busnr;
  char t1[10], t2[10], t3[10];

  /* Linje 12 17:20 18:30 19:40 */
  res = fscanf(ifp, " Linje %d %s %s %s", &busnr, t1, t2, t3);

  printf("Buslinje: %3d.  %5s. %5s. %5s. Scanres: %d\n", busnr, t1, t2, t3, res);
  return res;
}

Program: Output fra programmet.
Buslinje:  12.  17:20. 18:30. 19:40. Scanres: 4
Buslinje:   2.   7:20.  8:30.  9:40. Scanres: 4

Program: Et alternativt program der læser to linjer fra en simpel busplan.
#include <stdio.h>

int read_bus_line(FILE *ifp);

int main(void) {
  FILE *ifp = fopen("busplan", "r");  
  int res;

  res = read_bus_line(ifp);
  res = read_bus_line(ifp);

  fclose(ifp);
  
  return 0;
}

int read_bus_line(FILE *ifp){
  int res, busnr,
      t1,t2, t3,t4, t5,t6;

  /* Linje 12 17:20 18:30 19:40 */  
  res = fscanf(ifp, " Linje %d %d:%d %d:%d %d:%d ", &busnr, &t1, &t2, &t3, &t4, &t5, &t6);

  printf("Buslinje: %3d.  %2d:%2d %2d:%2d %2d:%2d.   Scanres: %d\n", busnr, t1,t2, t3,t4, t5,t6,  res);
  return res;
}

Program: Output fra programmet.
Buslinje:  12.  17:20 18:30 19:40.   Scanres: 7
Buslinje:   2.   7:20  8:30  9:40.   Scanres: 7

Program: Linje-opdelt input fil: TV-oversigt.
Kanal 1     Start 21.30   "tv avisen"        Slut 20.00
Kanal 13    Start 18.00   "bamse og kylling" Slut 18.25

Program: Et program der læser to linjer fra en simpel tv plan.
#include <stdio.h>

int read_tv_line(FILE *ifp);

int main(void) {
  FILE *ifp = fopen("tvplan", "r");  
  int res;

  res = read_tv_line(ifp);
  res = read_tv_line(ifp);

  fclose(ifp);
  
  return 0;
}

int read_tv_line(FILE *ifp){
  int res, kanal;
  char starttid[10], sluttid[10], program[20];

  /* Kanal 1     Start 21.30   "tv avisen"        Slut 20.00 */
  res = fscanf(ifp, " Kanal %d Start %s \"%[abcdefghijklmnopqrstuvwxyz ]\" Slut %s", 
               &kanal, starttid, program, sluttid);

  printf("Kanal: %3d. Program: '%20s'.  Start: %8s. Slut: %8s.\n  Scanres: %d.\n", 
          kanal, program, starttid, sluttid, res);

  return res;
}

Program: Program output.
Kanal:   1. Program: '           tv avisen'.  Start:    21.30. Slut:    20.00.
  Scanres: 4.
Kanal:  13. Program: '    bamse og kylling'.  Start:    18.00. Slut:    18.25.
  Scanres: 4.

Henvisning

Anbefaling: Indlæs de relevante dele af en input-linje som tekststrenge, som efterfølgende bearbejdes og transformeres til data af andre typer


Input og output af structures

Input/Output af structures (1)
Slide Indhold Stikord
Referencer 

Det er ofte nyttigt at udskrive en eller flere structs på en fil, og tilsvarende at kunne indlæse en eller flere structs fra en fil

Væsenligste egenskab: En udskrevet struct s skal genindlæses som en struct, der er magen til s

Serialisering

  • Tekstfil

    • Tekstuel encode og decode af en struct

    • F.eks. én linje pr. struct

  • Binær fil

    • Direkte input/output af de binære data i en struct

Input/Output af structures (2)
Slide Indhold Stikord
Referencer 

Vi viser en linje-orienteret løsning på en tekstfil

Program: Programmet der udskriver bøger på en output fil.
#include <stdio.h>
#include "book-read-write.h"

int main(void) {

  book *b1, *b2;
  FILE *output_file;

  b1 = make_book("C by Dissection", "Kelly and Pohl", 
                  "Addison Wesley", 2002, 1);
  b2 = make_book("The C Programming Language", 
                  "Kernighhan and Ritchie",
                  "Prentice Hall", 1988, 1);

  output_file = fopen("books.dat", "w");

  print_book(b1, output_file);
  print_book(b2, output_file);

  fclose(output_file);

  return 0;

}

Program: Den skrevne fil.
C@by@Dissection Kelly@and@Pohl Addison@Wesley 2002 1
The@C@Programming@Language Kernighhan@and@Ritchie Prentice@Hall 1988 1

Program: Programmet der indlæses bøger fra en input fil.
#include <stdio.h>
#include "book-read-write.h"

int main(void) {

  book *b1, *b2;

  FILE *input_file;
  input_file = fopen("books.dat", "r");

  b1 = read_book(input_file);
  b2 = read_book(input_file);

  prnt_book(b1);   prnt_book(b2);

  fclose(input_file);

  return 0;

}

Program: Output fra programmet - på skærmen.
Title: C by Dissection
Author: Kelly and Pohl
Publisher: Addison Wesley
Year: 2002
University text book: yes

Title: The C Programming Language
Author: Kernighhan and Ritchie
Publisher: Prentice Hall
Year: 1988
University text book: yes

Program: Header filen book-read-write.h.
#define PROTECTED_SPACE '@'
#define PROTECTED_NEWLINE '$'
#define BUFFER_MAX 1000

struct book {
  char *title, *author, *publisher;
  int publishing_year;
  int university_text_book;
};

typedef struct book book;

book *make_book(const char *title, const char *author, 
                const char *publisher, 
		int year, int text_book);

void prnt_book(book *b);

void print_book(book *b, FILE *ofp);

book *read_book(FILE *ifp);

Program: Implementationen af biblioteket - book-read-write.c.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "book-read-write.h"

/* Allocated memory to a book, and allocate strings to initialized by the 
   string constants passed as parameter */
book *make_book(const char *title, const char *author, const char *publisher, 
               int year, int text_book){
  static book *result;
  result = (book*)malloc(sizeof(book));
  //  strcpy(result->title,title);

  /* Copy string constants to dynamically allocated strings */
  result->title = strcpy((char *)calloc(strlen(title)+1,sizeof(char)), 
                         title);
  result->author = strcpy((char *)calloc(strlen(author)+1,sizeof(char)), 
                         author);
  result->publisher = strcpy((char *)calloc(strlen(publisher)+1,sizeof(char)), 
                         publisher); 
  result->publishing_year = year;
  result->university_text_book = text_book;
 
  return result;
}

/* print book b to standard output */
void prnt_book(book *b){
  char *yes_or_no;

  yes_or_no = (b->university_text_book ? "yes" : "no"); 
  printf("Title: %s\n"
         "Author: %s\n"
         "Publisher: %s\n"
         "Year: %4i\n"
         "University text book: %s\n\n",
         b->title, b->author, b->publisher, 
         b->publishing_year, yes_or_no);
}

char *white_space_protect(char *str){
  int str_lgt = strlen(str), i, j;
  for(i = 0; i < str_lgt; i++){
    if (str[i] == ' ')
      str[i] = PROTECTED_SPACE;
    else if (str[i] == '\n')
      str[i] = PROTECTED_NEWLINE;
  } 
  return str;
} 

char *white_space_deprotect(char *str){
  int str_lgt = strlen(str), i;
  for(i = 0; i < str_lgt; i++){
    if (str[i] == PROTECTED_SPACE)
      str[i] =  ' ';
    else if (str[i] == PROTECTED_NEWLINE)
      str[i] = '\n';
  }
  return str;
} 

/* Encode the book pointed to by p in the string str */
void encode_book(book *b, char *str){
  sprintf(str, "%s %s %s %i %i\n", 
          white_space_protect(b->title), 
          white_space_protect(b->author), 
          white_space_protect(b->publisher), 
          b->publishing_year, b->university_text_book);
} 

book *decode_book(char *str){
  char book_title[100], book_author[100], book_publisher[100];
  int book_year, book_uni;

  sscanf(str, "%s %s %s %i %i", 
         book_title, book_author, book_publisher,
         &book_year, &book_uni);
    
  return make_book(white_space_deprotect(book_title), 
                   white_space_deprotect(book_author),
                   white_space_deprotect(book_publisher), 
                   book_year, book_uni);
}


void print_book(book *b, FILE *ofp){
  char buffer[BUFFER_MAX];
  encode_book(b, buffer);
  fprintf(ofp, "%s", buffer);
}

book *read_book(FILE *ifp){
  char buffer[BUFFER_MAX];
  fgets(buffer, BUFFER_MAX, ifp);
  return decode_book(buffer);
}

Program: Compilering af programmerne.
gcc -c book-read-write.c

gcc book-write-prog.c book-read-write.o -o book-write-prog

gcc book-read-prog.c book-read-write.o -o book-read-prog

Opgave 13.4. Input og Output af structsVi vil antage at vi har en struct som beskriver data om en person, så som
  struct person {
    char *name;
    int age;
    char sex;
  }
hvor sex er enten tegnet 'm' eller 'f'. I denne øvelse bliver du bedt om at programmere funktioner, som kan udskrive et antal personer på en fil, og som efterfølgende kan indlæse disse igen.

Hvis det er hensigtsmæssigt, er det OK at allokere name som et char array: char name[MAX_NAME_SIZE].

Konkret, skal der skrives to funktioner
    void print_person(struct person *p, FILE *ofp);
    person *read_person(FILE *ifp);

Du kan vælge en tekst-baseret og linie-orienteret fremgangsmåde, som illustreret ved forelæsningen. Som et noget lettere alternativ kan du vælge at anvende en binær fremgangsmåde ved brug af fwrite og fread.

Overvej hvad der skal til for at anvende fwrite og freadstruct person.

Input/Output af structures (3)
Slide Indhold Stikord
Referencer 

Vi ser nu på generelle problemstillinger af struct IO

  • Problemstillinger:

    • Kun felter af taltyper, char, og string håndteres

    • Det giver ikke mening at udskrive og indlæse pointere

    • Evt. nestede structs og arrays kræver specialbehandling

    • Vi vælger en løsning med én struct per linie i en tekst fil

En mere generel anvendelig løsning til serialisering ville være ønskelig

I C er funktionerne fread og fwrite nyttige for læsning og skrivning af binære filer

Binær input/output med fread og fwrite
Slide Indhold Stikord
Referencer 

Structures i C med simple aritmetiske typer kan let udskrives og indlæses binært med fwrite og fread

  • fwrite(pointerTilStruct, størrelseAfStruct, antal, fil)

    • Skriver et antal structs fra et array til en fil

    • Udskriver blot bitmønstret i en struct

    • Returnerer antallet af skrevne structs

  • fread(pointerTilStruct, størrelseAfStruct, antal, fil)

    • Læser et antal structs fra en fil til elementer i det array, som udpeges af første parameter

    • Læser de rå bits

    • Returnerer antallet af læste structs

Eksempler: Binær input/output med fread og fwrite
Slide Indhold Stikord
Referencer 

Eksempler på anvendelse af fwrite og fread

Program: Skrivning af en struct til en binær fil.
#include <stdio.h>

struct str {
  int f1;
  double f2;
  char f3;
  char f4[4];
};

int main(void){
  FILE *ofp;
  struct str rec= {5, 13.71, 'c', "xyz"};
  ofp = fopen("data", "w");

  fwrite(&rec, sizeof(struct str), 1, ofp);  
  fclose(ofp);
  printf("The file named data has been written\n");
  return 0;
}

Program: Tilsvarende læsning af en struct fra en binær fil.
#include <stdio.h>

struct str {
  int f1;
  double f2;
  char f3;
  char f4[4];
};

int main(void){
  FILE *ifp;
  struct str rec;
  ifp = fopen("data", "r");

  fread(&rec, sizeof(struct str), 1, ifp);  

  printf("f1=%d, f2= %f, f3 = %c, f4 = %s\n",
         rec.f1, rec.f2, rec.f3, rec.f4);
  fclose(ifp);
  return 0;
}

Program: Output fra programmet - på skærmen.
f1=5, f2= 13.710000, f3 = c, f4 = xyz

Det giver ikke mening at udskrive og genindlæse pointere med fwrite og fread

Dette betyder at dynamisk allokerede strenge ikke kan håndteres af fread og fwrite

Program: Skrivning af en struct med et pointer felt til en binær fil.
#include <stdio.h>

struct str {
  int f1;
  double f2;
  char f3;
  char *f4;
};

int main(void){
  FILE *ofp;
  struct str rec= {5, 13.71, 'c', "xyz"};
  ofp = fopen("data", "w");

  fwrite(&rec, sizeof(struct str), 1, ofp);  
  fclose(ofp);
  return 0;
}

Program: Tilsvarende læsning af en struct fra en binær fil - virker ikke.
#include <stdio.h>

struct str {
  int f1;
  double f2;
  char f3;
  char *f4;
};

int main(void){
  FILE *ifp;
  struct str rec;
  ifp = fopen("data", "r");

  fread(&rec, sizeof(struct str), 1, ifp);  

  printf("f1=%d, f2= %f, f3 = %c, f4= %s\n", rec.f1, rec.f2, rec.f3, rec.f4);
  fclose(ifp);
  return 0;
}

Program: Output fra programmet - på skærmen.
f1=5, f2= 13.710000, f3 = c, f4= ( 

Opgaver
Slide Indhold Stikord
Referencer 

Opgave 13.6. Tynde matricer

En vilkårlig matrix kan generelt repræsenteres på en tekstfil, med én linje pr. række. Første linje kan angive antallet af rækker og søjler i matricen (altså to heltal). I denne opgave kalder vi dette for den generelle tekstfil repræsentation af vilkårlige matricer.

En tynd matrix er et array af to dimensioner, hvor mange af elementerne er nul.

I denne opgave vil vi skrive et program der undersøtter en speciel tekstfil repræsentation af tynde matricer på tekstfiler, som i visse tilfælde fylder mindre end den generelle repræsentation af matricer på tekstfiler. I den nye repræsentation skal den første linje af tekstfilen indeholde dimensionerne af matricen - to heltal - antal rækker og antal søjler. Hver af de efterfølgende linjer indholder tre tal: Rækkenummer, søjlenummer, og et tal fra matricen (som ikke er nul). Om nødvendigt kan man gøre sig antagelser om rækkefølgen af linjer i den specielle tekstfil repræsentation.

Skriv først en funktion som læser en matrix fra den generelle repræsentation på en tekstfil, og som udskriver den specielle tynde repræsentation på en ny tekstfil.

Skriv dernæst en funktion som løser det omvendte problem: Læsning af en tynd matrix fra en tekstfil og skrivning af en tilvarende matrix på generel form på en anden tekstfil.

Som et eksempel svarer følgende generelle tekstfil repræsentation af matricen

4 3
0.0 0.0 1.0
0.0 0.0 0.0
2.0 0.0 0.0
0.0 3.0 0.0

til følgende specielle tekstfil repræsentation af matricen - som er en tynd matrix

4 3
1 3 1.000000
3 1 2.000000
4 2 3.000000

Denne opgave svarer til opgave 4 side 679 i 6. udgave af lærebogen

Opgave 13.6. Læsning af personkartotek i komma-separeret tekstfil

Skriv et program der indlæser tekstfilen i et array af følgende struct:

  #define MAX_NAME_LGT 50
  struct person{
    char fornavn[MAX_NAME_LGT];
    char efternavn[MAX_NAME_LGT];
    char vejnavn[MAX_NAME_LGT];
    int vejnummer;
    int postnummer;
    char bynavn[MAX_NAME_LGT];
  };

Indholdet af tekstfilen findes også nederst i opgaven.

Tekstfilen er linje-orienteret. Data om én person findes på én linje. Navne-delen, gade-delen og by-delen er adskilt af kommaer, og linjen er afsluttet med et punktum. Der optræder ikke cifre i fornavn efternavne, gadenavne eller bynavne. Hver person har ét fornavn og ét efternavn. Gadenavnet kan bestå af flere ord. Bynavnet består af netop et ord.

Overvej omhyggeligt hvordan du læser bestanddelene af en linje. Det er måske nyttigt for dig at bruge scansets i din løsning, som et alternativ til %s . Vær forsigtig med at ikke at få indlæst et komma som en del af efternavnet. Vær også forsigtig med, at et vejnavn ikke ender med et 'space'.

Sorter de indlæste data efter efternavn og udskriv de sorterede data på en anden fil. Filen skal have følgende simple linje-format:

  By: Efternavn

Denne fil skal genereres med det givne input.

 

Her er input filen til programmet:

Lars Jensen, Engtoften 23, 7182 Bredsten.
Bo Olsen, Gammel Vestergade 56, 4261 Dalmose.
Kurt Jensen, Haderslevvej 15, 8370 Hadsten.
Birte Madsen, Universitetsvej 899, 9000 Aalborg.
Kaj Moberg, Halevindingevej 2, 2670 Greve.
Bo Rise, Hadsund Landvej Nord 56, 8900 Randers.
Ebbe Rise, Hadsund Landvej Syd 58, 9520 Hobro.
Kalle Rastrup, Under Broen 567, 2650 Hvidovre.
Emil Rask, Over Broen 765, 2650 Glostrup.
Jens Larsen, Finkevej 1, 6682 Hovborg.
Lars Olsen, Gammel Vindingevej 2, 7560 Hjerm.

Her er output filen fra programmet:

Hadsten: Jensen
Bredsten: Jensen
Hovborg: Larsen
Aalborg: Madsen
Greve: Moberg
Hjerm: Olsen
Dalmose: Olsen
Glostrup: Rask
Hvidovre: Rastrup
Randers: Rise
Hobro: Rise


Samlede referencer
Indhold Stikord
Læsning og skrivning af tegn fra/til standard input og output tidligere lektion: getchar og putchar
File Redirection
fopen og opening modes
Konvertering mellem strenge og tal
Konvertering mellem tekststrenge og tal

 

Kapitel 13: Input/Output og Filer
Kursets hjemmeside     Forfatteren's hjemmeside     Om frembringelsen af disse sider     Forrige lektion (top)     Næste lektion (top)     Forrige lektion (bund)     Næste lektion (bund)     
Genereret: 12. august 2021, 13:24:26