ΚΕΝΤΡΟ ΠΛΗ.ΝΕ.Τ. Ν. ΦΛΩΡΙΝΑΣ

Η Γλώσσα Προγραμματισμού C
(Μέρος 4 - Προχωρημένα Θέματα)

 

Είσοδος/Εξοδος Αρχείων

Ένα αρχείο είναι ένας χώρος της μνήμης, συνήθως σ' έναν σκληρό δίσκο, μ’ ένα όνομα. Η C βλέπει ένα αρχείο σαν μια ακολουθία από bytes, καθένα από τα οποία μπορεί να διαβαστεί μεμονωμένα. Η C έχει δύο προσεγγίσεις για τα αρχεία. Στη δυαδική προσέγγιση, κάθε byte του αρχείου είναι προσπελάσιμο σ' ένα πρόγραμμα, ενώ στην προσέγγιση κειμένου, αυτό που βλέπει το πρόγραμμα μπορεί να διαφέρει από το τι περιέχεται στο αρχείο.

Μπορούμε ακόμα να διαλέξουμε μεταξύ δύο επιπέδων εισόδου/εξόδου, δηλ. δύο επιπέδων διαχείρισης της προσπέλασης της αρχείων. Η κύρια διαφορά μεταξύ των δύο επιπέδων είναι ότι η Ε/Ε χαμηλού επιπέδου είναι χωρίς προσωρινό καταχωρητή, δηλ. κάθε κλήση μιας ρουτίνας εισόδου ή εξόδου διαβάζει από ή γράφει σ’ ένα αρχείο.

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

 

Τα Τυποποιημένα Αρχεία της C

Τα προγράμματα της C ανοίγουν αυτόματα τα εξής τρία αρχεία : την τυποποιημένη είσοδο, την τυποποιημένη έξοδο και το τυποποιημένο λάθος. Η τυποποιημένη είσοδος είναι η κανονική, συνηθισμένη συσκευή εισόδου του συστήματος (πληκτρολόγιο), εκτός κι αν καθορίζεται κάτι άλλο. Η τυποποιημένη είσοδος παρέχει είσοδο σ' ένα πρόγραμμα και είναι το αρχείο που διαβάζεται από τις συναρτήσεις getchar(), gets() και scanf().

Η τυποποιημένη έξοδος και το τυποποιημένο λάθος είναι η κανονική συσκευή εξόδου του συστήματος (οθόνη), εκτός κι αν καθορίζεται κάτι άλλο. Η τυποποιημένη έξοδος είναι εκεί όπου συνήθως πηγαίνει η έξοδος του προγράμματος και χρησιμοποιείται μέσω των συναρτήσεων putchar(), puts() και printf(). Ο σκοπός του αρχείου τυποποιημένου λάθους είναι να παρέχει έναν χώρο για την αποστολή μηνυμάτων λάθους. Θα μελετήσουμε την τυποποιημένη είσοδο/έξοδο αρχείων και θα ξεκινήσουμε μ’ ένα πρόγραμμα που επεξεργάζεται αρχεία τέτοιας μορφής.

/* prog54.c – χρήση τυποποιημένων αρχείων Ε/Ε */

#include <stdio.h>

main(argc, argv)

int argc;

char *argv[ ];

{

 

            int ch;

            /* δείχνει τη θέση αποθήκευσης κάθε χαρακτήρα που διαβάζεται */

            FILE *fp;       /* δείκτης αρχείου */

 

            if (argc != 2)

            {

                        printf("Χρήση : %s filename\n", argv[0]);

                        exit(1);

            }

            if ((fp = fopen(argv[1], "r")) == NULL)

            {

                        printf("Δεν ανοίγει το %s\n", argv[1]);

                        exit(1);

            }

            while ((ch = getc(fp)) != EOF)

            {

                        putc(ch, stdout);

                        count++;

            }

            fclose(fp);

            printf("Το αρχείο %s έχει %ld χαρακτήρες \n", argv[1], count);

}

Το παραπάνω πρόγραμμα ανοίγει ένα αρχείο, δείχνει τα περιεχόμενά του στην οθόνη και μετρά τον αριθμό των bytes που έχει. Το πρόγραμμα στην αρχή ελέγχει την τιμή του argc να δει αν υπάρχει ένα όρισμα γραμμής-διαταγής. Εάν δεν υπάρχει, το πρόγραμμα τυπώνει ένα μήνυμα. Το argv[0] είναι το όνομα του προγράμματος.

Η συνάρτηση exit() έχει ως αποτέλεσμα τον τερματισμό του προγράμματος, κλείνοντας κάθε ανοικτό αρχείο. Το όρισμα στην exit() περνά στο UNIX, όπου εκεί μπορεί να χρησιμοποιηθεί από τις εντολές του UNIX. Στο UNIX, μια τιμή 0 δείχνει ότι τα προγράμματα τελειώνουν κανονικά και μια τιμή διάφορη του μηδενός δείχνει ότι τα προγράμματα έχουν κάποιο πρόβλημα.

