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

Η Γλώσσα Προγραμματισμού C
(Μέρος 3 - Συναρτήσεις, Πίνακες και Δείκτες)

 

Εισαγωγή στις Συναρτήσεις

Η φιλοσοφία σχεδίασης της C βασίζεται στη χρήση των συναρτήσεων. Έχουμε ήδη δει και χρησιμοποιήσει πολλές συναρτήσεις που έχει το σύστημα, όπως είναι οι printf(), scanf(), strlen() και άλλες, αλλά έχουμε δημιουργήσει και δικές μας συναρτήσεις. Οι πιο πολλές είχαν το όνομα main(). Τα προγράμματα της C πάντα αρχίζουν με την εκτέλεση των εντολών της συνάρτησης main() και μετά η main() καλεί άλλες συναρτήσεις. Ήρθε βέβαια ο καιρός για να δούμε τις συναρτήσεις αναλυτικότερα.

Συνάρτηση αποκαλείται μια αυτοδύναμη μονάδα κώδικα προγράμματος που έχει σχεδιασθεί για να εκτελεί μια συγκεκριμένη εργασία. Μια συνάρτηση της C κάνει την ίδια δουλειά με τις συναρτήσεις, τις υπορουτίνες και τις διαδικασίες των άλλων γλωσσών. Μερικές συναρτήσεις, όπως η printf(), έχουν σαν αποτέλεσμα να γίνει μια ενέργεια. Μερικές άλλες συναρτήσεις υπολογίζουν μια τιμή, που χρησιμοποιείται από το πρόγραμμα, όπως η strlen(). Γενικά, μια συνάρτηση μπορεί να παράγει ενέργειες ή και να παράγει τιμές.

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

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

#include <stdio.h>

#define SIZE 50

main()

{

            float list[SIZE];

            readlist(list, SIZE);

            sort(list, SIZE);

            average(list, SIZE);

            bargraph(list, SIZE);

}

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

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

 

Δημιουργία και Χρήση μιας Απλής Συνάρτησης

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

/* prog35.c – διαμόρφωση επικεφαλίδας */

#include <stdio.h>

#define NAME "Florina pe sempre "

#define ADDRESS "Florina"

#define PLACE "West Macedonia"

#define LIMIT 65

main()

{

            void starbar();          /* δήλωση της συνάρτησης */

 

            starbar();

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

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

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

            starbar();

}

 

void starbar()            /* ορισμός της συνάρτησης starbar() */

{

            int count;

 

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

                        putchar(‘*’);

            putchar(‘\n’);

}

Το αποτέλεσμα του προγράμματος θα είναι :

*****************************************************************

Florina per sempre

Florina

West Macedonia

*****************************************************************

Ας δώσουμε λίγη προσοχή στα παρακάτω σημεία του προγράμματος :

1. Η συνάρτηση starbar() καλείται από τη συνάρτηση main() χρησιμοποιώντας απλά τ’ όνομά της. Μετά το όνομα της συνάρτησης και τις παρενθέ-σεις πρέπει να ακολουθεί το σύμβολο (;), ως εξής : starbar();

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

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

3. Οι παρενθέσεις στη starbar() λένε στον μεταγλωττιστή ότι η starbar() είναι μια συνάρτηση. Η starbar() δεν ακολουθείται από το σύμβολο ;, πράγμα που σημαίνει ότι η συνάρτηση αυτή ορίζεται παρά ότι χρησιμοποιείται.

4. Η μεταβλητή count στη starbar() είναι μια τοπική μεταβλητή, δηλ. μια μεταβλητή γνωστή μόνο για τη starbar(). Μπορούμε να χρησιμοποιήσουμε το όνομα count και σ’ άλλες συναρτήσεις χωρίς κανένα πρόβλημα, ακόμα και μέσα στην ίδια την main().

5. Οι συναρτήσεις έχουν κι αυτές τύπους όπως οι μεταβλητές. Ο τύπος void χρησιμοποιείται για συναρτήσεις χωρίς τιμές επιστροφής και ο τύπος αυτός εμφανίζεται στον ορισμό της συνάρτησης.

6. Προγράμματα που χρησιμοποιούν μια συνάρτηση πρέπει να περιέχουν τη δήλωση του τύπου πριν η συνάρτηση χρησιμοποιηθεί. Γι' αυτόν τον λόγο, η main() περιέχει την εξής δήλωση :

            void starbar();

Έτσι δηλώνουμε ότι το πρόγραμμα χρησιμοποιεί μια συνάρτηση τύπου void που ονομάζεται starbar() και ότι ο μεταγλωττιστής θα βρει τον ορισμό αυτής της συνάρτησης κάπου αλλού.

7. Η συνάρτηση starbar() δεν έχει είσοδο, αφού δεν χρειάζεται καμία πληροφορία από το καλούν πρόγραμμα.

 

Ορίσματα Συνάρτησης

Ας δούμε τώρα πώς θα τροποποιηθεί η συνάρτηση starbar() που χρησιμοποιήσαμε προηγουμένως για να κεντράρει αυτή τη φορά όλους τους τίτλους. Η νέα μας συνάρτηση ονομάζεται n_char() και θα χρησιμοποιήσουμε τώρα ορίσματα γι' αυτή τη συνάρτηση, όπου το ένα θα είναι ο εκτυπούμενος χαρακτήρας και το άλλο ο αριθμός των επαναλήψεων αυτού του χαρακτήρα.

Η γραμμή με τους αστερίσκους έχει πλάτος 65 διαστήματα, άρα η συνάρτηση στην περίπτωση αυτή καλείται σαν n_char('*', 65). Η φράση Florina per sempre έχει πλάτος 18 διαστημάτων, άρα για να την κεντράρουμε θα πρέπει να αφήσουμε 23 κενά διαστήματα από δεξιά και από αριστερά της, δηλ. καλούμε την n_char(' ', 23).

