Μάθημα 7-8: Δομές, Είσοδος Κειμένου & Αρχεία

scanf vs fgets — getchar() — Εισαγωγή στα Files

7.1 Ανάλυση: struct computer με scanf

Ας ξεκινήσουμε αναλύοντας βήμα-βήμα ένα πρόγραμμα που χρησιμοποιεί μια δομή struct computer για να αποθηκεύσει τα στοιχεία ενός υπολογιστή:

#include <stdio.h> // Βιβλιοθήκη για συναρτήσεις εισόδου - εξόδου // Δημιουργία δομής computer struct computer { char brand[50]; // Μάρκα υπολογιστή char model[50]; // Μοντέλο υπολογιστή — τι γίνεται αν έχει κενό; π.χ. Dell Vostro char cpu[50]; // Επεξεργαστής float price; // Τιμή υπολογιστή }; int main() { // Δήλωση μεταβλητής τύπου struct struct computer pc; // Εισαγωγή μάρκας printf("Enter brand: "); scanf("%s", pc.brand); // Διαβάζει string χωρίς κενά // Εισαγωγή μοντέλου printf("Enter model: "); scanf("%s", pc.model); // Εισαγωγή CPU printf("Enter cpu: "); scanf("%s", pc.cpu); // Εισαγωγή τιμής printf("Enter price: "); scanf("%f", &pc.price); // Εμφάνιση αποτελεσμάτων printf("\nBrand: %s, Model: %s, CPU: %s, Price: %.2f euros\n", pc.brand, pc.model, pc.cpu, pc.price); return 0; }

📌 Γιατί pc.brand χωρίς & αλλά &pc.price με &;

Το pc.brand είναι πίνακας χαρακτήρων — το όνομα ενός πίνακα στη C είναι αυτόματα pointer στο πρώτο στοιχείο. Αντίθετα, η pc.price είναι απλή μεταβλητή float, οπότε χρειάζεται & για να δώσουμε τη διεύθυνσή της.

💻 Εκτέλεση — Αν γράψουμε μία λέξη

Enter brand: Dell Enter model: XPS15 Enter cpu: i7-13700H Enter price: 1299.99 Brand: Dell, Model: XPS15, CPU: i7-13700H, Price: 1299.99 euros

Όλα δουλεύουν τέλεια — αρκεί κάθε πεδίο να είναι μία λέξη χωρίς κενά.

7.2 Το πρόβλημα: Κενά στο brand

Τι γίνεται αν θέλουμε να γράψουμε "Hewlett Packard" στο brand; Ας δοκιμάσουμε:

Enter brand: Hewlett Packard Enter model: Enter cpu: Enter price:

⚠️ Τι πήγε στραβά;

Η scanf("%s") σταματάει να διαβάζει μόλις βρει κενό (space), tab ή newline.

Έτσι η scanf διάβασε μόνο "Hewlett" στο brand. Η λέξη "Packard" έμεινε στο buffer εισόδου και «καταναλώθηκε» αμέσως από την επόμενη scanf για το model!

🔍 Τι συμβαίνει βήμα-βήμα στη μνήμη;

Ο χρήστης πληκτρολογεί: Hewlett Packard και πατάει Enter.

Το buffer εισόδου (stdin) περιέχει:

H
e
w
l
e
t
t
P
a
c
k
a
r
d
\n

Η πρώτη scanf("%s") παίρνει "Hewlett" — σταματάει στο κενό. Στο buffer μένουν:

P
a
c
k
a
r
d
\n

Η δεύτερη scanf("%s") (για model) αγνοεί το κενό, παίρνει "Packard". Και ο χρήστης δεν πρόλαβε καν να πληκτρολογήσει!

💡 Συμπέρασμα

Η scanf("%s") δεν κάνει για κείμενο με κενά. Χρειαζόμαστε μια συνάρτηση που διαβάζει ολόκληρη τη γραμμή, μαζί με τα κενά. Αυτή είναι η fgets().

7.3 Τι είναι το \n (newline);

Ο χαρακτήρας \n (newline) είναι ένας ειδικός χαρακτήρας που σημαίνει «αλλαγή γραμμής». Αν και φαίνεται σαν δύο χαρακτήρες (\ και n), στη C είναι ένας χαρακτήρας.

📌 Πότε εμφανίζεται το \n;

