Η παρουσίαση φορτώνεται. Παρακαλείστε να περιμένετε

Η παρουσίαση φορτώνεται. Παρακαλείστε να περιμένετε

5-1 ΜΑΘΗΜΑ 5 ο Δείκτες, Πίνακες και Δείκτες, Δείκτες σε Συναρτήσεις.

Παρόμοιες παρουσιάσεις


Παρουσίαση με θέμα: "5-1 ΜΑΘΗΜΑ 5 ο Δείκτες, Πίνακες και Δείκτες, Δείκτες σε Συναρτήσεις."— Μεταγράφημα παρουσίασης:

1 5-1 ΜΑΘΗΜΑ 5 ο Δείκτες, Πίνακες και Δείκτες, Δείκτες σε Συναρτήσεις

2 5-2 ΚΛΗΣΗ ΤΗΣ ΣΥΝΑΡΤΗΣΗΣ Μία συνάρτηση μπορεί να κληθεί με δύο τρόπους: 1.χρησιμοποιώντας το όνομά της και τη λίστα των ορισμάτων της σε μια αυτόνομη πρόταση, π.χ wait(12); ……………………. Εδώ θεωρούμε ότι η συνάρτηση δεν επιστρέφει κάποιο αποτέλεσμα αλλά έχει κάποιες παράπλευρες λειτουργίες. 2. χρησιμοποιώντας το όνομά της και τη λίστα των ορισμάτων της σε μια έκφραση, π.χ k = sum(2, 10); ……………………. Εδώ θεωρούμε ότι η συνάρτηση επιστρέφει κάποιο αποτέλεσμα.

3 5-3 ΕΠΙΣΤΡΟΦΗ ΑΠΟΤΕΛΕΣΜΑΤΟΣ ‘Οταν μία συνάρτηση καλείται: –Ο κώδικας στο σημείο κλήσης της συνάρτησης σταματά. –Ο κώδικας στο σώμα της συνάρτησης εκτελείται. Εάν η συνάρτηση που καλείται έχει παραμέτρους, οι παράμετροι παίρνουν τιμές από τις τιμές των ορισμάτων στο σημείο κλήσης. Τα ορίσματα και οι παράμετροι συνδυάζονται ανάλογα με τη θέση τους και τον τύπο τους (το πρώτο όρισμα με την πρώτη παράμετρο, το δεύτερο όρισμα με τη δεύτερη παράμετρο κ.ο.κ.). –Το αποτέλεσμα της συνάρτησης επιστρέφεται στο σημείο κλήσης. –Το αποτέλεσμα της συνάρτησης επιστρέφεται με την εντολή return –Ο κώδικας ακριβώς μετά από το σημείο κλήσης συνεχίζει την λειτουργία του.

4 5-4 ΜΗΧΑΝΙΣΜΟΣ ΚΛΗΣΗΣ Ο μηχανισμός κλήσης μιας συνάρτησης βασίζεται: 1.στη συσχέτιση του ονόματος της συνάρτησης στο σημείο ορισμού της με το όνομα της συνάρτησης στο σημείο κλήσης, 2.στη συσχέτιση του πλήθους των παραμέτρων της συνάρτησης στο σημείο ορισμού της με το πλήθος των ορισμάτων της συνάρτησης στο σημείο κλήσης, και την αντιστοιχία του τύπου της κάθε παραμέτρου στο σημείο ορισμού με τον τύπο του κάθε ορίσματος της συνάρτησης στο σημείο κλήσης, 3.στη συσχέτιση του τύπου επιστροφής της συνάρτησης στο σημείο ορισμού της με τη χρήση του αποτελέσματος της συνάρτησης στην έκφραση που υπάρχει η κλήση της συνάρτησης.

5 5-5 ΠΑΡΑΔΕΙΓΜΑ #include int x, y, z; int greater_of(int, int); int main() { puts(“Enter two different integer values”); scanf(“%d%d”, &x, &y); z = greater_of(x, y); printf(“\nThe higher value between %d, %d is %d”, x, y, z); return 1; } int greater_of(int a, int b) { if (a > b) return a; else return b; } Η μεταβίβαση των παραμέτρων της συνάρτησης greater_of γίνεται με την αντιστοίχιση των μεταβλητών x, y στο σημείο κλήσης με τις παραμέτρους a, b στο σημείο ορισμού της συνάρτησης.

6 5-6 ΜΕΘΟΔΟΙ ΜΕΤΑΒΙΒΑΣΗΣ ΟΡΙΣΜΑΤΩΝ Υπάρχουν δύο μέθοδοι μεταβίβασης ορισμάτων –Με μετάδοση τιμής – ανάθεση (By value) –Με αναφορά - απ’ ευθείας χρήση (By reference) Η μέθοδος με μετάδοση τιμής (By value) βασίζεται στην ιδέα ότι ό κώδικας (σώμα) της συνάρτησης λαμβάνει και χρησιμοποιεί ένα αντίγραφο του ορίσματος, το οποίο ισχύει όσο η συνάρτηση εκτελείται. Αυτό σημαίνει ότι η τιμή της μεταβλητής του ορίσματος στο σημείο κλήσης είναι η ίδια και μετά την επιστροφή της συνάρτησης μιας και μόνο ένα αντίγραφό της χρησιμοποιείται. Η μέθοδος με αναφορά (By reference) βασίζεται στην ιδέα ότι ό κώδικας (σώμα) της συνάρτησης λειτουργεί σαν να λαμβάνει και χρησιμοποιεί την ίδια την μεταβλητή του ορίσματος. Αυτό σημαίνει ότι η τιμή της μεταβλητής του ορίσματος στο σημείο κλήσης μπορεί να είναι διαφορετική μετά την επιστροφή της συνάρτησης, εάν η συνάρτηση στο σώμα της μεταβάλλει την τιμή της παραμέτρου που αντιστοιχεί στο όρισμα που μεταβιβάζεται με τη μέθοδο της αναφοράς.