/* prog36.c – κεντράρισμα επικεφαλίδας */

#include <stdio.h>

#define NAME "Florina pe sempre "

#define ADDRESS "Florina"

#define PLACE "West Macedonia"

#define LIMIT 65

#define SPACE ‘ ‘

main()

{

            int spaces;

            void n_char();

 

            n_char(‘*’, LIMIT);              /* χρήση σταθερών ως ορίσματα */

            putchar(‘\n’);

            n_char(SPACE, 23);

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

            spaces = (65 - strlen(ADDRESS))/2;         /* υπολογίζει τα κενά διαστήματα που θα αφήσει */

            n_char(SPACE, spaces);                             /* μια μεταβλητή ως όρισμα */

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

            n_char(SPACE, (65 - strlen(PLACE))/2);     /* μια έκφραση ως όρισμα */

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

            n_char(‘*’, LIMIT);

            putchar(‘\n’);

}

 

void n_char(ch, num)           /* ορισμός της συνάρτησης */

char ch;

int num;

{

            int count;

 

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

                        putchar(ch);

}

Το αποτέλεσμα του προγράμματος θα είναι :

*****************************************************************

Florina per sempre

Florina

West Macedonia

*****************************************************************

Ας δούμε τον ορισμό της παραπάνω συνάρτησης με ορίσματα αυτή τη φορά :

            void n_char(ch, num)

                        char ch;

                        int num;

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

Για να δώσουμε τιμές στα τυπικά ορίσματα ch και num χρησιμοποιούμε πραγματικά ορίσματα στην κλήση της συνάρτησης. Το τυπικό όρισμα είναι μια μεταβλητή στο πρόγραμμα που καλείται και το πραγματικό όρισμα είναι μια συγκεκριμένη τιμή, που καταχωρείται στη μεταβλητή αυτή από το πρόγραμμα που καλεί.

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

 

Η Χρήση του return

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

Ακολουθεί ένα πρόγραμμα :

/* prog37.c – βρίσκει τη μικρότερη από δύο τιμές */

#include <stdio.h>

main()

{

            int num1, num2;

            int imin();

 

            while (scanf("%d %d", &num1, &num2) == 2)

                        printf("Το μικρότερο μεταξύ του %d και %d είναι το %d. \n",

num1, num2, imin(num1, num2));

}

 

int imin(n, m)

            int n, m;

{

            int min;

 

            if (n < m)

                        min = n;

            else

                        min = m;

            return min;

}

Η λέξη-κλειδί return έχει ως αποτέλεσμα η τιμή οποιασδήποτε έκφρασης που την ακολουθεί, να είναι η τιμή επιστροφής της συνάρτησης. Στη συγκεκριμένη βέβαια περίπτωση, η τιμή επιστροφής της συνάρτησης είναι η τιμή της τοπικής μεταβλητής min. Η συνάρτηση imin() είναι τύπου int, όπως είναι και η μεταβλητή min. Η τιμή επιστροφής μιας συνάρτησης μπορεί να καταχωρηθεί σε μια μεταβλητή ή να χρησιμοποιηθεί σαν μέρος μιας έκφρασης.

Ακολουθεί και μια άλλη έκδοση της συνάρτησης imin() :

/* δεύτερη έκδοση της συνάρτησης ελάχιστης τιμής */

imin(n, m)

            int n, m;

{

            return (n<m) ? n : m;

}

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

Ακολουθεί και μια τρίτη έκδοση της συνάρτησης imin() :

/* τρίτη έκδοση της συνάρτησης ελάχιστης τιμής */

imin(n, m)

            int n, m;

{

            if (n<m)

                        return (n);

            else

                        return(m);

}

Η απλή πρόταση return; προκαλεί τον τερματισμό της συνάρτησης και την επιστροφή του ελέγχου στην καλούσα συνάρτηση. Αυτή η μορφή χρησιμοποιείται σε συναρτήσεις του τύπου void γιατί δεν επιστρέφει καμία τιμή.

 

Τύποι Συναρτήσεων

Και για τις συναρτήσεις πρέπει να δηλώνεται ο τύπος τους, ο οποίος είναι ίδιος με τον τύπο της τιμής επιστροφής της συνάρτησης. Συναρτήσεις χωρίς τιμή επιστροφής δηλώνονται σαν τύπου void και αν δεν δηλωθεί ο τύπος μιας συνάρτησης, τότε η C θεωρεί ότι είναι τύπου int. Η δήλωση του τύπου είναι μέρος του ορισμού της συνάρτησης και αναφέρεται στην τιμή επιστροφής και όχι στα ορίσματα της συνάρτησης. Μια δήλωση της συνάρτησης λέει στο πρόγραμμα τι τύπου είναι η συνάρτηση, ενώ ο ορισμός της συνάρτησης παρέχει τον πραγματικό κώδικά της. Η δήλωση μιας συνάρτησης πρέπει να γίνεται πριν από το σημείο όπου χρησιμοποιείται η συνάρτηση.

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

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

            int imax(int, int);

            int imax(int a, int b);

 

Ο Τελεστής &

Μερικές συναρτήσεις, όπως η scanf(), καταχωρούν τιμές σε μεταβλητές του καλούντος προγράμματος και χρησιμοποιούν τον τελεστή διεύθυνσης & στα ορίσματα. Ο τελεστής & μάς δίνει τη διεύθυνση στην οποία αποθηκεύεται μια μεταβλητή. Για παράδειγμα, αν p είναι το όνομα μιας μεταβλητής, τότε &p είναι η διεύθυνση της μεταβλητής p στη μνήμη. Αν η μεταβλητή p έχει τιμή 100 και είναι αποθηκευμένη στη διεύθυνση 15879, τότε η παρακάτω πρόταση :

            printf("%d %u\n", p, &p);

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

            100 15879

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

/* prog38.c – έλεγχος για το πού βρίσκονται αποθηκευμένες μεταβλητές */

#include <stdio.h>