Κάθε φορά που ο χρήστης πατάει Enter, ο χαρακτήρας \n προστίθεται στο τέλος αυτού που πληκτρολόγησε. Αυτό σημαίνει «τέλος γραμμής».

Παράδειγμα: Τι ακριβώς υπάρχει στο buffer;

Ο χρήστης πληκτρολογεί Dell και πατάει Enter. Στο buffer εισόδου (stdin) μπαίνουν:

D
e
l
l
\n

Τα 4 γράμματα + ο χαρακτήρας \n από το Enter.

Πού χρησιμοποιούμε το \n;

Χρήση Κώδικας Αποτέλεσμα
Αλλαγή γραμμής στην εκτύπωση printf("Hello\nWorld"); Hello
World
Κενή γραμμή printf("\n"); (κενή γραμμή)
Αποτέλεσμα Enter ο χρήστης πατάει Enter \n μπαίνει στο buffer

7.4 Η συνάρτηση fgets()

Η fgets() διαβάζει ολόκληρη τη γραμμή, μαζί με τα κενά, μέχρι ο χρήστης να πατήσει Enter.

Σύνταξη

fgets(buffer, size, stdin);
Παράμετρος Τι κάνει Παράδειγμα
buffer Ο πίνακας χαρακτήρων στον οποίο αποθηκεύεται η είσοδος pc.brand
size Μέγιστος αριθμός χαρακτήρων που θα διαβαστούν (μαζί με το \0) 50
stdin Από πού διαβάζει — stdin = πληκτρολόγιο stdin

Απλό παράδειγμα

char name[50]; printf("Enter your name: "); fgets(name, 50, stdin); // Διαβάζει ολόκληρη τη γραμμή μαζί με κενά printf("Hello %s", name);
Enter your name: Γιώργος Μπάρδης Hello Γιώργος Μπάρδης

Παράδειγμα: struct computer με fgets

Ας δούμε πώς γράφεται το πρόγραμμα με τον υπολογιστή χρησιμοποιώντας fgets() αντί για scanf στα strings:

#include <stdio.h> // Βιβλιοθήκη για συναρτήσεις εισόδου - εξόδου // Δημιουργία δομής computer struct computer { char brand[50]; // Μάρκα υπολογιστή char model[50]; // Μοντέλο υπολογιστή char cpu[50]; // Επεξεργαστής float price; // Τιμή υπολογιστή }; int main() { // Δήλωση μεταβλητής τύπου struct struct computer pc; // Εισαγωγή μάρκας — fgets διαβάζει ολόκληρη τη γραμμή μαζί με κενά printf("Enter brand: "); fgets(pc.brand, 50, stdin); // Διαβάζει ολόκληρη τη γραμμή μαζί με κενά // Εισαγωγή μοντέλου printf("Enter model: "); fgets(pc.model, 50, stdin); // Εισαγωγή CPU printf("Enter cpu: "); fgets(pc.cpu, 50, stdin); // Εισαγωγή τιμής — η τιμή είναι αριθμός, scanf δουλεύει μια χαρά printf("Enter price: "); scanf("%f", &pc.price); // Εμφάνιση αποτελεσμάτων printf("\nBrand: %s, Model: %s, CPU: %s, Price: %.2f euros\n", pc.brand, pc.model, pc.cpu, pc.price); return 0; }
Enter brand: Hewlett Packard Enter model: Pavilion 15 Enter cpu: Intel Core i7 Enter price: 899.99 Brand: Hewlett Packard , Model: Pavilion 15 , CPU: Intel Core i7 , Price: 899.99 euros

⚠️ Προσέξτε την έξοδο!

Το "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 ως εξής:

fgets(pc.brand, 50, stdin); pc.brand[strcspn(pc.brand, "\n")] = '\0'; // Αντικαθιστά το \n με \0

📌 Τι κάνει η strcspn();

Η strcspn() βρίσκει τη θέση του πρώτου χαρακτήρα που ταιριάζει από ένα σύνολο χαρακτήρων μέσα σε ένα string. Χρειάζεται #include <string.h>.

Από πού βγαίνει το όνομα

str → string
c → complementary (συμπληρωματικό σύνολο χαρακτήρων)
spn → span (εύρος / μήκος)

Σύνταξη

strcspn(string1, string2);
Παράμετρος Τι κάνει
string1 Το string που ψάχνουμε
string2 Οι χαρακτήρες που ψάχνουμε να βρούμε

