4ο σετ σημειώσεων - Χειρισμός αρχείων και structs

Σχετικά έγγραφα
Προγραμματισμός Ι. Είσοδος/Έξοδος. Δημήτρης Μιχαήλ. Ακ. Έτος Τμήμα Πληροφορικής και Τηλεματικής Χαροκόπειο Πανεπιστήμιο

Διάλεξη 18η: Διαχείρηση Αρχείων

Δομημένος Προγραμματισμός. Τμήμα Επιχειρηματικού Σχεδιασμού και Πληροφοριακών Συστημάτων

3ο σετ σημειώσεων - Πίνακες, συμβολοσειρές, συναρτήσεις

ιαφάνειες παρουσίασης #6 (β)

Κεφάλαιο VΙ: Προσπέλαση Αρχείων. 5.1 Αρχεία δεδομένων.

Διαδικασιακός Προγραμματισμός

ΑΡ Χ Ε Ι Α Κ Ε Ι Μ Ε Ν Ο Υ (text files)

Περιεχόμενα. Πρόλογος... 21

Μεταβλητές τύπου χαρακτήρα

Ανάπτυξη και Σχεδίαση Λογισμικού

Προγραμματισμός Η/Υ (ΤΛ2007 )

Λύβας Χρήστος Αρχική επιµέλεια Πιτροπάκης Νικόλαος και Υφαντόπουλος Νικόλαος

Η πρώτη παράμετρος είναι ένα αλφαριθμητικό μορφοποίησης

(Κεφάλαιο 2.7 και 12) Αρχεία στην C. (Διάλεξη 15)

Εισαγωγή στον Προγραμματισμό

Εργαστήριο 9: Αρχεία

Ενδεικτική περιγραφή μαθήματος

Προγραμματισμός Ι (ΗΥ120)

ΕΠΕΞΕΡΓΑΣΙΑ ΑΡΧΕΙΩΝ Λέµε αρχείο

Προγραμματισμός Υπολογιστών & Υπολογιστική Φυσική

5ο σετ σημειώσεων - Δείκτες

Δομημένος Προγραμματισμός (ΤΛ1006)

ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΣ Η/Υ Ακαδημαϊκό έτος ΤΕΤΡΑΔΙΟ ΕΡΓΑΣΤΗΡΙΟΥ #4

#define, 70, 575 #elif, 580 #else, 580 #endif, 580 #error, 584 #if, 580 #ifdef, 583 #ifndef, 580, 583 #include, 70, 227, 574 #undef, 579

Προγραμματισμός Ι. Δυναμική Διαχείριση Μνήμης. Δημήτρης Μιχαήλ. Ακ. Έτος Τμήμα Πληροφορικής και Τηλεματικής Χαροκόπειο Πανεπιστήμιο

Επεξεργασία Αρχείων Κειµένου

Ι Αρχεία δεδομένων, μέρος δεύτερο: δυαδικά αρχεία ΙΙ Δομές δεδομένων (struct)

Περιεχόμενα. Πρόλογος... 17

Αρχεία & Ρεύματα ΑΡΧΕΙΑ & ΡΕΥΜΑΤΑ. Γεώργιος Παπαϊωάννου ( ) gepap@aueb.gr

Μεθόδων Επίλυσης Προβλημάτων

H ΓΛΩΣΣΑ C. Μάθηµα 16: Είσοδος/Έξοδος: Συναρτήσεις Eξόδου. ηµήτρης Ψούνης

Προγραμματισμός Ι. Χαρακτήρες. Πανεπιστήμιο Πελοποννήσου Τμήμα Πληροφορικής & Τηλεπικοινωνιών

ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΣ ΥΠΟΛΟΓΙΣΤΩΝ & ΥΠΟΛΟΓΙΣΤΙΚΗ ΦΥΣΙΚΗ

Πίνακες. 1 Πίνακες. 30 Μαρτίου 2014

Προγραμματισμός Ι (ΗΥ120)

Διαδικαστικός Προγραμματισμός

Δομημένος Προγραμματισμός. Τμήμα Επιχειρηματικού Σχεδιασμού και Πληροφοριακών Συστημάτων

Διαδικασιακός Προγραμματισμός

scanf() scanf() stdin scanf() printf() int float double %lf float

Διαδικασιακός Προγραμματισμός

Βιβλιοθήκη stdio. Προγραμματισμός II 1

Ανάπτυξη και Σχεδίαση Λογισμικού

Εισαγωγή στον προγραμματισμό. Τμήμα Πληροφορικής & Επικοινωνιών ΤΕΙ Σερρών Εργαστήριο 2

Προγραμματισμός Ι. Προχωρημένα Θέματα. Δημήτρης Μιχαήλ. Τμήμα Πληροφορικής και Τηλεματικής Χαροκόπειο Πανεπιστήμιο

ΑΣΚΗΣΗ 2: ΔΟΜΗ ΠΡΟΓΡΑΜΜΑΤΟΣ C, ΧΕΙΡΙΣΜΟΣ ΜΕΤΑΒΛΗΤΩΝ ΚΑΙ ΣΥΝΑΡΤΗΣΕΙΣ ΕΙΣΟΔΟΥ ΚΑΙ ΕΞΟΔΟΥ

Η γλώσσα προγραμματισμού C Χειρισμός αρχείων

#include <stdlib.h> Α. [-128,127] Β. [-127,128] Γ. [-128,128]

(Κεφάλαιο 2.7 και 12) Αρχεία στην C. ( ιάλεξη 13) ιδάσκων: ηµήτρης Ζεϊναλιπούρ

Πίνακες: μια σύντομη εισαγωγή. Πίνακες χαρακτήρων: τα "Αλφαριθμητικά"

Διάλεξη 2: Επανάληψη Προγραμματισμού Συμβολοσειρές (strings) Διδάσκων: Παναγιώτης Ανδρέου

Η-Υ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΣ. Εργαστήριο 1 Εισαγωγή στη C. Σοφία Μπαλτζή s.mpaltzi@di.uoa.gr

Διδάσκων: Κωνσταντίνος Κώστα Διαφάνειες: Δημήτρης Ζεϊναλιπούρ

Πως θα αποθηκεύσει τη λίστα με τα ψώνια του και θα την ανακτήσει στο Σ/Μ; και πως θα προσθέσει στη λίστα του επιπλέον προϊόντα;

Δομημένος Προγραμματισμός

Προγραμματισμός Η/Υ (ΤΛ2007 )

Δομημένος Προγραμματισμός. Τμήμα Επιχειρηματικού Σχεδιασμού και Πληροφοριακών Συστημάτων

Προγραμματισμός Η/Υ (ΤΛ2007 )

Κεφάλαιο 8.7. Πολυδιάστατοι Πίνακες (Διάλεξη 19)

Διάλεξη 3η: Τύποι Μεταβλητών, Τελεστές, Είσοδος/Έξοδος

Προγραμματισμός Υπολογιστών & Υπολογιστική Φυσική

Εισαγωγή στον Προγραμματισμό

Η γλώσσα προγραμματισμού C

Εισαγωγή στην C. Μορφή Προγράµµατος σε γλώσσα C

Α' Εξάμηνο ΕΙΣΑΓΩΓΗ ΣΤΟ ΔΟΜΗΜΕΝΟ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟ

Η γλώσσα προγραμματισμού C

Διαδικασιακός Προγραμματισμός

Βιβλιοθήκη stdio. Προγραμματισμός II 1

2ο σετ σημειώσεων. 1 Εντολές εκτέλεσης υπό συνθήκη. 19 Μαρτίου 2012

Προγραμματισμός Ι. Δομές & Ενώσεις. Πανεπιστήμιο Πελοποννήσου Τμήμα Πληροφορικής & Τηλεπικοινωνιών

Δομημένος Προγραμματισμός. Τμήμα Επιχειρηματικού Σχεδιασμού και Πληροφοριακών Συστημάτων

Δομή Προγράμματος C++, Χειρισμός Μεταβλητών και Συναρτήσεις Εισόδου - Εξόδου

