ΠΕΡΙΕΧΟΜΕΝΑ

 

i.    ΕΠΙΣΤΡΕΦΟΝΤΑΣ ΑΠΟ ΜΙΑ ΣΥΝΑΡΤΗΣΗ

ii.   ΕΠΙΣΤΡΕΦΟΜΕΝΕΣ ΤΙΜΕΣ

iii. THE SCOPE RULES OF FUNCTIONS

iv.  ΚΑΘΟΡΙΣΤΕΣ ΤΑΞΗΣ ΑΠΟΘΗΚΕΥΣΗΣ

extern

static

static local variables

static global variables

register

v.   ΟΡΙΣΜΑΤΑ ΣΥΝΑΡΤΗΣΕΩΝ

vi.  ΚΛΗΣΗ ΚΑΤΑ ΤΙΜΗ, ΚΛΗΣΗ ΜΕ ΑΝΑΦΟΡΑ

vii. ΔΗΜΙΟΥΡΓΩΝΤΑΣ ΚΛΗΣΗ ΜΕ ΑΝΑΦΟΡΑ

viii. ΚΑΛΩΝΤΑΣ ΣΥΝΑΡΤΗΣΕΙΣ ΜΕ ΠΙΝΑΚΕΣ

ix.  ΟΡΙΣΜΑΤΑ ΣΤΗ ΣΥΝΑΡΤΗΣΗ main (argc, argv)

x.   ΣΥΝΑΡΤΗΣΕΙΣ ΠΟΥ ΕΠΙΣΤΡΕΦΟΥΝ ΜΗ ΑΚΕΡΑΙΕΣ ΤΙΜΕΣ

xi.  ΕΠΙΣΤΡΕΦΟΝΤΑΣ POINTERS

xii. ΠΡΩΤΟΤΥΠΑ ΣΥΝΑΡΤΗΣΕΩΝ

xiii. ΚΛΑΣΣΙΚΗ ΕΝΑΝΤΙΟΝ ΜΟΝΤΕΡΝΑΣ ΔΗΛΩΣΗΣ ΠΑΡΑΜΕΤΡΩΝ

xiv. ΕΠΑΝΑΦΟΡΑ Η ΑΝΑΔΡΟΜΗ

xv.  POINTERS ΣΕ ΣΥΝΑΡΤΗΣΕΙΣ

xvi. ΣΥΜΠΛΗΡΩΜΑΤΙΚΑ ΓΙΑ ΤΙΣ ΣΥΝΑΡΤΗΣΕΙΣ

 

 

 

 

ΚΕΦΑΛΑΙΟ 5ο

 

ΣΥΝΑΡΤΗΣΕΙΣ ΟΡΙΖΟΜΕΝΕΣ ΑΠΟ ΤΟΝ ΧΡΗΣΤΗ 

USER DEFINED FUNCTIONS

 

           

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

 

                          type_specifier  function_name (parameter list)

                          parameter declarations

                          {

                            body of the function

                          }

 

Το type_specifier ορίζει τον τύπο της τιμής, που θα επιστρέφει η συνάρτηση κάνοντας χρήση της δήλωσης return. Μπορεί να είναι κάθε αποδεκτός τύπος. Αν δεν καθορίζεται τύπος, τότε η συνάρτηση θεωρείται εξ’ ορισμού ότι επιστρέφει integer.

 

            Το parameter list είναι μία λίστα μεταβλητών, που χωρίζονται με κόμμα, οι οποίες δέχονται τις τιμές των παραμέτρων κατά την κλήση της συνάρτησης. Μία συνάρτηση θα καλείται χωρίς παραμέτρους, όταν το parameter list είναι άδειο. Πάντως οι παρενθέσεις απαιτούνται ακόμα κι αν δε χρησιμοποιηθούν καθόλου παράμετροι. Το τμήμα parameter declarations χρησιμοποιείται για να ξεκαθαριστεί τι τύπου θα είναι οι παράμετροι στη λίστα.

 

-          H δήλωση return:

 

Έχει δύο σημαντικές χρήσεις:

1)      Προκαλεί εσπευσμένη έξοδο από μία συνάρτηση

2)      Επιστρέφει την τιμή της συνάρτησης

 

i)                    Επιστρέφοντας από μία συνάρτηση

 

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

 

 

Π.χ.:                void pr_reverse(s)

                        char *s;

                        {

                          int t;

                          for ( t=strlen(s) –1 ; t> - 1; t--)

                          printf (“%c“, s[t]);

                        }

 

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

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

 

Π.χ.:                find_substr(s1, s2)

                        Char *s1, *s2;

                        {

                          int t;

                          char *p, *p2;

                          for( t=0; s1[t]; t++)

                          {

                            p = &s1[t];

                            p2=s2;

                            while (*p && *p2 == *p)

                            {

                              p++;

                              p2++;

                            }

                          if(!*p2) return t;

                         }

                        return – 1

                        }

 

            Η παραπάνω συνάρτηση επιστρέφει είτε την αρχική διεύθυνση της πρώτης επανάληψης του s2 μέσα στο s1, είτε – 1 αν δε βρεθεί ομοιότητα.

 

 

ii)                  Επιστρεφόμενες τιμές

 

Όλες οι συναρτήσεις εκτός από αυτές που έχουν δηλωθεί σαν void, επιστρέφουν τιμή. Αυτή η τιμή είτε έχει συνταχθεί μαζί με την return, είτε είναι 0, αν δεν έχει γίνει χρήση της return. Από τη στιγμή πάντως που μία συνάρτηση δεν έχει δηλωθεί σαν void, μπορεί να χρησιμοποιηθεί σαν τελεστέος σε κάθε αποδεκτή C έκφραση. Έτσι κάθε μία από τις παρακάτω εκφράσεις είναι αποδεκτή από τη C:

 

X = power (y) ;

Iif max (x, y) > 100) printf (“greater…”);

Ffor (ch = getchar( ); isdigit (ch);)…

 

Πάντως μία συνάρτηση δε μπορεί να γίνει στόχος μιας εντολής.

 

Π.χ.:     swap (x, y) = 100, είναι λάθος. Η C θα το χαρακτηρίσει σαν σφάλμα και δεν θα μεταφράσει ένα πρόγραμμα, το οποίο περιέχει μία παρόμοια δήλωση.

 

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

 

Ο πρώτος είναι απλά υπολογιστικός. Έχει ειδικά σχεδιαστεί για να εκτελεί μία διαδικασία πάνω στις παραμέτρους της και να επιστρέφει μία τιμή βασισμένη στη διαδικασία αυτή. Αυτή είναι βασικά η «καθαρή» μορφή συνάρτησης. Παραδείγματα τέτοιων συναρτήσεων είναι η spr( ) και sin( ), κλπ.

 

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

 

Ο τελευταίος τύπος δεν επιστρέφει τιμή. Στην περίπτωση αυτή η συνάρτηση είναι κυρίως διαδικαστική και δεν παράγει τιμή. Ένα τέτοιο παράδειγμα είναι η   srand( ), η οποία χρησιμοποιείται για να αρχικοποιεί τη γεννήτρια ψευδοτυχαίων αριθμών της rad( ).

 

Πάντως για κάποιους «σκοτεινούς» λόγους οι συναρτήσεις οι οποίες δεν παράγουν ενδιαφέροντα αποτελέσματα, πάντα κάτι επιστρέφουν (άσχετα αν αυτό είναι αδιάφορο σε εμάς). Για παράδειγμα, η printf( ) επιστρέφει τον αριθμό των χαρακτήρων που τυπώθηκαν αλλά αυτό είναι άχρηστο σε εμάς. Όπως και να έχει όλες οι συναρτήσεις (εκτός των void), κάτι επιστρέφουν έστω κι αν εμείς δε θέλουμε να κάνουμε τίποτα με αυτό. Η ερώτηση που συνήθως γίνεται εδώ είναι: «Δεν πρέπει να κάνουμε κάτι με την τιμή που επεστράφη από τη συνάρτηση;»  Η απάντηση είναι όχι. Αν δεν έχει καθοριστεί μία εντολή, η οποία να καταχωρεί την επιστρεφόμενη από τη συνάρτηση τιμή σε μία μεταβλητή, αυτή η τιμή αγνοείται και εξαφανίζεται.

 

