Διαβάζουμε ένα αρχείο με βαθμούς φοιτητών, υπολογίζουμε τον μέσο όρο, και χωρίζουμε τους φοιτητές σε δύο νέα αρχεία ανάλογα με το αν πέρασαν ή όχι.
students.txtΤο αρχείο εισόδου περιέχει μία γραμμή ανά φοιτητή. Κάθε γραμμή έχει 4 πεδία χωρισμένα με κενά ή tabs: όνομα, επώνυμο, και δύο βαθμούς. Δεν υπάρχει συγκεκριμένος διαχωριστής (π.χ. κόμμα) — ένα ή περισσότερα whitespaces αρκούν.
Δημήτρης Δημητρίου 5.4 8.6 Νίκος Νικολόπουλος 4.9 3.5 Γιώργος Γεωργίου 6.8 4.9 Μαρία Παπαδοπούλου 9.2 8.7 Ελένη Ιωάννου 3.1 4.0 Κώστας Αντωνίου 7.5 6.0
Πρόσθεσε φοιτητές παρακάτω και παρατήρησε πώς αυτόματα κατατάσσονται
στο success.txt ή στο fail.txt ανάλογα με
τον μέσο όρο τους. Όριο επιτυχίας: 5.0.
fscanfΠαρσάρισμα 4 πεδίων
Σε κάθε επανάληψη, η fscanf διαβάζει μία γραμμή και
«σπάει» τα 4 πεδία σε αντίστοιχες μεταβλητές. Το format string
"%s %s %f %f" της λέει τι περιμένει: δύο strings και
δύο floats, χωρισμένα με whitespace.
Σύντομη απάντηση: όσα κι αν είναι, δεν παίζει ρόλο.
Όταν η fscanf δει ένα %s ή %f,
χειρίζεται μόνη της τα whitespaces — τα παραβλέπει αυτόματα μέχρι να
βρει τον επόμενο «χρήσιμο» χαρακτήρα.
Δηλαδή τα κενά λειτουργούν απλώς ως «σύνορα μεταξύ πεδίων»
— όχι ως ακριβή διαχωριστές. Όλες οι παρακάτω εκδοχές του students.txt
δίνουν ταυτόσημα αποτελέσματα:
Δημήτρης Δημητρίου 5.4 8.6 Νίκος Νικολόπουλος 4.9 3.5
Δημήτρης Δημητρίου 5.4 8.6 Νίκος Νικολόπουλος 4.9 3.5
Δημήτρης Δημητρίου 5.4 8.6 Νίκος Νικολόπουλος 4.9 3.5
Δημήτρης Δημητρίου 5.4 8.6 Νίκος Νικολόπουλος 4.9 3.5
Δημήτρης Δημητρίου 5.4 8.6 Νίκος Νικολόπουλος 4.9 3.5
Φαντάσου τη fscanf σαν «ακορντεόν»: ό,τι κενά κι αν βάλεις,
τα συρρικνώνει σε ένα νοητό σύνορο. Η μορφή δεν τη νοιάζει — μόνο τα
tokens.
Άννα Μαρία Παπαδοπούλου 5.4 8.6
Η fscanf νομίζει ότι:
onoma = "Άννα", eponymo = "Μαρία" ⚠ λάθος!
Μετά προσπαθεί να διαβάσει "Παπαδοπούλου" ως float και αποτυγχάνει.
Η συνάρτηση επιστρέφει 2, ο βρόχος μας με == 4
τερματίζει σωστά, αλλά αυτή η γραμμή χάνεται.
Δημήτρης,Δημητρίου,5.4,8.6
Σε CSV format η fscanf("%s %s %f %f", ...)
δεν δουλεύει — διαβάζει ολόκληρη τη γραμμή ως ένα
string γιατί δεν υπάρχει whitespace. Χρειάζεται διαφορετικό format:
fscanf(fp, "%[^,],%[^,],%f,%f", ...)
== 4 και όχι != EOF;
Πολλοί γράφουν while (fscanf(...) != EOF) γιατί τους
«φαίνεται» πιο φυσικό. Όμως αυτό κρύβει μια παγίδα: αν μια γραμμή
είναι κακώς διαμορφωμένη (λείπει βαθμός, ή υπάρχει γράμμα στη θέση
αριθμού), η fscanf επιστρέφει 1, 2 ή 3 — που είναι
≠ EOF, οπότε ο βρόχος δεν σταματάει!
while (fscanf(fp, "%s %s %f %f", n, e, &b1, &b2) != EOF) { // Αν η γραμμή ήταν κακώς γραμμένη, // οι μεταβλητές έχουν παλιές/τυχαίες τιμές // αλλά εμείς συνεχίζουμε να επεξεργαζόμαστε! }
Επιστρέφει EOF (-1) μόνο στο τέλος αρχείου. Σε ελλιπή γραμμή επιστρέφει 1, 2, ή 3 — και ο βρόχος συνεχίζει με ψευδή δεδομένα.
while (fscanf(fp, "%s %s %f %f", n, e, &b1, &b2) == 4) { // Μπαίνουμε ΜΟΝΟ αν διαβάστηκαν // επιτυχώς και τα 4 πεδία. // Σε οτιδήποτε λιγότερο, σταματάμε. }
Συνεχίζουμε μόνο όταν διαβάστηκαν όλα τα πεδία. Σταματάει σε EOF και σε κακώς διαμορφωμένη γραμμή — διπλή ασφάλεια.
Το πρόγραμμα ανοίγει 3 αρχεία (1 για ανάγνωση, 2 για εγγραφή),
διαβάζει τους φοιτητές έναν-έναν με fscanf, υπολογίζει
τον μέσο όρο, και τους κατευθύνει στο σωστό αρχείο εξόδου με
fprintf.
#include <stdio.h> #include <stdlib.h> int main() { // Δηλώσεις δεικτών αρχείων (FILE pointers) FILE *fin; // δείκτης για το αρχείο εισόδου students.txt FILE *fsuccess; // δείκτης για το αρχείο επιτυχόντων success.txt FILE *ffail; // δείκτης για το αρχείο αποτυχόντων fail.txt // Μεταβλητές για την αποθήκευση των στοιχείων κάθε φοιτητή char onoma[50]; // όνομα φοιτητή char eponymo[50]; // επώνυμο φοιτητή float vathmos1; // πρώτος βαθμός float vathmos2; // δεύτερος βαθμός float mo; // μέσος όρος των δύο βαθμών // Μετρητές επιτυχόντων και αποτυχόντων φοιτητών int epityxontes = 0; int apotyxontes = 0; // Άνοιγμα αρχείου εισόδου σε λειτουργία ανάγνωσης ("r" = read) fin = fopen("students.txt", "r"); if (fin == NULL) { // Έλεγχος αν το άνοιγμα απέτυχε (π.χ. δεν υπάρχει το αρχείο) printf("Σφάλμα: Δεν είναι δυνατό το άνοιγμα του αρχείου students.txt\n"); return 1; // τερματισμός με κωδικό σφάλματος } // Άνοιγμα αρχείου επιτυχόντων σε λειτουργία εγγραφής ("w" = write) // Το "w" δημιουργεί νέο αρχείο ή σβήνει το υπάρχον αν υπάρχει fsuccess = fopen("success.txt", "w"); if (fsuccess == NULL) { printf("Σφάλμα: Δεν είναι δυνατή η δημιουργία του αρχείου success.txt\n"); fclose(fin); // κλείνουμε όσα αρχεία είχαμε ήδη ανοίξει return 1; } // Άνοιγμα αρχείου αποτυχόντων σε λειτουργία εγγραφής ffail = fopen("fail.txt", "w"); if (ffail == NULL) { printf("Σφάλμα: Δεν είναι δυνατή η δημιουργία του αρχείου fail.txt\n"); fclose(fin); fclose(fsuccess); return 1; } // ---------------------------------------------------------------------- // Ανάγνωση του αρχείου γραμμή-γραμμή με τη βοήθεια της fscanf. // Η fscanf διαβάζει 4 πεδία σε κάθε επανάληψη: 2 strings και 2 floats. // Επιστρέφει το πλήθος των πεδίων που διάβασε επιτυχώς. // Όταν φτάσει στο τέλος του αρχείου (EOF) θα επιστρέψει αριθμό // διαφορετικό από 4, οπότε ο βρόχος τερματίζει. // // Σημειώσεις για τη fscanf με %s: // - Το %s διαβάζει χαρακτήρες μέχρι το επόμενο whitespace // (κενό / tab / newline), οπότε αρκεί τα πεδία στο αρχείο να // χωρίζονται με κενά ή tabs - δεν χρειάζεται κάποιος ειδικός // διαχωριστής (π.χ. κόμμα). // // Σημειώσεις για τη συνθήκη του βρόχου ( == 4 ): // - Ελέγχουμε ότι διαβάστηκαν και τα 4 πεδία της γραμμής. // - Είναι πιο ασφαλές από το != EOF γιατί πιάνει και κακώς // διαμορφωμένες γραμμές (π.χ. γραμμή που λείπει βαθμός ή // υπάρχει γράμμα στη θέση αριθμού). // - Ο βρόχος σταματάει αυτόματα σε δύο περιπτώσεις: // 1) Τέλος αρχείου -> η fscanf επιστρέφει EOF (≠ 4) // 2) Κακώς διαμορφωμένη -> η fscanf επιστρέφει < 4 (≠ 4) // γραμμή // ---------------------------------------------------------------------- while (fscanf(fin, "%s %s %f %f", onoma, eponymo, &vathmos1, &vathmos2) == 4) { // Υπολογισμός του μέσου όρου των δύο βαθμών mo = (vathmos1 + vathmos2) / 2.0; // Έλεγχος επιτυχίας/αποτυχίας με βάση τον μέσο όρο if (mo >= 5.0) { // Εγγραφή των στοιχείων στο αρχείο επιτυχόντων fprintf(fsuccess, "%s %s %.1f %.1f\n", onoma, eponymo, vathmos1, vathmos2); epityxontes++; // αύξηση του μετρητή επιτυχόντων } else { // Εγγραφή των στοιχείων στο αρχείο αποτυχόντων fprintf(ffail, "%s %s %.1f %.1f\n", onoma, eponymo, vathmos1, vathmos2); apotyxontes++; // αύξηση του μετρητή αποτυχόντων } } // Κλείσιμο όλων των αρχείων - απαραίτητο για να αποθηκευτούν // σωστά τα δεδομένα στον δίσκο (flush του buffer) fclose(fin); fclose(fsuccess); fclose(ffail); // Εμφάνιση μηνύματος ολοκλήρωσης και στατιστικών printf("Η δημιουργία των αρχείων ολοκληρώθηκε με επιτυχία!\n"); printf("Αριθμός επιτυχόντων: %d\n", epityxontes); printf("Αριθμός αποτυχόντων: %d\n", apotyxontes); return 0; // επιτυχής τερματισμός }
Το πρόγραμμα διατηρεί τρεις δείκτες αρχείων ενεργούς: ένα για ανάγνωση και δύο για εγγραφή. Σε σφάλμα ανοίγματος του 2ου ή 3ου, κλείνουμε τα ήδη ανοιχτά πριν τερματίσουμε — αλλιώς αφήνουμε «ορφανά» file descriptors.
& μόνο σε floats;Τα onoma/eponymo είναι πίνακες
χαρακτήρων — το όνομά τους είναι ήδη διεύθυνση.
Τα vathmos1/vathmos2 είναι απλές μεταβλητές
float και χρειάζονται & για να πάρει
η fscanf τη διεύθυνσή τους.
%.1f στην έξοδοΣτη fprintf γράφουμε %.1f για
στρογγυλοποίηση σε 1 δεκαδικό. Έτσι το αρχείο
εξόδου είναι «καθαρό»: 5.4 αντί για
5.400000.
Η σειρά fclose δεν παίζει ρόλο για το αποτέλεσμα,
αλλά πάντα κλείνουμε όλα τα αρχεία πριν την έξοδο.
Χωρίς fclose, ο buffer εγγραφής μπορεί να μην γραφτεί στον δίσκο
και τα αρχεία να βγουν ελλιπή.