void myfunction();                /* δήλωση συνάρτησης */

main()

{

            int p=2, b=5;

 

            printf("Στη main(), p = %d και &p = %p \n", p, &p);

            printf("Στη main(), b = %d και &b = %p \n", b, &b);

            myfunction(p);

}

 

void myfunction(b);              /* ορισμός συνάρτησης */

            int b;

{

            int p = 10;

 

            printf("Στη mikado(), p = %d και &p = %p \n", p, &p);

            printf("Στη mikado(), b = %d και &b = %p \n", b, &b);

}

Χρησιμοποιήσαμε εδώ τη μορφή %p για την εκτύπωση των διευθύνσεων· αν, όμως, το σύστημά μας δεν διαθέτει αυτή τη μορφή, τότε μπορούμε να δοκιμάσουμε με τη μορφή %u.

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

            Στη main(), p = 2 και &p = FFD8

            Στη main(), b = 5 και &b = FFDA

            Στη myfunction(), p = 10 και &p = FFD0

            Στη myfunction(), b = 2 και &b = FFD6

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

 

Αλλαγή των Μεταβλητών στο Πρόγραμμα που Καλεί

Μερικές φορές θέλουμε μια συνάρτηση να προκαλεί αλλαγές στις μεταβλητές μιας άλλης συνάρτησης. Ας πάρουμε το δημοφιλές παράδειγμα της ανταλλαγής των τιμών δύο μεταβλητών x και y.

Ξέρουμε ότι αυτό γίνεται με τη χρήση μιας τρίτης βοηθητικής μεταβλητής temp, ως εξής :

            temp = x;

            x = y;

            y = temp;

Χρησιμοποιούμε τα ονόματα x και y για τις μεταβλητές της main() και u και v για τις μεταβλητές της συνάρτησης interchange().

/* prog39.c – ανταλλαγή τιμών μεταβλητών */

#include <stdio.h>

void interchange();

main()

{

            int x = 5, y = 10;

 

            printf("Αρχικά x = %d και y = %d. \n", x, y);

            interchange(x, y);

            printf("Τώρα x = %d και y = %d. \n", x, y);

}

 

void interchange(u, v)

int u, v;

{

            int temp;

            printf("Αρχικά u = %d και v = %d. \n", u, v);

            temp = u;

            u = v;

            v = temp;

            printf("Τώρα u = %d και v = %d. \n", u, v);

}

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

            Αρχικά x = 5 και y = 10

            Αρχικά u = 5 και v = 10

            Τώρα u = 10 και v = 5

            Τώρα x = 5 και y = 10

Βλέπουμε ότι η συνάρτηση interchange() λειτουργεί σωστά, αφού ανταλλάσσει τις τιμές των u και v. Εκείνο που μας ενδιαφέρει, όμως, εμάς είναι η μετάδοση των τιμών αυτών πίσω στη main(). Όπως ξέρουμε, με την πρόταση return μπορούμε να μεταδώσουμε πίσω στο καλούν πρόγραμμα μόνο μία τιμή.

Τι μπορούμε, όμως, να κάνουμε;

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

 

Εισαγωγή στους Δείκτες (Pointers)

Χρησιμοποιήσαμε προηγουμένως τον τελεστή & για να βρούμε τη διεύθυνση της μεταβλητής p. Η &p δηλαδή είναι ένας δείκτης της p. Η πραγματική διεύθυνση είναι ένας 16δικός αριθμός και η συμβολική παράσταση &p είναι μια σταθερά δείκτη. Η μεταβλητή p δεν πρόκειται να αλλάξει διεύθυνση μέσα στο πρόγραμμα, αν και μπορεί να αλλάξει τιμή. Η C έχει και μεταβλητές δείκτη που έχουν μια διεύθυνση ως τιμή.

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

            ptr = &p;       /* καταχωρεί τη διεύθυνση της p στην ptr */

            ptr = &b;       /* ο δείκτης ptr δείχνει τώρα στη b αντί για την p */

Η ptr δηλαδή είναι μια μεταβλητή και η &p είναι μια σταθερά.

Για να δηλώσουμε τον τύπο μιας μεταβλητής δείκτη, πρέπει να χρησιμοποιήσουμε τον τελεστή έμμεσης αναφοράς (*).

            val = *ptr      /* βρίσκει την τιμή που δείχνει ο δείκτης ptr */

Πώς δηλώνουμε, όμως, τους δείκτες;

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

Οι δείκτες δηλώνονται ως εξής :

            int *pi;                       /* pi είναι ένας δείκτης σε μια ακέραια μεταβλητή */

            char *pc;                   /* pc είναι ένας δείκτης σε μια μεταβλητή χαρακτήρα */

            float *pf, *pg;           /* pf, pg είναι δείκτες σε μεταβλητές float */

Από τα παραπάνω συμπεραίνουμε ότι pi είναι ο δείκτης και ότι η *pi είναι τύπου int. Παρόμοια, η τιμή *pc την οποία δείχνει ο pc είναι τύπου char.

 

Οι Δείκτες και οι Συναρτήσεις

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

/* prog40.c – χρήση δεικτών για την ανταλλαγή τιμών */

#include <sdtio.h>

void interchange();

main()

{

            int x = 5, y = 10;

 

            printf("Αρχικά x = %d και y = %d. \n", x, y);

            interchange(&x, &y);          /* στείλε τις διευθύνσεις στη συνάρτηση*/

            printf("Τώρα x = %d και y = %d. \n", x, y);

}

 

void interchange(u, v)

int *u, *v;      /* οι u και v είναι τώρα δείκτες */

{

            int temp;

            temp = *u;     /* η temp παίρνει την τιμή που δείχνει η u */

            *u = *v;

            *v = temp;

}

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

            Αρχικά x = 5 και y = 10

            Τώρα x = 10 και y = 5

Ας προσέξουμε τα εξής σημεία :