ΣΥΝΟΠΤΙΚΟΣ ΟΔΗΓΟΣ ΓΛΩΣΣΑΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ C

i M-1 1. ij f(i, j) N-1. (pixel) 2. M N (x, y) (x, y ) = 256. R(x, y), G(x, y), B(x, y)

Η γλώσσα προγραμματισμού C

Προγραμματισμός Η/Υ 1 (Εργαστήριο)

Προγραμματισμός Ι (ΗΥ120)

Προγραµµατισµός Ι (ΗΥ120)

Προγραμματισμός Η/Υ (ΤΛ2007 )

Παρακάτω δίνεται o σκελετός προγράμματος σε γλώσσα C. Σχολιάστε κάθε γραμμή του κώδικα.

2 η Διάλεξη C++ Δρ. Χρήστος Δρόσος ΑΕΙ ΠΕΙΡΑΙΑ ΤΤ ΤΜΗΜΑ ΑΥΤΟΜΑΤΙΣΜΟΥ

ΕΝΤΟΛΕΣ ΕΠΑΝΑΛΗΨΗΣ. for (παράσταση_1; παράσταση_2; παράσταση_3) εντολή επόμενη εντολή

Δομημένος Προγραμματισμός (ΤΛ1006)

Συναρτήσεις πρότυπης βιβλιοθήκης 1. Μερικές συνήθεις συναρτήσεις βιβλιοθήκης int atoi(const char *p) int fclose(file *fp)

ΑΡΧΕΙΑ ΚΕΙΜΕΝΟΥ ΣΤΗΝ C

Κεφάλαιο 12: Είσοδος και έξοδος δεδομένων σε αρχεία

Συναρτήσεις. Εισαγωγή

ΠΑΝΕΠΙΣΤΗΜΙΟ ΘΕΣΣΑΛΙΑΣ ΣΧΟΛΗ ΘΕΤΙΚΩΝ ΕΠΙΣΤΗΜΩΝ ΤΜΗΜΑ ΠΛΗΡΟΦΟΡΙΚΗΣ

Επανάληψη για τις Τελικές εξετάσεις. (Διάλεξη 24) ΕΠΛ 032: ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΣ ΜΕΘΟΔΩΝ ΕΠΙΛΥΣΗΣ ΠΡΟΒΛΗΜΑΤΩΝ

Προγραμματισμός Ι (ΗΥ120)

Υπερφόρτωση τελεστών

Προγραμματισμός Η/Υ (ΤΛ2007 )

ΒΑΣΙΚΟΙ ΤΥΠΟΙ ΚΑΙ ΠΙΝΑΚΕΣ

Προγραμματιστικές τεχνικές

Α. unsigned int Β. double. Γ. int. unsigned char x = 1; x = x + x ; x = x * x ; x = x ^ x ; printf("%u\n", x); Β. unsigned char

Η γλώσσα προγραμματισμού C Δυναμική διαχείριση μνήμης

ΕΡΓΑΣΤΗΡΙΟ 3: Προγραμματιστικά Περιβάλλοντα και το Πρώτο Πρόγραμμα C

Προγραμματισμός Ι. Δείκτες. Δημήτρης Μιχαήλ. Τμήμα Πληροφορικής και Τηλεματικής Χαροκόπειο Πανεπιστήμιο

Η γλώσσα προγραμματισμού C

Pascal. 15 Νοεμβρίου 2011

Transcript:

4ο σετ σημειώσεων - Χειρισμός αρχείων και structs 5 Ιουνίου 2012 1 Αρχεία Η στάνταρ βιβλιοθήκη stdio.h της γλώσσας μας δίνει τη δυνατότητα να χειριστούμε αρχεία του λειτουργικού συστήματος. Οι συναρτήσεις αυτές δεν περιορίζονται μόνο στο χειρισμό αρχείων σαν αυτά που αποθηκεύονται στο σκληρό δίσκο αλλά μπορούν να προσπελάσουν οποιαδήποτε ροή δεδομένων, δηλαδή μια ακολουθία από bytes¹ π.χ. τα πλήκτρα που πατάει ο χρήστης στο πληκτρολόγιο. Στο εξής οποτεδήποτε αναφερόμαστε σε αρχεία θα εννοούμε αρχεία ή ροές. Τα αρχεία μπορούν να είναι ανάγνωσης, εγγραφής ή και τα δύο ανάλογα με τον τύπο τους. Π.χ. οι ροές από το πληκτρολόγιο είναι μόνο ανάγνωσης. Στη βιβλιοθήκη ορίζεται ο τύπος FILE. Πρόκειται για ένα σύνθετο τύπο, μία δομή η οποία περιέχει διάφορες πληροφορίες σχετικά με μία ανοιχτή ροή. Οι μεταβλητές αυτού του τύπου ονομάζονται file handles. Τα αρχεία ανάλογα με το περιεχόμενό τους διακρίνονταν σε αρχεία κειμένου (text) και δυαδικά αρχεία (binary). Το τελευταίο πρότυπο της C έχει καταργήσει αυτήν τη διάκριση ήδη από το 1999 αλλά θα αναφερθούμε σε αυτό το θέμα στην τελευταία ενότητα αυτών των σημειώσεων καθώς η διάκριση αυτή συνεχίζει να ισχύει ακόμα και σήμερα σε διάφορα περιβάλλοντα. 1.1 Άνοιγμα και κλείσιμο αρχείων Για να διαβάσουμε ή/και να γράψουμε σε ένα αρχείο πρέπει πρώτα να το ανοίξουμε. Αυτό γίνεται με τη συνάρτηση fopen η οποία δέχεται δύο παραμέτρους τύπου συμβολοσειράς. Η πρώτη είναι το όνομα του αρχείου που θέλουμε να ανοιχτεί και η δεύτερη είναι η κατάσταση στην οποία θα ανοιχτεί. Η δεύτερη παράμετρος καθορίζει αν θέλουμε να ανοίξουμε το αρχείο για ανάγνωση ή/και εγγραφή κτλ. Η συνάρτηση επιστρέφει ένα δείκτη σε file handle αν εκτελεστεί με επιτυχία ή την τιμή NULL αν παρουσιαστεί σφάλμα. Π.χ. το παρακάτω πρόγραμμα ανοίγει το αρχείο D:\C\file1.txt για ανάγνωση. ¹Οι συναρτήσεις της βιβλιοθήκης εισόδου/εξόδου της C χειρίζονται τα bytes ως unsigned char. Η θεώρηση ενός αρχείου ως ακολουθία από bytes είναι επαρκής όσο κανείς χειρίζεται δυαδικά (binary) δεδομένα ή κείμενα στα οποία κάθε χαρακτήρας μπορεί να αναπαρασταθεί από ένα byte. Σε άλλες περιπτώσεις όμως όπως π.χ. τα κείμενα unicode αυτό δεν είναι βολικό οπότε δίνεται η δυνατότητα ανάγνωσης και εγγραφής μεγάλων χαρακτήρων (wide characters) των οποίων το μέγεθος εξαρτάται από την υλοποίηση. Στις σημειώσεις αυτές δεν θα ασχοληθούμε με wide characters. Για λόγους απλότητας θα ασχοληθούμε αποκλειστικά με ροές από bytes. 1