7 5-7 ΜΕΘΟΔΟΣ ΜΕ ΜΕΤΑΔΟΣΗ ΤΙΜΗΣ (By Value) Όταν μία συνάρτηση καλείται, οι παράμετροι λαμβάνουν την τιμή των ορισμάτων στο σημείο κλήσης. Η κάθε παράμετρος στο σημείο ορισμού δηλώνει τον τύπο του ορίσματος με το οποίο περιμένει να συνδυαστεί. Εάν ο τύπος του ορίσματος και της αντίστοιχης παραμέτρου είναι συμβατοί (π.χ.float και double), τότε ο μεταφραστής (compiler) παράγει ένα αντίγραφο της τιμής του ορίσματος και το αναθέτει στη μεταβλητή της παραμέτρου, ώστε να εκτελεσθεί η συνάρτηση. Εάν οι τύποι του ορίσματος και την αντίστοιχης παραμέτρου δεν είναι συμβατοί, τότε ο μεταφραστής παράγει ένα μήνυμα λάθους κατά τη διάρκεια της μετάφρασης (compilation). Από τη στιγμή που η τιμή έχει του ορίσματος έχει μεταβιβαστεί, τότε το όρισμα και η αντίστοιχη παράμετρος δεν έχουν καμία σχέση, οπότε ακόμη και εάν η μεταβλητή της παραμέτρου τροποποιηθεί δεν έχει κανένα αποτέλεσμα στη τιμή του ορίσματος. Όταν η συνάρτηση «επιστρέψει» με τη χρήση της εντολής return ή τον τερματισμό των εντολών του σώματος της, τα αντίγραφα των ορισμάτων παύουν να ισχύουν.

8 5-8 ΠΑΡΑΔΕΙΓΜΑ ΜΕ ΜΕΤΑΔΟΣΗ ΤΙΜΗΣ (BY VALUE) - 1 main() { float a, b; a = 10.0; while (a < 12.0) { b = plus(a); a = a+1; } return 1; } float plus(VAL float x) { x = x + x; return x; } Αυτή ΔΕΝ είναι η σύνταξη της C – Απλό παράδειγμα

9 5-9 ΠΑΡΑΔΕΙΓΜΑ ΜΕ ΑΠΟΘΕΣΗ ΤΙΜΗΣ - 2 main plus Μεταβίβαση ορίσματος. Η τιμή της x είναι αντίγραφο της τιμής της a. x = 10.0 x = x + x x = 20.0 return x; Η μεταβλητή x παύει να ισχύει */ a = 11.0 plus(a) b = 20.0 a = 10.0 plus(a)Μεταβίβαση ορίσματος. Η τιμή της x είναι αντίγραφο της τιμής της a. x = 11.0 x = x + x x = 22.0 return x; Η μεταβλητή x παύει να ισχύει. */

10 5-10 ΜΕΘΟΔΟΣ ΜΕ ΑΝΑΦΟΡΑ - ΑΠ’ΕΥΘΕΙΑΣ ΧΡΗΣΗ (By Reference) Από τη στιγμή που η τιμή του ορίσματος έχει μεταβιβαστεί τότε το όρισμα και η αντίστοιχη παράμετρος αναφέρονται στην ίδια θέση μνήμης (aliasing), οπότε, εάν η μεταβλητή της παραμέτρου τροποποιηθεί, τότε μεταβάλλεται και η τιμή του ορίσματος. Αυτή η μεταβολή παραμένει και μετά την έξοδο της συνάρτησης (θυμηθείτε ότι η παράμετρος και το όρισμα είναι η ίδια θέση μνήμης). Ουσιαστικά δεν υπάρχει διαφορά μεταξύ ορίσματος και παραμέτρου. Απλά χρησιμοποιούμε την ίδια θέση μνήμης με διαφορετικό μνημονικό όνομα (αυτά του ορίσματος και της αντίστοιχης παραμέτρου).

11 5-11 ΠΑΡΑΔΕΙΓΜΑ ΜΕ ΑΝΑΦΟΡΑ (BY REFERENCE) - 1 main() { float a; a = 10.0; plus(a); return 1; } float plus(REF float x) { x = x + x; } Αυτή ΔΕΝ είναι η σύνταξη της C – Απλό παράδειγμα

12 5-12 ΠΑΡΑΔΕΙΓΜΑ ΜΕ ΑΝΑΦΟΡΑ - 2 main plus Μεταβίβαση ορίσματος x = 10.0 x= x + x x = 20.0 a = 20.0 plus(a) a = 10.0 plus(a) Μεταβίβαση ορίσματος. x = 20.0 x= x + x Οι μεταβλητές a, x, αναφέρονται στην ίδια θέση μνήμης, οπότε κάθε μεταβολή της τιμής της μίας συνεπάγεται αλλαγή της τιμής της άλλης. x = 40.0 a = 40.0