Π.χ.:                #include <stdio.h>

                        main( )

                        {

                          int x, y, z;

                          x = 10 ;

                          y = 20 ;

                          z = mul (x,y) ;                                    / * 1 * /

                          printf( “%d”, mul(x,y)) ;                      / * 2 * /

                          mul (x,y) ;                                          / * 3 * /

                        }

                        mul (a,b)

                        int a,b;

                        {

                          return a * b;

                        }

 

Στο παράδειγμα αυτό έχει οριστεί και χρησιμοποιείται η συνάρτηση mul( ).

 

Στην πρώτη / * 1 * / γραμμή υπάρχει μία εντολή, η οποία υποχρεώνει τη συνάρτηση να καταχωρήσει την επιστρεφόμενη τιμή της στη μεταβλητή z.

 

Στη δεύτερη / * 2 * / γραμμή στην πραγματικότητα η επιστρεφόμενη τιμή δεν καταχωρείται πουθενά, αλλά χρησιμοποιείται από τη συνάρτηση printf( ) και εμφανίζεται στην οθόνη.

 

Τέλος στην τρίτη / * 3 * /  γραμμή η επιστρεφόμενη τιμή χάνεται, γιατί ούτε καταχωρείται σε μεταβλητή, ούτε χρησιμοποιείται σα μέρος κάποιας έκφρασης.

 

 

iii)                The Scope Rules of Functions

 

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

 

Κάθε συνάρτηση στη C αποτελεί ξεχωριστό τμήμα του κώδικα. Ο κώδικας μίας συνάρτησης εξαρτάται από αυτή και δε μπορεί να προσπελαστεί από άλλη δήλωση σε άλλη συνάρτηση, εκτός φυσικά κατά τη διάρκεια της κλήσης της συνάρτησης (για παράδειγμα, δεν είναι δυνατό να χρησιμοποιήσουμε τη goto( ), για να μεταβούμε στη μέση άλλης συνάρτησης). Ο κώδικας, που αποτελεί το σώμα μίας συνάρτησης, είναι κρυμμένος από το υπόλοιπο πρόγραμμα που, εκτός αν χρησιμοποιεί global μεταβλητές, μπορεί να επηρεάσει αλλά όχι να επηρεαστεί από άλλα τμήματα του προγράμματος. Με άλλα λόγια, ο κώδικας και τα δεδομένα, που έχουν δηλωθεί μέσα σε μία συνάρτηση, δε μπορούν να αλληλεπιδράσουν με τον κώδικα ή τα δεδομένα άλλης συνάρτησης και αυτό γιατί οι δύο συναρτήσεις έχουν διαφορετικό σκοπό (scope).

 

                        Οι μεταβλητές, που έχουν δηλωθεί μέσα σε μία συνάρτηση, καλούνται local variables. Μία τοπική μεταβλητή αρχίζει να υπάρχει όταν η ροή μπαίνει στη συνάρτηση και καταστρέφεται όταν η ροή φεύγει από τη συνάρτηση. Έτσι οι τοπικές μεταβλητές δε μπορούν να συγκρατήσουν τα περιεχόμενά τους μεταξύ των κλήσεων. Ο κανόνας αυτός παύει να ισχύει μόνο όταν η μεταβλητή έχει δηλωθεί σαν static. Έτσι ο compiler λαμβάνει τη μεταβλητή σαν global για γενικούς λόγους αποθήκευσης, αλλά και πάλι περιορίζει το σκοπό της μέσα στη συνάρτηση.

 

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

 

 

iv)               Καθοριστές Τάξης Αποθήκευσης (Storage Class Specifiers)

 

Η Turbo C υποστηρίζει τέσσερις καθοριστές τάξης αποθήκευσης. Αυτοί είναι οι εξής:

 

                        1. extern

                        2. static

                        3. register

                        4. auto

 

            Χρησιμοποιούνται για να δείξουν στον compiler με ποιο τρόπο θα αποθηκευτεί η μεταβλητή η οποία ακολουθεί. Οι καθοριστές αυτοί προηγούνται των υπολοίπων στη δήλωση των μεταβλητών.

 

            Η γενική τους μορφή είναι:

 

storage_specifier type var_name;

 

1.      extern

 

Επειδή η C επιτρέπει την ξεχωριστή μετάφραση τμημάτων ενός μεγάλου προγράμματος και τη σύνδεσή τους έπειτα (κατά το linking), επιτυγχάνοντας έτσι την πιο γρήγορη μετάφραση και τον καλύτερο έλεγχο του προγράμματος, πρέπει να υπάρχει ένας τρόπος να πούμε σε όλα αυτά τα ξεχωριστά προγράμματα για τις global μεταβλητές που θα χρησιμοποιηθούν. Υπενθυμίζουμε ότι μπορούμε να δηλώσουμε μία global variable μόνο μία φορά. Αν προσπαθήσουμε να δηλώσουμε δύο φορές μία συνολική μεταβλητή με το ίδιο όνομα, τότε η C θα «χτυπήσει» λάθος. Το ίδιο μήνυμα λάθους θα έχουμε κι αν προσπαθήσουμε να δηλώσουμε όλες τις συνολικές μεταβλητές σε κάθε υποπρόγραμμα ξεχωριστά. Φυσικά δε θα υπάρξει σφάλμα κατά τη διάρκεια του compilation, αφού τα modules θα μεταφραστούν ξεχωριστά. Το πρόβλημα θα παρουσιαστεί κατά τη διάρκεια του linking. Ο Linker (συνδέτης) θα εμφανίσει ένα μήνυμα λάθους γιατί δε θα ξέρει ποιες μεταβλητές να χρησιμοποιήσει.

 

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

 

 

Π.χ.:              Πρόγραμμα 1

 

                     int x, y;

                     char ch ;

                     main( )

                     {

                     ...

                     }

                     func1( )

                     {

                        x = 123 ;

                     }

 

Πρόγραμμα 2

 

extern int x,y ;

extern char ch ;

func22( )

{

x = y / 10;

}

func23( )

{

   y = 10 ;

}

 

 

 

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

 

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

 

Π.χ.:    int first, last;

            main( )

            {

            extern int first;  / * optional use for extern declaration * /

            ……

            }

 

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

 

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

 

2.      static

 

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

 

            Υπενθυμίζουμε ότι οι συναρτήσεις βιβλιοθήκης δε διαφέρουν σε τίποτα από τις οριζόμενες από τον χρήστη, μιας και αυτές είναι user-defined functions, οι οποίες δημιουργήθηκαν από τους προγραμματιστές της Borland. Επειδή η αντίδραση των static local μεταβλητών είναι διαφορετική από την αντίδραση των static global, θα τις εξετάσουμε ξεχωριστά.

 

α. static local variables

 

Όταν ο τροποποιητής static προστίθεται στη δήλωση μιας local μεταβλητής, αυτό προκαλεί τον compiler να δημιουργήσει μόνιμο χώρο αποθήκευσης γι’ αυτή τη μεταβλητή με τον ίδιο τρόπο που θα δέσμευε μνήμη για μία global μεταβλητή.

 

Η βασική διαφορά μεταξύ μιας static local και μιας global μεταβλητής είναι ότι η static local παραμένει γνωστή μόνο μέσα στο block που έχει δηλωθεί. Με απλά λόγια μια static local είναι μία local που διατηρεί τις τιμές της μεταξύ των διαφόρων κλήσεων του κώδικα.

 

