📚 Περιεχόμενα Μαθήματος
- 7.1 Ανάλυση: struct computer με scanf
- 7.2 Το πρόβλημα: Κενά στο brand
- 7.3 Τι είναι το \n (newline);
- 7.4 Η συνάρτηση fgets()
- 7.5 Ο χαρακτήρας '\0' (Null Terminator)
- 7.6 Το πρόβλημα του \n στο buffer
- 7.7 Η συνάρτηση getchar()
- 7.8 Παράδειγμα: scanf + getchar + fgets
- 7.9 Λύση: Πλήρες πρόγραμμα με fgets
- 7.10 Σύνοψη & Σύγκριση
- 8.1 Εισαγωγή στα Αρχεία (Files)
- 8.2 Τύποι Αρχείων
- 8.3 Άνοιγμα Αρχείου — fopen()
- 8.4 Κλείσιμο Αρχείου — fclose()
- 8.5 Θεωρία: Τοποθεσία Αρχείου (File Path)
- 8.6 Εγγραφή σε Αρχείο — fprintf()
- 8.7 Παράδειγμα: Εγγραφή & Ανάγνωση Αρχείου
7.1 Ανάλυση: struct computer με scanf
Ας ξεκινήσουμε αναλύοντας βήμα-βήμα ένα πρόγραμμα που χρησιμοποιεί μια δομή struct computer για να αποθηκεύσει τα στοιχεία ενός υπολογιστή:
📌 Γιατί pc.brand χωρίς & αλλά &pc.price με &;
Το pc.brand είναι πίνακας χαρακτήρων — το όνομα ενός πίνακα στη C είναι αυτόματα pointer στο πρώτο στοιχείο. Αντίθετα, η pc.price είναι απλή μεταβλητή float, οπότε χρειάζεται & για να δώσουμε τη διεύθυνσή της.
💻 Εκτέλεση — Αν γράψουμε μία λέξη
Όλα δουλεύουν τέλεια — αρκεί κάθε πεδίο να είναι μία λέξη χωρίς κενά.
7.2 Το πρόβλημα: Κενά στο brand
Τι γίνεται αν θέλουμε να γράψουμε "Hewlett Packard" στο brand; Ας δοκιμάσουμε:
⚠️ Τι πήγε στραβά;
Η scanf("%s") σταματάει να διαβάζει μόλις βρει κενό (space), tab ή newline.
Έτσι η scanf διάβασε μόνο "Hewlett" στο brand. Η λέξη "Packard" έμεινε στο buffer εισόδου και «καταναλώθηκε» αμέσως από την επόμενη scanf για το model!
🔍 Τι συμβαίνει βήμα-βήμα στη μνήμη;
Ο χρήστης πληκτρολογεί: Hewlett Packard και πατάει Enter.
Το buffer εισόδου (stdin) περιέχει:
Η πρώτη scanf("%s") παίρνει "Hewlett" — σταματάει στο κενό. Στο buffer μένουν:
Η δεύτερη scanf("%s") (για model) αγνοεί το κενό, παίρνει "Packard". Και ο χρήστης δεν πρόλαβε καν να πληκτρολογήσει!
💡 Συμπέρασμα
Η scanf("%s") δεν κάνει για κείμενο με κενά. Χρειαζόμαστε μια συνάρτηση που διαβάζει ολόκληρη τη γραμμή, μαζί με τα κενά. Αυτή είναι η fgets().
7.3 Τι είναι το \n (newline);
Ο χαρακτήρας \n (newline) είναι ένας ειδικός χαρακτήρας που σημαίνει «αλλαγή γραμμής». Αν και φαίνεται σαν δύο χαρακτήρες (\ και n), στη C είναι ένας χαρακτήρας.
📌 Πότε εμφανίζεται το \n;
Κάθε φορά που ο χρήστης πατάει Enter, ο χαρακτήρας \n προστίθεται στο τέλος αυτού που πληκτρολόγησε. Αυτό σημαίνει «τέλος γραμμής».
Παράδειγμα: Τι ακριβώς υπάρχει στο buffer;
Ο χρήστης πληκτρολογεί Dell και πατάει Enter. Στο buffer εισόδου (stdin) μπαίνουν:
Τα 4 γράμματα + ο χαρακτήρας \n από το Enter.
Πού χρησιμοποιούμε το \n;
| Χρήση | Κώδικας | Αποτέλεσμα |
|---|---|---|
| Αλλαγή γραμμής στην εκτύπωση | printf("Hello\nWorld"); | Hello World |
| Κενή γραμμή | printf("\n"); | (κενή γραμμή) |
| Αποτέλεσμα Enter | ο χρήστης πατάει Enter | \n μπαίνει στο buffer |
7.4 Η συνάρτηση fgets()
Η fgets() διαβάζει ολόκληρη τη γραμμή, μαζί με τα κενά, μέχρι ο χρήστης να πατήσει Enter.
Σύνταξη
| Παράμετρος | Τι κάνει | Παράδειγμα |
|---|---|---|
| buffer | Ο πίνακας χαρακτήρων στον οποίο αποθηκεύεται η είσοδος | pc.brand |
| size | Μέγιστος αριθμός χαρακτήρων που θα διαβαστούν (μαζί με το \0) | 50 |
| stdin | Από πού διαβάζει — stdin = πληκτρολόγιο | stdin |
Απλό παράδειγμα
Παράδειγμα: struct computer με fgets
Ας δούμε πώς γράφεται το πρόγραμμα με τον υπολογιστή χρησιμοποιώντας fgets() αντί για scanf στα strings:
⚠️ Προσέξτε την έξοδο!
Το "Hewlett Packard" ακολουθείται από αλλαγή γραμμής πριν το κόμμα. Αυτό συμβαίνει γιατί η fgets αποθήκευσε και το \n (Enter) μέσα στο string. Στη συνέχεια θα δούμε πώς το αφαιρούμε!
✅ Η fgets διαβάζει τα κενά!
Σε αντίθεση με τη scanf("%s"), η fgets() διαβάζει ολόκληρη τη γραμμή — κενά, tab, ό,τι κι αν πληκτρολογήσει ο χρήστης — μέχρι να πατήσει Enter.
⚠️ Προσοχή: Η fgets κρατάει και το \n!
Η fgets() αποθηκεύει και τον χαρακτήρα \n (Enter) στο τέλος του string. Αν γράψουμε "Dell" και πατήσουμε Enter, στο πεδίο αποθηκεύεται "Dell\n" αντί για "Dell".
Πώς αφαιρούμε το \n από το fgets;
Μετά από κάθε fgets(), μπορούμε να αφαιρέσουμε το \n ως εξής:
📌 Τι κάνει η strcspn();
Η strcspn() βρίσκει τη θέση του πρώτου χαρακτήρα που ταιριάζει από ένα σύνολο χαρακτήρων μέσα σε ένα string. Χρειάζεται #include <string.h>.
Από πού βγαίνει το όνομα
str → string
c → complementary (συμπληρωματικό σύνολο χαρακτήρων)
spn → span (εύρος / μήκος)
Σύνταξη
| Παράμετρος | Τι κάνει |
|---|---|
| string1 | Το string που ψάχνουμε |
| string2 | Οι χαρακτήρες που ψάχνουμε να βρούμε |
Η συνάρτηση επιστρέφει τη θέση (index) όπου εμφανίζεται ο πρώτος χαρακτήρας από το string2 μέσα στο string1.
Παράδειγμα
Η strcspn() βρίσκει:
Γιατί το \n είναι στη θέση 5 αλλά η strcspn επιστρέφει το μήκος πριν από αυτό → 5 χαρακτήρες πριν το newline.
Πώς χρησιμοποιείται με fgets()
Βήμα 1: Η strcspn() βρίσκει πού είναι το \n
Βήμα 2: Το αντικαθιστούμε με \0
Βήμα 3: Το string κόβεται εκεί
Πριν και μετά — οπτικά
Πριν (μετά fgets, ο χρήστης έγραψε "Dell" + Enter):
Μετά (αφού κάνουμε brand[strcspn(brand, "\n")] = '\0'):
7.5 Ο χαρακτήρας '\0' στη C (Null Terminator)
Στη C, τα strings δεν αποθηκεύονται μόνα τους. Αντίθετα, ένα string είναι πίνακας χαρακτήρων που τελειώνει με τον ειδικό χαρακτήρα '\0'.
Ο χαρακτήρας:
ονομάζεται:
- Null character
- Null terminator
- String terminator
και σημαίνει τέλος του string.
Παράδειγμα αποθήκευσης string
Στη μνήμη αποθηκεύεται έτσι:
Δηλαδή:
📌 Σημαντικό
Το '\0' λέει στο πρόγραμμα: 👉 "Το string τελειώνει εδώ"
7.6 Το πρόβλημα του \n στο buffer
Υπάρχει ένα κλασικό πρόβλημα όταν αναμειγνύουμε scanf() με fgets(). Ας δούμε τι πάει στραβά:
Ο χρήστης δεν πρόλαβε καν να γράψει το όνομά του!
⚠️ Γιατί συμβαίνει αυτό;
Όταν ο χρήστης πληκτρολογεί 25 και πατάει Enter, στο buffer μπαίνουν:
Η scanf("%d") διαβάζει μόνο τους αριθμούς 2 και 5. Ο χαρακτήρας \n (από το Enter) μένει στο buffer!
Η fgets() που ακολουθεί βρίσκει αυτό το \n στο buffer, νομίζει ότι ο χρήστης πάτησε ήδη Enter, και επιστρέφει αμέσως χωρίς να περιμένει νέα είσοδο!
7.7 Η συνάρτηση getchar()
Η getchar() διαβάζει ακριβώς έναν χαρακτήρα από το buffer εισόδου. Τη χρησιμοποιούμε σαν «σκούπα» που καθαρίζει το \n που μένει πίσω μετά από scanf.
Σύνταξη
Πώς λύνει το πρόβλημα;
✅ Η getchar() έλυσε το πρόβλημα!
Η getchar() «κατανάλωσε» τον χαρακτήρα \n που είχε αφήσει η scanf, έτσι η fgets βρήκε άδειο buffer και περίμενε κανονικά τον χρήστη.
🔍 Οπτικά — Βήμα-βήμα τι γίνεται στο buffer
1. Ο χρήστης γράφει 25 + Enter → buffer:
2. scanf("%d") παίρνει 25 → στο buffer μένει:
3. getchar() παίρνει το \n → το buffer είναι άδειο:
4. fgets() βρίσκει άδειο buffer → περιμένει τον χρήστη κανονικά!
💡 Κανόνας
Κάθε φορά που χρησιμοποιούμε scanf πριν από fgets, βάζουμε getchar(); ανάμεσά τους για να καθαρίσουμε το \n από το buffer.
7.8 Παράδειγμα: scanf + getchar + fgets
Ας δούμε ένα ολοκληρωμένο παράδειγμα που συνδυάζει scanf, getchar και fgets μαζί:
7.9 Λύση: Πλήρες πρόγραμμα με fgets
Ας ξαναγράψουμε το πρόγραμμα με τον υπολογιστή, αλλά αυτή τη φορά με fgets() ώστε να δέχεται κενά στα πεδία:
✅ Τώρα τα κενά δουλεύουν!
Κάθε πεδίο μπορεί πλέον να περιέχει πολλές λέξεις με κενά. Η fgets διαβάζει ολόκληρη τη γραμμή και η strcspn αφαιρεί το \n.
📌 Γιατί η τιμή (price) παραμένει με scanf;
Η price είναι αριθμός (float). Η scanf("%f") δουλεύει μια χαρά για αριθμούς — δεν υπάρχει θέμα κενών. Δεν χρειάζεται getchar() εδώ γιατί η scanf είναι τελευταία — δεν ακολουθεί fgets μετά.
7.10 Σύνοψη & Σύγκριση
📋 Σύνοψη Μαθήματος
✔ Η scanf("%s") σταματάει στο πρώτο κενό — δεν διαβάζει κείμενο με κενά
✔ Η fgets(buffer, size, stdin) διαβάζει ολόκληρη τη γραμμή μαζί με κενά
✔ Η fgets κρατάει το \n — το αφαιρούμε με strcspn
✔ Η getchar() «τρώει» τον χαρακτήρα \n που αφήνει η scanf
✔ Κανόνας: scanf πριν fgets → βάζουμε getchar(); ανάμεσά τους
Πίνακας σύγκρισης scanf vs fgets
| Χαρακτηριστικό | scanf("%s") | fgets() |
|---|---|---|
| Διαβάζει κενά; | ❌ Όχι | ✅ Ναι |
| Κρατάει \n; | Όχι | Ναι (πρέπει να το αφαιρέσουμε) |
| Κατάλληλη για | Μία λέξη, αριθμούς | Κείμενο με κενά, ολόκληρες γραμμές |
| Πρόβλημα buffer; | Αφήνει \n στο buffer | Καταναλώνει το \n (το κρατάει στο string) |
8.1 Εισαγωγή στα Αρχεία (Files)
Μέχρι τώρα, όλα τα προγράμματά μας αποθηκεύουν δεδομένα σε μεταβλητές. Αλλά οι μεταβλητές ζουν στη μνήμη RAM — μόλις τελειώσει το πρόγραμμα, τα δεδομένα χάνονται.
💡 Παράδειγμα από την καθημερινή ζωή
Φανταστείτε ότι φτιάχνετε ένα πρόγραμμα που αποθηκεύει στοιχεία υπολογιστών (brand, model, cpu, price). Ο χρήστης καταχωρεί 100 υπολογιστές. Μόλις κλείσει το πρόγραμμα — χάθηκαν όλα. Την επόμενη φορά ξεκινάει από το μηδέν.
Με αρχεία, τα δεδομένα αποθηκεύονται στον σκληρό δίσκο και παραμένουν ακόμα κι αν κλείσει ο υπολογιστής.
Γιατί χρειαζόμαστε αρχεία;
| Χωρίς αρχεία (μόνο RAM) | Με αρχεία (σκληρός δίσκος) |
|---|---|
| Τα δεδομένα χάνονται μόλις κλείσει το πρόγραμμα | Τα δεδομένα παραμένουν μόνιμα |
| Ο χρήστης ξαναδίνει τα ίδια στοιχεία κάθε φορά | Το πρόγραμμα «θυμάται» τι είχε καταχωρήσει |
| Δεν μπορούμε να μοιραστούμε δεδομένα | Μπορούμε να στείλουμε το αρχείο σε κάποιον άλλο |
| Περιορισμένος χώρος (RAM) | Μεγάλος χώρος αποθήκευσης (δίσκος) |
📌 Αρχεία στην καθημερινή χρήση
Ήδη χρησιμοποιείτε αρχεία κάθε μέρα: ένα .txt στο Notepad, ένα .docx στο Word, ένα .jpg φωτογραφία, ακόμα και ο πηγαίος κώδικας .c που γράφετε — όλα είναι αρχεία. Στη C, μαθαίνουμε να δημιουργούμε, να γράφουμε και να διαβάζουμε αρχεία μέσα από τον κώδικά μας.
8.2 Τύποι Αρχείων
Στη C, τα αρχεία χωρίζονται σε δύο κατηγορίες:
1. Αρχεία Κειμένου (Text Files)
Περιέχουν αναγνώσιμο κείμενο — χαρακτήρες που μπορείτε να δείτε αν ανοίξετε το αρχείο με ένα text editor (π.χ. Notepad).
Παραδείγματα: .txt, .csv, .c, .html
Ένα αρχείο κειμένου computers.txt μπορεί να μοιάζει κάπως έτσι:
2. Δυαδικά Αρχεία (Binary Files)
Περιέχουν δεδομένα σε δυαδική μορφή (0 και 1) — δεν μπορείτε να τα διαβάσετε με text editor. Αποθηκεύουν τα δεδομένα ακριβώς όπως είναι στη μνήμη.
Παραδείγματα: .jpg, .exe, .mp3, .pdf
📌 Σε αυτό το μάθημα
Θα ασχοληθούμε μόνο με αρχεία κειμένου (text files) — είναι πιο απλά στην κατανόηση και καλύπτουν τις περισσότερες ανάγκες μας.
8.3 Άνοιγμα Αρχείου — fopen()
Πριν κάνουμε οτιδήποτε με ένα αρχείο (γράψουμε ή διαβάσουμε), πρέπει πρώτα να το ανοίξουμε. Αυτό γίνεται με τη συνάρτηση fopen().
Ο FILE είναι ένας ειδικός τύπος δεδομένων (struct) που ορίζεται στη βιβλιοθήκη <stdio.h>.
Σύνταξη
| Παράμετρος | Τι κάνει | Παράδειγμα |
|---|---|---|
| "filename" | Το όνομα του αρχείου που θέλουμε να ανοίξουμε | "data.txt" |
| "mode" | Τι θέλουμε να κάνουμε με το αρχείο (ανάγνωση, εγγραφή, κτλ.) | "r", "w", "a" |
Η fopen() επιστρέφει έναν pointer τύπου FILE, που χρησιμοποιείται ως αναφορά στο αρχείο για τις επόμενες λειτουργίες ανάγνωσης, εγγραφής ή κλεισίματος.
Modes της fopen() — Επεξήγηση
| Mode | Τι κάνει | Αν το αρχείο δεν υπάρχει |
|---|---|---|
| "r" | Άνοιγμα για ανάγνωση μόνο. Δεν μπορούμε να γράψουμε στο αρχείο. | Αποτυγχάνει (NULL) |
| "w" | Άνοιγμα για εγγραφή. Αν υπάρχει αρχείο, διαγράφει όλο το παλιό περιεχόμενο και γράφει από την αρχή. | Δημιουργεί νέο αρχείο |
| "a" | Άνοιγμα για προσθήκη στο τέλος. Τα υπάρχοντα δεδομένα παραμένουν και νέα δεδομένα μπαίνουν στο τέλος. | Δημιουργεί νέο αρχείο |
⚠️ Προσοχή με το "w"!
Αν ανοίξετε ένα αρχείο με mode "w" και αυτό υπάρχει ήδη, το περιεχόμενό του σβήνεται αμέσως! Αν θέλετε να προσθέσετε δεδομένα χωρίς να χάσετε τα παλιά, χρησιμοποιήστε "a" (append).
💡 Αναλογία
Σκεφτείτε το σαν ένα τετράδιο:
"r" → Ανοίγεις το τετράδιο μόνο για να το διαβάσεις. Αν δεν υπάρχει τετράδιο, δεν μπορείς να κάνεις τίποτα.
"w" → Παίρνεις ένα καινούργιο τετράδιο (ή σβήνεις ό,τι είχε το παλιό) και γράφεις από την αρχή.
"a" → Ανοίγεις το τετράδιο στην τελευταία σελίδα και συνεχίζεις να γράφεις.
Τι είναι το NULL;
Αν η fopen() αποτύχει (π.χ. το αρχείο δεν υπάρχει και χρησιμοποιήσαμε "r"), επιστρέφει NULL. Το NULL σημαίνει «τίποτα» — ο pointer δεν δείχνει πουθενά. Γι' αυτό πρέπει πάντα να ελέγχουμε αν η fopen() πέτυχε:
📌 Γιατί return 1;
Η return 0; στη main() σημαίνει «όλα πήγαν καλά». Η return 1; (ή οποιοσδήποτε αριθμός ≠ 0) σημαίνει «κάτι πήγε στραβά». Έτσι το λειτουργικό σύστημα ξέρει ότι το πρόγραμμα τερματίστηκε με σφάλμα.
Σωστό παράδειγμα
8.4 Κλείσιμο Αρχείου — fclose()
Όταν τελειώσουμε τη δουλειά μας με ένα αρχείο, πρέπει να το κλείσουμε με τη συνάρτηση fclose().
Σύνταξη
Γιατί πρέπει να κλείνουμε τα αρχεία;
| Λόγος | Εξήγηση |
|---|---|
| Αποθήκευση δεδομένων | Τα δεδομένα μπορεί να βρίσκονται ακόμα σε buffer — η fclose τα γράφει στο δίσκο |
| Απελευθέρωση πόρων | Κάθε ανοιχτό αρχείο δεσμεύει πόρους του συστήματος |
| Αποφυγή απώλειας | Αν δεν κλείσουμε σωστά, μπορεί να χαθούν δεδομένα |
💡 Κανόνας
Κάθε fopen() πρέπει να έχει το αντίστοιχο fclose(). Σκεφτείτε το σαν τις αγκύλες στη C: κάθε { χρειάζεται το } της.
Η βασική δομή εργασίας με αρχεία
Κάθε πρόγραμμα που δουλεύει με αρχεία ακολουθεί πάντα αυτά τα 3 βήματα:
📋 Τα 3 βήματα
Βήμα 1: Άνοιγμα αρχείου → fopen()
Βήμα 2: Εργασία (ανάγνωση ή εγγραφή) → fprintf(), fscanf(), fgets(), κ.ά.
Βήμα 3: Κλείσιμο αρχείου → fclose()
8.5 Θεωρία: Τοποθεσία Αρχείου (File Path)
Όταν γράφουμε:
η C ψάχνει ή δημιουργεί το αρχείο στον ίδιο φάκελο με το πρόγραμμα.
👉 Δηλαδή στον τρέχοντα φάκελο εκτέλεσης (current working directory)
📌 Τι σημαίνει "ίδια τοποθεσία";
Αν το πρόγραμμα βρίσκεται εδώ:
C:\Users\Maria\Projects\
τότε το:
fopen("message.txt", "w");
θα δημιουργήσει το αρχείο εδώ:
C:\Users\Maria\Projects\message.txt
📌 Μπορούμε να δώσουμε πλήρη διαδρομή (path)
Παράδειγμα:
Τώρα το αρχείο δημιουργείται στο Desktop.
📌 Σχετική διαδρομή (relative path)
Μπορούμε επίσης να γράψουμε:
Αυτό σημαίνει:
👉 Φάκελος files μέσα στον τρέχοντα φάκελο
📌 Αν ο φάκελος δεν υπάρχει
Αν γράψεις:
fopen("data/message.txt", "w");
και δεν υπάρχει ο φάκελος data
👉 Η fopen() αποτυγχάνει
👉 Επιστρέφει NULL
📌 Γι' αυτό κάνουμε έλεγχο
Γιατί:
- μπορεί να μην υπάρχει φάκελος
- μπορεί να μην έχουμε δικαιώματα
- μπορεί να υπάρχει άλλο πρόβλημα
📌 Συνοπτικά
"message.txt" → ίδιος φάκελος με πρόγραμμα
"folder/message.txt" → μέσα σε φάκελο
"C:\\path\\message.txt" → πλήρης διαδρομή
Αυτό είναι σημαντικό γιατί πολλοί νομίζουν ότι το αρχείο πάει δίπλα στο .c αρχείο, αλλά στην πραγματικότητα πάει στον τρέχοντα φάκελο εκτέλεσης (current working directory).
8.6 Εγγραφή σε Αρχείο — fprintf()
Η fprintf() δουλεύει ακριβώς όπως η printf(), με μία μόνο διαφορά: η πρώτη παράμετρος είναι ο pointer στο αρχείο (fp). Αντί να εμφανίζει στην οθόνη, γράφει στο αρχείο.
Σύνταξη
Παράμετροι
- fp → δείκτης στο αρχείο (FILE *)
- "format string" → μορφή εκτύπωσης (όπως στην printf)
- arguments → μεταβλητές που θα γραφτούν στο αρχείο
📌 Παράδειγμα
Το αρχείο data.txt θα περιέχει:
Ολοκληρωμένο παράδειγμα
Σύγκριση printf vs fprintf
| printf (οθόνη) | fprintf (αρχείο) |
|---|---|
| printf("Hello\n"); | fprintf(fp, "Hello\n"); |
| printf("Age: %d\n", age); | fprintf(fp, "Age: %d\n", age); |
| printf("Price: %.2f\n", price); | fprintf(fp, "Price: %.2f\n", price); |
✅ Κανόνας
Αν ξέρεις printf, ξέρεις και fprintf — απλά βάζεις το fp ως πρώτη παράμετρο και τα υπόλοιπα μένουν ίδια!
8.7 Παράδειγμα: Εγγραφή & Ανάγνωση Αρχείου
Ας δούμε ένα απλό παράδειγμα που γράφει ένα μήνυμα σε αρχείο και μετά το διαβάζει πίσω.
Βήμα 1: Εγγραφή σε αρχείο
Μετά την εκτέλεση, δημιουργείται το αρχείο message.txt στον ίδιο φάκελο με το πρόγραμμα. Αν το ανοίξουμε με Notepad βλέπουμε:
Βήμα 2: Ανάγνωση από αρχείο
📌 Πώς δουλεύει η ανάγνωση;
Η fgets(line, 100, fp) διαβάζει μία γραμμή από το αρχείο (αντί για stdin βάζουμε fp). Το while loop συνεχίζει μέχρι η fgets να επιστρέψει NULL, που σημαίνει «τέλος αρχείου».