Η συνάρτηση επιστρέφει τη θέση (index) όπου εμφανίζεται ο πρώτος χαρακτήρας από το string2 μέσα στο string1.

Παράδειγμα

char text[] = "Nikos\n"; int pos = strcspn(text, "\n"); printf("%d", pos);

Η strcspn() βρίσκει:

N
i
k
o
s
\n
0
1
2
3
4
5
5

Γιατί το \n είναι στη θέση 5 αλλά η strcspn επιστρέφει το μήκος πριν από αυτό → 5 χαρακτήρες πριν το newline.

Πώς χρησιμοποιείται με fgets()

fgets(name, 50, stdin); name[strcspn(name, "\n")] = '\0';

Βήμα 1: Η strcspn() βρίσκει πού είναι το \n

Βήμα 2: Το αντικαθιστούμε με \0

Βήμα 3: Το string κόβεται εκεί

Πριν και μετά — οπτικά

Πριν (μετά fgets, ο χρήστης έγραψε "Dell" + Enter):

D
e
l
l
\n
\0

Μετά (αφού κάνουμε brand[strcspn(brand, "\n")] = '\0'):

D
e
l
l
\0

7.5 Ο χαρακτήρας '\0' στη C (Null Terminator)

Στη C, τα strings δεν αποθηκεύονται μόνα τους. Αντίθετα, ένα string είναι πίνακας χαρακτήρων που τελειώνει με τον ειδικό χαρακτήρα '\0'.

Ο χαρακτήρας:

'\0'

ονομάζεται:

και σημαίνει τέλος του string.

Παράδειγμα αποθήκευσης string

char name[] = "Maria";

Στη μνήμη αποθηκεύεται έτσι:

M
a
r
i
a
\0

Δηλαδή:

'M' 'a' 'r' 'i' 'a' '\0'

📌 Σημαντικό

Το '\0' λέει στο πρόγραμμα: 👉 "Το string τελειώνει εδώ"

7.6 Το πρόβλημα του \n στο buffer

Υπάρχει ένα κλασικό πρόβλημα όταν αναμειγνύουμε scanf() με fgets(). Ας δούμε τι πάει στραβά:

int age; char name[50]; printf("Enter age: "); scanf("%d", &age); // Ο χρήστης γράφει 25 + Enter printf("Enter name: "); fgets(name, 50, stdin); // Αυτό «τρώει» το \n αντί να περιμένει! printf("Age: %d, Name: %s\n", age, name);
Enter age: 25 Enter name: Age: 25, Name:

Ο χρήστης δεν πρόλαβε καν να γράψει το όνομά του!

⚠️ Γιατί συμβαίνει αυτό;

Όταν ο χρήστης πληκτρολογεί 25 και πατάει Enter, στο buffer μπαίνουν:

2
5
\n

Η scanf("%d") διαβάζει μόνο τους αριθμούς 2 και 5. Ο χαρακτήρας \n (από το Enter) μένει στο buffer!

\n

Η fgets() που ακολουθεί βρίσκει αυτό το \n στο buffer, νομίζει ότι ο χρήστης πάτησε ήδη Enter, και επιστρέφει αμέσως χωρίς να περιμένει νέα είσοδο!

7.7 Η συνάρτηση getchar()

Η getchar() διαβάζει ακριβώς έναν χαρακτήρα από το buffer εισόδου. Τη χρησιμοποιούμε σαν «σκούπα» που καθαρίζει το \n που μένει πίσω μετά από scanf.

Σύνταξη

getchar(); // Διαβάζει και πετάει 1 χαρακτήρα από το buffer

Πώς λύνει το πρόβλημα;

int age; char name[50]; printf("Enter age: "); scanf("%d", &age); // Διαβάζει τον αριθμό, αφήνει \n στο buffer getchar(); // «Τρώει» το \n που έμεινε — καθαρίζει το buffer printf("Enter name: "); fgets(name, 50, stdin); // Τώρα περιμένει κανονικά τον χρήστη! printf("Age: %d, Name: %s\n", age, name);
Enter age: 25 Enter name: Γιώργος Μπάρδης Age: 25, Name: Γιώργος Μπάρδης

✅ Η getchar() έλυσε το πρόβλημα!

Η getchar() «κατανάλωσε» τον χαρακτήρα \n που είχε αφήσει η scanf, έτσι η fgets βρήκε άδειο buffer και περίμενε κανονικά τον χρήστη.