Είναι πολύ σημαντικό στη δημιουργία αυτόνομων συναρτήσεων, ότι η C επιτρέπει τη χρήση static local μεταβλητών, γιατί υπάρχουν πολλές περιπτώσεις όπου μία μεταβλητή πρέπει να διατηρεί τις τιμές της κατά την κλήση ρουτινών ή συναρτήσεων. Αν δεν υπήρχαν οι static local μεταβλητές, θα έπρεπε να χρησιμοποιούνται global, οι οποίες θα άνοιγαν το δρόμο σε πιθανά «περίεργα» αποτελέσματα. Ένα παράδειγμα χρήσης static local variables αποτελεί το παρακάτω:

 

main( )

            {

              do {

                     count (0);

              }  while ( !kbhit( ) );

              printf (“count called %d times”, count ( 1));

            }

            count(i)

            int i;

            {

              static int c=0;

              if (i) return c;

              else c++;

              return 0;

            }

 

 

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

Στο παραπάνω παράδειγμα, η count( ) κλήθηκε με παράμετρο 0, με αποτέλεσμα να αυξηθεί η τιμή της C κατά ένα (C++). Αν η count( ) κληθεί με άλλο όρισμα, επιστρέφει τον αριθμό των φορών, που η συνάρτηση κλήθηκε. Η μέτρηση των φορών που μία συνάρτηση κλήθηκε μπορεί να φανεί χρήσιμη κατά τη διάρκεια της ανάπτυξης της εφαρμογής.

 

Ένα άλλο καλό παράδειγμα συνάρτησης, η οποία χρειάζεται τη χρήση static local μεταβλητών είναι μία γεννήτρια σειράς αριθμών, η οποία παράγει ένα νέο αριθμό βασισμένο στον προηγούμενο. Είναι πιθανό να δηλώσουμε μία global μεταβλητή γι’ αυτό το σκοπό. Πάντως, κάθε φορά που θα καλείται η συνάρτηση, θα πρέπει να θυμόμαστε να δηλώνουμε ξανά τη μεταβλητή, αλλά και να σιγουρευτούμε ότι η μεταβλητή αυτή δε θα μπερδευτεί με άλλη global μεταβλητή. Χρησιμοποιώντας επίσης μία global μεταβλητή είναι δύσκολο να χρησιμοποιήσουμε τη συνάρτηση μέσα σε function library.

 

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

 

                               series( )

                               {

                                 static int series_num;

                                 series_num = series_num+23;

                                 return(series_num);

                               }

 

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

           

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

 

β. Static global variables

 

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

 

Για να δούμε πως μπορούν να χρησιμοποιηθούν οι static global variables, η γεννήτρια σειρών από το προηγούμενο παράδειγμα, επανακωδικοποιείται, ώστε να χρησιμοποιείται μία τιμή, για να αρχικοποιεί τη σειρά έως την κλήση μιας άλλης συνάρτησης, που λέγεται series_start( ). Το κυρίως πρόγραμμα περιλαμβάνει τις συναρτήσεις series( ), series_start( ) και series_num( ):

 

                               static int series_num;

                               series( )

                               {

                                 series_num = series_num + 23;

                                 return (series_num);

                               }

                               void series_start(seed)

                               int seed;

                               {

                               series_num = seed;

                               }

 

 

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

 

Τα ονόματα των local static μεταβλητών είναι γνωστά μόνο μέσα στη συνάρτηση ή το μπλοκ όπου έχουν δηλωθεί, ενώ οι static global μεταβλητές είναι γνωστές μόνο στο πρόγραμμα που τις χρησιμοποιεί. Αυτό σημαίνει ότι αν τοποθετήσουμε τις συναρτήσεις series( ) και series_start( ) σε χωριστά προγράμματα θα μπορούμε να χρησιμοποιήσουμε τις συναρτήσεις αλλά δε θα μπορούμε να αναφερθούμε στη μεταβλητή series_num( ). Είναι κρυμμένη από τον υπόλοιπο κώδικα του προγράμματος. Στην πραγματικότητα θα πρέπει (για να δουλέψει το πρόγραμμα) να δηλώσουμε άλλη μία μεταβλητή series_num (στο άλλο πρόγραμμα φυσικά). Αντικειμενικά η χρήση του τροποποιητή static γίνεται για να επιτρέπει σε μεταβλητές να εξακολουθούν να υπάρχουν και να είναι γνωστές μέσα σε συγκεκριμένα τμήματα κώδικα, χωρίς να επηρεάζουν το υπόλοιπο πρόγραμμα.

 

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

 

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

 

3.      register

 

H Turbo C διαθέτει ακόμη ένα καθοριστή αποθήκευσης ο οποίος χρησιμοποιείται μόνο με μεταβλητές τύπου int και char. Ο καθοριστής register απαιτεί από την C να συγκρατεί τις τιμές των μεταβλητών, που δηλώθηκαν μαζί του στο τμήμα της CPU και όχι στη μνήμη, που καταχωρούνται οι άλλες μεταβλητές. Αυτό σημαίνει, ότι οι διαδικασίες μπορούν να εκτελεστούν πολύ πιο γρήγορα με τις register μεταβλητές παρά με άλλες, που έχουν αποθηκευτεί στη μνήμη και αυτό γιατί στην πραγματικότητα οι τιμές τους βρίσκονται καταχωρημένες στη CPU και δεν απαιτούν προσπέλαση της μνήμης για την τροποποίηση ή ανάγνωσή τους. Αυτό κάνει τις register μεταβλητές ιδανικές για έλεγχο βρόγχων.

 

Ο καθοριστής register μπορεί να συνταχθεί μόνο με local μεταβλητές και formal parameters συναρτήσεων, οι οποίες είναι εξ’ ορισμού auto μεταβλητές.

 

Π.χ.:                     int_pwr(m,e)

                            int m;

                            register int e;

                            {

                               register int temp;

                               temp = 1;

                               for(;e;e--) temp * = m;

                               return temp;

                            }

 

Εδώ χρησιμοποιούμε μία register μεταβλητή για τον έλεγχο ενός loop. Η συνάρτηση αυτή υπολογίζει τη δύναμη m στην e.

 

Βλέπουμε, ότι το e και το temp έχουν δηλωθεί σαν register μεταβλητές, επειδή και οι δύο χρησιμοποιούνται μέσα στο loop. Στη γενική πρακτική μεταβλητές αυτού του είδους χρησιμοποιούνται σε σημεία, όπου πιστεύεται, ότι θα κάνουν το μεγαλύτερο καλό, όπως σε περιπτώσεις, όπου απαιτούνται πολλές αναφορές στην ίδια μεταβλητή. Αυτό είναι σημαντικό γιατί το πλήθος των register μεταβλητών που θα χρησιμοποιούνται σε μία χρονική στιγμή είναι περιορισμένο. Η Turbo C επιτρέπει τη χρήση δύο register μεταβλητών την ίδια χρονική στιγμή. Φυσικά δεν πρέπει να ανησυχούμε για τις δηλώσεις των μεταβλητών αυτών, αφού όταν το όριο ανιχνευθεί από τον compiler, αυτός μετατρέπει όλες τις register variables σε non-register (αυτό γίνεται κυρίως για να ενισχυθεί η μεταφερσιμότητα του κώδικα της C από μηχάνημα σε μηχάνημα).

 

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

 

 

v)                 Ορίσματα Συναρτήσεων

 

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

 

Π.χ.:                     is_in( s, c)

                            char *s;

                            char c;

                            {

                               while (*s)

                                 if (*s==c) return 1;

                                 else s++;

                               return 0;

                            }

 

Η συνάρτηση is_in( ) έχει δύο παραμέτρους, την s και τη c. Η συνάρτηση αυτή επιστρέφει 1, αν ο χαρακτήρας c αποτελεί μέρος του string, αλλιώς επιστρέφει 0.

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

 

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

 

            Όπως με τις local μεταβλητές, μπορούμε να «στήσουμε» εντολές χρησιμοποιώντας τυπικές παραμέτρους συναρτήσεων ή να τις χρησιμοποιούμε σε οποιαδήποτε αποδεκτή C έκφραση.

 

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

 

 