Το πρόγραμμα χρησιμοποιεί τη συνάρτηση fopen() για το άνοιγμα του αρχείου. Το πρώτο όρισμα στην fopen() είναι ένας δείκτης-σε-συμβολοσειρά, που αναγνωρίζει το αρχείο από το όνομα. Το argv[1] δείχνει στη θέση μνήμης όπου είναι αποθηκευμένο το πρώτο όρισμα γραμμής-διαταγής. Το δεύτερο όρισμα δείχνει το πώς θα ανοιχθεί το αρχείο. Η συνάρτηση fopen() επιστρέφει έναν δείκτη αρχείου. Ο τύπος FILE ορίζεται στο αρχείο stdio.h και ο δείκτης fp δεν δείχνει το πραγματικό αρχείο, αλλά μια δομή, που είναι ένα αντικείμενο δεδομένων.

Η δομή αυτή περιέχει πληροφορίες σχετικές με το αρχείο που διαβάζεται και τον προσωρινό καταχωρητή. Η δομή που δείχνεται από το fp έχει όλες αυτές τις πληροφορίες. Αν δεν μπορέσει να ανοίξει το αρχείο, η συνάρτηση fopen() επιστρέφει την τιμή NULL, οπότε σε συνδυασμό με την if και την exit() σταματά το πρόγραμμα. Η fopen() δημιουργεί ακόμη και έναν προσωρινό καταχωρητή.

 

Η Συνάρτηση fopen()

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

Μπορεί να πάρει τις εξής τιμές :

"r"      Άνοιγμα αρχείου κειμένου για διάβασμα.

"w"    Άνοιγμα αρχείου κειμένου για γράψιμο, που σημαίνει ότι αν υπάρχει

ήδη το αρχείο, τότε ότι ο,τιδήποτε  υπάρχει στο αρχείο χάνεται ή

αλλιώς δημιουργείται ένα νέο αρχείο.

"a"      Άνοιγμα αρχείου κειμένου για γράψιμο, που σημαίνει ότι ο,τιδήποτε γράφεται προστίθεται στο τέλος του αρχείου ή αλλιώς δημιουργείται

ένα νέο αρχείο.

"r+"   Άνοιγμα αρχείου για διάβασμα και για γράψιμο κειμένου.

"w+"  Άνοιγμα αρχείου κειμένου για διάβασμα και γράψιμο, σβήνοντας       ο,τιδήποτε υπάρχει σ' αυτό το αρχείο ή αλλιώς δημιουργείται ένα νέο     αρχείο.

"a+"   Άνοιγμα αρχείου κειμένου για διάβασμα και γράψιμο, όπου

ο,τιδήποτε γράφεται, προστίθεται στο τέλος του αρχείου ή αλλιώς

δημιουργείται ένα νέο αρχείο.

Αν στις προηγούμενες καταστάσεις υπάρχει και ο χαρακτήρας b, π.χ. "rb", "wb", τότε ισχύει ό,τι και προηγουμένως, με τη διαφορά ότι είμαστε στη δυαδική κατάσταση αντί για την κατάσταση κειμένου.

 

Οι Συναρτήσεις getc() και putc()

Αυτές οι συναρτήσεις δουλεύουν περίπου όπως οι getchar() και putchar(). Η διαφορά είναι στο ότι πρέπει να διευκρινίζουμε ποιο αρχείο χρησιμοποιούμε.

Δηλαδή, η έκφραση :

            ch = getchar();

σημαίνει «πάρε έναν χαρακτήρα από την τυποποιημένη είσοδο (πληκτρολόγιο)»,

αλλά η έκφραση :

            ch = getc(fp);

σημαίνει «πάρε έναν χαρακτήρα από το αρχείο που καθορίζει το fp».

Παρόμοια, η έκφραση :

            putc(ch, fpout);

σημαίνει «βάλε τον χαρακτήρα ch στο αρχείο που καθορίζεται από τον δείκτη FILE fpout».

Το putc(ch, stdout) είναι το ίδιο με το putchar(ch).

 

Η Συνάρτηση fclose()

Αυτή η συνάρτηση κλείνει το αρχείο, που καθορίζεται από τον fp. Η συνάρτηση fclose() επιστρέφει την τιμή 0 για επιτυχή κλείσιμο του αρχείου και EOF στην αντίθετη περίπτωση :

            if (fclose(fp) != 0)

                        printf("Λάθος στο κλείσιμο του αρχείου %s\n", argv[1]);

 

Τυποποιημένα Αρχεία

Το αρχείο stdio.h συσχετίζει τρεις δείκτες αρχείων με τα τρία τυποποιημένα αρχεία, που ανοίγονται από προγράμματα της C : Τον stdin για την τυποποιημένη είσοδο, τον stdout για την τυποποιημένη έξοδο και τον stderr για το τυποποιημένο λάθος. Όλοι τους είναι δείκτες σε FILE και μπορούν να χρησιμοποιηθούν σαν ορίσματα στις τυποποιημένες συναρτήσεις Ε/Ε, όπως δηλ. και ο fp.

 

Πρόγραμμα Αντιγραφής Αρχείων

Θα δούμε τώρα ένα ενδιαφέρον πρόγραμμα όπου ανοίγουμε ένα αρχείο και μεταφέρουμε μερικά από τα περιεχόμενά του σ’ ένα άλλο αρχείο. Το ένα αρχείο είναι σε κατάσταση "r" και το άλλο σε κατάσταση "w".

Το πρόγραμμα θα παίρνει από το ένα αρχείο μόνο κάθε τρίτο χαρακτήρα του και θα τοποθετεί την έξοδό του σ’ ένα νέο αρχείο, το όνομα του οποίου αποτελείται από το παλιό όνομα αλλά με την επέκταση .red.

/* prog55.c – συμπίεση αρχείου κατά 2/3 */