1. Η κλήση της συνάρτησης γίνεται ως εξής : interchange(&x, &y);, δηλαδή, αντί να στέλνονται οι τιμές των x και y, στέλνονται οι διευθύνσεις τους. Έτσι, τα τυπικά ορίσματα u και v της συνάρτησης interchange() έχουν διευθύνσεις ως τιμές και πρέπει να δηλωθούν ως δείκτες.

2. Εφόσον οι x και y είναι ακέραιοι, δηλώνουμε τις u και v ως δείκτες σε ακέραιους.

3. Θυμηθείτε ότι η u έχει την τιμή &x, έτσι η u δείχνει τη x, δηλαδή η *u μάς δίνει την τιμή του x.

4. Δεν πρέπει να γράψουμε temp = u;, γιατί έτσι θα αποθηκευτεί η διεύθυνση της x και όχι η τιμή της.

Θέλουμε μια συνάρτηση να ανταλλάσσει τις τιμές δύο μεταβλητών. Δίνοντας στη συνάρτηση τις διευθύνσεις των μεταβλητών αυτών, αποκτάμε πρόσβαση σ' αυτές τις μεταβλητές. Χρησιμοποιώντας δείκτες και τον τελεστή *, η συνάρτηση μπορεί να εξετάσει τις τιμές που βρίσκονται σ' αυτές τις θέσεις και να τις αλλάξει.

Με τους δείκτες δηλαδή μπορούμε να μπούμε στη main() και να αλλάξουμε ό,τι είναι αποθηκευμένο εκεί.

 

Εισαγωγή στους Πίνακες

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

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

main()

{

            float times[365];      /* πίνακας με 365 στοιχεία τύπου float */

            char cities[12];        /* πίνακας με 12 στοιχεία τύπου char */

            int numbers[50];                  /* πίνακας με 50 στοιχεία τύπου int */

}

Οι αγκύλες [ και ] μάς λένε ότι πρόκειται για πίνακα και ο αριθμός που είναι μέσα στις αγκύλες μάς δείχνει τον αριθμό των στοιχείων του πίνακα. Για να αναφερθούμε σ’ ένα συγκεκριμένο στοιχείο του πίνακα, χρησιμοποιούμε έναν αριθμό που ονομάζεται δείκτης πίνακα και που η αρίθμησή του αρχίζει από το 0.

Ο πίνακας δηλ. είναι μια μεταβλητή με δείκτη και για να αναφερθούμε πλήρως σ’ ένα στοιχείο του, χρειαζόμαστε το όνομα του πίνακα και την τιμή του δείκτη του πίνακα. Έτσι, π.χ. το times[0] είναι το πρώτο στοιχείο του πίνακα times και το times[364] είναι το 365ο και τελευταίο στοιχείο του.

 

Αυτόματες, Εξωτερικές και Στατικές Μεταβλητές

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

Μπορούμε να δώσουμε αρχικές τιμές σ’ αυτόματους πίνακες ως εξής :

main()

{

            int powers[8] = {1, 2, 4, 6, 8, 16, 32, 64};

}

 

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

Ακολουθεί ένα παράδειγμα :

int report;

int numbers[5] = {12, 10, 8, 9, 6};

main()

{

            ...

}

 

int feed(n)

int n;

{

            ...

}

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

Μια στατική μεταβλητή ή ένας στατικός πίνακας ορίζονται μέσα σε μια συνάρτηση με τη λέξη-κλειδί static :

int account(n,m)

int n, m;

{

            static int beans[2] = {343, 332};

            ...

}

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

 

Απόδοση Αρχικών Τιμών σε Πίνακες

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

/* prog41.c – απόδοση αρχικών τιμών στις ημέρες κάθε μήνα */

#include <stdio.h>

#define MONTHS 12

int days[MONTHS] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

main()

{

            int index;

 

            for (index=0; index<MONTHS; index++)

                        printf ("Ο μήνας %d έχει %d ημέρες.\n", index+1, days[index]);

}

Το αποτέλεσμα θα είναι ως εξής :

            Ο μήνας 1 έχει 31 ημέρες.

            Ο μήνας 2 έχει 28 ημέρες.

            Ο μήνας 3 έχει 31 ημέρες.

            Ο μήνας 4 έχει 30 ημέρες.

            Ο μήνας 5 έχει 31 ημέρες.

            Ο μήνας 6 έχει 30 ημέρες.

            Ο μήνας 7 έχει 31 ημέρες.

            Ο μήνας 8 έχει 31 ημέρες.

            Ο μήνας 9 έχει 30 ημέρες.

            Ο μήνας 10 έχει 31 ημέρες.

            Ο μήνας 11 έχει 30 ημέρες.

            Ο μήνας 12 έχει 31 ημέρες.

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

 

Καταχώρηση Τιμών σε Πίνακες

Ακολουθεί ένα πρόγραμμα που καταχωρεί άρτιους αριθμούς σ’ έναν αυτόματο πίνακα :

#include <stdio.h>

#define SIZE 20

main()

{

            int counter, evens[SIZE];

 

            for (counter=0; counter<SIZE; counter++)

                        evens[counter] = 2 * counter;

}

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

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

#include <stdio.h>

#define SIZE 5

main()

{

            int pina[SIZE] = {5, 3, 2, 8};          /*  όλα εντάξει */

            int pinb[SIZE];

 

            pinb = pina;                                      /* δεν επιτρέπεται */

            pinb[SIZE] = pina[SIZE];              /* ανεπίτρεπτο */

            pinb[SIZE] = {5, 3, 2, 8};                /* δεν δουλεύει εδώ */

}

 

Δείκτες σε Πίνακες

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

Το όνομα ενός πίνακα είναι και ένας δείκτης, που δείχνει στο πρώτο στοιχείο του. Δηλαδή, αν pina είναι ένας πίνακας, τότε με :

            pina == &pina[0]

παριστάνεται και η διεύθυνση μνήμης του πρώτου στοιχείου του πίνακα.