vi)               Call by Value, Call by Reference

 

            Σε γενικές υπορουτίνες μπορούμε να περάσουμε ορίσματα με δύο τρόπους.

 

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

 

            Ο δεύτερος τρόπος λέγεται κλήση με αναφορά (call by reference). Σ’ αυτή τη μέθοδο η διεύθυνση ενός ορίσματος αντιγράφεται στην παράμετρο. Μέσα στην υπορουτίνα η διεύθυνση χρησιμοποιείται για την προσπέλαση των ενεργών ορισμάτων, που χρησιμοποιήθηκαν στην κλήση. Αυτό σημαίνει ότι οι αλλαγές στην παράμετρο θα επηρεάσουν τη μεταβλητή, που χρησιμοποιείται στην κλήση της ρουτίνας.

 

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

 

Π.χ.:                     main( )

                            {

                               int t = 10;

                               printf( “%d %d”, sqr(x), t);

                            }

                            sqr(x)

                            int x;

                            {

                               x = x * x

                               return (x);

                            }

 

Στο παραπάνω παράδειγμα η τιμή του ορίσματος στη συνάρτηση sqrt( ), 10, αντιγράφεται στην παράμετρο x. Όταν η εντολή x = x * x εκτελείται, το μόνο που αλλάζει είναι η τοπική μεταβλητή x. Η μεταβλητή t, η οποία χρησιμοποιείται για την κλήση της sqr( ), εξακολουθεί να είναι 10. Έτσι το αποτέλεσμα θα είναι 100 10.

 

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

 

 

vii)             Δημιουργώντας μία κλήση με αναφορά

 

Ακόμα κι όταν η διαδικασία περάσματος παραμέτρων γίνεται με κλήση κατά τιμή, είναι δυνατό να δημιουργήσουμε ένα call by reference περνώντας έναν pointer στο όρισμα. Αφού αυτό περνά τη διεύθυνση του ορίσματος στη συνάρτηση, είναι δυνατό να αλλάξουμε την τιμή του ορίσματος έξω από τη συνάρτηση.

 

Οι pointers περνώνται στις συναρτήσεις απλά όπως όλες οι άλλες τιμές. Φυσικά είναι απαραίτητο οι παράμετροι να έχουν δηλωθεί σαν τύποι pointer.

 

Π.χ.: η συνάρτηση swap( ), η οποία ανταλλάσσει τα περιεχόμενα δύο μεταβλητών όπως παρακάτω:

 

                   void swap( x,y)

                   int *x, *y;

                   {

                     int temp ;

                     temp = *x ;

                     *x = *y ;

                     *y = temp ;

                   }

 

Ο τελεστής * (at address) χρησιμοποιείται για να προσπελάσουμε τη μεταβλητή, που δείχνεται από τον pointer αυτόν. Έτσι τα περιεχόμενα των μεταβλητών, που χρησιμοποιούνται για την κλήση της συνάρτησης, ανταλλάχθηκαν.

 

Είναι σημαντικό να θυμόμαστε ότι η swap( ) ή κάθε άλλη συνάρτηση που χρησιμοποιεί pointer παραμέτρους, πρέπει να καλείται με τις διευθύνσεις των ορισμάτων της. Έτσι ο σωστός τρόπος κλήσης της συνάρτησης είναι ο εξής:

 

Π.χ.:              void swap( );

                     main( );

                     {

                        int x, y;

                        x =10 ;

                        y = 20 ;

                        swap (&x, &y) ;

                     }

 

Σ’ αυτό το παράδειγμα καταχωρείται η τιμή 10 στη μεταβλητή x, ενώ η τιμή 20 στη μεταβλητή y. Η swap( ) καλείται με τις διευθύνσεις του x και του y. Ο μονοσήμαντος τελεστής & χρησιμοποιείται για να παράγει τις διευθύνσεις των μεταβλητών x και y και όχι τις τιμές τους. Έτσι στη συνάρτηση περνώνται οι διευθύνσεις και όχι οι τιμές των ορισμάτων x και y.

 

 

viii)           Καλώντας συναρτήσεις με πίνακες

 

Η διαδικασία περάσματος πινάκων σαν ορίσματα συναρτήσεων αποτελεί εξαίρεση στη standard μέθοδο κλήσης με τη χρήση call-by-value.

 

Όταν ένας πίνακας χρησιμοποιείται σαν όρισμα σε συνάρτηση, μόνο η διεύθυνση μνήμης του πίνακα περνά και όχι ένα αντίγραφο ολόκληρου του πίνακα. Όταν καλούμε μία συνάρτηση με το όνομα ενός πίνακα, ένας pointer που δείχνει στο πρώτο στοιχείο του πίνακα, περνά στη συνάρτηση (υπενθυμίζεται ότι στη C ένα όνομα πίνακα χωρίς δείκτες είναι pointer στο πρώτο στοιχείο του πίνακα). Η δήλωση των παραμέτρων θα πρέπει να είναι pointers του ίδιου τύπου. Υπάρχουν τρεις τρόποι, για να δηλώσουμε μία παράμετρο, η οποία πρόκειται να δεχτεί έναν array pointer.

 

Ο πρώτος είναι να τη δηλώσουμε σαν πίνακα.

 

Π.χ.:              void display( );

                     main( )

                     {

                        int t[10], i;

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

                                                t[i] = i;

                        display(t);

                     }

                     void display(num)

                     int num[10];

                     {

                        int i;

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

                                                printf(“%d”, num[i]);

                     }

 

Παρ’ όλο που η παράμετρος num έχει δηλωθεί σαν πίνακας ακεραίων 10 στοιχείων, η C αυτομάτως τη μετατρέπει σε έναν integer pointer, επειδή στην ουσία καμία παράμετρος δε μπορεί να δεχτεί έναν ολόκληρο πίνακα. Μόνο ένας pointer στο πίνακα θα περαστεί στη συνάρτηση, έτσι μία pointer παράμετρος θα πρέπει να βρίσκεται εκεί για να τον δεχτεί.

 

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

 

Π.χ.:                void display(num)

                        int num[ ] ;

                        {

                          int i;

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

                            printf(“%d”, num[i]);

                     }

 

Όπου το num είναι ένας πίνακας ακεραίων αγνώστων διαστάσεων. Εφόσον η C δεν παρέχει έλεγχο διαστάσεων στους πίνακες, η πραγματική χωρητικότητα του πίνακα επαφίεται στην παράμετρο (αλλά όχι φυσικά στο πρόγραμμα). Παρ’ όλ’ αυτά η παρούσα μέθοδος δήλωσης στην πραγματικότητα δηλώνει το num σαν ακέραιο pointer.

 

Ο τρίτος τρόπος (και ο πιο κοινός στον προγραμματισμό με Turbo C) είναι ένας pointer όπως εδώ:

 

                        void display(num)

                        int *num;

                        {

                          int i;

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

                            printf(“%d”, num[i]);

                     }

 

Αυτό επιτρέπεται γιατί όλοι οι pointers μπορούν να χρησιμοποιήσουν [ ]  σα να ήταν πίνακες (στην πραγματικότητα οι pointers και οι πίνακες συνδέονται στενά).

 

Και οι τρεις παραπάνω τρόποι δήλωσης πίνακα σαν παράμετρο επιστρέφουν το ίδιο αποτέλεσμα: έναν pointer.

 

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

 

Π.χ.:                void display( )

                        main( )

                        {

                          int t[10], i;

                          for (i = 0; i<10; ++i)   t[i] = i;

                          for (i = 0; i<10; ++i)   display (t[i]);

                     }

                     void display( num)

                     int num;

                     {

                          printf(“%d”, num);

                     }

 

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

 