13 5-13 ΑΝΑΔΡΟΜΗ Μία συνάρτηση μπορεί να καλεί τον εαυτό της! Αυτή η τεχνική λέγεται αναδρομή. Η αναδρομή είναι σημαντική τεχνική για τον υπολογισμό..... αναδρομικών συναρτήσεων. Ένα τυπικό παράδειγμα αναδρομικής συνάρτησης είναι αυτής που χρησιμοποιείται για τον υπολογισμό του παραγοντικού γινομένου. Σημαντικό: Μια αναδρομική συνάρτηση θα πρέπει: 1.Να έχει μια συνθήκη τερματισμού της αναδρομής. 2.Να καλεί αναδρομικά τη συνάρτηση με τέτοιο τρόπο ώστε η συνθήκη τερματισμού κάποια στιγμή (μετά από n αναδρομές) να γίνει αληθής.

14 5-14 ΠΩΣ ΔΟΥΛΕΥΕΙ Η ΑΝΑΔΡΟΜΗ - ΠΕΡΙΒΑΛΛΟΝ ΔΙΑΔΙΚΑΣΙΑΣ Πριν μιλήσουμε για το πώς δουλεύει η αναδρομή ας πούμε δύο λόγια για το περιβάλλον μιας συνάρτησης (γενικότερα μιας διαδικασίας). Stack Text Heap Aux. Memory 0 2 N Το περιβάλλον μίας διαδικασίας (process); όπως λίγο-πολύ και το περιβαλλον μιας συνάρτησης αποτελείται από το (text) κείμενο του κώδικα σε γλώσσα μηχανής), (heap) θέσεις μνήμης για τις μεταβλητές της συνάρτησης, (stack) μία δομή για την χρήση των παραμέτρων και των μεταβλητών, και (aux. memory) βοηθητική μνήμη.

15 5-15 ΠΩΣ ΔΟΥΛΕΥΕΙ Η ΑΝΑΔΡΟΜΗ Η αναδρομή δουλεύει ως εξής: –Η συνάρτηση όταν καλεί τον εαυτό της: Σταματά τη λειτουργία της στο σημείο κλήσης (όπως άλλωστε θα περιμέναμε). Αποθηκεύει το περιβάλλον της (όλες τις μεταβλητές και τις τιμές τους, καθώς και το σημείο κλήσης, δηλαδή ποια θα είναι η επόμενη εντολή όταν η συνάρτηση θα συνεχίσει τη λειτουργία της). Καλεί τον εαυτό της (την ίδια συνάρτηση) σαν να ήταν μια οποιαδήποτε άλλη συνάρτηση. Η κλήση γίνεται σ’ ένα νέο περιβάλλον κλήσης. Μη ξεχνάμε όμως ότι κάποια ορίσματα μπορεί να μεταβιβάζονται με αναφορά.