1 # include <stdio.h> 2 int main() 3 { Listing 1: Άνοιγμα αρχείου για ανάγνωση 4 FILE* f = fopen("d:/ C/ file1. txt", "r+"); 5 6 fclose(f); 7 return 0; 8 } Παρατηρήστε ότι στην πρώτη παράμετρο πρέπει να χρησιμοποιούμε την κάθετο / και όχι την \ καθώς η δεύτερη είναι ο χαρακτήρας διαφυγής της C. Επίσης είναι καλή πρακτική οποτεδήποτε τελειώνουμε την επεξεργασία ενός αρχείου να το κλείνουμε καλώντας τη συνάρτηση fclose η οποία παίρνει ως παράμετρο το file handle που έχει συνδεθεί με το αρχείο, όπως στη γραμμή 6 του παραπάνω προγράμματος². Η fclose αδειάζει τους buffers που σχετίζονται με το αρχείο. Σε περίπτωση λοιπόν που δεν κληθεί είναι πιθανόν κάποια δεδομένα να μην γραφτούν στο αρχείο ενώ δείχνουν να έχουν γραφτεί κανονικά. Ας δούμε ποιες είναι οι δυνατές τιμές για την δεύτερη παράμετρο: r: Άνοιγμα για ανάγνωση w: Άνοιγμα για γράψιμο a: Άνοιγμα για προσθήκη (append) δηλαδή γράψιμο μετά το τέλος του αρχείου. Σε κάθε μία από τις παραπάνω επιλογές μπορεί να χρησιμοποιηθεί ο χαρακτήρας +. Σε αυτήν την περίπτωση εννοείται ότι το αρχείο θα ανοιχτεί και για ανάγνωση και για εγγραφή. Π.χ. r+ σημαίνει άνοιγμα του αρχείου για ανάγνωση αλλά και για εγγραφή και w+ σημαίνει άνοιγμα του αρχείου για εγγραφή αλλά και για ανάγνωση. Παρόλο που αυτές οι επιλογές με την προσθήκη του χαρακτήρα + δείχνουν ταυτόσημες, διαφέρουν σε κάποιες λεπτομέρειες της συμπεριφοράς τους όπως για παράδειγμα πώς ανταποκρίνονται στην περίπτωση που το όνομα του αρχείου που δίνεται ως πρώτη παράμετρος υπάρχει ή όχι και ως προς το που τοποθετούν το τρέχον σημείο πρόσβασης του αρχείου όταν αυτό ανοιχτεί. Συγκεκριμένα αν το αρχείο δεν υπάρχει τότε η επιλογή r επιστρέφει σφάλμα ενώ οι επιλογές w και a το δημιουργούν. Αν το αρχείο υπάρχει η επιλογή w το σβήνει. Σχετικά με την τρέχουσα θέση ανάγνωσης/εγγραφής, οι επιλογές r και w την τοποθετούν στην αρχή του αρχείου ενώ η a στο τέλος. Εκτός από το χαρακτήρα + μπορεί επίσης να ακολουθεί είτε ο χαρακτήρας t είτε ο χαρακτήρας b. Στην πρώτη περίπτωση θεωρείται ότι το αρχείο είναι αρχείο κειμένου ενώ στη δεύτερη ότι είναι δυαδικό αρχείο. Αν δεν υπάρχει κάποιος από τους δύο τότε θεωρείται ότι το αρχείο είναι αρχείο κειμένου. Περισσότερα για αυτό το θέμα, στην τελευταία ενότητα αυτών των σημειώσεων. Σύμφωνα με τα παραπάνω λοιπόν αν δοκιμάσετε να τρέξετε το παραπάνω πρόγραμμα και το αρχείο D:\C\file1.txt δεν υπάρχει τότε η fopen θα πρέπει αντί για δείκτη σε file handle να επιστρέψει την τιμή NULL. Κατά πάσα πιθανότητα, όταν αυτή η μη-νόμιμη τιμή χρησιμοποιηθεί ² Το συγκεκριμένο πρόγραμμα πιθανώς να κρασάρει αν δεν πετύχει το άνοιγμα του αρχείου στη γραμμή 4. Θα δούμε πώς μπορεούμε να αποφύγουμε κάτι τέτοιο στη συνέχεια 2

ως παράμετρος στην fclose θα προκαλέσει κρασάρισμα του προγράμματός σας. Είναι λοιπόν σημαντικό να ελέγχετε τις επιστρεφόμενες τιμές από συναρτήσεις χειρισμού αρχείων όπως η fopen πριν τις χρησιμοποιήσετε. Το παρακάτω πρόγραμμα ελέγχει ότι η fopen εκτελέστηκε κανονικά πριν κλείσει το αρχείο. Μπορούμε να ελέγξουμε αν η fopen εκτελέστηκε κανονικά ή όχι απλώς και μόνο ελέγχοντας αν η τιμή που επέστρεψε είναι NULL ή όχι. Παρ όλα αυτά δεν μπορούμε από αυτό και μόνο να συμπεράνουμε τι ακριβώς σφάλμα παρουσιάστηκε. Στο παρακάτω πρόγραμμα χρησιμοποιείται και η εντολή perror(s) η οποία τυπώνει τον κωδικό του τελευταίου σφάλματος που παρουσιάστηκε στο πρόγραμμα. Επιπρόσθετα τυπώνει τη συμβολοσειρά s. 1 # include <stdio.h> 2 3 int main() 4 { Listing 2: Άνοιγμα αρχείου για ανάγνωση και έλεγχος λαθών 5 FILE* f = fopen("d:/ C/ file1. txt", "r+"); 6 if (f == NULL) { 7 printf(" Error in opening the file\n"); 8 perror("a message"); 9 } 10 else 11 fclose(f); 12 return 0; 13 } Σημειώστε ότι στο παραπάνω πρόγραμμα χρησιμοποιούμε τη συνθήκη f == NULL για σαφήνεια. Στην πράξη χρησιμοποιείται η συντομότερη!f. Μια άλλη συνάρτηση που μπορεί να μας δώσει μια ένδειξη σχετικά με το αν παρουσιάστηκε κάποιο σφάλμα σε ένα αρχείο είναι η ferror(f) η οποία επιστρέφει μη-μηδενική τιμή αν έχει παρουσιαστεί κάποιο σφάλμα στο αρχείο που αντιστοιχεί στο file handle f. Πρέπει φυσικά να δείχνουμε προσοχή όταν χρησιμοποιούμε αυτήν τη συνάρτηση γιατί πρέπει η f να έχει μία νόμιμη τιμή δηλαδή να μην είναι NULL ή με άλλα λόγια να έχει ανοίξει το αρχείο. 1.2 Στάνταρ είσοδος, έξοδος και έξοδος λαθών Σε κάθε πρόγραμμα C ανοίγουν αυτόματα τρία αρχεία με ονόματα stdin, stdout και stderr τα οποία αντιστοιχούν στην στάνταρ είσοδο, έξοδο και έξοδο λαθών αντίστοιχα. Τα αρχεία αυτά έχουν το παρακάτω νόημα: stdin: Στάνταρ είσοδος. Είναι η συσκευή (ή αρχείο) από την οποία το πρόγραμμα διαβάζει δεδομένα που του δίνει ο χρήστης. Εξ ορισμού είναι το πληκτρολόγιο. stdout: Στάνταρ έξοδος. Είναι η συσκευή (ή αρχείο) στην οποία το πρόγραμμα τυπώνει μηνύματα. Εξ ορισμού είναι η οθόνη. stderr: Στάνταρ έξοδος λαθών. Είναι η συσκευή (ή αρχείο) στην οποία το πρόγραμμα γράφει μηνύματα λάθους. Εξ ορισμού είναι η οθόνη. 3