Είναι σημαντικό να κατανοηθεί ότι, όταν ένας πίνακας χρησιμοποιείται σαν όρισμα συνάρτησης, η διεύθυνσή του περνά στη συνάρτηση. Αυτό αποτελεί εξαίρεση στην κλασσική call-by-value μέθοδο περάσματος παραμέτρων, που χρησιμοποιεί η C. Αυτό σημαίνει ότι ο κώδικας μέσα στη συνάρτηση μπορεί να διαχειρίζεται, αλλά και να αλλάζει τα περιεχόμενα του πίνακα, που χρησιμοποιείται κατά την κλήση της συνάρτησης.

 

Π.χ.:                void print_upper( );

                        main( )

                        {

                          char s[80];

                          gets(s);

                          print_upper(s);         

                        }

                        void print_upper(string)

                        char *string;

                        {

                          register int t;

                          for( t=0; string[t]; ++t)    {

                                    string [t] = toupper(string [t]);

                                    printf(“%c“, string[t]);

                          }        

                        }

 

            Η συνάρτηση print_upper( ) εμφανίζει το όρισμά της (που είναι ένα string) στα κεφαλαία. Μετά την κλήση της συνάρτησης αυτής τα περιεχόμενα του πίνακα s έχουν αλλάξει σε κεφαλαία. Βλέπουμε έτσι, ότι η συνάρτηση μπορεί να διαχειριστεί, αλλά και να αλλάξει τα περιεχόμενα των ορισμάτων που την καλούν. Αν όμως δε θέλαμε να συμβεί αυτό θα γράφαμε:

 

Π.χ.:                void print_upper( );

                        main( )

                        {

                          char s[80];

                          gets(s);

                          print_upper(s);         

                        }

                        void print_upper(string)

                        char *string;

                        {

                          register int t;

                          for( t=0; string[t]; ++t)    {

                                    string [t] = toupper(string [t]);

                                    printf(“%c“, toupper(string[t] ) );

                          }        

                        }

 

Σ’ αυτή την έκδοση του προγράμματος τα περιεχόμενα του πίνακα s παραμένουν άθικτα, επειδή οι τιμές του δεν αλλάζουν.

 

Ένα κλασικό παράδειγμα περάσματος πινάκων σε συναρτήσεις βρίσκεται στη standard βιβλιοθήκη στη συνάρτηση gets( ). Παρ’ όλο που οι βιβλιοθήκες της C είναι πολύ πιο σύνθετες, η παρακάτω συνάρτηση δίνει μία ιδέα για το πώς δουλεύει η gets( ). Για να αποφύγουμε τη σύγχυση με τις συναρτήσεις βιβλιοθήκης, θα ονομάσουμε τη συνάρτησή μας xgets( ).

 

 

Π.χ.:            void xgets(s)

                   char *s;

                   {

                     char ch;

                     int t;

                     for ( t = 0; t < 80; ++t)

                     {

                        ch = getche( );

                        switch(ch)    {

                               case ‘ \r ’:

                                    s[t] = ‘ \ 0 ‘;       / * NULL TERMINATOR * /

                                    return;

                               case ‘ \ b ‘ :

                                    if (t>0)   t--;

                                    break;

                               default:

                                    s [t] = ch;

                        }

                     }

                     s [80] = ‘ \ 0 ‘ ;

                   }

 

            Η συνάρτηση gets( ) καλείται με έναν character pointer, όπου μπορεί να είναι είτε μία μεταβλητή που έχει δηλωθεί να είναι pointer σε χαρακτήρα, είτε το όνομα ενός πίνακα χαρακτήρων, το οποίο εξ’ ορισμού είναι ένας character pointer. Κατά την είσοδο στην xgets( ) η συνάρτηση ορίζει έναν βρόγχο από 0 – 80. Αν πληκτρολογηθούν πάνω από 80 χαρακτήρες, η συνάρτηση επιστρέφει. Επειδή η C δεν προσφέρει boundary checking, πρέπει να έχουμε σιγουρέψει από πριν ότι η μεταβλητή που θα χρησιμοποιηθεί στην κλήση της xgets( ) θα μπορεί να δεχτεί 80 χαρακτήρες. Καθώς πληκτρολογούμε χαρακτήρες στο πληκτρολόγιο αυτοί καταχωρούνται στο string. Αν πατήσουμε backspace ( Ñ ), ο μετρητής t μειώνεται κατά 1. όταν πληκτρολογηθεί return ( ), το null τοποθετείται στο τέλος του string, οριοθετώντας το τέλος του (κάθε string τερματίζεται με null). Επειδή ο πίνακας, που χρησιμοποιήθηκε στην κλήση της xgets( ), έχει ήδη τροποποιηθεί περιέχει τους χαρακτήρες που πληκτρολογήθηκαν.

 

 

ix)                Ορίσματα στη συνάρτηση main( ) – argc και argv

 

            Πολλές φορές είναι χρήσιμο να περνάμε πληροφορίες στο πρόγραμμά μας κατά τη διάρκεια της εκτέλεσής του. Η γενική μέθοδος είναι να περνάμε πληροφορίες στη συνάρτηση main( ) χρησιμοποιώντας command-line arguments. Ένα command-line argument είναι οι πληροφορίες, που ακολουθούν το όνομα του προγράμματος στη γραμμή εντολών του λειτουργικού συστήματος.

 

Π.χ.:     Όταν μεταφράζουμε προγράμματα χρησιμοποιώντας τον compiler της Turbo C πληκτρολογούμε:

 

C:\ > tcc program_name

 

 όπου program_name είναι το όνομα του προγράμματος που θέλουμε να μεταφράσουμε. Το όνομα του προγράμματος περνά σαν όρισμα στο πρόγραμμα TCC.

 

            Δύο σημαντικά ενσωματωμένα ορίσματα, τα argv και argc, χρησιμοποιούνται για να δεχτούν command-line arguments. Είναι και τα μοναδικά ορίσματα που μπορεί να λάβει η main( ).

            Η παράμετρος argc κρατά τον αριθμό των ορισμάτων στη γραμμή εντολών και είναι ακέραια. Είναι πάντα τουλάχιστον 1, γιατί το όνομα του προγράμματος καθορίζεται σαν το 1ο όρισμα. Η παράμετρος argv είναι ένας pointer σε έναν πίνακα από character pointers. Κάθε στοιχείο στον πίνακα αυτό δείχνει σε ένα command-line argument. Όλα τα command-line arguments είναι strings και όλοι οι αριθμοί πρέπει να μεταφράζονται από το πρόγραμμα στο ανάλογο format.

 

Π.χ.:         #include <stdio.h>

                 main(argc, argv)

                 int argc;

                 char *argv[ ] ;

                 {

                   if (argc!=2)

                   {

                     printf(“Ξέχασες να γράψεις το όνομά σου \ n “);

                     exit (0);

                     }

                     printf(“Hello %s”, argv[1]);

                 }

 

            Το πρόγραμμα αυτό εμφανίζει Hello ακολουθούμενο από ένα όνομα, αν αυτό έχει πληκτρολογηθεί στη γραμμή εντολών του DOS κατά την κλήση του προγράμματος. Για παράδειγμα, αν το πρόγραμμα έχει ονομαστεί name.exe κι εμείς γράψουμε:                                    

C : \ > name Tom

 

… το πρόγραμμα θα εμφανίσει:         Hello Tom.

 

Αν δε γράψουμε όνομα, τότε το argc έχει τιμή 1 και εμφανίζεται το ανάλογο μήνυμα. Όπως βλέπουμε τυπώνουμε το argv[1], το οποίο είναι το δεύτερο στοιχείο του πίνακα (οι πίνακες στη C αρχίζουν από το 0).

 

            Σημειώνεται, ότι τα command-line arguments πρέπει να διαχωρίζονται από κενό ή Tab. Κόμματα, τελείες, μπάρες, κλπ, δεν είναι αποδεκτοί διαχωριστές και δε θα ληφθούν σαν μέρη του string.

 

            Είναι σημαντικός ο τρόπος με τον οποίο θα δηλωθεί το argv. Η πιο κοινή μέθοδος είναι:

