Thema indholdsfortegnelse -- Tastaturgenvej: 'u'  Forrige tema i denne lektion -- Tastaturgenvej: 'p'  Input/Output og Filer
46.  Input og output af structures

Vi har indtil dette sted kun beskæftiget os med input og output af de fundamentale, skalare typer i C, samt af tekststrenge. I dette afsnit diskuterer vi input og output af structures. Structures blev introduceret i kapitel 39.

I mange praktiske sammenhænge er det nyttigt at kunne udskrive en struct på en fil og at kunne indlæse den igen på et senere tidspunkt, måske fra et helt andet program.

46.1 Input/Output af structures (1)46.3 Binær input/output med fread og fwrite
46.2 Input/Output af structures (2)
 

46.1.  Input/Output af structures (1)
Indhold   Op Forrige Næste   Slide Aggregerede slides    Stikord Programindeks Opgaveindeks 

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

Det er nyttigt at have en standard løsning - et mønster - for dette problem

I dette afsnit vil vi udvikle et mønster for hvordan man kan udskrive og genindlæse structures på/fra filer. Løsningen kræver desværre en del arbejde, som knytter sig de specifikke detaljer i structure typen. I visse situationer kan man bruge en mere maskinnær løsning baseret på udskrivning af et bestemt bitmønster. Sidstnævnte er emnet i afsnit 46.3.

Vi ser på en række sammenhængende C programmer, og et tilhørende programbibliotek som udskriver og indlæser struct book, som vi introducerede i afsnit 39.6.

Det første program, program 46.1, laver to bøger med make_book, som vi mødte i program 39.6. Disse to bøger skrives ud med funktionen print_book på det røde sted. Udskrivningen sker i filen books.dat. Som vi vil se lidt senere er books.dat en tekstfil, dvs. en fil som er inddelt i tegn. Læg mærke til, at vi inkluderer biblioteket book-read-write.h. I denne header fil findes struct book og prototyper af de nødvendig tværgående funktioner for bog output og bog input. Biblioteket book-read-write.h kan ses i program 46.3. Implementationen af funktionerne i biblioteket ses i program 46.4.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#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 46.1    Programmet der udskriver bøger på en output fil.

Når vi nu har udskrevet bøgerne på filen books.dat skal vi naturligvis også være i stand til at indlæse disse igen. Det sker typisk i et andet program. I program 46.2 inkluderer vi igen book-read-write.h, og vi åbner en fil i læse mode. Dernæst læser vi to bøger fra input filen på de røde steder. Vi kalder også prnt_book med det formål at skriver bogdata ud på skærmen. På denne måde kan vi overbevise os selv om, at det læste svarer til det skrevne.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#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 46.2    Programmet der indlæses bøger fra en input fil.

Næste punkt på dagsordenen, i program 46.3 er header filen book-read-write.h. Vi ser først nogle symbolske konstanter, hvoraf PROTECTED_SPACE og PROTECTED_NEWLINE anvendes i indkodningen af bogdata som en tekststreng. Dernæst følger struct book og den tilhørende typedef. De sidste linier i book-read-write.h er prototyper (funktionshoveder) af de funktioner, vi har anvendt til bogkonstruktion, bogskrivning og boglæsning i program 46.1 og program 46.2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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 46.3    Header filen book-read-write.h.

Endelig følger implementationen af funktionerne i book-read-write bibliotektet. Den fulde implementation kan ses vi den tilhørende slide. Alternativt kan du bruge dette direkte link. I udgaven i program 46.4 har vi udeladt funktionerne make_book og prnt_book. Disse funktioner er vist tidligere i program 39.6.

Funktionen print_book indkoder først bogens felter i et tegn array, kaldet buffer. Indkodningen foregår med funktionen encode_book. I encode_book skriver vi hvert felt i bogen ind i en tekststreng med hjælp af sprintf. Hver bog indkodes på én linie i en tekst fil. Felterne er adskilt med mellemrum (space). For at dette kan virke skal vi sikre os at mellemrum og evt. linieskift i tekstfelter transformeres til andre tegn. Dette sker i white_space_protect, som kaldes af encode_book på alle tekstfelter.

Tilsvarende indlæser funktionen read_book en bog fra en åben fil i læsemode. Da hver bog er repræsenteret som én linie i filen er det let at læse denne med fgets. Funktionen decode_book afkoder denne tekststreng. Essentielt kan arbejdet udføres med sscanf, som jo læser/parser tekst fra en tekststreng. I funktionen white_space_deprotect udfører vi det omvendte arbejde af white_space_protect. Det betyder at de normale mellemrum og linieskift vil vende tilbage i bøgernes tekstfelter.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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 46.4    Implementationen af biblioteket - book-read-write.c.

I program 46.5 viser vi hvordan det samlede program, bestående af biblioteket, skriveprogrammet og læseprogrammet, oversættes med gcc.

1
2
3
4
5
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
Program 46.5    Compilering af programmerne.


Opgave 10.1. Input og Output af structs

Vi 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.

Konkret, skal der skrives to funktioner

    print_person(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.

Løsning


 

46.2.  Input/Output af structures (2)
Indhold   Op Forrige Næste   Slide Aggregerede slides    Stikord Programindeks Opgaveindeks 

I afsnit 46.1 gennemgik vi alle detaljer om structure input/output via et eksempel. Vi vil nu hæve abstraktionsniveauet en del, og diskutere den generelle problemstilling ved structure IO.

Følgende observationer er relevante:

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

  • Pointers til andre typer end strenge kan ikke udskrives og indlæses

  • Evt. nestede structs og arrays kræver specialbehandling

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

    • Newline tegn må derfor ikke indgå i strenge

    • Det er behændigt hvis en streng altid kan læses med scanf og %s

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

Nogle sprog understøtter en meget simpel, binær file of records for dette behov

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

I afsnit 46.3 viser vi et simpelt eksempel på brug af binær input/output med fread og fwrite.

 

46.3.  Binær input/output med fread og fwrite
Indhold   Op Forrige Næste   Slide Aggregerede slides    Stikord Programindeks Opgaveindeks 

Som allerede omtalt er det forholdsvis let at bruge fwrite og fread til direkte udskrivning og indlæsning af bitmønstre i C. Vi skal dog være opmærksomme på, at vi kun kan udskrive værdier af de simple aritmetiske typer. Pointere kan under ingen omstændigheder udskrives og genindlæses meningsfyldt. Sidstnævnte betyder, at de fleste strenge ikke kan håndteres med fwrite og fread, idet strenge jo er pointere til det første tegn, og idet mange strenge er dynamisk allokerede.

I program 46.6 ser vi et program der udskriver en struct bestående af en int, en double og en char. Anden og tredje parameter af fwrite er størrelsen af strukturen (i bytes) og antallet af disse.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#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);
  return 0;
}
Program 46.6    Skrivning af en struct til en binær fil.

I program 46.7 ser vi det tilsvarende læseprogram, som anvender fread.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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 46.7    Tilsvarende læsning af en struct fra en binær fil.

Genereret: Onsdag 7. Juli 2010, 15:13:26
Thema indholdsfortegnelse -- Tastaturgenvej: 'u'  Forrige tema i denne lektion -- Tastaturgenvej: 'p'