#include <stdio.h>

main(argc, argv)

int argc;

char *argv[ ];

{

 

            int ch;

            FILE *in, *out;       /* δήλωση δύο δεικτών FILE */

            char name[40];        /* θα αποθηκεύσει το όνομα του αρχείου εξόδου */

            int count = 0;          /* μετρητής */

 

            if (argc < 2)               /* έλεγχος αν υπάρχει ένα αρχείο εισόδου */

            {

                        fprintf(stderr, "Δώστε ως όρισμα ένα όνομα αρχείου. \n");

                        exit(1);

            }

            if ((in = fopen(argv[1], "r")) == NULL)

            {

                        fprintf(stderr, "Δεν ανοίγει το \"%s\".\n", argv[1]);

                        exit(2);

            }

            strcpy(name, argv[1]);        /* αντιγραφή του ονόματος του αρχείου */

            strcat(name, ".red");                        /* πρόσθεση του .red στο όνομα */

 

            if ((out = fopen(name, "w")) == NULL)

            {

                        fprintf(stderr, "Δεν ανοίγει το αρχείο εξόδου. \n");

                        exit(3);

            }

            while ((ch = getc(in)) != EOF)

            {

                        if (count++ % 3 == 0)

                                    putc(ch, out); /* εκτύπωση κάθε 3ου χαρακτήρα */

            }

            fclose(in);

            fclose(out);

}

Η συνάρτηση fprintf() είναι όμοια με την printf(), εκτός από το ότι απαιτεί έναν δείκτη αρχείου ως το πρώτο της όρισμα. Με τον δείκτη stderr στέλνουμε τα μηνύματα λάθους στο αρχείο τυποποιημένου λάθους. Ακόμη, στο παραπάνω πρόγραμμα έχουμε ανοικτά δύο αρχεία ταυτόχρονα και έτσι δηλώνουμε δύο δείκτες FILE.

 

Οι Συναρτήσεις των Αρχείων Εισόδου/Εξόδου

Αυτές οι συναρτήσεις χρησιμοποιούν έναν δείκτη σε FILE για να γνωρίζουν με ποιο αρχείο να δουλέψουν και χρησιμοποιούνται αφού πρώτα μια εντολή fopen() ανοίξει ένα αρχείο και πριν μια εντολή fclose() το κλείσει.

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

 

Οι Συναρτήσεις fprintf() και fscanf()

Αυτές οι δύο συναρτήσεις αρχείων Ε/Ε δουλεύουν όπως οι printf() και scanf(), με τη διαφορά ότι απαιτούν ένα επιπλέον όρισμα που να δείχνει με ποιο αρχείο θα δουλέψουν. Αυτό το όρισμα είναι το πρώτο στη λίστα των ορισμάτων.

Ακολουθεί ένα πρόγραμμα που προσθέτει λέξεις σ' ένα αρχείο. Με την κατάσταση "a+", το πρόγραμμα μπορεί να διαβάζει από το αρχείο καθώς και να γράφει σ' αυτό. Ανοίγουμε πρώτα το αρχείο με το όνομα "words" και μετά εισάγουμε διάφορες λέξεις που γράφονται στο τέλος του. Οι λέξεις δίνονται με τη μεταβλητή words που είναι ένας πίνακας χαρακτήρων και η καταχώρηση τελειώνει όταν πατήσουμε το πλήκτρο enter.

Η εντολή rewind() πηγαίνει το πρόγραμμα στην αρχή του αρχείου και ο βρόχος της while διαβάζει τα περιεχόμενα του αρχείου, τα τοποθετεί στη μεταβλητή words και μετά τα εκτυπώνει με μια εντολή puts.

/* prog56.c – γράψιμο και εκτύπωση αρχείου */

#include <stdio.h>

#include <stdlib.h>

#define MAX 20

main()

{

            FILE *fp;

            char words[MAX];

 

            if (fp = fopen("words", "a+")) == NULL)

            {

                        fprintf(stdin, "Δεν ανοίγει το αρχείο \"words\". \n");

                        exit(1);

            }

            puts("Δώστε λέξεις για να προστεθούν στο αρχείο. Πατήστε το enter");

            puts("στην αρχή μιας γραμμής για να σταματήσετε.");

 

            while (gets(words) != NULL && words[0] != '\0')

                        fprintf(fp, "%s", words);      /* προσθήκη λέξεων στο αρχείο */

 

            puts("Περιεχόμενα αρχείου : ");

            rewind(fp);                /* πάμε στην αρχή του αρχείου */

 

            while (fscanf(fp, "%s", words) == 1)

                        puts(words);  /* εμφάνιση των περιεχομένων του αρχείου */

            fclose(fp);

}

 

Οι Συναρτήσεις fgets() και fputs()

Η συνάρτηση fgets() δέχεται τρία ορίσματα, όπου το πρώτο όρισμα είναι ένας δείκτης σε char που δείχνει πού πρέπει να αποθηκευθεί η είσοδος. Το δεύτερο όρισμα είναι ένας ακέραιος, που παριστά το απώτερο όριο μεγέθους της συμβολοσειράς εισόδου. Το τρίτο όρισμα είναι ο δείκτης αρχείου απ' όπου θα γίνει το διάβασμα.