char *argv[ ];

 

            Οι άδειες αγκύλες καθορίζουν ότι πρόκειται για string μη καθορισμένου μεγέθους. Από εδώ και πέρα μπορούμε να προσπελάσουμε τα ορίσματα θέτοντας δείκτες στον argv.

 

Π.χ.:     argv[0] δείχνει στο πρώτο string, ενώ argv[1] στο δεύτερο, κλπ. Υπενθυμίζεται ότι το πρώτο string που δείχνεται από το argv[0], περιλαμβάνει το όνομα του προγράμματος.

           

            Ένα σύντομο παράδειγμα κλήσης ορισμάτων γραμμής εντολών (command-line arguments) είναι το παρακάτω πρόγραμμα. Μετρά ανάποδα από μία καθορισμένη τιμή ως το 0 και κάνει beep, όταν τελειώνει.

 

Π.χ.:            main( argc, argv)

                   int argc;

                   char *argv[ ];

                   {

                     int disp, count;

                     if (argc<2)    {

                          printf(“You must enter the length of the count\n”);

                          printf(“On the command line. Try again.\n”);

                          exit(0);

                     }   

                     if (argc == 3 && !strcmp (argv[2], “display”) ) disp = 1;

                     else disp = 0;

                     for (count = atoi (argv[1]); count; --count)

                          if(disp) printf(“%d”, count);

                     printf(“%c”, 7);                / * Bell - - ASCII 7 * /

                   }

 

 

            Παρατηρούμε ότι τα περιεχόμενα του argv[ ] μετατρέπονται σε ακέραιο (από αλφαριθμητικό) μέσω την συνάρτησης atoi( ). Επίσης υπάρχει ακόμη μία παράμετρος, η “display”, η οποία όταν χρησιμοποιηθεί κάνει τα δεδομένα (το μέτρημα) να εμφανίζονται στην οθόνη. Τέλος για τη δημιουργία του beep χρησιμοποιείται ο ASCII χαρακτήρας #7.

 

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

 

            Για να προσπελάσουμε ένα συγκεκριμένο χαρακτήρα σε ένα από τα command strings, πρέπει να προσθέσουμε και δεύτερο δείκτη στο argv (να το δούμε σαν δισδιάστατο πίνακα).

 

Π.χ.:              main( argc, argv)

                     int argc;

                     char * argv[ ] ;

                     {

                        int t, i;

                        for (t= 0 ; t<argc ; ++t)               {

                                 i=0 ;

                                 while(argv[t][i]   {

                                                printf(“%c”, argv[t] [i]);

                                                ++ii;

                                 }   / * Ends while  * /

                        }           /  * Ends for  * /

                     }              / * Ends main  * /

 

            Το παραπάνω πρόγραμμα εμφανίζει τα ορίσματα με τα οποία κλήθηκε το πρόγραμμα ένα-ένα κατά χαρακτήρα. Ο πρώτος δείκτης t διαβάζει ένα-ένα τα ορίσματα (strings), ενώ ο δεύτερος i, τους χαρακτήρες μέσα σε αυτά. Θεωρητικά μπορούμε να έχουμε 32767 ορίσματα, αλλά τα περισσότερα OS επιτρέπουν μόνο λίγα.

 

 

x)                  Συναρτήσεις, που επιστρέφουν μη ακέραιες τιμές

 

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

 

Α. Η συνάρτηση πρέπει να λάβει έναν ακριβή καθοριστή τύπου.

Β. Ο τύπος της συνάρτησης πρέπει να αναφερθεί πριν την πρώτη κλήση της.

 

            Μόνο μ’ αυτόν τον τρόπο μπορεί η C να δημιουργήσει σωστό κώδικα για συναρτήσεις, που επιστρέφουν non-integer τιμές.

 

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

 

                        type_specifier function_name (parameter list)

                        parameter declarations;

                        {

                          body of function statements;

                        }

 

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

 

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

 

Π.χ.:                float sum( );

                        main( )

                        {

                          float first, second;

                          first = 123.23;

                          second = 99.09;

                          printf(“%f”, sum(first, second));

                        }

                        float sum(a,b)

                        float a,b;

                        {

                        return a+b;

                        }

 

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

 

            Η πρόταση της δήλωσης τύπου της συνάρτησης έχει της εξής μορφή:

 

type_specifier function_name;

 

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

 

            Αν δεν προηγηθεί η δήλωση της συνάρτησης με τον τύπο της, τότε θα προκύψει συμφωνία των δεδομένων, που παράγει με αυτά, που ο compiler της C περιμένει (o compiler θα περιμένει int, ενώ η συνάρτηση θα επιστρέψει άλλο τύπο). Αυτή η ασυμφωνία παράγει παράξενα και μη αναμενόμενα αποτελέσματα. Αν οι δύο συναρτήσεις είναι στο ίδιο αρχείο, η Turbo C θα βρει το σφάλμα και δε θα παράγει τον αντικειμενικό κώδικα. Αν όμως βρίσκονται σε ξεχωριστά αρχεία, ο compiler δε θα καταφέρει να βρει το σφάλμα. Σημειώνεται ότι δε γίνεται έλεγχος κατά τη διάρκεια της εκτέλεσης (run time). Επομένως ο προγραμματιστής πρέπει να είναι ιδιαίτερα προσεκτικός σε τέτοιες περιπτώσεις.

 

            Όταν ένας χαρακτήρας επιστρέφεται από μία συνάρτηση δηλωμένη σαν int, αυτός μετατρέπεται αυτομάτως στης ακέραια τιμή του. Επειδή η C διαχειρίζεται τη μετατροπή από χαρακτήρες σε αριθμούς και ανάποδα αρκετά καθαρά, μία συνάρτηση, που επιστρέφει χαρακτήρα, συχνά δε δηλώνεται έτσι αλλά χρησιμοποιεί την εξ’ ορισμού δήλωση (επιστρέφοντας int).

 

 

xi)                Επιστρέφοντας pointers

 

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

 

            Οι δείκτες σε μεταβλητές δεν είναι ούτε ακέραιοι, ούτε μη προσημασμένοι ακέραιοι (unsigned int). Είναι διευθύνσεις μνήμης ενός συγκεκριμένου τύπου δεδομένων. Ο λόγος αυτής της διάκρισης έγκειται στο γεγονός, ότι η αριθμητική των δεικτών εκτελείται σχετικά με τον βασικό τύπο. Αυτό σημαίνει ότι όταν ένας integer pointer αυξάνεται, περιέχει μία τιμή η οποία είναι κατά δύο αυξημένη σε σχέση με την προηγούμενη τιμή (ένας ακέραιος χρειάζεται 2 bytes). Γενικότερα, κάθε φορά που  ένας pointer αυξάνεται δείχνει στο επόμενο αντικείμενο του τύπου του. Εφόσον οι τύποι δεδομένων μπορεί να είναι διαφορετικού μεγέθους, ο compiler πρέπει να ξέρει σε τι είδους δεδομένα δείχνει ο pointer, για να τον κάνει να δείχνει στο επόμενο αντικείμενο (το θέμα της αριθμητικής των δεικτών καλύπτεται αναλυτικότερα στο αντίστοιχο κεφάλαιο).

 

Π.χ.:     Το παρακάτω παράδειγμα επιστρέφει έναν pointer σε ένα string στο σημείο που θα βρεθεί ομοιότητα με έναν χαρακτήρα:

 

                   char *match( c,s)

                   char c, *s;

                   {

                     int count;

                     while(c!= s[count] && s [count]!= ‘ \ 0 ‘)   count ++;

                     return (&s[count]);

                   }

 

            Η συνάρτηση match( ) επιστρέφει έναν pointer στο σημείο ενός string, όπου βρέθηκε η πρώτη ομοιότητα με τον χαρακτήρα c. Αν δε βρεθεί ομοιότητα, επιστρέφεται ένας pointer στον null terminator. Παρακάτω τίθεται πρόγραμμα που χρησιμοποιεί τη συνάρτηση match( ).

 

                   char match( )

                   main( )

                   {

                     char s[80], *p, ch;

                     gets (s);

                     ch=getche( );

                     p = match (ch, s);

                     if (p)                                /  *  Βρέθηκε ομοιότητα  * /

                        printf(“%s”, p);

                     else

                        printf( “No match found”);

                   }

 

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

 

 

