Μετάβαση στο περιεχόμενο
Topos Uranos

Περίληψη

Ό,τι υπολογίζει ένα πρόγραμμα χάνεται όταν τελειώσει, εκτός αν το αφήσει γραμμένο κάπου που θα επιζήσει μετά από αυτό. Το μάθημα αυτό παρουσιάζει αυτόν τον τόπο, το αρχείο κειμένου, και την ιδέα που επιτρέπει να το χρησιμοποιούμε χωρίς να μάθουμε τίποτα ριζικά καινούργιο: ένα αρχείο διαβάζεται και γράφεται σαν ροή, με τις ίδιες πράξεις που ισχύουν για το πληκτρολόγιο και την οθόνη. Μελετώνται οι std::ifstream και std::ofstream, ο έλεγχος ότι ένα αρχείο άνοιξε πριν το χρησιμοποιήσουμε και ο φάκελος εργασίας, που καθορίζει πού γίνεται η αναζήτηση· ο σωστός βρόχος ανάγνωσης, while (std::getline(in, line)), απέναντι στον βρόχο με eof(), που επεξεργάζεται ένα δεδομένο παραπάνω, και ο ακριβής λόγος για αυτό το δεδομένο· η εγγραφή με std::println, η λειτουργία προσάρτησης και το κλείσιμο κατά την έξοδο από το μπλοκ. Το μάθημα κλείνει με τις μορφές γραμμής: διαχωρισμός πεδίων με κόμματα, επικύρωσή τους με std::from_chars και απόφαση, μέσα στην προδιαγραφή, για το τι γίνεται με τις κακοσχηματισμένες γραμμές.

Μαθησιακοί στόχοι

  1. Να ανοίγουμε ένα αρχείο για ανάγνωση ή για εγγραφή και να ελέγχουμε ότι άνοιξε πριν το χρησιμοποιήσουμε, διακρίνοντας το αρχείο που δεν υπάρχει από εκείνο που αναζητείται σε άλλον φάκελο.
  2. Να διαβάζουμε ένα αρχείο γραμμή προς γραμμή με while (std::getline(in, line)) και να εξηγούμε, παρακολουθώντας την κατάσταση της ροής, γιατί το while (!in.eof()) επεξεργάζεται ένα δεδομένο παραπάνω.
  3. Να γράφουμε αποτελέσματα σε ένα αρχείο με std::println(out, …), επιλέγοντας ανάμεσα στην αντικατάσταση του περιεχομένου του και στην προσάρτηση στο τέλος.
  4. Να επεξεργαζόμαστε ένα αρχείο τιμών χωρισμένων με κόμματα σε ένα διάνυσμα εγγραφών, επικυρώνοντας κάθε γραμμή και αναφέροντας τις άκυρες με τον αριθμό τους.

Ένα αρχείο είναι μια ροή

Η μονιμότητα

Οι μεταβλητές ενός προγράμματος ζουν στη μνήμη, και η μνήμη ενός προγράμματος αποδεσμεύεται όταν αυτό τελειώσει: η εκτέλεση που ακολουθεί αρχίζει χωρίς να θυμάται τίποτα. Για να επιζήσει ένα δεδομένο μετά το πρόγραμμα που το παρήγαγε, πρέπει να μείνει σε ένα μόνιμο μέσο, και το μέσο γενικής χρήσης είναι το αρχείο: μια ακολουθία από byte με όνομα, αποθηκευμένη στον δίσκο, που το λειτουργικό σύστημα διατηρεί από τη μία εκτέλεση στην άλλη. Αρχείο κειμένου είναι εκείνο του οποίου τα byte είναι χαρακτήρες, σε UTF-8 όπως είδαμε στο μάθημα Το κείμενο δεν είναι ASCII, οργανωμένοι σε γραμμές που χωρίζονται με τον χαρακτήρα '\n'. Είναι η πιο ταπεινή και η πιο ανθεκτική μορφή: την ανοίγει κάθε επεξεργαστής κειμένου, τη διαβάζει κάθε γλώσσα, και ένας άνθρωπος μπορεί να την ελέγξει με μια ματιά.

Ωστόσο, η C++ δεν αντιμετωπίζει το αρχείο ως κάτι διαφορετικό από το πληκτρολόγιο. Από το μάθημα Είσοδος, έξοδος και σφάλματα, το std::cin είναι μια ροή εισόδου: μια πηγή χαρακτήρων που καταναλώνονται με τη σειρά, με μια κατάσταση που καταγράφει αν απέτυχε η τελευταία πράξη. Ένα αρχείο ανοιχτό για ανάγνωση είναι μια άλλη ροή εισόδου, τύπου std::ifstream (από το input file stream)· ένα αρχείο ανοιχτό για εγγραφή, μια ροή εξόδου, τύπου std::ofstream. Και οι δύο δηλώνονται στην κεφαλίδα <fstream>. Έπεται ότι ό,τι μάθαμε για το std::cin στο μάθημα Σωστή ανάγνωση της εισόδου, ο τελεστής >>, η std::getline και η κατάσταση σφάλματος, ισχύει χωρίς αλλαγές για ένα αρχείο.

Άνοιγμα και έλεγχος