Ακολουθεί ένα παράδειγμα που κάνει πράξεις με τις τιμές ενός δείκτη :

/* prog42.c – πρόσθεση δείκτη */

#include <stdio.h>

#define SIZE 4

main()

{

            int pina[SIZE], *pti, index;

            float pinb[SIZE], *ptf;

 

            pti = pina;     /* καταχωρεί τη διεύθυνση του πίνακα σε δείκτη */

            ptf = pinb;

            for (index=0; index<SIZE; index++)

                        printf("δείκτες + %d : %10u %10u\n", index, pti+index,

                                    ptf+index);

}

Το αποτέλεσμα θα είναι ως εξής :

            δείκτες + 0 : 56014 56026

            δείκτες + 1 : 56016 56030

            δείκτες + 2 : 56018 56034

            δείκτες + 3 : 56020 56038

 

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

Για να δούμε τι γίνεται από κοντά :

            56014 + 1 = 56016 ;

            56026 + 1 = 56030 ;

Ξέρουμε ότι ο τύπος int χρησιμοποιεί 2 bytes για αποθήκευση και ο τύπος float 4 bytes. Όταν λοιπόν προσθέτουμε 1 στον αντίστοιχο δείκτη, τότε η C προσθέτει μια μονάδα αποθήκευσης. Για τους πίνακες, όμως, αυτό σημαίνει ότι η διεύθυνση αυξάνεται στη διεύθυνση του επόμενου στοιχείου και όχι στο επόμενο byte.

Προσθέτοντας έναν ακέραιο σ' έναν δείκτη ή αυξάνοντας έναν δείκτη, αλλάζει η τιμή του δείκτη κατά τον αριθμό των bytes του στοιχείου που δείχνει ο δείκτης.

Ακολουθούν μερικές ενδιαφέρουσες εκφράσεις :

            dates + 2 == &dates[2]      /* ίδια διεύθυνση */

            *(dates + 2) == dates[2]    /* ίδια τιμή */

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

Δεν πρέπει, όμως, να συγχέουμε την τιμή της *(dates+2) με την τιμή *dates+2, γιατί :

            *(dates + 2)   /* είναι η τιμή του 3ου στοιχείου του πίνακα dates */

            *dates + 2     /* το 2 προστίθεται στην τιμή του 1ου στοιχείου */

Μπορούμε συνεπώς να χρησιμοποιούμε και δείκτες για να δουλέψουμε με τους πίνακες.

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

/* prog43.c – χρησιμοποιεί δείκτες αντί για πίνακες */

#include <stdio.h>

#define MONTHS 12

int days[MONTHS] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

main()

{

            int index;

 

            for (index=0; index<MONTHS; index++)

                        printf ("Ο μήνας %d έχει %d ημέρες.\n", index+1,

                                    *(days+index));

}

 

Συναρτήσεις, Πίνακες και Δείκτες

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

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

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

/* prog44.c – άθροισμα στοιχείων ενός πίνακα */

#include <stdio.h>

#define SIZE 10

long sum();

main()

{

            static int pina[SIZE] = {20, 10, 5, 39, 4, 16, 19, 26, 31, 20};

            long answer;

 

            answer = sum(pina, SIZE);

            printf("Το άθροισμα των στοιχείων είναι %d.\n", answer);

}

 

long sum(ar,n)                      /* χρήση δείκτη */

int *ar;                                   /* ο ar είναι ένας δείκτης */

int n;

{

            int i;

            long total = 0;

            for (i=0; i<n; i++)

            {

                        total += *ar;             /* πρόσθεση τιμής στο total */

                        ar++;                         /* ο δείκτης στο επόμενο στοιχείο */

            }

            return total;

}

Ο ar ξεκινάει δείχνοντας το πρώτο στοιχείο του πίνακα pina και με την πρόταση καταχώρησης total += *ar προστίθεται η τιμή του πρώτου στοιχείου, δηλ. η 20, στη μεταβλητή total. Μετά, η έκφραση ar++ αυξάνει τη μεταβλητή δείκτη ar έτσι ώστε αυτή να δείχνει στο επόμενο στοιχείο του πίνακα.

Μπορούμε να γράψουμε τις προηγούμενες δύο εντολές και έτσι :

            total += *ar++;

Δηλαδή, αυξάνεται ο δείκτης κατά ένα και όχι η τιμή που δείχνει και μάλιστα αυξάνεται μετά τη χρήση της τιμής του. Εάν χρησιμοποιήσουμε *++ar, τότε η σειρά θα ήταν : αύξηση του δείκτη και μετά χρήση της τιμής που δείχνει. Αλλά αν χρησιμοποιήσουμε την έκφραση (*ar)++, τότε το πρόγραμμα θα κάνει χρήση της τιμής που δείχνει η ar και μετά θα αυξήσει την τιμή αυτή κατά ένα και όχι τον δείκτη.

 

Οι Λειτουργίες των Δεικτών

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

Ακολουθεί ένα παράδειγμα :

/* prog45.c – λειτουργίες δεικτών */

#include <stdio.h>

main()

{

            static int pina[3] = {100, 200, 300};

            int *ptr1, *ptr2;

            ptr1 = pina;/* καταχώρηση της διεύθυνσης του πίνακα σ' έναν δείκτη */

            ptr2 = &pina[2]; /* καταχώρηση της διεύθυνσης του 3ου στοιχείου */

 

            printf("ptr1 = %u, *ptr1 = %d, &ptr1 = %u\n", ptr1, *ptr1, &ptr1);

            ptr1++;                      /* αυξάνουμε τον δείκτη */

            printf("ptr1 = %u, *ptr1 = %d, &ptr1 = %u\n", ptr1, *ptr1, &ptr1);

 

            printf("ptr2 = %u, *ptr2 = %d, &ptr2 = %u\n", ptr2, *ptr2, &ptr2);

            ++ptr2;                      /* περνάμε τα όρια του πίνακα */

            printf("ptr2 = %u, *ptr2 = %d, &ptr2 = %u\n", ptr2, *ptr2, &ptr2);

 

            printf("ptr2 - ptr1 = %u\n", ptr2 - ptr1);

            /* τυπώνουμε τη διαφορά των δεικτών */

}