🔍 Οπτικά — Βήμα-βήμα τι γίνεται στο buffer

1. Ο χρήστης γράφει 25 + Enter → buffer:

2
5
\n

2. scanf("%d") παίρνει 25 → στο buffer μένει:

\n

3. getchar() παίρνει το \n → το buffer είναι άδειο:

4. fgets() βρίσκει άδειο buffer → περιμένει τον χρήστη κανονικά!

💡 Κανόνας

Κάθε φορά που χρησιμοποιούμε scanf πριν από fgets, βάζουμε getchar(); ανάμεσά τους για να καθαρίσουμε το \n από το buffer.

7.8 Παράδειγμα: scanf + getchar + fgets

Ας δούμε ένα ολοκληρωμένο παράδειγμα που συνδυάζει scanf, getchar και fgets μαζί:

#include <stdio.h> #include <string.h> int main() { int age; char name[50]; printf("Enter age: "); scanf("%d", &age); // Διαβάζει τον αριθμό getchar(); // Καθαρίζει το '\n' από το buffer printf("Enter name: "); fgets(name, 50, stdin); // Αφαίρεση του '\n' από το fgets name[strcspn(name, "\n")] = '\0'; printf("\nAge: %d\n", age); printf("Name: %s\n", name); return 0; }

7.9 Λύση: Πλήρες πρόγραμμα με fgets

Ας ξαναγράψουμε το πρόγραμμα με τον υπολογιστή, αλλά αυτή τη φορά με fgets() ώστε να δέχεται κενά στα πεδία:

#include <stdio.h> // printf, scanf, fgets, getchar #include <string.h> // strcspn — για αφαίρεση \n // Δημιουργία δομής computer struct computer { char brand[50]; // Μάρκα υπολογιστή char model[50]; // Μοντέλο υπολογιστή char cpu[50]; // Επεξεργαστής float price; // Τιμή υπολογιστή }; int main() { // Δήλωση μεταβλητής τύπου struct struct computer pc; /* ===== Εισαγωγή μάρκας ===== */ printf("Enter brand: "); fgets(pc.brand, 50, stdin); // Διαβάζει ολόκληρη τη γραμμή pc.brand[strcspn(pc.brand, "\n")] = '\0'; // Αφαίρεση \n /* ===== Εισαγωγή μοντέλου ===== */ printf("Enter model: "); fgets(pc.model, 50, stdin); pc.model[strcspn(pc.model, "\n")] = '\0'; /* ===== Εισαγωγή CPU ===== */ printf("Enter cpu: "); fgets(pc.cpu, 50, stdin); pc.cpu[strcspn(pc.cpu, "\n")] = '\0'; /* ===== Εισαγωγή τιμής ===== */ printf("Enter price: "); scanf("%f", &pc.price); // Η τιμή είναι αριθμός — scanf OK /* ===== Εμφάνιση αποτελεσμάτων ===== */ printf("\nBrand: %s, Model: %s, CPU: %s, Price: %.2f euros\n", pc.brand, pc.model, pc.cpu, pc.price); return 0; }
Enter brand: Hewlett Packard Enter model: Pavilion 15 Enter cpu: Intel Core i7 Enter price: 899.99 Brand: Hewlett Packard, Model: Pavilion 15, CPU: Intel Core i7, Price: 899.99 euros

✅ Τώρα τα κενά δουλεύουν!

Κάθε πεδίο μπορεί πλέον να περιέχει πολλές λέξεις με κενά. Η 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 μπορεί να μοιάζει κάπως έτσι:

Dell,XPS15,i7-13700H,1299.99 Hewlett Packard,Pavilion 15,Intel Core i7,899.99 Lenovo,ThinkPad X1,i7-1365U,1499.00

2. Δυαδικά Αρχεία (Binary Files)

Περιέχουν δεδομένα σε δυαδική μορφή (0 και 1) — δεν μπορείτε να τα διαβάσετε με text editor. Αποθηκεύουν τα δεδομένα ακριβώς όπως είναι στη μνήμη.

Παραδείγματα: .jpg, .exe, .mp3, .pdf

📌 Σε αυτό το μάθημα

Θα ασχοληθούμε μόνο με αρχεία κειμένου (text files) — είναι πιο απλά στην κατανόηση και καλύπτουν τις περισσότερες ανάγκες μας.

8.3 Άνοιγμα Αρχείου — fopen()

