4. Οι έννοιες: της συνάρτησης, του δείκτη και του πίνακα

Η έννοια της συνάρτησης

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

Γενικά μια συνάρτηση επεξεργάζεται αριθμητικά δεδομένα που της περνιούνται από τη θέση του προγράμματος που την καλεί και μπορεί να επιστρέφει μια απλή τιμή. Τα ορίσματα που περνιούνται σε μια συνάρτηση είναι τα αριθμητικά δεδομένα που θα επεξεργαστεί. Η εκτέλεση της εντολής return μέσα στη καλούμενη συνάρτηση, επιστρέφει το αποτέλεσμα που θέλουμε στη καλούσα συνάρτηση.

Υπάρχουν συναρτήσεις οι οποίες δεν παίρνουν ή δεν επιστρέφουν τιμές. Ο σκοπός τους είναι να ομαδοποιούν το πρόγραμμα για να το κάνουν αναγνώσιμο και ευκολότερο να αναπτυχθεί. Μια συνάρτηση δηλώνεται με void μπροστά από το όνομα ορισμού της εάν δεν επιστρέφει καμία τιμή στο σημείο που καλέστηκε. Αν μια συνάρτηση δεν παίρνει παραμέτρους τότε εισάγουμε τον τύπο void.

Η σύνταξη ενός ορισμού μιας συνάρτησης είναι:

επιστρεφόμενητιμή  όνομασυνάρτησης(δήλωση παραμέτρων)
{
               δηλώσεις και εντολές
}

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

char  lower2upper(char cx)
{
      if(cx>='a' && cx<='z')
            return (cx – ('a' – 'A'));
      else
            return cx;
}

Ένας χαρακτήρας παριστάνεται από τον αντίστοιχο ASCII κωδικό. Ένα γράμμα είναι πεζό όταν ο ASCII κωδικός  είναι μεταξύ 97 (0x61) και 122 (0x7A). Για να μετατρέψουμε ένα πεζό γράμμα στο αντίστοιχο κεφαλαίο αφαιρούμε από την ASCII τιμή του την διαφορά των ASCII κωδικών των γραμμάτων ‘a’ και ‘Α’.

Η έννοια του δείκτη

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

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

int *pvar;

Με αυτό τον ορισμό δηλώνεται μια μεταβλητή δείκτη με όνομα pvar όπου το περιεχόμενο της διεύθυνσης που δείχνει (*pvar) είναι ένας ακέραιος αριθμός. Ο τελεστής περιεχομένου διεύθυνσης (*) στον ορισμό του δείκτη επισημαίνει ότι η μεταβλητή αυτή δεν είναι απλή μεταβλητή, αλλά ένας δείκτης που έχει τιμή την διεύθυνση κάποιου ακέραιου δεδομένου. Η τιμή της μεταβλητής δείκτη pvar μπορεί να οριστεί με τη βοήθεια του τελεστή διεύθυνσης (&). Για παράδειγμα η ακέραια μεταβλητή var έχει οριστεί με την εντολή:

int var = 100;

αφού ορίσουμε ένα δείκτη σε ακέραιο με την εντολή:

int *pvar

με την εντολή:

pvar = &var;

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

(1)  int a = 0,  b = 50;
(2)  int var = 100;
(3)  int  *pvar;
(4)  pvar = &var;
(5)  a = *pvar;
(6)  *pvar = b;

Στις γραμμές (1) και (2) δηλώνονται τρεις ακέραιες μεταβλητές στις οποίες προσδίδουμε αρχικές τιμές. Στη γραμμή (3) δηλώνουμε μια μεταβλητή δείκτη που δείχνει σε ακέραιο τύπο δεδομένων. Στη γραμμή (4) η μεταβλητή δείκτη pvar έχει τιμή την διεύθυνση της μεταβλητής var. Στη γραμμή (5) το περιεχόμενο της διεύθυνσης μνήμης που δείχνει η μεταβλητή δείκτη pvar εκχωρείται στη μεταβλητή a  (από όπου παίρνει νέα τιμή ίση με 100). Στη γραμμή (6) η τιμή της μεταβλητής b εκχωρείται στη θέση μνήμης στην οποία δείχνει η μεταβλητή δείκτη pvar που τώρα παίρνει τιμή ίση με 50.