Το αποτέλεσμα θα είναι ως εξής :

            ptr1 = 234, *ptr1 = 100, &ptr1 = 3606

            ptr1 = 236, *ptr1 = 200, &ptr1 = 3606

            ptr2 = 238, *ptr2 = 300, &ptr2 = 3604

            ptr2 = 240, *ptr2 = 1910, &ptr2 = 3604

            ptr2 – ptr1 = 2

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

1. Καταχώρηση. Μπορούμε να καταχωρήσουμε μια διεύθυνση σ' έναν δείκτη, είτε χρησιμοποιώντας ένα όνομα πίνακα ή χρησιμοποιώντας τον τελεστή διεύθυνσης & και την τιμή μιας μεταβλητής.

2. Εύρεση τιμής. Ο τελεστής * μάς δίνει την τιμή που βρίσκεται αποθηκευμένη στη θέση που δείχνεται από τον δείκτη.

3. Απόκτηση της διεύθυνσης ενός δείκτη. Και οι μεταβλητές δείκτη έχουν μια διεύθυνση και μια τιμή.

4. Αύξηση-μείωση ενός δείκτη. Με την αύξησή του ένας δείκτης μετακινείται στο επόμενο στοιχείο του πίνακα. Φυσικά, με ανάλογο τρόπο μπορούμε και να μειώσουμε έναν δείκτη. Η λειτουργία, όμως, ++ptr2 στο προηγούμενο παράδειγμα είχε σαν αποτέλεσμα ο ptr2 να μετακινηθεί κατά δύο bytes και να δείχνει ο,τιδήποτε μπορεί να αποθηκευθεί μετά τον πίνακα. Επίσης, πρέπει να θυμόμαστε ότι μπορούμε να χρησιμοποιούμε τους τελεστές αύξησης ή μείωσης με τις μεταβλητές δείκτη, αλλά όχι με σταθερές δείκτη. Δηλαδή, το ptr2 = urn++; δεν επιτρέπεται, όπως δεν επιτρέπεται και το 3++.

5. Διαφορά. Μπορούμε να βρούμε τη διαφορά μεταξύ δύο δεικτών.

 

Οι Πίνακες Πολλών Διαστάσεων

Ένας μετεωρολόγος θέλει να αναλύσει τα δεδομένα της μηνιαίας βροχόπτωσης σε μια διάρκεια 5 ετών, δηλ. θα χρειαστεί συνολικά 5 Χ 12 = 60 μεταβλητές. Για να παραστήσει αυτά τα δεδομένα μπορεί να χρησιμοποιήσει 60 ανεξάρτητες μεταβλητές, μία για κάθε στοιχείο δεδομένων, κάτι φυσικά πολύ άβολο. Κάτι καλύτερο θα ήταν ένας πίνακας με 60 στοιχεία, αλλά ακόμα καλύτερο θα ήταν αν μπορούσε να κρατήσει ξεχωριστά τα 12 δεδομένα για τον κάθε χρόνο.

Μπορεί δηλαδή να χρησιμοποιήσει 5 πίνακες, έναν για τα 12 στοιχεία του κάθε έτους, αλλά τι γίνεται αν τα έτη αυξηθούν και γίνουν 50; Μια καλή λύση είναι να χρησιμοποιήσουμε έναν πίνακα από πίνακες. Δηλ., ο κύριος πίνακας θα έχει 5 στοιχεία και το κάθε στοιχείο του θα είναι ένας πίνακας από 12 στοιχεία. Μιλάμε δηλαδή για πίνακα δύο διαστάσεων.

Αυτό γίνεται ως εξής :

            static float rain[5][12];

Ο πίνακας rain είναι δισδιάστατος και αποτελείται από 5 γραμμές και από 12 στήλες. Αν αλλάξουμε τον δεύτερο δείκτη, κινούμαστε σε μια γραμμή και αν αλλάξουμε τον πρώτο δείκτη, κινούμαστε κατά μήκος μιας στήλης. Για το συγκεκριμένο παράδειγμα, ο δεύτερος δείκτης δείχνει τους μήνες και ο πρώτος τα χρόνια. 

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

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

 

Σταθερές Συμβολοσειράς Χαρακτήρα

Όταν ο μεταγλωττιστής της C συναντά κάτι ανάμεσα σε διπλά εισαγωγικά " ", το θεωρεί σαν σταθερά συμβολοσειράς και οι χαρακτήρες μέσα στα εισαγωγικά αυτά μαζί με τον χαρακτήρα '\0' αποθηκεύονται σε γειτονικές θέσεις της μνήμης.

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

            printf("\"Τρέξε, Μαρία!\" είπε η Βάσω.\n");

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

            "Τρέξε, Μαρία!" είπε η Βάσω.

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

 

Πίνακες Συμβολοσειράς Χαρακτήρα

Μπορούμε να δώσουμε αρχικές τιμές σε μια συμβολοσειρά χαρακτήρα ως εξής:

            char line[ ] = "Περιορίσου σε μια γραμμή";

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

            char line[ ] = {'Π', 'ε', 'ρ', 'ι', 'ο', 'ρ', 'ί', 'σ', 'ο', 'υ', ' ', 'σ', 'ε', ' ', 'μ', 'ι', 'α', ' ', 'γ', 'ρ', 'α', 'μ', 'μ', 'ή', '\0'};