xii)              Πρωτότυπα Συναρτήσεων

 

            Όπως αναφέρθηκε πριν, μία συνάρτηση η οποία επιστρέφει τιμή άλλη εκτός από int, πρέπει να έχει δηλωθεί πριν τη χρήση της. Στην Turbo C μπορούμε να προχωρήσουμε αυτή την ιδέα λίγο περισσότερο δηλώνοντας επίσης τον αριθμό και τον τύπο των ορισμάτων της συνάρτησης. Αυτή η προηγμένη δήλωση της συνάρτησης καλείται πρωτότυπο της συνάρτησης. Τα πρωτότυπα των συναρτήσεων δεν αποτελούν τμήμα της UNIXC, αλλά προστέθηκαν αργότερα από την επιτροπή τυποποίησης της ANSI. Επιτρέπουν στη C να ενεργεί δυνατό έλεγχο συμβατότητας τύπων, κάτι το οποίο θεωρείται δεδομένο σε γλώσσες όπως η Turbo Pascal (σε τέτοιες γλώσσες o compiler απαντά με μηνύματα λαθών αν μία συνάρτηση κληθεί με λάθος τύπους παραμέτρων ή με διαφορετικό αριθμό ορισμάτων). Παρ’ όλ’ αυτά, η C σχεδιάστηκε να είναι αρκετά ελαστική σε τέτοια θέματα και κάποιοι συνδυασμοί τύπων απλά δεν υποστηρίζονται. Για παράδειγμα, είναι λάθος να προσπαθήσουμε να μετατρέψουμε έναν pointer σε float. Χρησιμοποιώντας πρωτότυπα συναρτήσεων πιάνουμε αυτά τα είδη λαθών.

 

Το πρωτότυπο μιας συνάρτησης παίρνει την εξής γενική μορφή:

 

type function_name (arg_type1, arg_type2, … arg_typeN);

 

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

 

Π.χ.:                float func(int, float);

                        main( )

                        {

                          int x, *y;

                          x = 10 ;  y = 10 ;

                          func( x,y) ;                 / * Σφάλμα – Type Mismatch  * /

                        }

                        float func (x, y)

                        int x;

                        float y;

                        {

                          printf(“%f”, y/ (float) x);

                        }

 

            Στο παραπάνω παράδειγμα θα προκαλέσουμε ένα μήνυμα λάθους αφού προσπαθούμε να περάσουμε έναν pointer αντί  για float στη συνάρτηση (το y είναι pointer, ενώ η συνάρτηση απαιτεί float).

 

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

 

 

xiii)            Κλασική εναντίον Μοντέρνας δήλωσης παραμέτρων

 

            Μέχρι στιγμής, στα παραδείγματα που έχουν συναντηθεί, έχει χρησιμοποιηθεί η παραδοσιακή ή κλασική μορφή δήλωσης των παραμέτρων. Πάντως το ANSI standard και η Turbo C επιτρέπουν ένα δεύτερο, πιο συμπαγή, τρόπο δήλωσης, ο οποίος καλείται μοντέρνος τρόπος. Σ’ αυτή τη μέθοδο χρησιμοποιούμε και τον τύπο και το όνομα της μεταβλητής μέσα σε παρένθεση. Έτσι η δήλωση των παραμέτρων παίρνει τη μορφή πρωτοτύπου, μόνο που περιέχει και τα ονόματα των παραμέτρων. Στη μοντέρνα μέθοδο η δήλωση έχει την εξής μορφή:

 

            type function_name(type parm1, type parm2, …, type parmN)

            {

              body of function

            }

 

όπου type είναι ο τύπος των παραμέτρων που ακολουθούν και parm το όνομα της παραμέτρου.

 

Π.χ.:                float func( int x, float y)

                        {

                          printf(“%f”, y / (float) x) ;

                        }

 

 

xiv)           Επαναφορά ή αναδρομή ( Recursion )

 

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

 

            Ένας αναδρομικός τρόπος να ορίσεις έναν ακέραιο αριθμό είναι σαν τα ψηφία 0,1,2,3,…, 9, συν πλην έναν ακέραιο αριθμό.

 

Π.χ.:    ο αριθμός 15 είναι ο αριθμός 7 συν τον αριθμό 8, το 21 είναι 9 συν 12 και 12 είναι 9 συν 3.

 

            Σε μία γλώσσα προγραμματισμού για να είναι αναδρομική μία συνάρτηση, πρέπει να μπορεί να καλεί τον εαυτό της. Ένα παράδειγμα αποτελεί η συνάρτηση factr( ), η οποία υπολογίζει το παραγοντικό ενός ακεραίου. Παραγοντικό είναι το γινόμενο όλων των ακεραίων από 1 ως Ν.

 

Π.χ.:    το παραγοντικό του 3 είναι 1*2*3, δηλαδή 6.

 

ΚΛΗΣΗ ΜΕ ΑΝΑΔΡΟΜΗ

 

factr(n)  / * Recursive  * /

int n ;

{

  int answer ;

  if(n==1)  return(1) ;

  answer=factr(n-1)*n;

  return(answer);

}

ΚΛΗΣΗ ΧΩΡΙΣ ΑΝΑΔΡΟΜΗ

 

fact(int n)  / *Non Recursive * /

{

  int t, answer;

  answer = 1;

  for (t = 1; t<=n; t++)

      answer=answer *t;

  return (answer);

}

 

            Η λειτουργία της μη αναδρομικής συνάρτησης fact( ) είναι ξεκάθαρη. Χρησιμοποιεί ένα loop από 1 ως Ν και πολλαπλασιάζει κάθε αριθμό με το γινόμενο.

 

            Η λειτουργία της αναδρομικής συνάρτησης factr( ) είναι λίγο πιο σύνθετη. Όταν η factr( ) καλείται με όρισμα 1, η συνάρτηση επιστρέφει 1, αλλιώς επιστρέφει το γινόμενο factr(n-1)*n. Για να το καταφέρουμε αυτό η συνάρτηση καλείται με όρισμα  n-1. Αυτό συμβαίνει έως ότου το n γίνει ίσο με 1, οπότε οι κλήσεις στη συνάρτηση αρχίζουν να επιστρέφουν.

 

            Υπολογίζοντας το παραγοντικό του 2, η πρώτη κλήση της συνάρτησης προκαλεί μία δεύτερη κλήση της. Αυτή η κλήση θα επιστρέψει 1, το οποίο θα πολλαπλασιαστεί επί 2 (που είναι η πραγματική τιμή του n). Τότε η απάντηση θα είναι 2. θα ήταν ενδιαφέρον να προστεθούν συναρτήσεις printf( ) μέσα στην factr( ) για να δείχνουν τις απαντήσεις κάθε φορά και το επίπεδο της κάθε κλήσης.

 

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

 

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

 

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

 

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

 

Π.χ.:                #include <stdio.h>

                        void backwards (void);

                        main( )

                        {

                          backwards( );

                        }

                        void backwards (void)             / * Recursive * /

                        {

                          int num;

                          printf(“Δώσε αριθμό: “);

                          scanf (“%d”, &num);

                          printf( “\n”);

                          if (num! = 0) backwards( );

                          printf(“%d”, num);

                        }

 

            Το παραπάνω πρόγραμμα εισάγει Ν αριθμούς χρησιμοποιώντας μία recursive function. Αν δοθεί 0 στον αριθμό, οι συναρτήσεις αρχίζουν να επιστρέφουν.

 

 