Πίνακες

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

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

int x[5];

όπου τα στοιχεία του είναι τα εξής: x[0], x[1], . . . ,x[4], όπου ο δείκτης ξεκινά πάντα απο το 0 και σε αυτή την περίπτωση ο τελευταίος δείκτης είναι το 4. Ο αριθμός των δεικτών ορίζει τη διάσταση του πίνακα. Για παράδειγμα ο x[i] αναφέρεται σε ένα στοιχείο ενός μονοδιάστατου πίνακα. Όμοια το y[i][j] αναφέρεται σε ένα στοιχείο ενός δισδιάστατου πίνακα. Ωστόσο οι πολυδιάστατοι πίνακες δεν χρησιμοποιούνται συχνά σε εφαρμογές με μικροελεγκτές. Γενικά ένας μονοδιάστατος πίνακας μπορεί να δηλωθεί με την ακόλουθη σύνταξη:

τυποσδεδομένων όνομαπινακα[αριθμητικήεκφραση];

Δείκτες και πίνακες

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

int  ax[20];

δηλώνει ένα πίνακα με όνομα ax με 20 στοιχεία τύπου ακεραίων. Η δήλωση ax[i] αναφέρεται στο i-στο στοιχείο του πίνακα. Εάν ip είναι ένας δείκτης σε ακέραιο , που δηλώνεται ως:

int *ip;

τότε η εκχώρηση:

ip = &ax[0]

εκχωρεί στο ip την διεύθυνση του στοιχείου ax[0]. Τώρα η εκφραση:

x = *ip

αντιγράφει την τιμή του στοιχείου ax[0] στη μεταβλητή  x. Εάν ο ip δείχνει στο ax[0] τότε ο δείκτης ip+1 δείχνει στο ax[1], και το ip+i δείχνει στο ax[i] κ.τ.λ.

Περνώντας πίνακες σε συναρτήσεις

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

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

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

float average(int n, int arr[]);
void main()
{
       int n, avg;    // δήλωση μεταβλητών
       int  arr[50];   // δήλωση πίνακα
         . . .
       avg = average(n,  arr);  // κλήση συνάρτησης
         . . .
      }
float average(int k, int brr[])      // δήλωση συνάρτησης
{
   int sum = 0, i;
   for(i = 0; i < k; i++)  sum += brr[i];
   return sum / k;
}

Η δήλωση της συνάρτησης average αρχίζει με τον επιστρεφόμενο τύπο, το όνομα της συνάρτησης και ακολουθούν παρενθέσεις μέσα στις οποίες βάζουμε τους ορισμούς των παραμέτρων. Κάθε παράμετρος συνοδεύεται από τον τύπο της. Για την παράμετρο που δέχεται πίνακα δηλώνουμε τον τύπο του πίνακα, το όνομα του και τέλος αγκύλες χωρίς δείκτη.
Οι  εκφράσεις:  int brr[];  και  int  *brr; είναι ισοδύναμες, σαν ένα δείκτη στο πρώτο στοιχείο του πίνακα.

Αρχικοποίηση πινάκων

Για να δηλώσουμε ένα πίνακα, ορίζουμε τον τύπο των στοιχείων του, ορίζουμε το όνομα του καθώς και τον αριθμό των στοιχείων του. Για να μπορέσουμε να χρησιμοποιήσουμε ένα πίνακα θα πρέπει να έχουμε αποδώσει τιμές σε κάθε στοιχείο του. Π.χ.
int  ar[4];
a[0] = 10;
a[1] = 11;
a[2] = 12;
a[3] = 13;

Θα μπορούσαμε να του αποδώσουμε αρχικές τιμές κατά την δήλωση του:
int  ar[4] = {10, 11, 12, 13};

Για να διευκολυνθούμε μπορούμε να αφήσουμε στον compiler να βάλει αυτόματα τον σωστό δείκτη στη δήλωση του πίνακα, δηλαδή μπορούμε να γράψουμε το ακόλουθο:
int ar[] = {10, 11, 12, 13};

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