Μάθημα C // Άσκηση 1

Επιτυχόντες
& Αποτυχόντες

Διαβάζουμε ένα αρχείο με βαθμούς φοιτητών, υπολογίζουμε τον μέσο όρο, και χωρίζουμε τους φοιτητές σε δύο νέα αρχεία ανάλογα με το αν πέρασαν ή όχι.

Δημήτρης Δημητρίου 5.4 8.6
Νίκος Νικολόπουλος 4.9 3.5
Γιώργος Γεωργίου 6.8 4.9
Μαρία Παπαδοπούλου 9.2 8.7

01 — Τα δεδομέναΗ μορφή του students.txt

Το αρχείο εισόδου περιέχει μία γραμμή ανά φοιτητή. Κάθε γραμμή έχει 4 πεδία χωρισμένα με κενά ή tabs: όνομα, επώνυμο, και δύο βαθμούς. Δεν υπάρχει συγκεκριμένος διαχωριστής (π.χ. κόμμα) — ένα ή περισσότερα whitespaces αρκούν.

students.txt
Δημήτρης     Δημητρίου     5.4   8.6
Νίκος        Νικολόπουλος  4.9   3.5
Γιώργος      Γεωργίου      6.8   4.9
Μαρία        Παπαδοπούλου  9.2   8.7
Ελένη        Ιωάννου       3.1   4.0
Κώστας       Αντωνίου      7.5   6.0

02 — Δοκίμασέ τοΖωντανός διαχωρισμός

Πρόσθεσε φοιτητές παρακάτω και παρατήρησε πώς αυτόματα κατατάσσονται στο success.txt ή στο fail.txt ανάλογα με τον μέσο όρο τους. Όριο επιτυχίας: 5.0.

📄 success.txt 0 εγγραφές
📄 fail.txt 0 εγγραφές
Σύνολο 0
Επιτυχόντες 0
Αποτυχόντες 0

03 — Πώς δουλεύει η fscanfΠαρσάρισμα 4 πεδίων

Σε κάθε επανάληψη, η fscanf διαβάζει μία γραμμή και «σπάει» τα 4 πεδία σε αντίστοιχες μεταβλητές. Το format string "%s %s %f %f" της λέει τι περιμένει: δύο strings και δύο floats, χωρισμένα με whitespace.

Δημήτρης Δημητρίου 5.4 8.6
%s
onoma
"Δημήτρης"
%s
eponymo
"Δημητρίου"
%f
vathmos1
5.4
%f
vathmos2
8.6
Επιστρεφόμενη τιμή: 4 — δηλαδή διαβάστηκαν επιτυχώς και τα 4 πεδία ✓

04 — Ευελιξία στα κενάΠόσα κενά «μετράνε»;

Σύντομη απάντηση: όσα κι αν είναι, δεν παίζει ρόλο. Όταν η fscanf δει ένα %s ή %f, χειρίζεται μόνη της τα whitespaces — τα παραβλέπει αυτόματα μέχρι να βρει τον επόμενο «χρήσιμο» χαρακτήρα.

1
Παραβλέπει όσα whitespaces (κενά, tabs, newlines) υπάρχουν μπροστά.
2
Διαβάζει τους χρήσιμους χαρακτήρες (γράμματα ή ψηφία).
3
Σταματάει όταν βρει το επόμενο whitespace.

Δηλαδή τα κενά λειτουργούν απλώς ως «σύνορα μεταξύ πεδίων» — όχι ως ακριβή διαχωριστές. Όλες οι παρακάτω εκδοχές του students.txt δίνουν ταυτόσημα αποτελέσματα:

Εκδοχή 1 · Ένα κενό
Δημήτρης Δημητρίου 5.4 8.6
Νίκος Νικολόπουλος 4.9 3.5
Εκδοχή 2 · Πολλά κενά
Δημήτρης        Δημητρίου     5.4   8.6
Νίκος           Νικολόπουλος  4.9   3.5
Εκδοχή 3 · Tabs
Δημήτρης Δημητρίου 5.4 8.6
Νίκος Νικολόπουλος  4.9 3.5
Εκδοχή 4 · Άναρχος συνδυασμός
Δημήτρης    Δημητρίου  5.4       8.6
   Νίκος       Νικολόπουλος    4.9   3.5