xv)             Pointers To Functions

 

            Ένα πολύπλοκο μα δυνατό σημείο της C είναι οι Function Pointers. Μία συνάρτηση δεν είναι μεταβλητή, όμως έχει μια φυσική θέση στη μνήμη, η οποία μπορεί να εκφραστεί με έναν pointer. Η διεύθυνση, η οποία αποδίδεται στον pointer, αποτελεί το σημείο εισόδου της συνάρτησης. Αυτός ο pointer μπορεί να χρησιμοποιηθεί στη θέση του ονόματος της συνάρτησης. Επιτρέπει επίσης σε συναρτήσεις να περνούν σαν ορίσματα σε άλλες συναρτήσεις.

 

            Για να κατανοηθεί το πώς δουλεύουν οι Function Pointers πρέπει να κατανοήσουμε πρώτα πως μεταφράζεται μία συνάρτηση από τον compiler και πως καλείται από την Turbo C. Καθώς μεταφράζεται κάθε συνάρτηση, ο πηγαίος κώδικας μεταφράζεται σε αντικειμενικό και δημιουργείται ένα σημείο εισόδου (entry point). Καθώς κατά την εκτέλεση του προγράμματος γίνεται μία κλήση στη συνάρτηση, εκτελείται μία κλήση σε γλώσσα μηχανής στο σημείο εισόδου. Επομένως ένας pointer σε μία συνάρτηση στην ουσία περιέχει τη διεύθυνση μνήμης του σημείου εισόδου της συνάρτησης.

 

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

 

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

 

                   #include <cytpe.h>

                   main( )

                   {

                     int strcmp( );                  / * Δήλωση της συνάρτησης * /

                     char s1 [80], s2 [80];

                     p=(char *) strcmp;

                     gets(s1);

                     gets(s2);

                     check (s1, s2, p);

                   }

                   check (a, b, cmp)

                   char *a, *b;

                   int (* cmp) ( );

                   {

                     printf(“Testing For Equality\n”);

                     if (! (*cmp) (a,b) ) printf( “Equal”);

                     else printf (“Not Equal”);

                   }

 

            Υπάρχουν δύο λόγοι που δηλώνουμε την strcmp( ) στην main( ):

            Α. Το πρόγραμμα πρέπει να γνωρίζει τι τύπος τιμής επιστρέφεται (στην περίπτωση αυτή ένας ακέραιος).

            Β. Το όνομα αυτό πρέπει να είναι γνωστό στον compiler σαν συνάρτηση.

 

            Στην C δεν υπάρχει τρόπος να δηλώσουμε μία μεταβλητή κατευθείαν σαν Function Pointer. Για να το πετύχουμε, δηλώνουμε μια μεταβλητή σαν char pointer και χρησιμοποιούμε ένα cast, για να της δώσουμε τιμή (τη διεύθυνση της συνάρτησης).

 

            Όταν καλείται η συνάρτηση check( ), σαν παράμετροι περνώνται δύο char pointers και ένας Function Pointer. Μέσα στη συνάρτηση τα ορίσματα έχουν δηλωθεί και αυτά σαν char pointers και Function Pointers. Προσέξτε τον τρόπο με τον οποίο έχει δηλωθεί ο Function Pointer. Πρέπει να χρησιμοποιείτε στον ίδιο τρόπο δήλωσης και άλλων Function Pointers, εκτός φυσικά αν ο επιστρεφόμενος τύπος μπορεί να είναι διαφορετικός. Οι παρενθέσεις είναι απαραίτητες στο ( *cmp), για να μεταφράσει ο compiler σωστά τη δήλωση. Χωρίς τις παρενθέσεις ο compiler θα συγχυστεί.

 

            Μέσα στην check( ) μπορούμε να δούμε πως καλείται η strcmp( ). H δήλωση:

 

(*cmp) (a, b)

 

εκτελεί την κλήση στη συνάρτηση, σ’ αυτή την περίπτωση την strcmp( ), η οποία δείχνεται από το cmp με ορίσματα το a και το b.

 

            Η δήλωση αυτή πάντως αποτελεί το γενικό τρόπο χρήσης ενός Function Pointer για την κλήση μιας συνάρτησης.

 

            Είναι δυνατό να καλέσουμε της check( ) χρησιμοποιώντας κατευθείαν την strcmp( ) όπως εδώ:

 

check ( s1, s2, strcmp);

 

            Αυτή η εντολή μετριάζει την ανάγκη για μία πρόσθετη pointer μεταβλητή.

 

            Σε γενικές γραμμές μπορεί ο αναγνώστης να αναρωτιέται, ποιος ο λόγος να γραφεί ένα πρόγραμμα με αυτόν τον τρόπο. Στο προηγούμενο παράδειγμα δεν έχει γίνει τίποτα το ουσιαστικό, ενώ αντίθετα έχει δημιουργηθεί αρκετή σύγχυση. Πάντως σε πολλές περιπτώσεις είναι αρκετά χρήσιμο να μπορούμε να περνάμε συναρτήσεις σε διαδικασίες ή να κρατάμε πίνακες συναρτήσεων. Το παρακάτω θα βοηθήσει στην κατανόηση των Function Pointers. Όταν ένας compiler γράφεται (μην ξεχνάμε ότι ο compiler είναι πρόγραμμα), είναι κοινό για τον αναλυτή (parser – το τμήμα του compiler που αξιολογεί τις αριθμητικές εκφράσεις) να εκτελεί κλήσεις συναρτήσεων σε διάφορες ρουτίνες υποστήριξης (π.χ. ημίτονο, συνημίτονο, κλπ). Αντί να χρησιμοποιούμε ένα μεγάλο switch με τις συναρτήσεις που θέλουμε, μπορούμε να χρησιμοποιήσουμε έναν πίνακα από Function Pointers καλώντας την απαιτούμενη συνάρτηση.


Π.χ.:                # include <cytpe.h>

                        main( )

                        {

                          int strcmp( );

                          int numcmp( );

                          char s1[80], s2[80];

                          gets(s1);

                          gets(s2);

                          if(isalpha (*s1) )

                             check (s1, s2, strcmp);

                          else

                             check (s1, s2, numcmp);

                        }

                        check (a, b, cmp)

                        char *a, *b;

                        int (*cmp)( );

                        {

                          printf(“Testing For Equality \n”);

                          if (! (*cmp) (a, b)    printf(“Equal”);

                          else printf(“Not Equal”);

                        }

                        numcmp (a, b)

                        char *a, *b;

                        {

                          if (atoi(a) == atoi(b) ) return 0;

                        }

 

            Σ’ αυτό το παράδειγμα η συνάρτηση check( ) μπορεί να χρησιμοποιηθεί, είτε για αλφαριθμητική, είτε για αριθμητική ισότητα, μέσω της απλής κλήσης της με ξεχωριστή συνάρτηση ελέγχου ισότητας.

 

 

xvi)           Συμπληρωματικά για τις Συναρτήσεις

 

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

 

            Α) Παράμετροι και Συναρτήσεις Γενικών Καθηκόντων

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

 

            Β) Αποτελεσματικότητα

            Οι συναρτήσεις είναι δομικά στοιχεία της C. Πάντως σε μερικές εφαρμογές μπορεί να χρειαστεί να χρησιμοποιηθεί in-line κώδικας αντί για συνάρτηση. In-line κώδικας είναι το ισότιμο των εντολών μιας συνάρτησης χωρίς την κλήση της. Ο in-line κώδικας είναι πιο γρήγορος από μία συνάρτηση για δύο λόγους:

 

            α) Μία κλήση συνάρτησης παίρνει χρόνο.

 

            β) Το πέρασμα παραμέτρων απαιτεί χρόνο, γιατί τα ορίσματα πρέπει να τοποθετηθούν στη στοίβα.

 

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

 

            Καθώς θα δημιουργούμε προγράμματα, ίσως χρειαστεί να θυσιάσουμε το χρόνο για χάρη της αναγνωσιμότητας και της μεταφερσιμότητας του κώδικα.