16 5-16 ΠΑΡΑΔΕΙΓΜΑ ΑΝΑΔΡΟΜΗΣ - 1 Το παραγοντικό γινόμενο ενός αριθμού x υπολογίζεται ως το γινόμενο x! = x * (x-1) * (x-2) * (x-3) …. *1 = x * (x-1)! όπου 0! = 1 Δηλαδή x! = { 1 Εάν x = 0 x * (x-1)! Εάν x > 0

17 5-17 ΠΑΡΑΔΕΙΓΜΑ ΑΝΑΔΡΟΜΗΣ - 2 #include int factorial(int); int main() { int value; value = factorial(5); } int factorial(int n) { if (n == 0) return 1 ; else return (n * factorial(n-1) ); }

18 5-18 ΠΑΡΑΔΕΙΓΜΑ ΑΝΑΔΡΟΜΗΣ - 3 factorial(5) 5* factorial(4) 4* factorial(3) 3* factorial(2) 2* factorial(1) Αναδρομικές Κλήσεις 1 factorial(5) 5* factorial(4) 4* factorial(3) 3* factorial(2) 2* factorial(1) Επιστρέφεται 1 Επιστρέφεται 2 * 1 = 2 = 2! Επιστρέφεται 3 * 2 = 6 = 3! Επιστρέφεται 4 * 6 = 24 = 4! Επιστρέφεται 5 * 24 = 120 = 5!

19 5-19 ΓΕΝΙΚΑ ΠΕΡΙ ΜΕΤΑΒΛΗΤΩΝ Όπως έχουμε αναφέρει, ένα πρόγραμμα είναι μία ακολουθία εντολών, που χρησιμοποιούν μεταβλητές για την παροδική αποθήκευση των δεδομένων που χρειάζεται το πρόγραμμα (οι εντολές του προγράμματος για την ακρίβεια) για τη λειτουργία του. Η κάθε μεταβλητή ουσιαστικά είναι ένα συμβολικό όνομα το οποίο αναφέρεται σε κάποιο δεδομένο (data). Τα χαρακτηριστικά μιας μεταβλητής θα μπορούσαμε να πούμε ότι είναι: 1.Το συμβολικό όνομά της 2.Ο τύπος της, που επίσης ορίζει το μέγεθος της μνήμης στην οποία αναφέρεται (που «καταλαμβάνει») η μεταβλητή. 3.Η διεύθυνση της μνήμης, από την οποία αρχίζουν τα δεδομένα στα οποία αναφέρεται η μεταβλητή. Για παράδειγμα, εάν υποθέσουμε ότι η μεταβλητή x αρχίζει από τη θέση μνήμης 1000 και είναι τύπου unsigned int, ο οποίος στην αρχιτεκτονική Pentium 3 καταλαμβάνει 4 bytes, τότε τα δεδομένα στα οποία αναφέρεται η μεταβλητή x βρίσκονται από την θέση μνήμης 1000 μέχρι και τη θέση μνήμης 1003 (θεωρώντας ότι μία θέση μνήμης είναι ένα byte).

20 5-20 ΕΙΣΑΓΩΓΗ ΣΤΟΥΣ ΔΕΙΚΤΕΣ (pointers) Οι μεταβλητές που έχουμε δει μέχρι τώρα: 1.Είναι ή απλού τύπου (int, float, double, char) ή πιο σύνθετου τύπου, όπως διανύσματα, τα οποία όμως και πάλι τα είδαμε στην απλή τους μορφή, δηλαδή διανύσματα και πίνακες από στοιχεία απλών τύπων. 2.Η διεύθυνση της μνήμης στην οποία βρίσκονται τα δεδομένα στα οποία αναφέρεται η μεταβλητή δεν μας ενδιέφερε. Το ερώτημα είναι τι πρέπει να χρησιμοποιήσουμε εάν θελήσουμε να αναφερθούμε σε θέσεις μνήμης αντί να αναφερθούμε απλά σε δεδομένα. Είναι προφανές ότι πρέπει να χρησιμοποιήσουμε πάλι κάποιο είδος μεταβλητής (άλλωστε, όπως έχουμε δει, το πρόγραμμα δουλεύει με μεταβλητές). Οι τιμές αυτών των «ειδικών» μεταβλητών είναι πάντα θέσεις μνήμης. Θα μπορούσαμε να πούμε ότι οι τιμές αυτές είναι σαν κάποιοι ειδικοί ακέραιοι αριθμοί που αναφέρονται σε θέσεις δυναμικής μνήμης του Η/Υ μας. Αυτές οι μεταβλητές που οι τιμές τους είναι θέσεις μνήμης του Η/Υ ονομάζονται δείκτες (pointers). Είναι επίσης προφανές ότι χρειαζόμαστε κάποιο τρόπο να ξεχωρίσουμε τις δηλώσεις των «απλών» μεταβλητών από τις δηλώσεις αυτών των «ειδικών» μεταβλητών που ονομάζονται δείκτες

21 5-21 ΕΙΣΑΓΩΓΗ ΣΤΟΥΣ ΔΕΙΚΤΕΣ (pointers) Πριν όμως προχωρήσουμε ας δούμε τί αντιπροσωπεύουν οι δείκτες. Στους περισσότερους σύγχρονους υπολογιστές η δυναμική μνήμη (RAM) είναι κατανεμημένη σε «κελιά» μήκους ενός byte κάθε ένα από τα οποία συνήθως μπορεί να αποθηκεύσει 8 bits. Δηλαδή ένας Η/Υ με 16ΜΒ RAM έχει περίπου 16 εκατομμύρια bytes. Κάθε byte έχει μία μοναδική διεύθυνση, ώστε να ξεχωρίζει από κάθε άλλο byte στη δυναμική μνήμη. Εάν υποθέσουμε ότι η μνήμη έχει μέγεθος κ bytes, τότε οι διευθύνσεις είναι αριθμοί από το 0 μέχρι το (κ-1).

22 5-22 ΠΑΡΑΔΕΙΓΜΑ Διεύθυνση Περιεχόμενο κ-1

23 5-23 ΠΑΡΑΔΕΙΓΜΑ Εάν υποθέσουμε ότι η μεταβλητή x είναι ενός τύπου που χρειάζεται 4 bytes, τότε η διεύθυνσή της είναι η διεύθυνση του πρώτου byte της. Η τιμή της x είναι 2 (ουσιαστικά είναι η τιμή

24 5-24 ΕΙΣΑΓΩΓΗ ΣΤΟΥΣ ΔΕΙΚΤΕΣ (pointers) Είπαμε προηγουμένως ότι χρειαζόμαστε κάποιο τρόπο να ξεχωρίσουμε τις δηλώσεις των «απλών» μεταβλητών από τις δηλώσεις των «ειδικών» μεταβλητών που ονομάζονται δείκτες. Η εντολή που δηλώνει μια μεταβλητή ως δείκτη είναι *

25 5-25 ΠΑΡΑΔΕΙΓΜΑ int a_var; int * a_ptr_var; Δηλώνει μία «απλή» μεταβλητή με όνομα a_var. Δηλώνει μία «ειδική» μεταβλητή δείκτη με όνομα a_ptr_var. Τι όμως σημαίνει η a_ptr_var? Σημαίνει μία μεταβλητή : 1.στην οποία μπορεί να αναφερθούμε στο πρόγραμμά μας με το συμβολικό όνομα a_ptr_var, 2.η μεταβλητή αυτή παίρνει τιμές που είναι διευθύνσεις μνήμης, και 3.αυτές οι διευθύνσεις μνήμης πρέπει να αποθηκεύουν δεδομένα που είναι τύπου int.

26 5-26 ΔΕΙΚΤΕΣ - ΣΧΗΜΑΤΙΚΑ p i Εδώ η p είναι μεταβλητή δείκτη και η τιμή της είναι μια διεύθυνση (στη προκειμένη περίπτωση η διεύθυνση αυτή περιέχει τα δεδομένα της μεταβλητής i). Εδώ η τιμή της p είναι η διεύθυνση Εάν η p είχε δηλωθεί ως int * p; τότε η i θα έχει δηλωθεί ως int i; Διεύθυνση : 2000

27 5-27 ΔΕΙΚΤΕΣ - ΣΧΗΜΑΤΙΚΑ p i Προφανώς και η μεταβλητή δείκτη p έχει και αυτή μία διεύθυνση!! Στις περισσότερες περιπτώσεις όμως δεν μας ενδιαφέρει αυτή η διεύθυνση της μεταβλητής δείκτη p. Απλά ας σκεφτούμε ότι μπορούμε να έχουμε μεταβλητές δείκτη σε δείκτες !!! Διεύθυνση : 2000 Διεύθυνση : 4000

28 5-28 ΠΑΡΑΔΕΙΓΜΑ int *p; /* Δείκτης μόνο σε int */ float *q /* Δείκτης μόνο σε float */ char * r /* Δείκτης μόνο σε char */ Δεν υπάρχουν περιορισμοί σε τι είδους τύπο «δείχνει» ένας δείκτης.

29 5-29 ΤΕΛΕΣΤΕΣ ΣΧΕΤΙΚΟΙ ΜΕ ΔΕΙΚΤΕΣ Έχουμε δύο τελεστές σχετικούς με δείκτες: 1.Τελεστής διεύθυνσης (address operator) & 2.Τελεστής έμμεσης αναφοράς (indirection, dereferencing) *

30 5-30 ΤΕΛΕΣΤΗΣ ΔΙΕΥΘΥΝΣΗΣ & Όταν δηλώνουμε μία μεταβλητή δείκτη, το μόνο που κάνουμε είναι να κρατάμε μνήμη αρκετά μεγάλη για την τιμή της μεταβλητής δείκτη (όχι την τιμή της μεταβλητής που «δείχνει» η μεταβλητή δείκτη). Στην αρχή η μεταβλητή δείκτη δεν έχει καμία τιμή (όπως άλλωστε θα περιμέναμε και από κάθε άλλη δήλωση μεταβλητής χωρίς αρχική τιμή). Ένας τρόπος να θέσουμε μία αρχική τιμή σε μία μεταβλητή δείκτη είναι να δώσουμε τη διεύθυνση μιας μεταβλητής του προγράμματος. Η διεύθυνση μιας μεταβλητής ενός προγράμματος υπολογίζεται από το τελεστή διεύθυνσης &. Δηλαδή, εάν: int i; Τότε &i επιστρέφει τη διεύθυνση της μεταβλητής i. Οπότε εάν έχουμε τις δηλώσεις: int *p; int i; Μπορούμε να αναθέσουμε τη διεύθυνση της μεταβλητής i, στη μεταβλητή p p = &i;

31 5-31 ΤΕΛΕΣΤΗΣ ΕΜΜΕΣΗΣ ΑΝΑΦΟΡΑΣ * Αντίστροφα με τον τελεστή διεύθυνσης &, ο τελεστής έμμεσης αναφοράς *, επιστρέφει την τιμή των δεδομένων που βρίσκονται σε μια θέση μνήμης. Οπότε, εάν: int *p; int i = 10; p = &i; τότε η έκφραση *p επιστρέφει τη τιμή που βρίσκεται στη θέση μνήμης που «δείχνει» η μεταβλητή δείκτη p (δηλαδή τη διεύθυνση της μεταβλητής i). Δηλαδή η έκφραση *p επιστρέφει τη τιμή 10.

32 5-32 ΔΕΙΚΤΕΣ ως ΠΑΡΑΜΕΤΡΟΙ Μία από τις σημαντικές χρήσεις των δεικτών είναι ότι επιτρέπουν τη μεταβίβαση ορισμάτων σε συναρτήσεις με αναφορά (by reference). Αυτό σημαίνει ότι, αντί να περνάμε ένα αντίγραφο του ορίσματος στην αντίστοιχη παράμετρο, περνάμε τη διεύθυνση του ορίσματος. Οπότε, κάθε αλλαγή στην παράμετρο (στο σώμα της συνάρτησης) ουσιαστικά μεταβάλλει τα περιεχόμενα στη θέση μνήμης του ορίσματος!

33 5-33 ΠΑΡΑΔΕΙΓΜΑ ΜΕ ΑΝΑΦΟΡΑ (BY REFERENCE) - 1 int main() { int *a; int k = 10; a = &k; *a = 20; plus(a); return *a; } double plus(int * x) { *x = *x + *x; } Η παράμετρος x λαμβάνει ως τιμή τη διεύθυνση a.

34 5-34 ΠΑΡΑΔΕΙΓΜΑ ΜΕ ΑΝΑΦΟΡΑ (BY REFERENCE) - 2 int main() { int a; a = 10.0; plus(& a); return a; } double plus(int * x) { *x = *x + *x; } Η παράμετρος x λαμβάνει ως τιμή τη διεύθυνση της μεταβλητής a.

35 5-35 ΔΕΙΚΤΕΣ ΚΑΙ scanf Εάν ανατρέξουμε στη χρήση της scanf θα προσέξουμε ότι χρησιμοποιούσαμε τον τελεστή διεύθυνσης, π.χ. scanf(“%d”, &i); Σ’ αυτή την περίπτωση, η συνάρτηση scanf λαμβάνει ως δεύτερο όρισμα ένα δείκτη (ουσιαστικά τη διεύθυνση της μεταβλητής που θέλουμε να της αποδώσουμε τιμή – συγκεκριμένα εδώ τη μεταβλητή i). Τι γίνεται όμως εάν η μεταβλητή (π.χ. i) είναι δείκτης; H scanf θα διαβάσει το δεδομένο και (δυστυχώς) χωρίς προειδοποίηση θα το αναθέσει ως τιμή του δείκτη. Γενικά, όταν χρησιμοποιούμε δείκτες ως παραμέτρους, θα πρέπει να είμαστε σίγουροι ότι τα ορίσματα είναι διευθύνσεις και όχι οποιοσδήποτε ακέραια τιμή. Η χρήση των πρωτότυπων μας επιτρέπει να είμαστε σίγουροι ότι ο μεταφραστής θα καταλάβει τέτοιου είδους λάθη στις περισσότερες περιπτώσεις.

36 5-36 ΠΑΡΑΔΕΙΓΜΑ int i, *p; p = &i; scanf(“%d”, p); /* διαβάζει ακέραιο και τόν */ /* αναθέτει στη μεταβλητή i !! */ scanf(“%d”, &p); /* Λάθος – Διαβάζει ακέραιο */ /* και τον αναθέτει στη μεταβλητή p !!! */ /* Οπότε η τιμή που διαβάζεται γίνεται */ /* η τιμή του p, δηλαδή η p «δείχνει» σε κάποια */ /* υποτιθέμενη θέση μνήμης. */

37 5-37 ΠΑΡΑΔΕΙΓΜΑ #include #define N 100 void max_min(int a[], int n, int *max, int *min); void main() { int b[N], i, high, low; printf(“Enter %d numbers: “, N); for (i=0; i *max) *max = a[i]; else if (a[i] < *min) *min = a[i]; } /* Αυτή η συνάρτηση επιστρέφει το μέγιστο */ /* και το ελάχιστο στοιχείο ενός διανύσματος. */

38 5-38 ΔΕΙΚΤΕΣ ως ΤΙΜΕΣ ΕΠΙΣΤΡΟΦΗΣ ΣΥΝΑΡΤΗΣΗΣ Οι δείκτες δεν χρησιμοποιούνται μόνο για μεταβίβαση ορισμάτων με αναφορά. Χρησιμοποιούνται και για επιστροφή τιμών από συναρτήσεις π.χ. int *p, x=10, y=20; void main() { …… p = max(&x, &y) } int * max(int *a, int *b) { if (*a > *b) return a; else return b; } επιστρέφει δείκτη. Σημαίνει ότι η συνάρτηση max επιστρέφει δείκτη.

39 5-39 ΣΗΜΕΙΩΣΗ Μην επιστρέφετε ως αποτέλεσμα συνάρτησης δείκτη σε τοπική μεταβλητή, π.χ. int *fcn(void) { int i; …….. return &i; } Η μεταβλητή i δεν θα υπάρχει μετά το τέλος της συνάρτησης οπότε ο δείκτης (η διεύθυνση του i) θα είναι άκυρος.

40 5-40 ΔΕΙΚΤΕΣ ΚΑΙ ΔΙΑΝΥΣΜΑΤΑ Η σχέση των διανυσμάτων και των δεικτών είναι πολύ σημαντική. Εάν θυμηθούμε τη δήλωση ενός διανύσματος: float a[10]; /* Μονοδιάστατο διάνυσμα 10 στοιχείων τύπου float */ ουσιαστικά υποδηλώνει τη διεύθυνση του πρώτου του στοιχείου a[0], και μετά τόσες θέσεις μνήμης όσες να χωρέσουν 10 float αριθμοί. Με αυτόν τον τρόπο η C καταχωρεί διανύσματα. Μια μεταβλητή τύπου πίνακα συμπεριφέρεται σαν (σταθερός) δείκτης. Οπότε *a = 8; /* Θέτει την τιμή 8 στο a[0]. */ a0a0a1a1a9a

41 5-41 ΔΕΙΚΤΕΣ ΚΑΙ ΔΙΑΝΥΣΜΑΤΑ int a[10], *p; p = a; *p = 5 /* To a[0] έχει τιμή 5. */ a[0]a[1] p

42 5-42 ΑΡΙΘΜΗΤΙΚΗ ΔΕΙΚΤΩΝ Μπορούμε να προσθέσουμε ένα ακέραιο αριθμό σε έναν δείκτη, να αφαιρέσουμε ένα ακέραιο αριθμό από έναν δείκτη, ή να αφαιρέσουμε έναν δείκτη από έναν άλλο ! Όταν έχουμε να κάνουμε με διανύσματα, αυτές οι πράξεις γίνονται πολύ ενδιαφέρουσες.

43 5-43 ΠΑΡΑΔΕΙΓΜΑ int a[10], *p, *q, *w; p = &a[0]; *p = 5 /* To a[0] έχει τιμή 5. */ q = p + 3; w = q-1; a[0]a[1] p a[0]a[1]a[2]a[3]a[4] pq w

44 5-44 ΑΦΑΙΡΕΣΗ ΔΕΙΚΤΩΝ Ας θεωρήσουμε: int a[10], *p, *q, i; p = &a[5]; q = &a[3]; i = p - q; /* Η τιμή της μεταβλητής i είναι 2. */ i = q - p; /* Η τιμή της μεταβλητής i είναι -2. */ Σημείωση: Η αριθμητική σε δείκτες έχει νόημα συνήθως όταν χρησιμοποιείται σε διανύσματα, όπως στα παραδείγματα παραπάνω.

45 5-45 ΠΑΡΑΔΕΙΓΜΑ #define N 100 int a[N], sum, *p; int main() { sum = 0; for (p = &a[0]; p < &a[N]; p++) sum = sum + *p; return (sum); }

46 5-46 ΠΑΡΑΔΕΙΓΜΑ include #define N 20 void main() { int a[N], *p; printf(“Enter %d numbers”, N); for (p = a; p < a+N; p++) scanf(“%d”, p); printf(“In reverse :”); for(p = a+N-1; p >= a; p--) printf(“%d”, *p); printf(“\n”); return 1; }

47 5-47 ΔΙΑΝΥΣΜΑΤΑ ΩΣ ΟΡΙΣΜΑΤΑ Όταν το όνομα ενός διανύσματος χρησιμοποιείται ως όρισμα σε μία συνάρτηση μεταβιβάζεται με αναφορά (by reference), σαν να ήταν δείκτης. Δηλαδή, οποιαδήποτε αλλαγή στην αντίστοιχη παράμετρο στο σώμα της συνάρτησης επιφέρει αλλαγή και στο όρισμα.

48 5-48 ΠΑΡΑΔΕΙΓΜΑ #include #define N 100 void min_max(int a[], int n, int *max, int *min); int main() { int b[N], i, high, low; printf(“Enter %d numbers: “, N); for (i=0; i *max) *max = a[i]; else if (a[i] < *min) *min = a[i]; } /* Αυτή η συνάρτηση επιστρέφει το μέγιστο */ /* και το ελάχιστο στοιχείο ενός διανύσματος. */ Μεταβιβάζεται η διεύθυνση του b[0].

49 5-49 ΠΑΡΑΔΕΙΓΜΑ int main() { int x = 10, y = 20, number = 10; int a[] = {0, 1, 4, 9, 16}; int sum = 0, i, *p; printf("Initial values: %d %d\n", x, y); swap_by_value(x, y); printf("Call of swap by value: %d %d\n", x, y); swap_by_reference(&x, &y); printf("Call of swap by reference: %d %d\n", x, y); printf("Original value: %d\n", number); square(&number); printf("Square value: %d\n", number); //The next for loop simply sums the elements of array a[] // // using index i for (i=0; i < 5; i++) sum += a[i]; printf("The sum of the elements of array a is: %d\n", sum); //The next loop sums the elements of array a[] using pointer p sum = 0; for (p = a; p < &a[5]; p++) sum += *p; printf("The sum of the elements of array a is: %d\n", sum); return 0; } void swap_by_value(int a, int b) { int temp = a; a = b; b = temp; } void swap_by_reference(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } void square(int *nptr) { *nptr = (*nptr) * (*nptr); // square number }

50 5-50 const ΠΑΡΑΜΕΤΡΟΙ Όπως είπαμε, όταν ένα όρισμα μεταβιβάζεται σε μία συνάρτηση (και δεν είναι δείκτης), τότε η τιμή του αντιγράφεται σε μία προσωρινή μεταβλητή και χρησιμοποιείται στο σώμα της συνάρτησης. Όμως, όταν μεταβιβάζουμε ένα διάνυσμα (array) ως όρισμα τότε ουσιαστικά περνάμε ένα δείκτη. Σε μερικές όμως περιπτώσεις, θέλουμε να είμαστε σίγουροι ότι το διάνυσμα δεν θα αλλαχθεί. Τότε χρησιμοποιούμε τη λέξη κλειδί const στις παραμέτρους της συνάρτησης.

51 5-51 ΠΑΡΑΔΕΙΓΜΑ void init_zeros (int a[ ], int size) { int i; for (i = 0; i < size; i++) a[i] = 0; } void use_array(const int a[ ], int size, ….) { /* Ο μεταφραστής ελέγχει έαν αλλάζουμε την τιμή του */ /* διανύσματος a. */ }

52 5-52 ΔΕΙΚΤΕΣ ΚΑΙ ΠΙΝΑΚΕΣ Πίνακες δεικτών και δείκτες σε πίνακες int *b[10]; /* Πίνακας 10 δεικτών σε ακέραιους */ int (*b)[10]; /* Δείκτης σε πίνακα 10 ακεραίων */ Ας θυμηθούμε ότι οι πολυδιάστατοι πίνακες στη C καταχωρούνται στη μνήμη σαν μία γραμμή μετά την άλλη. int a[2][5]; int a[][5] = { {1, 2, 3, 4, 5}, {10, 20, 30, 40, 50} }; int *a[2];

53 5-53 ΠΑΡΑΔΕΙΓΜΑ #define NUM_ROWS 10 #define NUM_COLS 10 int a[NUM_ROWS][NUM_COLS]; int row, col; for(row=0; row< NUM_ROWS; row++) for(col=0; col< NUM_COLS; col++) a[row][col] = 0; Εναλλακτικά: int *p; for (p = &a[0][0]; p<= &a[NUM_ROWS-1][NUM_COLS-1]; p++) *p = 0;

54 5-54 ΔΕΙΚΤΕΣ ΣΕ ΣΥΝΑΡΤΗΣΕΙΣ Όπως έχουμε μέχρι τώρα δει, οι συναρτήσεις καλούνται με το όνομά τους. Όμως υπάρχει και ένας άλλος τρόπος κλήσης μιας συνάρτησης στη C!! Αυτός ο δεύτερος τρόπος χρησιμοποιεί τη διεύθυνση της συνάρτησης (invocation by function address). Καλώντας μια συνάρτηση με τη διεύθυνσή της και όχι με το όνομά της δίνει στον προγραμματιστή αρκετή ευελιξία. Ας υποθέσουμε το σενάριο όπου ο προγραμματιστής γνωρίζει τι θα πρέπει να κάνει η συνάρτηση που θέλει να καλέσει, αλλά δεν γνωρίζει το όνομά της! Η τεχνική αυτή χρησιμοποιείται επίσης όταν έχουμε μια συνάρτηση που κάνει κάτι γενικό, αλλά όταν «παραμετροποιηθεί» με μία άλλη συνάρτηση θα κάνει κάτι πιο εξειδικευμένο. Η πιο εξειδικευμένη λειτουργία έρχεται από την συνάρτηση που «παραμετροποιεί» την πιο γενική συνάρτηση.

55 5-55 ΠΑΡΑΔΕΙΓΜΑ Ας υποθέσουμε ότι έχουμε μια συνάρτηση που τυπώνει τα στοιχεία ενός διανύσματος. Υποθέτουμε ότι το διάνυσμα τελειώνει με το στοιχείο null. Όμως δεν γνωρίζουμε τον τύπο των στοιχείων του διανύσματος. Μια τέτοια συνάρτηση θα φαινόταν: void printArray(void *thisArray[]) { int n; void *nextElement; for (n=0; thisArray[n]; n++) nextElement = thisArray[n]; /* Εδώ θα θέλουμε να τυπώσουμε το στοιχείο */ /* ‘Όμως, επειδή δεν γνωρίζουμε τον τύπο των στοιχείων, */ /* δεν είμαστε σίγουροι τι κώδικα θα πρεπει να γράψουμε εδώ. */ }

56 5-56 ΠΑΡΑΔΕΙΓΜΑ Ας υποθέσουμε τώρα ότι περνάμε μια παράμετρο στη συνάρτηση printArray που είναι δείκτης σε συνάρτηση! Η γενική σύνταξη δεικτών σε συνάρτηση όταν περνάνε ως παράμετροι είναι (* ) (Τύπος_παραμ1 παραμ1, Τύπος_παραμ2 παραμ2,...) Η κλήση της συνάρτησης δείκτη γίνεται με τη σύνταξη (* ) (όρισμα, όρισμα2,...) Οπότε το παράδειγμά μας γίνεται:

57 5-57 ΠΑΡΑΔΕΙΓΜΑ void printAray(void *thisArray[], void (*printFunction) (void *thisElement)) { int n; for(n=0; thisArray[n]; n++) { (*printFunction) (thisArray[n]); } printf(“ \n”) } Ορίζουμε τώρα δύο συναρτήσεις printPerson και printFlavor void printPerson(char *thisPerson) { printf(“Person: %s\n”, thisPerson);} Και void printFlavor(char *thisFlavor) { printf(“Flavor: %s\n”, thisFlavor);}

58 5-58 ΠΑΡΑΔΕΙΓΜΑ - ΜΑΙΝ #include void printPerson(char *thisPerson); void PrintFlavor(char *thisFlavor); void PrintArray(void *thisArray[], void (*printFunction)(void *thisElenment) ); int main() { char *flavors[4] = {“Choco”, “Strawb”, “Vanilla”, 0}; char *people[5] = {“John”, “Kostas”, “Jasmine”, “Mary”, 0}; printArray(flavors, printFlavor); printArray(people, printPerson); return (0); }

59 5-59 ΠΑΡΑΔΕΙΓΜΑ Το αποτέλεσμα του προγράμματος είναι να τυπωθεί στο stdout το μήνυμα: Flavor: Choco Flavor: Strawb Flavor: Vanilla Person: John Person: Kostas Person: Jasmine Person: Mary


Κατέβασμα ppt "5-1 ΜΑΘΗΜΑ 5 ο Δείκτες, Πίνακες και Δείκτες, Δείκτες σε Συναρτήσεις."

Παρόμοιες παρουσιάσεις


Διαφημίσεις Google