Τα παραπάνω file handles υπάρχουν είτε τα χρησιμοποιήσουμε είτε όχι. Διαβάζοντας ή γράφοντας σε αυτά τα αρχεία είναι σαν να διαβάζουμε ή γράφουμε στις αντίστοιχες συσκευές, π.χ. γράφοντας στο αρχείο stdout στην ουσία τυπώνουμε κάτι στην οθόνη ενώ διαβάζοντας κάτι από το αρχείο stdin προσπελαύνουμε τα πλήκτρα που πατάει ο χρήστης στο πληκτρολόγιο. Μπορούμε τόσο προγραμματιστικά όσο και μέσα από το λειτουργικό σύστημα να ανακατευθύνουμε οποιοδήποτε από αυτά τα τρία αρχεία σε κάποιο άλλο. Π.χ. μπορούμε να γράψουμε ένα πρόγραμμα το οποίο θα διαβάζει δεδομένα από ένα αρχείο αντί από το πληκτρολόγιο ή να γράφει τα μηνύματα λάθους σε κάποιο αρχείο log. 1.3 Ανάγνωση και εγγραφή δεδομένων Η στάνταρ βιβλιοθήκη εισόδου/εξόδου παρέχει διάφορες συναρτήσεις ανάγνωσης και εγγραφής δεδομένων. Ποια θα χρησιμοποιήσει κανείς έχει να κάνει περισσότερο με την ευκολία που δίνεται από ορισμένες από αυτές. Οι πιο απλές από αυτές είναι οι fgetc και fputc οι οποίες διαβάζουν και γράφουν αντίστοιχα ένα byte από ένα αρχείο. Οι δηλώσεις τους στη βιβλιοθήκη είναι αντίστοιχα int fgetc(file* stream) int fputc(int c, FILE* stream) Η fgetc δέχεται ως παράμετρο ένα αρχείο, διαβάζει ένα byte (δηλαδή unsigned char) από αυτό και το επιστρέφει σε μορφή int. Ο λόγος που γίνεται η μετατροπή σε int είναι ώστε να μπορεί επιπλέον από τις 256 τιμές που μπορούν να αναπαρασταθούν σε ένα byte να επιστρέψει την ειδική τιμή EOF (End Of File, τέλος αρχείου). Η fputc δέχεται δύο παραμέτρους. Η πρώτη είναι ο χαρακτήρας που θα γραφτεί στο αρχείο ενώ η δεύτερη είναι το file handle του αρχείου. Αν η εγγραφή πετύχει τότε η συνάρτηση επιστρέφει το byte που γράφτηκε (αφού το μετατρέψει πρώτα σε int). Στην περίπτωση που παρουσιαστεί σφάλμα επιστρέφει την τιμή EOF. Το παρακάτω πρόγραμμα μπορεί να χρησιμοποιηθεί για να γράψει μία συμβολοσειρά χαρακτήρα προς χαρακτήρα σε ένα αρχείο. 1 # include <stdio.h> 2 # include <stdlib.h> 3 # include <string.h> Listing 3: Γράψιμο συμβολοσειράς 4 5 int main() 6 { 7 char s[] = "Ena minima"; 8 FILE* f = fopen("d:/ C/ test5_3. txt", "w"); 9 int i; 10 int c; 11 12 if (f == NULL) { 13 printf(" Paroysiastnke sfalma sto anoigma\n"); 14 perror(""); 4

15 exit(1); 16 } 17 18 for (i = 0; i < strlen(s); ++i) { 19 c = fputc(s[i], f); 20 if (c == EOF) { 21 printf(" Sfalma sthn eggrafn\n"); 22 exit(1); 23 } 24 } 25 fclose(f); 26 27 return 0; 28 } Αντίστοιχα η συνάρτηση fgetc παίρνει παράμετρο ένα file handle και επιστρέφει το χαρακτήρα που διαβάστηκε ή EOF αν παρουσιάστηκε κάποιο σφάλμα ή έφτασε το τέλος του αρχείου. Παρατηρήστε ότι η fgetc παρόλο που διαβάζει bytes τα οποία χωράνε κανονικά σε μία μεταβλητή τύπου unsigned char, τελικά επιστρέφει τιμή τύπου int. Ο λόγος είναι ότι δεν γίνεται σε μία μεταβλητή τύπου unsigned char να χωρέσουν και οι 256 τιμές που μπορεί να παρασταθούν σε ένα byte και η ειδική τιμή EOF. Οπότε είναι πολύ σημαντικό οποτεδήποτε χρησιμοποιείτε τη συνάρτηση fgetc για να διαβάσετε bytes από ένα αρχείο, να αποθηκεύετε την επιστρεφόμενη τιμή σε μεταβλητή τύπου int και όχι char. Το παρακάτω πρόγραμμα διαβάζει το αρχείο που γράφτηκε στο προηγούμενο πρόγραμμα. 1 # include <stdio.h> 2 # include <stdlib.h> 3 # include <string.h> Listing 4: Διάβασμα συμβολοσειράς 4 5 int main() 6 { 7 FILE* f = fopen("d:/ C/ test5_3. txt", "r"); 8 int c; 9 10 if (f == NULL) { 11 printf(" Paroysiastnke sfalma sto anoigma \n"); 12 perror(""); 13 exit(1); 14 } 15 16 while (( c = fgetc(f))!= EOF) { 17 printf(" Diabasa to %c\n", c); 18 } 19 fclose(f); 20 5

21 return 0; 22 } Για ευκολία υπάρχουν οι συναρτήσεις getchar και putchar αντίστοιχες με τις fgetc και fputc οι οποίες διαβάζουν ή γράφουν ένα byte από τη στάνταρ είσοδο ή προς τη στάνταρ έξοδο αντίστοιχα. Επίσης αντίστοιχες των fgetc και fputc είναι οι getc και putc οι οποίες κάνουν ακριβώς την ίδια δουλειά αλλά δίνεται η δυνατότητα σε όποιον γράφει το μεταγλωττιστή να τις υλοποιήσει ως macros αντί για συναρτήσεις. Υποτίθεται ότι με αυτόν τον τρόπο μπορεί η εκτέλεσή τους να είναι ταχύτερη. Άποψη του γράφοντα είναι ότι καλύτερα να τις αποφεύγει κανείς. Είδαμε στα παραπάνω παραδείγματα πώς μπορεί κανείς να γράψει ή να διαβάσει μια συμβολοσειρά χαρακτήρα προς χαρακτήρα. Προφανώς αυτό είναι άβολο στην περίπτωση που θέλει κάποιος να γράψει ή να διαβάσει μία ολόκληρη συμβολοσειρά. Για αυτό το σκοπό χρησιμοποιούνται οι συναρτήσεις fgets και fputs οι οποίες διαβάζουν και γράφουν αντίστοιχα συμβολοσειρές σε αρχεία. Οι δηλώσεις τους είναι όπως παρακάτω: char* fgets(char* restrict s, int n, FILE* restrict stream) int fputs(char* restrict s, FILE* restrict stream) Η fgets διαβάζει το πολύ n-1 χαρακτήρες από το αρχείο stream και τους αποθηκεύει στον πίνακα χαρακτήρων s. Αν η fgets διαβάσει έναν χαρακτήρα αλλαγής γραμμής πριν διαβάσει n-1 χαρακτήρες, σταματάει να διαβάζει χαρακτήρες από το αρχείο. Ο χαρακτήρας αλλαγής γραμμής αποθηκεύεται και αυτός στην περίπτωση που υπάρχει. Το ίδιο συμβαίνει και στην περίπτωση που η fgets φτάσει στο τέλος του αρχείου. Σε κάθε περίπτωση η fgets προσθέτει μετά τον τελευταίο χαρακτήρα που έγραψε στον πίνακα s το χαρακτήρα τερματισμού συμβολοσειράς (αυτός είναι και ο λόγος που διαβάζει n-1 και όχι n χαρακτήρες). Αν όλα πάνε καλά, η fgets επιστρέφει ένα δείκτη προς τη συμβολοσειρά στην οποία έγραψε τα δεδομένα. Αλλιώς (αν παρουσιαστεί σφάλμα) επιστρέφει NULL. Αν δεν υπάρχουν άλλοι χαρακτήρες για διάβασμα στο αρχείο, δηλαδή έχει φτάσει στο τέλος, επίσης επιστρέφει NULL. Η fputs γράφει τη συμβολοσειρά s στο αρχείο stream. Αν όλα πάνε καλά επιστρέφει μία μη αρνητική τιμή. Αλλιώς επιστρέφει EOF. Το παρακάτω πρόγραμμα ζητάει από το χρήστη συμβολοσειρές τις οποίες και γράφει σε ένα αρχείο. 1 # include <stdio.h> 2 # include <string.h> Listing 5: Γράψιμο συμβολοσειρών σε αρχείο 3 4 int main() 5 { 6 char s[100]; 7 FILE* f; 8 9 f = fopen("d:/c/ex5_5.txt", "w"); 10 11 do { 12 printf(" Dose onomateponymo: "); 6