Όπως η gets(), έτσι και η fgets() επιστρέφει την τιμή NULL όταν συναντά EOF (τέλος αρχείου). Έτσι, μπορούμε να ελέγχουμε το τέλος ενός αρχείου. Αλλιώς, επιστρέφει την τιμή του δείκτη συμβολοσειράς που πέρασε σ' αυτήν.

Η συνάρτηση fputs() δέχεται δύο ορίσματα, όπου το πρώτο είναι ένας δείκτης σε char και το άλλο είναι ένας δείκτης αρχείου. Μετά, γράφει τη συμβολοσειρά που βρίσκεται στη θέση του αρχείου που δείχνει ο δείκτης. Η fgets() διαβάζει τον χαρακτήρα enter, τον οποίο αγνοεί η gets().

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

/* prog57.c – χρήση των fgets() και fputs() */

#include <stdio.h>

#define MAXLINE 20

main()

{

            char line[MAXLINE];

 

            while (fgets(line, MAXLINE, stdin) != NULL && line[0] != '\n')

                        fputs(line, stdout);

}

 

Η Τυχαία Προσπέλαση Αρχείων

Η συνάρτηση fseek() μάς επιτρέπει να μετακινούμαστε σε οποιοδήποτε byte ενός αρχείου που έχουμε ανοίξει με την fopen(). Η fseek() έχει τρία ορίσματα και επιστρέφει μια τιμή int. Η συνάρτηση ftell() επιστρέφει την τωρινή (τρέχουσα) θέση σ' ένα αρχείο.

Ακολουθεί ένα πρόγραμμα που εμφανίζει τα περιεχόμενα ενός αρχείου με την αντίθετη σειρά και χρησιμοποιεί ένα όρισμα γραμμής-διαταγής για την απόκτηση του ονόματος του αρχείου.

/* prog58.c – εμφάνιση περιεχομένων αρχείου με την αντίστροφη σειρά */

#include <stdio.h>

#include <stdlib.h>

#define CNTL_Z '\032'         /* είναι το EOF σε αρχεία κειμένου του DOS */

main(argc, argv)

            int argc;

            char *argv[ ];

{

            char ch;

            FILE *fp;

            long count, last;

 

            if (argc != 2)              /* έλεγχος αν υπάρχει ένα αρχείο εισόδου */

            {

                        fprintf(stderr, "Δώστε ως όρισμα ένα όνομα αρχείου. \n");

                        exit(1);

            }

 

            if ((fp = fopen(argv[1], "rb")) == NULL)

            {

                        fprintf(stderr, "Δεν ανοίγει το \"%s\".\n", argv[1]);

                        exit(1);

            }

 

            fseek(fp, 0L, SEEK_END);   /* πηγαίνει στο τέλος του αρχείου */

            last = ftell(fp);

 

            for (count=1L; count<=last; count++)

            {

                        fseek(fp, -count, SEEK_END);        /* πηγαίνει προς τα πίσω */

                        ch = getc(fp);

                        if (ch != CNTL_Z && ch != '\r')

                                    putchar(ch);

            }

            fclose(fp);

}

Το πρώτο από τα τρία ορίσματα της fseek() είναι ένας δείκτης FILE, που δείχνει στο αρχείο που ψάχνεται. Το αρχείο θα πρέπει να ανοιχθεί με χρήση της fopen().

Το δεύτερο όρισμα καλείται προέκταση και μας λέει πόσο μακριά να κινηθούμε από το σημείο εκκίνησης και πρέπει να είναι μια τιμή τύπου long. Μπορεί να είναι θετική για κίνηση προς τα μπρος ή αρνητική για κίνηση προς τα πίσω.

Το τρίτο όρισμα είναι η κατάσταση και αναφέρεται στο σημείο εκκίνησης. Υπάρχουν οι παρακάτω σταθερές για την κατάσταση :

            SEEK_SET                 Αρχή του αρχείου

            SEEK_CUR                Τωρινή θέση

            SEEK_END                Τέλος του αρχείου

Η fseek() επιστρέφει την τιμή 0 αν το πρόγραμμα δουλεύει καλά και την -1 αν υπάρχει κάποιο λάθος. Η ftell() είναι τύπου long και επιστρέφει την τωρινή θέση του αρχείου. Η ftell() προσδιορίζει τη θέση επιστρέφοντας τον αριθμό των bytes από την αρχή, με πρώτο byte το 0.

Χρησιμοποιούμε την εντολή :

            fseek(fp, 0L, SEEK_END);

για να πάμε στο τέλος του αρχείου, ενώ η επόμενη εντολή :

            last = ftell(fp);

καταχωρεί στη μεταβλητή last τον αριθμό των bytes από την αρχή του αρχείου μέχρι το τέλος.

 

Κατηγορίες Μνήμης και Εμβέλεια

Οι κατηγορίες μνήμης της C μάς επιτρέπουν να καθορίζουμε ποιες συναρτήσεις θα γνωρίζουν ποιες μεταβλητές και πόσο μια μεταβλητή θα υπάρχει σ’ ένα πρόγραμμα. Μπορούμε να κάνουμε μια μεταβλητή να είναι εξωτερική, ορίζοντάς την έξω από οποιοδήποτε ορισμό συνάρτησης και μετά, μέσα στις συναρτήσεις που χρησιμοποιούν αυτή τη μεταβλητή, δηλώνουμε τη μεταβλητή γράφοντας πριν από τον τύπο της τη λέξη-κλειδί extern. Αυτή η λέξη-κλειδί πληροφορεί τον υπολογιστή ότι ο ορισμός αυτής της μεταβλητής βρίσκεται έξω από τη συνάρτηση.