Εκδοχή 5 · Όλα σε μία γραμμή
Δημήτρης Δημητρίου 5.4 8.6 Νίκος Νικολόπουλος 4.9 3.5

Φαντάσου τη fscanf σαν «ακορντεόν»: ό,τι κενά κι αν βάλεις, τα συρρικνώνει σε ένα νοητό σύνορο. Η μορφή δεν τη νοιάζει — μόνο τα tokens.

Πότε δεν δουλεύει;

⚠ Παγίδα 1

Πεδίο που περιέχει κενό

Άννα Μαρία   Παπαδοπούλου   5.4   8.6

Η fscanf νομίζει ότι:
onoma = "Άννα", eponymo = "Μαρία" ⚠ λάθος!
Μετά προσπαθεί να διαβάσει "Παπαδοπούλου" ως float και αποτυγχάνει. Η συνάρτηση επιστρέφει 2, ο βρόχος μας με == 4 τερματίζει σωστά, αλλά αυτή η γραμμή χάνεται.

⚠ Παγίδα 2

Διαχωριστής εκτός whitespace

Δημήτρης,Δημητρίου,5.4,8.6

Σε CSV format η fscanf("%s %s %f %f", ...) δεν δουλεύει — διαβάζει ολόκληρη τη γραμμή ως ένα string γιατί δεν υπάρχει whitespace. Χρειάζεται διαφορετικό format:
fscanf(fp, "%[^,],%[^,],%f,%f", ...)

Συνοπτικά

1 κενό
Πολλά κενά
Tab
Newline μεταξύ πεδίων
Μικτά (κενά + tabs)
Κόμμα ( , )
Άνω-κάτω τελεία ( : )
Pipe ( | )
Διδακτικό συμπέρασμα: το "%s %s %f %f" είναι αγνωστικιστικό προς τη στοίχιση

05 — Η συνθήκη του βρόχουΓιατί == 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 και σε κακώς διαμορφωμένη γραμμή — διπλή ασφάλεια.

Κανόνας: Έλεγχος συγκεκριμένου αριθμού πεδίων > γενικός έλεγχος EOF

06 — Ο κώδικας σε CΥλοποίηση βήμα προς βήμα

Το πρόγραμμα ανοίγει 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;  // επιτυχής τερματισμός
}
01

Τρία αρχεία ανοιχτά ταυτόχρονα

Το πρόγραμμα διατηρεί τρεις δείκτες αρχείων ενεργούς: ένα για ανάγνωση και δύο για εγγραφή. Σε σφάλμα ανοίγματος του 2ου ή 3ου, κλείνουμε τα ήδη ανοιχτά πριν τερματίσουμε — αλλιώς αφήνουμε «ορφανά» file descriptors.

02

Γιατί & μόνο σε floats;

Τα onoma/eponymo είναι πίνακες χαρακτήρων — το όνομά τους είναι ήδη διεύθυνση. Τα vathmos1/vathmos2 είναι απλές μεταβλητές float και χρειάζονται & για να πάρει η fscanf τη διεύθυνσή τους.

03

Το format %.1f στην έξοδο

Στη fprintf γράφουμε %.1f για στρογγυλοποίηση σε 1 δεκαδικό. Έτσι το αρχείο εξόδου είναι «καθαρό»: 5.4 αντί για 5.400000.

04

Σωστή σειρά κλεισίματος

Η σειρά fclose δεν παίζει ρόλο για το αποτέλεσμα, αλλά πάντα κλείνουμε όλα τα αρχεία πριν την έξοδο. Χωρίς fclose, ο buffer εγγραφής μπορεί να μην γραφτεί στον δίσκο και τα αρχεία να βγουν ελλιπή.