13 gets(s); 14 if (strlen(s)) { 15 fwrite(s, sizeof(char), strlen(s), f); 16 fputc('\n', f); 17 } 18 } while(strlen(s)); 19 fclose(f); 20 21 return 0; 22 } Το επόμενο πρόγραμμα διαβάζει τις συμβολοσειρές από το αρχείο και τις τυπώνει στην οθόνη. 1 # include <stdio.h> 2 # include <string.h> 3 4 int main() 5 { 6 char s[100]; 7 FILE* f; 8 Listing 6: Διάβασμα συμβολοσειρών από αρχείο 9 f = fopen("d:/c/ex5u5.txt", "r"); 10 11 while( fgets(s, 100, f)) { 12 printf(" Read string %s\n", s); 13 14 } 15 16 return 0; 17 } Επίσης υπάρχουν και οι εντολές fread και fwrite οι οποίες διαβάζουν ή γράφουν μπλοκς δεδομένων. Οι δηλώσεις τους είναι οι παρακάτω. int fwrite(const void* buffer, size_t size, size_t count, FILE* stream) size_t fread(void* buffer, size_t size, size_t count, FILE* stream) Η fread διαβάζει από το αρχείο stream τόσα στοιχεία όσα και η παράμετρος count. Κάθε στοιχείο έχει μέγεθος size bytes. Τα στοιχεία αυτά αποθηκεύονται στη θέση μνήμης στην οποία δείχνει η παράμετρος buffer. Η fread επιστρέφει το πλήθος των στοιχείων που διαβάστηκαν. Μπορεί να είναι λιγότερα από όσα ζητήθηκαν με την παράμετρο count αν παρουσιάστηκε σφάλμα στο αρχείο ή έφτασε στο τέλος του. Αντίστοιχα η fwrite γράφει δεδομένα σε αρχείο και επιστρέφει το πλήθος των στοιχείων που γράφτηκαν με επιτυχία. Η fwrite επιστρέφει τιμή μικρότερη από count μόνο αν παρουσιαστεί σφάλμα κατά την εγγραφή. Το παρακάτω παράδειγμα γράφει έναν πίνακα 10 ακεραίων σε ένα αρχείο. 7

1 # include <stdio.h> 2 # include <string.h> Listing 7: Γράψιμο σε μπλοκς με την fwrite 3 4 int main() 5 { 6 int p[10] = { 5, 6, 7, 8, 9, 1, 2, 3, 4, 6 }; 7 int i; 8 FILE* f; 9 10 f = fopen("d:/c/ex5u7.txt", "w"); 11 12 i = fwrite(p, sizeof(int), 10, f); 13 if (i == 10) 14 printf(" Ta dedomena graftnkav\n"); 15 fclose(f); 16 17 return 0; 18 } Και το επόμενο το διαβάζει. 1 # include <stdio.h> 2 # include <string.h> Listing 8: Διάβασμα με την fread 3 4 int main() 5 { 6 int p[10]; 7 int i, m; 8 FILE* f; 9 10 f = fopen("d:/c/ex5u7.txt", "r"); 11 12 m = fread(p, sizeof(int), 10, f); 13 printf(" Diabastknav %d stoixeia\n", m); 14 for (i = 0; i < m; ++i) 15 printf(" Arithmos #% d: %d\n", i+1, p[i]); 16 fclose(f); 17 18 return 0; 19 } Τέλος, μπορεί κανείς να χρησιμοποιήσει και τις εντολές φορμαρισμένης εισόδου/εξόδου fprintf και fscanf οι οποίες λειτουργούν ακριβώς όπως και οι αντίστοιχες printf και scanf αλλά σε αρχεία αντί για την οθόνη και το πληκτρολόγιο. 8

2 Δομές (structs) Οι βασικοί τύποι που είναι ενσωματωμένοι στη C δεν μπορούν παρά να εκφράσουν ένα μικρό μόνο μέρος των οντοτήτων που μπορεί να συναντήσουμε στον πραγματικό κόσμο και να πρέπει να χειριστούμε με ένα πρόγραμμα. Για παράδειγμα, ένα πρόγραμμα γεωμετρίας θα πρέπει να χειρίζεται σημεία τα οποία χαρακτηρίζονται όχι από έναν αριθμό αλλά δύο, τρεις ή και παραπάνω ανάλογα με το πλήθος των διαστάσεων του χώρου στον οποίο βρίσκονται. Ή ένα πρόγραμμα διαχείρισης αποθήκης πρέπει για κάθε προϊόν να γνωρίζει την ποσότητα, μία λεκτική περιγραφή, την τιμή κτλ. Για να επανέλθουμε στο παράδειγμα των σημείων στο δισδιάστατο χώρο, μπορεί κάποιος να θεωρήσει (σωστά) ότι μπορούμε να χειριστούμε τις x και y συντεταγμένες ως δύο μεταβλητές. Αν όμως πρέπει να ασχοληθούμε με περισσότερα από ένα σημεία, τα οποία προφανώς θα πρέπει να βάλουμε σε έναν πίνακα τότε θα πρέπει να έχουμε έναν πίνακα με τις x συντεταγμένες και έναν πίνακα με τις y. Φυσικά, μία τέτοια αντιμετώπιση δεν είναι αδύνατη αλλά έχει το μειονέκτημα ότι δεν υπάρχει κάποια προγραμματιστική σύνδεση ανάμεσα στη x και την y συντεταγμένη ενός σημείου. Δηλαδή, το γεγονός ότι το πρώτο στοιχείο του πίνακα με τις x συντεταγμένες και το αντίστοιχο του πίνακα με τις y είναι λογικά συνδεδεμένα, δεν το γνωρίζει το πρόγραμμα. Έτσι θα μπορούσαμε κατά λάθος τυπώνοντας τις συντεταγμένες ενός σημείου να τυπώσουμε την x του ενός και την y του άλλου. Αν αυτό δεν σας πείθει, τότε φανταστείτε ένα πρόγραμμα το οποίο θα ζωγράφιζε τρισδιάστατους κύβους. Κάθε σημείο θα είχε τρεις συντεταγμένες, οπότε θα έπρεπε να έχουμε τρεις πίνακες για τις συντεταγμένες των σημείων. Επίσης, καθώς κάθε κύβος θα είχε 8 κορυφές θα έπρεπε στους τρεις αυτούς πίνακες να κάνουμε τις απαραίτητες πράξεις ώστε να συμπεραίνουμε ότι τα στοιχεία από 0 έως και 7 ανήκουν στον πρώτο κύβο, τα στοιχεία από 8 έως 15 ανήκουν στον δεύτερο κοκ. Η ζωή θα ήταν πιο εύκολη αν μπορούσαμε να πούμε ότι ένα σημείο αποτελείται από τρεις συντεταγμένες και να φτιάξουμε ένα νέο τύπο δεδομένων, πέρα από τους βασικούς τύπους της C που θα περιείχε τρεις αριθμούς συνδεδεμένους μεταξύ τους, στους οποίους θα αναφερόμασταν με το όνομα σημείο. Αντίστοιχα θα μπορούσαμε να πούμε ότι μία οκτάδα σημείων είναι ένας κύβος και να ορίσουμε έναν πίνακα κύβων χωρίς να αναφερόμαστε στις επιμέρους συντεταγμένες τους αν δεν το επιθυμούμε. Στη C μπορούμε να ορίσουμε νέους, σύνθετους τύπους με τη struct. Η σύνταξή της είναι όπως παρακάτω: 1 struct { 2 decl_1; 3 decl_2; 4... 5 decl_n; 6 }; Αρχικά έχουμε τη δεσμευμένη λέξη struct ακολουθούμενη από άγκιστρα. Μέσα στα άγκιστρα έχουμε δηλώσεις (decl_1, decl_2 κτλ) οι οποίες είναι όπως ακριβώς και οι δηλώσεις μεταβλητών, δηλαδή ένα όνομα τύπου ακολουθούμενο από ένα όνομα επιλογής του χρήστη. Για παράδειγμα, αν θέλαμε να φτιάξουμε μια δομή για να χειριστούμε σημεία στον τρισδιάστατο χώρο θα μπορούσαμε να χρησιμοποιήσουμε την παρακάτω struct η οποία έχει τρία ακέραια μέλη x, y και z: 1 struct { 9

2 int x; 3 int y; 4 int z; 5 }; Οι παραπάνω γραμμές από άποψη συντακτικού είναι η περιγραφή ενός νέου τύπου. Παρόλο που η περιγραφή αυτή είναι συντακτικά σωστή δεν είναι ιδιαίτερα χρήσιμη από μόνη της γιατί δεν δηλώνει κάποια μεταβλητή οπότε δεν δεσμεύεται μνήμη την οποία να μπορούμε να προσπελάσουμε για κάποιο σκοπό. Απλώς περιγράφει έναν τύπο που θα μπορούσε να υπάρξει αν το ήθελε ο προγραμματιστής. Για να δηλώσουμε μία μεταβλητή, ένας συντακτικά σωστός αλλά άβολος τρόπος είναι να γράψουμε ένα όνομα μεταβλητής απ ευθείας μετά την περιγραφή της struct. Αν για παράδειγμα θέλαμε η μεταβλητή να λέγεται point1 θα μπορούσαμε να γράψουμε το εξής: 1 struct { 2 int x; 3 int y; 4 int z; 5 } point1; Το παραπάνω σημαίνει ότι δηλώνουμε μια μεταβλητή point1 της οποίας ο τύπος δεν είναι κάποιος από τους βασικούς ή παραγόμενους τύπους της C αλλά ένας σύνθετος, φτιαγμένος από τον προγραμματιστή τύπος ο οποίος αποτελείται από τρία ακέραια μέλη. 2.1 Πρόσβαση σε μέλη των structs Συνεχίζοντας το προηγούμενο παράδειγμα, τίθεται το ζήτημα ποιες συναρτήσεις μας παρέχει η βιβλιοθήκη της C για να χειριστούμε δομές τέτοιου τύπου. Η απάντηση είναι ότι δεν παρέχει τίποτα και αυτό είναι προφανές: Όταν φτιάχνονταν οι βιβλιοθήκες της C εμείς δεν είχαμε δηλώσει ακόμα τον τύπο μας. Οπότε, ότι χρειαζόμαστε για το νέο μας τύπο θα πρέπει να το κάνουμε μόνοι μας χρησιμοποιώντας τα εργαλεία της γλώσσας για τα επιμέρους μέλη της δομής και όχι για τη δομή στο σύνολό της. Αν ας πούμε θέλουμε να ζητήσουμε από το χρήστη να δώσουμε τιμές στο πληκτρολόγιο για τις τρεις συντεταγμένες θα πρέπει να του το ζητήσουμε τρεις φορές χρησιμοποιώντας μία συνάρτηση όπως η scanf για κάθε ένα ακέραιο μέλος της δομής μας. Πώς όμως μπορούμε να προσπελάσουμε τα επιμέρους στοιχεία της δομής; Γράφουμε το όνομα της σύνθετης μεταβλητής ακολουθούμενο από μία τελεία και μετά το όνομα του μέλους. Δείτε για παράδειγμα τις παρακάτω εντολές: 1 point1.x = 7; 2 point1.y = point1.z + 2; 3 scanf("%d", &point1.x); 4 printf("%d", point1.z); Η πρώτη αναθέτει την τιμή 7 στο μέλος x της point1, η δεύτερη αναθέτει στο μέλος y το άθροισμα του μέλους z και του 2, η τρίτη ζητάει από το χρήστη μία τιμή και την αναθέτει στο μέλος x, ενώ η τελευταία τυπώνει το μέλος z ως ακέραιο. 10