H χρήση του μηδενικού χαρακτήρα '\0' εξηγεί ότι πρόκειται για συμβολοσειρά και όχι για πίνακα χαρακτήρων. Και στις δύο μορφές, ο μεταγλωττιστής μετράει τους χαρακτήρες και δίνει στον πίνακα το αντίστοιχο μέγεθος. Όπως συμβαίνει και με τους άλλους πίνακες, το όνομα του πίνακα line είναι ένας δείκτης στο πρώτο στοιχείο του πίνακα :

            line == &line[0]

            *line == 'Π'

            *(line+1) == line[1] == 'ε'

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

            char *Florina = "\nFlorina per sempre";

που ισοδυναμεί με το :

            static char Florina[ ] = "\nFlorina per sempre";

 

Είσοδος Συμβολοσειράς

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

            char name[81];

Αφού έχει δημιουργηθεί χώρος στη μνήμη για τη συμβολοσειρά, μπορούμε πλέον να τη διαβάσουμε. Θα δούμε τις συναρτήσεις διαβάσματος συμβολοσειρών gets() και scanf().

 

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

Η συνάρτηση αυτή διαβάζει χαρακτήρες μέχρι να συναντήσει τον χαρακτήρα enter '\n'. Η συνάρτηση αυτή δέχεται όλους τους χαρακτήρες πριν από τον χαρακτήρα '\n', προσθέτει τον μηδενικό χαρακτήρα '\0' και δίνει τη συμβολοσειρά στο καλούν πρόγραμμα. Ο χαρακτήρας νέας γραμμής διαβάζεται και αγνοείται.

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

/* prog46.c – διάβασμα ενός ονόματος */

#include <stdio.h>

#define MAX 81

main()

{

            char name[MAX];    /* δέσμευση χώρου */

 

            printf("Γεια σου, ποιο είναι το όνομά σου ;\n");

            gets(name);

            printf("Γεια σου, %s.\n", name);

}

Αν όλα δουλέψουν σωστά, η gets() επιστρέφει τη διεύθυνση της συμβολοσειράς που διάβασε, αν όμως κάτι πάει στραβά ή αν η gets() συναντήσει EOF, επιστρέφει μια μηδενική διεύθυνση ή τίποτα.

Η κενή διεύθυνση καλείται δείκτης κενού, παριστάνεται στο αρχείο <stdio.h> με τη σταθερά NULL και δεν έχει φυσικά καμία σχέση με τον χαρακτήρα του κενού διαστήματος. Έτσι, με τη gets() μπορούμε να ελέγχουμε για πιθανά λάθη ως εξής :

            while (gets(name) != NULL)

 

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

Η βασική διαφορά των δύο συναρτήσεων είναι στον τρόπο που αποφασίζουν ότι έχουν φθάσει στο τέλος της συμβολοσειράς. Μπορούμε να πούμε ότι η scanf() είναι περισσότερο μια συνάρτηση "απόκτησης λέξης" παρά "απόκτησης συμβολοσειράς".

Η συνάρτηση scanf() έχει δύο επιλογές. Ξεκινά πάντα με τον πρώτο μη-λευκό χαρακτήρα που συναντάει. Αν χρησιμοποιούμε τον προσδιοριστή %s, η συμβολοσειρά φθάνει μέχρι τον επόμενο λευκό χαρακτήρα χωρίς να τον περιλαμβάνει. Αν καθορίσουμε ένα εύρος πεδίου, π.χ. %10s, τότε η scanf() λαμβάνει υπόψη της μέχρι 10 χαρακτήρες ή μέχρι τον πρώτο λευκό χαρακτήρα.

Όπως είδαμε και στα προηγούμενα, η τιμής επιστροφής της scanf() είναι μια ακέραια τιμή, που είναι ίση με το αριθμό των στοιχείων που διαβάστηκαν με επιτυχία ή με τον χαρακτήρα EOF αν συναντήσει το τέλος αρχείου.

Ακολουθεί ένα παράδειγμα :

/* prog47. c – χρήση της scanf() */

#include <stdio.h>

main()

{

            static char name1[11], name2[11];

            int count;

 

            printf("Παρακαλώ δώστε 2 ονόματα. \n");

            count = scanf("%5s %10s", name1, name2);

            printf("Διάβασα τα %d ονόματα %s και %s. \n", count, name1, name2);

}

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

            Παρακαλώ δώστε 2 ονόματα.

            Τάκης  Αντώνης

            Διάβασα τα 2 ονόματα Τάκης και Αντώνης.

ή

            Παρακαλώ δώστε 2 ονόματα.

            Λένα Παπαδημητρίου

            Διάβασα τα 2 ονόματα Λένα και Παπαδημητρ.

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

 

Έξοδος Συμβολοσειράς

Θα δούμε δύο συναρτήσεις εξόδου, τις puts() και printf().

 

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

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

Ακολουθεί ένα παράδειγμα :

/* prog48.c – χρήση της puts */

#include <stdio.h>

#define DEF "Είμαι μια συμβολοσειρά που έχει ορισθεί."

main()

{

            static char str1[ ] = "Ένας πίνακας μού απέδωσε αρχικές τιμές.";

            char *str2 = "Ένας δείκτης μού απέδωσε αρχικές τιμές.";

 

            puts("Είμαι ένα όρισμα στην puts().");

            puts(DEF);

            puts(str1);

            puts(str2);

            puts(&str1[4]);

            puts(str2+4);

}

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

            Είμαι ένα όρισμα στην puts().

            Είμαι μια συμβολοσειρά που έχει ορισθεί.

            Ένας πίνακας μού απέδωσε αρχικές τιμές.

            Ένας δείκτης μού απέδωσε αρχικές τιμές.

            πίνακας μού απέδωσε αρχικές τιμές.

            δείκτης μού απέδωσε αρχικές τιμές.

Προσέχουμε στα δύο τελευταία παραδείγματα από πού αρχίζει να τυπώνει η puts. Κάθε συμβολοσειρά με την puts() εμφανίζεται σε μια νέα γραμμή.

 

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