Ένα αρχείο ανοίγει όταν δημιουργείται η ροή, με το όνομα του αρχείου μέσα σε άγκιστρα: std::ifstream in{"grades.txt"};. Το άνοιγμα μπορεί να αποτύχει (το αρχείο δεν υπάρχει, δεν υπάρχει άδεια ανάγνωσης, το όνομα είναι γραμμένο λάθος), και η αποτυχία δεν σταματά το πρόγραμμα ούτε παράγει κανένα μήνυμα: αφήνει τη ροή σε κατάσταση σφάλματος. Μια ροή σε σφάλμα, όταν χρησιμοποιείται ως συνθήκη, αποτιμάται σε false· επομένως ο έλεγχος είναι if (!in), και πρέπει να γίνει πριν από την πρώτη ανάγνωση. Το πρόγραμμα που ακολουθεί προσπαθεί να ανοίξει ένα αρχείο που δεν υπάρχει.

Πρόγραμμα σε C++
#include <fstream>#include <iostream>#include <print>​int main(){    std::ifstream in{"grades.txt"};    if (!in) {        std::println(std::cerr, "Δεν ήταν δυνατό να ανοίξει το grades.txt");        return 1;    }    int first{};    in >> first;    std::println("Πρώτος βαθμός: {}", first);}
Έξοδος
Δεν ήταν δυνατό να ανοίξει το grades.txt

Το πρόγραμμα τερμάτισε με κωδικό 1.

Τρεις λεπτομέρειες αξίζουν προσοχή. Η πρώτη είναι το std::cerr (από την <iostream>), η ροή σφαλμάτων: γράφει στην οθόνη όπως η τυπική έξοδος, αλλά είναι ξεχωριστό κανάλι, ώστε τα μηνύματα σφάλματος να μην ανακατεύονται με τα αποτελέσματα όταν η έξοδος ανακατευθύνεται σε αρχείο· η std::println δέχεται ως πρώτο όρισμα τη ροή στην οποία πρέπει να γράψει. Η δεύτερη είναι το return 1;: ένας κωδικός τερματισμού διαφορετικός από το μηδέν, όπως είδαμε στο μάθημα Ανατομία ενός προγράμματος, ειδοποιεί όποιον εκτέλεσε το πρόγραμμα ότι κάτι πήγε στραβά. Η τρίτη είναι τι θα είχε συμβεί χωρίς τον έλεγχο: το in >> first πάνω σε ροή σε σφάλμα δεν κάνει τίποτα, και το πρόγραμμα θα είχε γράψει «Πρώτος βαθμός: 0» με απόλυτη ησυχία. Ένα ψευδές αποτέλεσμα με όψη αληθινού είναι χειρότερο από ένα μήνυμα σφάλματος· εξού και ο κανόνας αυτού του μαθήματος: κάθε αρχείο ελέγχεται όταν ανοίγει.

Σχετικές διαδρομές και ο φάκελος εργασίας

Το όνομα "grades.txt" δεν λέει σε ποιον φάκελο βρίσκεται το αρχείο. Ένα τέτοιο όνομα είναι μια σχετική διαδρομή, και επιλύεται με αφετηρία τον φάκελο εργασίας: τον φάκελο από τον οποίο εκτελείται το πρόγραμμα, που δεν είναι κατ' ανάγκην ο φάκελος του εκτελέσιμου ούτε εκείνος του πηγαίου κώδικα. Το πρόγραμμα που ακολουθεί, αποθηκευμένο στον φάκελο project μαζί με ένα αρχείο data.txt, ανοίγει αυτό το αρχείο και γράφει την πρώτη του γραμμή· εκτελέστηκε δύο φορές, πρώτα από τον project και μετά από τον γονικό φάκελο.

Τερματικό
$ cd project$ g++ -std=c++23 -Wall -Wextra -Wpedantic -Wconversion -Wshadow -g -fsanitize=address,undefined -fno-sanitize-recover=undefined main.cpp -o main$ ./mainΠρώτη γραμμή: Γεια από το αρχείο$ cd ..$ ./project/mainΔεν ήταν δυνατό να ανοίξει το data.txt

Το εκτελέσιμο είναι το ίδιο, και το αρχείο βρίσκεται ακόμη στη θέση του· το μόνο που άλλαξε είναι ο φάκελος εργασίας, και μαζί του ο τόπος όπου αναζητείται το data.txt. Αυτή είναι η συχνότερη αιτία του «δεν βρίσκει το αρχείο». Ένας επεξεργαστής που εκτελεί το πρόγραμμα με ένα κουμπί επιλέγει μόνος του τον φάκελο εργασίας (στο Visual Studio Code είναι συνήθως ο φάκελος που είναι ανοιχτός στον επεξεργαστή, αν και εξαρτάται από τη ρύθμιση της εργασίας), οπότε συμφέρει να εκτελούμε από το τερματικό, μέσα στον φάκελο των δεδομένων, μέχρι να ξέρουμε τι κάνει ο επεξεργαστής. Ο αντίθετος πειρασμός, να γράψουμε στον κώδικα μια απόλυτη διαδρομή όπως "/home/ana/curso/data.txt" ή "C:\\Users\\Ana\\data.txt", λύνει το πρόβλημα σε ένα μόνο μηχάνημα και το δημιουργεί σε όλα τα άλλα: το πρόγραμμα παύει να λειτουργεί μόλις το εκτελέσει άλλο πρόσωπο ή μετακινηθεί ο φάκελος. Το συνιστώμενο είναι η σχετική διαδρομή με γνωστό φάκελο εργασίας ή, ακόμη καλύτερα, το όνομα του αρχείου ως δεδομένο που δίνει ο χρήστης.