2.2 Ονοματοδοσία structs και typedef Μπορούμε δηλώσουμε και άλλες μεταβλητές όπως η point1 του προηγούμενου παραδείγματος αλλά θα πρέπει κάθε φορά να γράφουμε την πλήρη δήλωση της struct, πράγμα άβολο. Υπάρχουν δύο τρόποι για να δώσουμε σύντομα ονόματα σε structs τα οποία να χρησιμοποιούμε στη συνέχεια όπως ακριβώς και τα ονόματα των βασικών τύπων της γλώσσας. Ο πρώτος τρόπος φαίνεται στο παρακάτω παράδειγμα: 1 struct point { 2 int x; 3 int y; 4 int z; 5 }; 6 struct point point1; 7 struct point point2; Παρατηρήστε ότι στη γραμμή 1 παρεμβάλαμε ένα όνομα, το όνομα point ανάμεσα στη λέξη struct και το άγκιστρο. Αυτό το όνομα μαζί με τη λέξη struct αποτελεί ένα νέο όνομα τύπου. Οι δηλώσεις των γραμμών 6 και 7 δηλώνουν δύο μεταβλητές point1 και point2 αντίστοιχα οι οποίες είναι τύπου struct point. Η παραπάνω δομή (struct) μαζί με το όνομά της point είναι μία ονοματισμένη δομή και μπορούμε να χρησιμοποιήσουμε τις λέξεις struct point όπως οποιοδήποτε τύπο της C, όπως είδαμε στις γραμμές 6 και 7. Η C μας δίνει τη δυνατότητα να ορίσουμε νέα ονόματα για υπάρχοντες τύπους χρησιμοποιώντας την εντολή typedef η οποία συντάσσεται έτσι: 1 typedef existing_type_name new_type_name; και η οποία σημαίνει ότι αν υπάρχει ένας τύπος με όνομα existing_type_name τότε μπορεί αντί για το όνομά του να χρησιμοποιηθεί το όνομα new_type_name. Το παλιό όνομα δεν καταργείται απλώς δημιουργείται ένα νέο συνώνυμο. Ο σκοπός είναι να ορίζουμε απλούστερα ονόματα για υπάρχοντες τύπους των οποίων το όνομα είναι άβολο (γιατί είναι μεγάλο ή γιατί δεν μας αρέσει). Για παράδειγμα, αν ο τύπος unsigned long long int μας φαίνεται άβολος μπορούμε να του δώσουμε ένα νέο όνομα, το ullint το οποίο να χρησιμοποιούμε όποτε θέλουμε έναν unsigned long long int, κάπως έτσι: 1 typedef unsigned long long int ullint; 2 ullint a, b, c; Στο προηγούμενο παράδειγμα οι μεταβλητές a, b και c είναι τύπου ullint δηλαδή unsigned long long int. Ανάλογα, μπορούμε να χρησιμοποιήσουμε την typedef για να δώσουμε πιο απλά ονόματα σε struct με δύο διαφορετικούς τρόπους: Ο ένας είναι να χρησιμοποιήσουμε την typedef για να δώσουμε ένα νέο όνομα σε μια ονοματισμένη struct όπως παρακάτω: 1 struct point_str { 2 int x; 3 int y; 4 int z; 5 }; 6 typedef struct point_str point3d; 11