Όπως ήδη γνωρίζουμε, η printf() δεν τοποθετεί αυτόματα κάθε συμβολοσειρά σε μια νέα γραμμή, αλλά εμείς πρέπει να δείξουμε πού θέλουμε να ξεκινούν οι νέες γραμμές. Γενικά, με την printf() πρέπει να γράψουμε περισσότερα, αλλά μ’ αυτήν μπορούμε να συνδυάσουμε σε μια γραμμή πολλές συμβολοσειρές.

 

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

Η συνάρτηση αυτή βρίσκει το μήκος μιας συμβολοσειράς. Ακολουθεί ένα πρόγραμμα όπου μπορούμε να μικρύνουμε μια μεγάλη συμβολοσειρά σε μέγιστο μήκος χαρακτήρων ίσο με size :

 

/* prog49.c – προκρούστεια συνάρτηση */

void match(string, size)

char *string;

int size;

{

            if (strlen(string) > size)

                        *(string + size) = '\0';

}

 

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

Με τη συνάρτηση strcat() μπορούμε να ενώσουμε συμβολοσειρές :

/* prog50.c – ένωση δύο συμβολοσειρών */

#include <stdio.h>

#include <string.h>

#define SIZE 80

main()

{

            static char color[SIZE];

            static char addon[ ] = ", πολύ ωραίο χρώμα";

 

            puts("Ποιο είναι το αγαπημένο σου χρώμα;");

            gets(color);

            strcat(color, addon);

            puts(color);

            puts(addon);

}

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

            Ποιο είναι το αγαπημένο σου χρώμα;

            Κόκκινο

            Κόκκινο, πολύ ωραίοα χρώμα

            , πολύ ωραίο χρώμα

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

 

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

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

Ακολουθεί ένα πρόγραμμα :

/* prog51.c – σύγκριση δύο συμβολοσειρών */

#include <stdio.h>

#include <string.h>

#define ANSWER "Βέροια"

#define MAX 40

main()

{

            char try[MAX];

 

            puts("Ποια είναι η πρωτεύουσα της Ημαθίας;");

            gets(try);

            while ((strcmp(try, ANSWER) != 0))

            {

                        puts("Λάθος. Προσπάθησε ξανά.");

                        gets(try);

            }

            puts("Μπράβο! Σωστή απάντηση!");

}

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

 

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

Η συνάρτηση αυτή αντιγράφει συμβολοσειρές.

Ακολουθεί ένα παράδειγμα :

/* prog52.c – επίδειξη της strcpy() */

#include <stdio.h>

#include <string.h>

#define MOTO "Florina per sempre"

#define SIZE 20

main()

{

            static char *second = MOTO;

            static char first[SIZE] = "Florina";

            puts(second);

            puts(first);

            strcpy(first, second);

            puts(second);

            puts(first);

}

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

            Florina per sempre

            Florina

            Florina per sempre

            Florina per sempre

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

 

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

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

Το πρώτο όρισμα της sprintf() είναι η διεύθυνση της τελικής συμβολοσειράς και τα υπόλοιπα ορίσματα είναι τα ίδια όπως και για την printf() :

            char first = "Florina";

            char second = " per sempre";

            char pina[20];

            sprintf(pina, "%s %s", first, second);

Με τις παραπάνω εντολές αντιγράφεται η συμβολοσειρά "Florina per sempre" στον πίνακα pina.

 

Οι Συναρτήσεις του Αρχείου ctype.h

Το αρχείο επικεφαλίδων ctype.h περιέχει μια οικογένεια από συναρτήσεις χαρακτήρα, που είναι οι εξής :

Όνομα            Αληθής (True) αν το Όρισμα είναι :

isalnum()        Αλφαριθμητικό

isalpha()         Αλφαβητικό

iprgtrl()          Ένας χαρακτήρας ελέγχου, π.χ. Control-B

isdigit()          Ένα ψηφίο

isgraph()         Οποιοσδήποτε εκτυπούμενος μη μηδενικός χαρακτήρας

islower()        Ένα μικρό γράμμα

isprint()          Ένας εκτυπούμενος χαρακτήρας

ispunct()         Ένας χαρακτήρας σημείου στίξης

isspace()         Ένας λευκός χαρακτήρας

isupper()         Ένα κεφαλαίο γράμμα

isxdigit()        Ένας χαρακτήρας σε 16δική μορφή

Υπάρχουν δύο ακόμη συναρτήσεις, η toupper() που μετατρέπει πεζά γράμματα σε κεφαλαία και η tolower() που κάνει το αντίστροφο.

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

/* prog53.c – τροποποίηση μιας συμβολοσειράς */

#include <stdio.h>

#include <string.h>

#include <ctype.h>

#define LIMIT 80

char line[LIMIT];

void modify();

main()

{

            puts("Παρακαλώ, εισάγετε μια γραμμή:");

            gets(line);

            modify(line);

            puts(line);

}

 

void modify(str)

char *str;

{

            while (*str != '\0')

                        {

                        if (isupper(*str))

                                    *str = tolower(*str);

                        else if (islower(*str))

                                    *str = toupper(*str);

                        srt++;

                        }

}

 

Μετατροπή Συμβολοσειράς σε Αριθμό

Για να μετατρέψουμε μια συμβολοσειρά σε αριθμό, μπορούμε να χρησιμοποιήσουμε τη συνάρτηση atoi() που δέχεται μια συμβολοσειρά ως όρισμα και επιστρέφει την αντίστοιχη ακέραια τιμή. Για να δουλέψει αυτή η συνάρτηση πρέπει να συμπεριλάβουμε το αρχείο stdlib.h. 

Αυτό το αρχείο περιλαμβάνει δηλώσεις και για τις συναρτήσεις atof() και atol(), όπου η μεν πρώτη μετατρέπει μια συμβολοσειρά σε μια τιμή τύπου double και η δεύτερη μετατρέπει μια συμβολοσειρά σε μια τιμή τύπου long.

 

back.gif (9867 bytes)

Επιστροφή