Πριν κάνουμε οτιδήποτε με ένα αρχείο (γράψουμε ή διαβάσουμε), πρέπει πρώτα να το ανοίξουμε. Αυτό γίνεται με τη συνάρτηση fopen().

Ο FILE είναι ένας ειδικός τύπος δεδομένων (struct) που ορίζεται στη βιβλιοθήκη <stdio.h>.

Σύνταξη

FILE *fp = fopen("filename", "mode");
Παράμετρος Τι κάνει Παράδειγμα
"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() πέτυχε:

FILE *fp = fopen("data.txt", "r"); if (fp == NULL) { printf("Error: Cannot open file!\n"); return 1; // Τερματισμός με κωδικό σφάλματος }

📌 Γιατί return 1;

Η return 0; στη main() σημαίνει «όλα πήγαν καλά». Η return 1; (ή οποιοσδήποτε αριθμός ≠ 0) σημαίνει «κάτι πήγε στραβά». Έτσι το λειτουργικό σύστημα ξέρει ότι το πρόγραμμα τερματίστηκε με σφάλμα.

Σωστό παράδειγμα

#include <stdio.h> int main() { FILE *fp = fopen("data.txt", "r"); if (fp == NULL) { printf("Error: Cannot open file!\n"); return 1; } printf("File opened successfully\n"); fclose(fp); return 0; }

8.4 Κλείσιμο Αρχείου — fclose()

Όταν τελειώσουμε τη δουλειά μας με ένα αρχείο, πρέπει να το κλείσουμε με τη συνάρτηση fclose().

Σύνταξη

fclose(fp); // Κλείνει το αρχείο που δείχνει ο pointer fp

Γιατί πρέπει να κλείνουμε τα αρχεία;

Λόγος Εξήγηση
Αποθήκευση δεδομένων Τα δεδομένα μπορεί να βρίσκονται ακόμα σε buffer — η fclose τα γράφει στο δίσκο
Απελευθέρωση πόρων Κάθε ανοιχτό αρχείο δεσμεύει πόρους του συστήματος
Αποφυγή απώλειας Αν δεν κλείσουμε σωστά, μπορεί να χαθούν δεδομένα

💡 Κανόνας

Κάθε fopen() πρέπει να έχει το αντίστοιχο fclose(). Σκεφτείτε το σαν τις αγκύλες στη C: κάθε { χρειάζεται το } της.

Η βασική δομή εργασίας με αρχεία

Κάθε πρόγραμμα που δουλεύει με αρχεία ακολουθεί πάντα αυτά τα 3 βήματα:

📋 Τα 3 βήματα

Βήμα 1: Άνοιγμα αρχείου → fopen()

Βήμα 2: Εργασία (ανάγνωση ή εγγραφή) → fprintf(), fscanf(), fgets(), κ.ά.

Βήμα 3: Κλείσιμο αρχείου → fclose()

8.5 Θεωρία: Τοποθεσία Αρχείου (File Path)

Όταν γράφουμε:

FILE *fp = fopen("message.txt", "w");

η C ψάχνει ή δημιουργεί το αρχείο στον ίδιο φάκελο με το πρόγραμμα.

👉 Δηλαδή στον τρέχοντα φάκελο εκτέλεσης (current working directory)

📌 Τι σημαίνει "ίδια τοποθεσία";

Αν το πρόγραμμα βρίσκεται εδώ:

C:\Users\Maria\Projects\

τότε το:

fopen("message.txt", "w");

θα δημιουργήσει το αρχείο εδώ:

C:\Users\Maria\Projects\message.txt

📌 Μπορούμε να δώσουμε πλήρη διαδρομή (path)

Παράδειγμα:

FILE *fp = fopen("C:\\Users\\Maria\\Desktop\\message.txt", "w");

Τώρα το αρχείο δημιουργείται στο Desktop.

📌 Σχετική διαδρομή (relative path)

Μπορούμε επίσης να γράψουμε:

FILE *fp = fopen("files/message.txt", "w");

Αυτό σημαίνει:

👉 Φάκελος files μέσα στον τρέχοντα φάκελο

Project ├── program.c └── files └── message.txt

📌 Αν ο φάκελος δεν υπάρχει

Αν γράψεις:

fopen("data/message.txt", "w");

και δεν υπάρχει ο φάκελος data

👉 Η fopen() αποτυγχάνει

👉 Επιστρέφει NULL

📌 Γι' αυτό κάνουμε έλεγχο

if (fp == NULL) { printf("Error: Cannot open file!\n"); }

Γιατί:

📌 Συνοπτικά

"message.txt" → ίδιος φάκελος με πρόγραμμα

"folder/message.txt" → μέσα σε φάκελο

"C:\\path\\message.txt" → πλήρης διαδρομή

Αυτό είναι σημαντικό γιατί πολλοί νομίζουν ότι το αρχείο πάει δίπλα στο .c αρχείο, αλλά στην πραγματικότητα πάει στον τρέχοντα φάκελο εκτέλεσης (current working directory).

8.6 Εγγραφή σε Αρχείο — fprintf()

Η fprintf() δουλεύει ακριβώς όπως η printf(), με μία μόνο διαφορά: η πρώτη παράμετρος είναι ο pointer στο αρχείο (fp). Αντί να εμφανίζει στην οθόνη, γράφει στο αρχείο.

Σύνταξη

fprintf(fp, "format string", arguments);

Παράμετροι

📌 Παράδειγμα

FILE *fp = fopen("data.txt", "w"); int age = 25; fprintf(fp, "Age = %d\n", age); fclose(fp);

Το αρχείο data.txt θα περιέχει:

Age = 25

Ολοκληρωμένο παράδειγμα

#include <stdio.h> int main() { // Άνοιγμα αρχείου για εγγραφή FILE *fp = fopen("data.txt", "w"); // Έλεγχος αν άνοιξε σωστά το αρχείο if (fp == NULL) { printf("Error: Cannot open file!\n"); return 1; } // Δεδομένα προς εγγραφή int age = 25; // Εγγραφή στο αρχείο fprintf(fp, "Age = %d\n", age); // Κλείσιμο αρχείου fclose(fp); printf("Data written successfully!\n"); return 0; }

Σύγκριση 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: Εγγραφή σε αρχείο

#include <stdio.h> int main() { // Βήμα 1: Άνοιγμα αρχείου για εγγραφή FILE *fp = fopen("message.txt", "w"); // Έλεγχος αν άνοιξε σωστά if (fp == NULL) { printf("Error: Cannot open file!\n"); return 1; } // Βήμα 2: Εγγραφή στο αρχείο — fprintf δουλεύει όπως η printf fprintf(fp, "Hello from C!\n"); fprintf(fp, "This is line 2.\n"); // Βήμα 3: Κλείσιμο αρχείου fclose(fp); printf("File written successfully!\n"); return 0; }
File written successfully!

Μετά την εκτέλεση, δημιουργείται το αρχείο message.txt στον ίδιο φάκελο με το πρόγραμμα. Αν το ανοίξουμε με Notepad βλέπουμε:

Hello from C! This is line 2.

Βήμα 2: Ανάγνωση από αρχείο

#include <stdio.h> int main() { // Δημιουργία πίνακα χαρακτήρων (buffer) για αποθήκευση κάθε γραμμής char line[100]; // Βήμα 1: Άνοιγμα αρχείου για ανάγνωση ("r" = read) FILE *fp = fopen("message.txt", "r"); // Έλεγχος αν το αρχείο άνοιξε σωστά // Αν το fopen αποτύχει, επιστρέφει NULL if (fp == NULL) { // Εμφάνιση μηνύματος σφάλματος printf("Error: File not found!\n"); // Τερματισμός προγράμματος με κωδικό σφάλματος return 1; } // Βήμα 2: Διάβασμα του αρχείου γραμμή-γραμμή // fgets διαβάζει μέχρι: // - να τελειώσει η γραμμή // - να γεμίσει ο πίνακας // - να φτάσει στο τέλος αρχείου (EOF) while (fgets(line, 100, fp) != NULL) { // Εκτύπωση της γραμμής που διαβάστηκε printf("%s", line); } // Βήμα 3: Κλείσιμο αρχείου // Πάντα κλείνουμε το αρχείο μετά τη χρήση fclose(fp); // Τερματισμός προγράμματος χωρίς σφάλμα return 0; }
Hello from C! This is line 2.

📌 Πώς δουλεύει η ανάγνωση;

Η fgets(line, 100, fp) διαβάζει μία γραμμή από το αρχείο (αντί για stdin βάζουμε fp). Το while loop συνεχίζει μέχρι η fgets να επιστρέψει NULL, που σημαίνει «τέλος αρχείου».