Στη γραμμή 1, ξεκινάει η δήλωση μιας ονοματισμένης struct (είναι ονοματισμένη γιατί παρεμβάλλεται το όνομα point_str ανάμεσα στη λέξη struct και το άγκσιτρο που ξεκινάει την περιγραφή της δομής) οπότε το struct point_str είναι ένα νέο όνομα τύπου. Στη γραμμή 6, με την typedef δίνουμε στον υπάρχοντα τύπο struct point_str το όνομα point3d. Ο άλλος είναι να μην ονοματίσουμε τη struct αλλά να τη δηλώσουμε στην ίδια γραμμή με μία typedef και μετά το άγκιστρο που κλείνει τη struct να γράψουμε το νέο όνομα τύπου, όπως εδώ: 1 typedef struct point_str { 2 int x; 3 int y; 4 int z; 5 } point3d; Στα παρακάτω θα θεωρήσουμε δεδομένη την προηγούμενη typedef και θα χρησιμοποιούμε το όνομα point3d ως όνομα τύπου. 2.3 Πράξεις με structs Συνεχίζοντας με το παράδειγμα των σημείων, θα δούμε πώς μπορούμε να κάνουμε πράξεις με struct. Δυστυχώς, δεν μπορούμε να κάνουμε και πολλές με τις δομές ως ενιαίες οντότητες. Ευτυχώς ο τελεστής απόδοσης τιμής = δουλεύει με τις struct και έτσι αν τρέξουμε σε ένα πρόγραμμα τις παρακάτω εντολές 1 point3d a, b; 2 a.x = 2; a.y = 4; a.z = 8; 3 b = a; 4 printf("b:(% d %d %d)\ n", b.x, b.y, b.z); θα επιβεβαιώσουμε ότι το σημείο b έχει πάρει τις συντεταγμένες του σημείου a. Η εντολή απόδοσης τιμής δηλαδή στη γραμμή 3 είχε ως αποτέλεσμα να αντιγραφούν ένα προς ένα τα μέλη της δομής a στα αντίστοιχα μέλη της δομής b. Δυστυχώς δεν συμβαίνει το ίδιο και με άλλους τελεστές που έχουμε συνηθίσει να χρησιμοποιούμε. Για παράδειγμα δεν μπορούμε να χρησιμοποιήσουμε τον τελεστή της πρόσθεσης για να προσθέσουμε δύο σημεία έτσι: a+b. Στην πράξη για να κάνουμε τέτοιες πράξεις ορίζουμε συναρτήσεις οι οποίες κάνουν τις πράξεις που θέλουμε χρησιμοποιώντας τους γνωστούς τελεστές αλλά στα επιμέρους στοιχεία των δομών. Το παρακάτω παράδειγμα ορίζει δύο τρισδιάστατα σημεία και τους δίνει κάποιες τιμές. Στη συνέχεια τα δύο σημεία αθροίζονται σε ένα τρίτο του οποίου τυπώνονται οι συντεταγμένες. Τη δουλειά την κάνει η συνάρτηση add_point3d. 1 # include <stdio.h> Listing 9: Συνάρτηση πρόσθεσης σημείων 2 3 typedef struct { 4 int x; 5 int y; 6 int z; 12

7 } point3d; 8 9 point3d add_point3d( point3d a, point3d b) 10 { 11 point3d result; 12 result.x = a.x + b.x; 13 result.y = a.y + b.y; 14 result.z = a.z + b.z; 15 16 return result; 17 } 18 19 int main() 20 { 21 22 point3d a, b, sum; 23 24 a.x = 2; a.y = 4; a.z = 8; 25 b.x = 1; b.y = 3; b.z = 7; 26 27 sum = add_point3d(a, b); 28 printf(" sum: (% d %d %d)\ n", sum.x, sum.y, sum.z); 29 30 return 0; 31 } 2.4 Γράψιμο και διάβασμα structs σε αρχείο Στα προηγούμενα παραδείγματα χειρισμού αρχείων είδαμε αρκετές συναρτήσεις που μπορούμε να χρησιμοποιήσουμε για να γράψουμε και να διαβάσουμε δεδομένα από ένα αρχείο. Κάποιες από αυτές όπως οι fputc/fgetc, fputs/fgets χειρίζονται τα δεδομένα σε επίπεδο byte και είναι φυσικά βολικές για χειρισμό χαρακτήρων. Όταν όμως χειριζόμαστε πιο σύνθετους τύπους όπως οι structs αλλά ακόμα και σε απλούστερες περιπτώσεις όπως ένας int, δεν μπορούμε (ή δεν θέλουμε) να ξέρουμε ακριβώς πώς είναι οργανωμένα τα bytes που τους απαρτίζουν, οπότε οι παραπάνω συναρτήσεις γραψίματος/διαβάσματος δεν είναι κατάλληλες. Εδώ οι μόνες μας εναλλακτικές είναι οι συναρτήσεις fread/fwrite ³. Το παρακάτω παράδειγμα, ζητάει από το χρήστη να δώσει τιμές για τις συντεταγμένες δύο σημείων, υπολογίζει το μέσο τους (είναι το άθροισμα των αντίστοιχων συντεταγμένων διά δύο) και αποθηκεύει τελικά σε αρχείο και τα τρία σημεία. Το επόμενο παράδειγμα, ανοίγει το αρχείο για διάβασμα και τυπώνει τα αποτελέσματα. 1 # include <stdio.h> Listing 10: Γράψιμο structs σε αρχείο ³Και όπως αναφέρεται σε επόμενη ενότητα θα πρέπει να χρησιμοποιούμε δυαδικά αρχεία. 13

2 # include <stdlib.h> 3 4 typedef struct { 5 float x; 6 float y; 7 float z; 8 } point3d; 9 10 point3d add_point3d( point3d a, point3d b) 11 { 12 point3d result; 13 result.x = a.x + b.x; 14 result.y = a.y + b.y; 15 result.z = a.z + b.z; 16 17 return result; 18 } 19 20 int main() 21 { 22 point3d a, b, sum; 23 FILE* f; 24 25 a.x = 2; a.y = 4; a.z = 8; 26 b.x = 1; b.y = 3; b.z = 7; 27 28 sum = add_point3d(a, b); 29 sum.x /= 2; 30 sum.y /= 2; 31 sum.z /= 2; 32 33 if (!(f = fopen("points.dat", "wb"))) { 34 perror(" Error opening file"); 35 exit(1); 36 } 37 38 if ( fwrite(&a, sizeof( point3d), 1, f)!= 1) { perror(" Writing a"); exit(1); } 39 if ( fwrite(&b, sizeof( point3d), 1, f)!= 1) { perror(" Writing b"); exit(1); } 40 if ( fwrite(& sum, sizeof( point3d), 1, f)!= 1) { perror(" Writing sum"); exit(1); } 41 42 fclose(f); 43 44 return 0; 45 } 14

1 # include <stdio.h> 2 # include <stdlib.h> Listing 11: Διάβασμα structs από αρχείο 3 4 typedef struct { 5 float x; 6 float y; 7 float z; 8 } point3d; 9 10 int main() 11 { 12 point3d a, b, sum; 13 FILE* f; 14 15 if (!(f = fopen("points.dat", "rb"))) { 16 perror(" Error opening file"); 17 exit(1); 18 } 19 20 if ( fread(&a, sizeof( point3d), 1, f)!= 1) { perror(" Writing a"); exit(1); } 21 if ( fread(&b, sizeof( point3d), 1, f)!= 1) { perror(" Writing b"); exit(1); } 22 if ( fread(& sum, sizeof( point3d), 1, f)!= 1) { perror(" Writing sum"); exit(1); } 23 24 printf("a: (% f %f %f)\ n", a.x, a.y, a.z); 25 printf("b: (% f %f %f)\ n", b.x, b.y, b.z); 26 printf(" sum: (% f %f %f)\ n", sum.x, sum.y, sum.z); 27 fclose(f); 28 29 return 0; 30 } 3 Σημειώσεις για τα αρχεία Παρόλο που το πρότυπο της C έχει καταργήσει τη διάκριση ανάμεσα σε δυαδικά και αρχεία κειμένου, σε κάποια περιβάλλοντα η διάκριση συνεχίζει να υφίσταται οπότε έχει νόημα να γίνει κάποια αναφορά σε αυτά. Τα δυαδικά αρχεία είναι υποτίθεται σειρές από bytes τις οποίες το λειτουργικό σύστημα δεν θεωρεί ότι έχουν κάποια ιδιαίτερη δομή. Αντίθετα, τα αρχεία κειμένου υποτίθεται ότι είναι ακολουθίες εγγραφών (γραμμών κειμένου δηλαδή) οι οποίες χωρίζονται με ένα χαρακτήρα αλλαγής γραμμής τον οποίο ορίζει το λειτουργικό σύστημα. Το σκεπτικό είναι ότι επειδή ο χαρακτήρας που σηματοδοτεί την αλλαγή γραμμής είναι (ή μπορεί να είναι) διαφορετικός σε κάθε λειτουργικό σύστημα καλό θα ήταν όταν ένα πρόγραμμα C ζητάει να αλλάξει η γραμμή σε ένα αρχείο κειμένου, το λειτουργικό σύστημα να γράφει τον χαρακτήρα ή τους χαρακτήρες που απαιτούνται (π.χ. ASCII 10 στο UNIX, ASCII 13 και ASCII 10 στα Windows κοκ) χωρίς να χρειάζεται 15