Αν δεν χρησιμοποιήσουμε την extern και ορίσουμε μια άλλη μεταβλητή με το ίδιο όνομα μέσα στη main(), τότε θα έχουμε δύο μεταβλητές με το ίδιο όνομα, πράγμα μεν αποδεκτό, γιατί ανήκουν αλλού, αλλά σίγουρα επικίνδυνο για λάθη. Αν δεν δηλώσουμε τίποτα για μια μεταβλητή μέσα στη main(), τότε η main() χρησιμοποιεί αυτόματα τον εξωτερικό ορισμό της μεταβλητής.

Για την περιγραφή των κατηγοριών μνήμης χρησιμοποιούνται οι εξής 4 λέξεις-κλειδιά : extern (για εξωτερική), auto (για αυτόματη), static και register. Οι μεταβλητές που δηλώνονται μέσα σε μια συνάρτηση θεωρούνται ότι είναι κατηγορίας auto, εκτός κι αν δηλωθεί κάτι άλλο. Η κατηγορία μνήμης καθορίζει δύο πράγματα : Πρώτον, ελέγχει ποιες συναρτήσεις έχουν πρόσβαση σε μια μεταβλητή. Η έκταση στην οποία είναι διαθέσιμη μια μεταβλητή ονομάζεται εμβέλεια. Δεύτερον, η κατηγορία μνήμης καθορίζει για πόσο θα υπάρχει η μεταβλητή στη μνήμη. Θα δούμε αναλυτικά τις ιδιότητες του κάθε τύπου.

 

Οι Αυτόματες Μεταβλητές