ο προγραμματιστής να ασχολείται με το ποιοι ακριβώς είναι οι ASCII κωδικοί των χαρακτήρων αλλαγής γραμμής που χρησιμοποιούνται σε εκείνο το λειτουργικό. Πρακτικά αυτό σημαίνει ότι αν στο λειτουργικό σύστημα Windows ανοίξετε ένα αρχείο σε κατάσταση κειμένου για να το γράψετε και ζητήσετε να γραφτεί ο χαρακτήρας '\n' τότε στο αρχείο θα γραφτούν δύο bytes, ένα με τιμή 13 και ένα με τιμή 10. Αν κάνετε το ίδιο στο UNIX θα γραφτεί η τιμή 10. Αντίστοιχα, αν ανοίξετε ένα αρχείο κειμένου για να το διαβάσετε και έχοντας φτάσει στο τέλος μίας γραμμής ζητήσετε τον επόμενο χαρακτήρα, το πρόγραμμά σας θα σας πει ότι ο επόμενος είναι ο '\n' (ASCII κωδικός 10) παρόλο που στο αρχείο θα είνια γραμμένες οι τιμές 13 και 10. Δείτε σχετικά και την άσκηση 3. Τελικά, τι πρέπει να κάνει κανείς όταν πρέπει να δουλέψει με αρχεία; Σε τι κατάσταση να ανοίγει τα αρχεία; Δείτε τους παρακάτω πρακτικούς κανόνες: Όταν δημιουργείτε εσείς το αρχείο και πρέπει να επιλέξετε, τότε κατά κανόνα θα πρέπει να το δημιουργείτε σε δυαδική μορφή, δηλαδή με τους προσδιοριστές "wb". Σε αυτήν την περίπτωση, όταν το ανοίξετε (πάλι σε δυαδική μορφή) θα διαβάσετε ακριβώς τα ίδια που γράψατε. Αν είστε απολύτως σίγουροι ότι το αρχείο σας θα περιέχει αποκλειστικά χαρακτήρες κειμένου χωρισμένους από αλλαγές γραμμής, μπορείτε να το ανοίξετε για γράψιμο σε κατάσταση κειμένου ("wt"). Όταν διαβάζετε ένα αρχείο, το σωστό είναι να το ανοίγετε με τον ίδιο τρόπο που το ανοίξατε για γράψιμο. Όταν διαβάζετε ένα αρχείο και δεν γνωρίζετε με ποιον τρόπο γράφτηκε (δυαδικό ή κειμένου) τότε ο μόνος ασφαλής τρόπος είναι να το ανοίξετε σε δυαδική κατάσταση. Σε αυτήν την περίπτωση απλώς θα πρέπει να κάνετε τη μετατροπή των χαρακτήρων αλλαγής γραμμής με το χέρι μέσα στο πρόγραμμα ενώ στην αντίθετη περίπτωση δηλαδή αν ανοίξετε ως αρχείο κειμένου ένα αρχείο που έχει γραφεί ως δυαδικό τότε πιθανώς να διαβάσετε εντελώς διαφορετικά πράγματα από εκείνα που έχουν γραφτεί. 4 Ασκήσεις Άσκηση 1: Γράψτε ένα πρόγραμμα το οποίο τυπώνει την τιμή του ειδικού χαρακτήρα τερματισμού αρχείου EOF. Άσκηση 2: Γράψτε ένα πρόγραμμα το οποίο ζητάει από το χρήστη συμβολοσειρές και τις γράφει σε ένα αρχείο κειμένου χρησιμοποιώντας τη συνάρτηση fputs. Αφού τελειώσει το πρόγραμμά σας, ανοίξτε το αρχείο με το Wordpad. Ανοίξτε το και με το Notepad. Βλέπετε καμία διαφορά; Τροποποιήστε το πρόγραμμά σας ώστε να ανοίγει το αρχείο σε δυαδική μορφή. Ξαναανοίξτε το με το wordpad και το notepad. Βλέπετε τη διαφορά; Αυτό είναι αυτό που κερδίζετε αν ανοίξετε ένα αρχείο σε κατάσταση κειμένου αντί για δυαδική. Άσκηση 3: Γράψτε ένα πρόγραμμα το οποίο θα ζητάει ένα όνομα αρχείου από το χρήστη. Θα ανοίγει το αρχείο σε δυαδική κατάσταση ("rb", όχι σκέτο r ) και θα τυπώνει στην οθόνη τα περιεχόμενά του ως εξής: Θα διαβάζει ένα-ένα byte με την fgetc και θα τυπώνει τον ASCII κωδικό του byte που διάβασε, μία άνω κάτω τελεία και μετά τον χαρακτήρα που διάβασε αν είναι εκτυπώσιμος⁴, αλλιώς ⁴Η συνάρτηση isgraph(c) επιστρέφει true αν ο χαρακτήρας είναι εκτυπώσιμος, αλλιώς επιστρέφει false. 16

θα τυπώνει την κάτω παύλα (underscore). Θα βάζει και ένα κενό ανάμεσα στους χαρακτήρες για να ξεχωρίζουν. Στο τέλος θα τυπώνει το πλήθος των χαρακτήρων που διάβασε. Άσκηση 4: Δοκιμάστε το πρόγραμμά της προηγούμενης άσκησης σε διάφορα αρχεία. Σημειώστε αν το μέγεθος του αρχείου συμπίπτει με αυτό που σας δείχνει η εντολή dir ή το μέγεθος που βλέπετε στον Windows file explorer. Τροποποιήστε το πρόγραμμα της άσκησης 3 ώστε να ανοίγει το αρχείο σε κατάσταση κειμένου ("rt") και ξανακάντε τις δοκιμές. Βλέπετε καμία διαφορά στο μέγεθος που αναφέρει το πρόγραμμά σας; Κάντε μία δοκιμή με ένα αρχείο κειμένου, όπως π.χ. ο πηγαίος κώδικας των προγραμμάτων C που γράφετε. Πόσο διαφέρει η τιμή του προγράμματος της άσκησης 3 με αυτή της άσκησης 4; Πόσες γραμμές έχει το αρχείο του οποίου το μέγεθος προσπαθείτε να βρείτε; Που οφείλεται η διαφορά; Άσκηση 5: Θεωρήστε έναν τύπο με όνομα complex όπως ο παρακάτω 1 typedef struct { 2 float re; 3 float im; 4 } complex; Αυτός υποτίθεται ότι παριστάνει έναν μιγαδικό αριθμό, αποθηκεύοντας το πραγματικό και το φανταστικό του μέρος. Χρησιμοποιώντας τον παραπάνω τύπο, γράψτε μία συνάρτηση με όνομα print_complex η οποία παίρνει παράμετρο έναν complex και τον τυπώνει στη μορφή (5.00,7.00j) δηλαδή το πραγματικό και το φανταστικό μέρος μέσα σε παρενθέσεις χωρισμένα με κόμμα και το φανταστικό μέρος να ακολουθείται από τον χαρακτήρα j. Δηλώστε μια μεταβλητή τύπου complex, δώστε τιμές με το χέρι στο πραγματικό και το φανταστικό μέρος της και καλέστε τη συνάρτησή σας για να τυπώσει τη μεταβλητή. Γράψτε επίσης μία συνάρτηση add_complex η οποία θα προσθέτει δύο μιγαδικούς και θα επιστρέφει το αποτέλεσμα. Δηλώστε άλλη μία μεταβλητή, δώστε της τιμές με το χέρι και προσθέστε την στην πρώτη χρησιμοποιώντας αυτήν τη δεύτερη συνάρτηση. Τυπώστε το αποτέλεσμα. Σχετικές ασκήσεις πιστοποίησης: 148, 149, 150, 153 (Αρχεία), 133, 144 (structs). 17