Εξ ορισμού, οι μεταβλητές που δηλώνονται μέσα σε μια συνάρτηση είναι αυτόματες. Μπορούμε, όμως, να δηλώσουμε και τη λέξη-κλειδί auto, ως εξής :

            main()

            {

                        auto int a;

            ...

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

Μια αυτόματη μεταβλητή έχει τιμή για όσο καιρό καλείται η συνάρτηση που την περιέχει από κάποια άλλη συνάρτηση και χάνεται όταν τελειώσει η κλήση της συνάρτησής της.

 

Οι Εξωτερικές Μεταβλητές

Μια μεταβλητή που ορίζεται έξω από μια συνάρτηση ανήκει στην εξωτερική κατηγορία μνήμης. Μια εξωτερική μεταβλητή πρέπει ακόμα να δηλωθεί και στη συνάρτηση που τη χρησιμοποιεί, με τη λέξη-κλειδί extern, όπως είδαμε προηγουμένως. Ακολουθούν μερικές δηλώσεις :

            int num1;

            char namel;

            double times[100];

            main()

            {

                        extern int num1;

                        extern char name;

                        extern double times[100];

            ...

Μπορούμε να παραλείψουμε τη δήλωση extern αν οι αρχικοί ορισμοί υπάρχουν στο ίδιο αρχείο και πριν από τη συνάρτηση που τις χρησιμοποιεί. Αν, όμως, δηλώσουμε μέσα σε μια συνάρτηση μια μεταβλητή με το ίδιο όνομα, τότε αυτή θα θεωρηθεί σαν αυτόματη (τοπική) μεταβλητή και η εξωτερική μεταβλητή θα αγνοηθεί.

Οι εξωτερικές μεταβλητές παίρνουν αυτόματα σαν αρχική τιμή 0 όταν δεν δηλωθεί κάτι άλλο, πράγμα που δεν ισχύει για τις αυτόματες μεταβλητές και δεν μπορούμε να δώσουμε αρχικές τιμές με τον παρακάτω τρόπο :

            extern char name = 'Y';       /* λάθος */

 

Οι Στατικές Μεταβλητές

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

/* prog59.c – χρήση μιας στατικής μεταβλητής */

#include <stdio.h>

void try();

main()

{

            int count;

            for (count=1; count<=3; count++)

            {

                        printf("Εδώ είναι η επανάληψη %d: \n", count);

                        try();

            }

}

void try()

{

            int a=1;

            static int b=1;

 

            printf("a = %d και b = %d\n", a++, b++);

}

Το αποτέλεσμα θα είναι :

            Εδώ είναι η επανάληψη 1 :

            a = 1 και b = 1

            Εδώ είναι η επανάληψη 2 :

            a = 1 και b = 2

            Εδώ είναι η επανάληψη 3 :

            a = 1 και b = 3

 

Βλέπουμε ότι η a, που είναι καθαρά τοπική μεταβλητή, παίρνει την ίδια τιμή = 1 κάθε φορά που καλείται η συνάρτηση try() που την περιέχει, ενώ η στατική μεταβλητή b διατηρεί την αρχική τιμή της = 1 και κάθε φορά αυξάνει κατά ένα.

 

Δομές και Άλλοι Τύποι Δεδομένων

Οι δομές της C είναι κάτι ανάλογο με τα records (εγγραφές) της Pascal. Ακολουθεί ένα απλό παράδειγμα μιας δομής που περιέχει τον τίτλο, τον συγγραφέα και την τιμή ενός βιβλίου. Το πρόγραμμα θα επεκταθεί αργότερα και για πολλά βιβλία.

/* prog60. c – στοιχεία βιβλίων */

#include <stdio.h>

#define MAXTITLE 41          /* μέγιστο μήκος του τίτλου */

#define MAXAUTHOR 31    /* μέγιστο μήκος του ονόματος του συγγραφέα */

struct book    {                      /* η μορφοποίηση της δομής */

            char title[MAXTITLE];

            char author[MAXAUTHOR];

            float value;

            };         /* τέλος μορφοποίησης δομής */

main()

{

            struct book libry;     /* η libry είναι μεταβλητή τύπου book */

 

            printf("Δώστε τον τίτλο του βιβλίου.\n");

            gets(libry.title);

            printf("Δώστε τον συγγραφέα του βιβλίου : \n");

            gets(libry.author);

            printf("Δώστε την τιμή του βιβλίου : \n");

            scanf("%f", &libry.value);

            printf("%s by %s : $%.2f\n", libry.title, libry.author, libry.value);

            printf("%s : \"%s\" \($%.2f\)\n", libry.author, libry.title, libry.value);

}

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

 

Δημιουργία μιας Δομής

Η μορφοποίηση μιας δομής περιγράφει το πώς δημιουργείται η δομή. Ας δούμε πάλι τη δομή που είχαμε στο προηγούμενο παράδειγμα :

            struct book    {

                        char title[MAXTITLE];

                        char author[MAXAUTHOR];

                        float value;

                        };

Εδώ έχουμε μια δομή από δύο πίνακες char και μια μεταβλητή float. Η λέξη-κλειδί struct δηλώνει ότι ακολουθεί μια δομή. Μετά ακολουθεί μια προαιρετική ετικέττα, η λέξη book, που χρησιμοποιείται για να αναφερόμαστε στη δομή.

Έτσι, πιο κάτω έχουμε τη δήλωση :

            struct book libry;

που δηλώνει ότι η μεταβλητή libry είναι μια δομή τύπου book.

Τα μέλη της δομής περικλείονται από τις αγκύλες { και } και κάθε μέλος περιγράφεται από τη δική του δήλωση. Τα μέλη μπορεί να είναι οποιουδήποτε τύπου δεδομένων, ακόμα και άλλες δομές. Υπάρχουν δομές τοπικές και καθολικές.

Η μεταβλητή δομής είναι η libry και ο υπολογιστής κρατάει χώρο στη μνήμη μόνο γι' αυτήν και όχι και για τη book. Μπορούμε ακόμη να δηλώσουμε δύο μεταβλητές τύπου struct book ή ακόμη και έναν δείκτη αυτού του τύπου δομής :

            struct book book1, book2, *pointerbook;

Μπορούμε, όμως, να συνδυάσουμε τη διαδικασία ορισμού μορφοποίησης μιας δομής και τη διαδικασία ορισμού μιας μεταβλητής δομής μαζί, οπότε και δεν είναι απαραίτητη η χρήση μιας ετικέττας (book) :

            struct {

                        char title[MAXTIT];

                        char author[MAXAUT];

                        float value;

                        } libry;

Η χρήση της ετικέττας είναι βέβαια χρήσιμη όταν χρησιμοποιούμε την ίδια μορφοποίηση δομής περισσότερες από μία φορά.

 

Απόδοση Αρχικών Τιμών σε μια Δομή

Η απόδοση αρχικών τιμών σε μια δομή γίνεται ως εξής :

            static struct book libry = {

                        "Μάθετε για τη Φλώρινα", "Παπαδόπουλος Αντ.", 10.32

                        };

Ανάμεσα στα πεδία πρέπει να βάζουμε κόμματα για να ξεχωρίζουν οι τμές.

 

Πρόσβαση στα Πεδία μιας Δομής

Μπορούμε να έχουμε πρόσβαση στα μέλη μιας δομής με τη χρήση του τελστή μέλους δομής (.). Δηλαδή, το libry.value είναι το τμήμα value της libry. Στην ουσία, οι εκφράσεις .title, .author και .value παίζουν τον ρόλο των δεικτών αρίθμησης της δομής book. Η libry.value είναι μια τιμή τύπου float και και μπορεί να χρησιμοποιηθεί όπως κάθε άλλος τύπος float.

 

Γενικά για το Δυαδικό Σύστημα

Η γλώσσα C έχει την ικανότητα να χειρίζεται μεμονωμένα δυαδικά ψηφία σ’ ένα byte. Για παράδειγμα, μπορεί να ελέγχεται μια συσκευή του hardware στέλνοντάς της ένα byte, του οποίου κάθε δυαδικό ψηφίο έχει μια ειδική σμασία για τη συσκευή.

Το δυαδικό σύστημα (σύστημα με βάση το 2) είναι ένα φυσικό σύστημα για τον υπολογιστή και χρησιμοποιεί δυνάμεις του 2 αντί για δυνάμεις του 10. Ένας δυαδικός αριθμός, όπως ο 1101, σημαίνει :

            1 Χ 23 + 1Χ 22 + 0 Χ 21 + 1 Χ20

το οποίο δίνει το αποτέλεσμα 13 στο δεκαδικό σύστημα.

Ένα byte περιέχει 8 δυαδικά ψηφία που είναι αριθμημένα από το 0 μέχρι το 7 και το δυαδικό ψηφίο 7 ονομάζεται δυαδικό ψηφίο υψηλής τάξης, ενώ το δυαδικό ψηφίο 0 ονομάζεται δυαδικό ψηφίο χαμηλής τάξης. Η αρίθμηση των δυαδικών ψηφίων αντιστοιχεί σε δυνάμεις του 2, αρχίζοντας από το 20 μέχρι το 27.

Ο μεγαλύτερος δυαδικός αριθμός που μπορεί να κρατήσει ένα byte είναι ο 11111111 = 255 και ο μικρότερος είναι ο 00000000 = 0, έτσι ένα byte μπορεί να αποθηκεύσει αριθμούς από το 0 – 255 ή από το -128 έως +127, δηλ. σύνλο 256 ξεχωριστές τιμές.

 

Λογικοί Τελεστές Κατά Δυαδικό Ψηφίο

Η C έχει λογικούς τελεστές κατά δυαδικό ψηφίο και τελεστές ολίσθησης κατά δυαδικό ψηφίο. Στα παραδείγματα που ακολουθούν, οι αριθμοί θα είναι γραμμένοι σε δυαδικό συμβολισμό. Στα προγράμματα, όμως, θα πρέπει να χρησιμοποιούμε ακέραιες μεταβλητές, δηλ. αντί του 00011001, θα μπορούμε να χρησιμοποιούμε το 25 σε δεκαδική μορφή, το 031 σε οκταδική ή το 0x19 σε δεκαεξαδική.

Στα παραδείγματά μας, θα χρησιμοποιήσουμε αριθμούς 8 ψηφίων, με τα δυαδικά ψηφία αριθμημένα από 7 έως 0, από αριστερά προς τα δεξιά.

 

Συμπλήρωμα του 1 ή Άρνηση κατά Δυαδικό Ψηφίο : ˜

Αυτός ο μοναδικός τελεστής αλλάζει κάθε 1 σε 0 και κάθε 0 σε 1, ως εξής :

            ˜(10011010) == (01100101)

Αν υποθέσουμε ότι η μεταβλητή val είναι του τύπου unsigned int με τιμή 2, τότε στο δυαδικό σύστημα αυτός ο αριθμός είναι ο 00000010 και το

            ˜val = 11111101 = 253

Η νέα τιμή (˜val) που προκύπτει μπορεί να καταχωρηθεί κάπου αλλού ή και στην ίδια την val, ως εξής :

            newval = ˜val;

            printf("%d", ˜val);

            val = ˜val;

 

Το AND κατά Δυαδικό Ψηφίο : &

Αυτός ο δυαδικός τελεστής παράγει μια νέα τιμή κάνοντας μια σύγκριση δυαδικού ψηφίου ανά δυαδικό ψηφίο μεταξύ των δύο τελεστέων. Για κάθε θέση το αποτέλεσμα είναι 1 μόνο αν και τα δύο αντίστοιχα δυαδικά ψηφία των τελεστέων είναι 1, δηλ. αληθή :

            (10010011) & (00111101) == (00010001)

 

Το OR κατά Δυαδικό Ψηφίο : |

Αυτός ο δυαδικός τελεστής παράγει μια νέα τιμή κάνοντας μια σύγκριση δυαδικού ψηφίου ανά δυαδικό ψηφίο μεταξύ των δύο τελεστέων. Για κάθε θέση το αποτέλεσμα είναι 1 αν ένα από τα αντίστοιχα δυαδικά ψηφία των τελεστέων είναι 1, δηλ. αληθές :

            (10010011) | (00111101) == (10111111)

 

Το EXOR κατά Δυαδικό Ψηφίο : Ù

Αυτός ο δυαδικός τελεστής παράγει μια νέα τιμή κάνοντας μια σύγκριση δυαδικού ψηφίου ανά δυαδικό ψηφίο μεταξύ των δύο τελεστέων. Για κάθε θέση το αποτέλεσμα είναι 1 αν μόνο το ένα από τα αντίστοιχα δυαδικά ψηφία των τελεστέων είναι 1, δηλ. αληθές :

            (10010011) Ù (00111101) == (10101110)

 

Τελεστές Ολίσθησης Κατά Δυαδικό Ψηφίο

Ολίσθηση προς τα Αριστερά : <<

Αυτός ο τελεστής ολισθαίνει τα δυαδικά ψηφία της τιμής του αριστερού τελεστέου προς τα αριστερά κατά τόσες θέσεις όσες προσδιορίζονται από τον δεξιό τελεστέο. Οι θέσεις που αδειάζουν γεμίζουν με 0 και τα δυαδικά ψηφία που μετακινούνται πέραν του τέλους του αριστερού τελεστέου χάνονται.

Στο παράδειγμα :

            (10001010) << 2 == (00101000)

κάθε δυαδικό ψηφίο μετακινείται δύο θέσεις προς τα αριστερά.

 

Ολίσθηση προς τα Δεξιά : >>

Αυτός ο τελεστής ολισθαίνει τα δυαδικά ψηφία της τιμής του αριστερού τελεστέου προς τα δεξιά κατά τόσες θέσεις όσες προσδιορίζονται από τον δεξιό τελεστέο. Οι θέσεις που αδειάζουν γεμίζουν με 0 και τα δυαδικά ψηφία που μετακινούνται πέραν του δεξιού τέλους του αριστερού τελεστέου χάνονται.

Στο παράδειγμα :

            (10001010) >> 2 == (00100010)

κάθε δυαδικό ψηφίο μετακινείται δύο θέσεις προς τα δεξιά.

 

Συμβολικές Σταθερές με την #define

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

Στο επόμενο πρόγραμμα δείχνονται μερικές από τις δυνατότητες και ιδιότητες της οδηγίας #define :

/* prog61.c – επίδειξη της #define */

#include <sdtio.h>

#define TWO 2

#define MSG "Florina per sempre"

#define FOUR TWO*TWO

#define PX printf("Το X is %d\n", x)

#define FMT "Το X είναι %d\n"

main()

{

            int x = TWO;

 

            PX;

            x = FOUR;

            printf(FMT, x);

            printf("%s\n", MSG);

            printf("TWO: MSG\n");

}

Κάθε γραμμή έχει τρία τμήματα. Το πρώτο είναι η οδηγία #define. Το δεύτερο μέρος είναι η δική μας σύντμηση, γνωστή σαν μακροεντολή. Μια μακροεντολή δεν μπορεί να περιέχει κενά διαστήματα και η ονομασία της ακολουθεί τους κανόνες της ονομασίας των μεταβλητών.

Το υπόλοιπο της γραμμής καλείται το σώμα.

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

Το αποτέλεσμα του προγράμματος της προηγούμενης σελίδας είναι το εξής :

            Το Χ είναι 2

            Το Χ είναι 4

            Florina per sempre

            TWO: MSG

Ας δούμε από κοντά τι γίνεται.

Η εντολή int x = TWO; γίνεται τώρα int x = 2; και η πρόταση ΡΧ γίνεται printf("Το X είναι %d\n", x); Βλέπουμε συνεπώς ότι μια μακροεντολή μπορεί εκτός από σταθερές να εκφράσει και οποιαδήποτε συμβολοσειρά, ακόμη και μια ολόκληρη έκφραση της C. Γενικά, όπου ο προεπεξεργαστής βρει μια μακροεντολή στο πρόγραμμα, την αντικαθιστά με την ισοδύναμη συμβολοσειρά αντικατάστασης.

Εάν αυτή η συμβολοσειρά περιέχει μακροεντολές, τότε αντικαθίστανται κι αυτές. Η μόνη εξαίρεση γίνεται για μια μακροεντολή που βρίσκεται μεταξύ διπλών εισαγωγικών, όπως : Η printf("TWO: MSG\n"); εκτυπώνει απλά το κείμενο TWO: MSG

 

Προσάρτηση Αρχείου με την #include

Όταν ο προεπεξεργαστής της C συναντήσει μια οδηγία #include, κοιτάζει το όνομα αρχείου που ακολουθεί και συμπεριλαμβάνει το κείμενο του αρχείου μέσα στο τρέχον πρόγραμμα. Τα αρχεία τα προσαρτούμε γιατί περιέχουν πληροφορίες που χρειάζεται ο μεταγλωττιστής. Η επέκταση .h χρησιμοποιείται συμβατικά για τα αρχεία επικεφαλίδων.

Η οδηγία #include υπάρχει σε δύο παραλλαγές :

            #include <stdio.h>

και

            #include "mystuff.h"

Οι γωνιακές παρενθέσεις < και > λένε στον προεπεξεργαστή να ψάξει για το αρχείο σ’ έναν ή περισσότερους από τους τυποποιημένους καταλόγους αρχείων του συστήματος, ενώ τα διπλά εισαγωγικά λένε στον προεπεξεργαστή να ψάξει πρώτα τον δικό μας κατάλογο αρχείων (ή σε κάποιον άλλον που εμείς έχουμε καθορίσει) και μετά να ψάξει στα τυποποιημένα μέρη.

Ακολουθούν παραδείγματα :

#include <stdio.h>                           Ψάχνει τους καταλόγους του συστήματος

#include "mycatalog.h"                   Ψάχνει τον τρέχοντα κατάλογο εργασίας

#include "/usr/biff/mywork.h"        Ψάχνει τον κατάλογο /usr/biff

 

Η Οδηγία #undef

Καταργεί μια δεδομένη μακροεντολή. Δηλαδή, αν έχουμε τον ορισμό :

            #define LIMIT 400

Τότε η οδηγία :

            #undef LIMIT

καταργεί αυτόν τον ορισμό. Μπορούμε, ακόμα, να επαναορίσουμε τη LIMIT για να έχει μια νέα τιμή.

 

Οι Τύποι Απαρίθμησης

Ο τύπος απαρίθμησης μάς επιτρέπει να ορίζουμε με συμβολικά ονόματα ακέραιες σταθερές και με τη λέξη-κλειδί enum να δημιουργούμε έναν νέο "τύπο" και να καθορίζουμε τις τιμές που μπορεί να πάρει. Ο σκοπός των δηλώσεων απαρίθμησης είναι να κάνουν τα προγράμματα πιο ευανάγνωστα.

Ακολουθούν μερικές δηλώσεις :

            enum car {fiat, citroen, bmw, skoda, opel, ford};

            enum car voiture;

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

            color = fiat;

            if (color == skoda)

Η εντολή

            printf("fiat = %d, citroen = %d\n", fiat, citroen);

θα δώσει το εξής αποτέλεσμα :

            fiat = 0, citroen = 1

Οι τιμές, δηλαδή, που παίρνουν αυτές οι μεταβλητές είναι ακέραιες και ξεκινάνε από το 0, 1, 2 κλπ. Μπορούμε, όμως, να επιλέξουμε εμείς τις τιμές που θέλουμε να έχουν οι σταθερές, ως εξής :

            enum levels {low = 100, medium = 500, high = 2000};

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

 

back.gif (9867 bytes)

Επιστροφή