Κατέβασμα παρουσίασης
Η παρουσίαση φορτώνεται. Παρακαλείστε να περιμένετε
ΔημοσίευσεThea Nikas Τροποποιήθηκε πριν 10 χρόνια
1
Δείκτες, Πίνακες σε Δείκτες, Δείκτες σε Συναρτήσεις
ΜΑΘΗΜΑ 5ο Δείκτες, Πίνακες σε Δείκτες, Δείκτες σε Συναρτήσεις
2
ΚΛΗΣΗ ΤΗΣ ΣΥΝΑΡΤΗΣΗΣ Μία συνάρτηση μπορεί να κληθεί με δύο τρόπους:
χρησιμοποιώντας το όνομά της και τη λίστα των ορισμάτων της σε μια αυτόνομη πρόταση π.χ. wait(12); ……………………. Εδώ θεωρούμε ότι η συνάρτηση δεν επιστρέφει κάποιο αποτέλεσμα αλλά έχει κάποιες παράπλευρη λειτουργίες 2. χρησιμοποιώντας το όνομά της και τη λίστα των ορισμάτων της σε μια έκφραση π.χ. k = sum(2, 10); Εδώ θεωρούμε ότι η συνάρτηση επιστρέφει κάποιο αποτέλεσμα
3
ΕΠΙΣΤΡΟΦΗ ΑΠΟΤΕΛΕΣΜΑΤΟΣ
‘Οταν μία συνάρτηση καλείται: Ο κώδικας στο σημείο κλήσης της συνάρτηση σταματά Ο κώδικας στο σώμα της συνάρτησης εκτελείται Εάν η συνάρτηση που καλείται έχει παραμέτρους οι παράμετροι παίρνουν τιμές από τις τιμές των ορισμάτων στο σημείο κλήσης Τα ορίσματα και οι παράμετροι συνδυάζονται ανάλογα με τη θέση τους και το τύπο τους (το πρώτο όρισμα με τη πρώτη παράμετρο, το δεύτερο όρισμα με τη δεύτερη παράμετρο κ.ο.κ.) Το αποτέλεσμα της συνάρτησης επιστρέφεται στο σημείο κλήσης Το αποτέλεσμα της συνάρτησης επιστρέφεται με την εντολή return <έκφραση> Ο κώδικας ακριβώς μετά από το σημείο κλήσης συνεχίζει την λειτουργία του
4
ΜΗΧΑΝΙΣΜΟΣ ΚΛΗΣΗΣ Ο μηχανισμός κλήσης μιάς συνάρτησης βασίζεται:
Στην συσχέτιση του ονόματος της συνάρτησης στο σημείο ορισμού της με το όνομα της συνάρτησης στο σημείο κλήσης Στη συσχέτιση του πλήθους των παραμέτρων της συνάρτησης στο σημείο ορισμού της με το πλήθος των ορισμάτων της συνάρτησης στο σημείο κλήσης, και το αντιστοιχία του τύπου της κάθε παραμέτρου στο σημείο ορισμού με το τύπο του κάθε ορίσματος της συνάρτησης στο σημείο κλήσης Στη συσχέτιση του τύπου επιστροφής της συνάρτησης στο σημείο ορισμού της με τη χρήση του αποτελέσματος της συνάρτησης στο έκφραση που υπάρχει η κλήση της συνάρτησης
5
ΠΑΡΑΔΕΙΓΜΑ Η διοχέτευση των παραμέτρων
#include <stdio.h> 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
ΜΕΘΟΔΟΙ ΔΙΟΧΕΤΕΥΣΗΣ ΟΡΙΣΜΑΤΩΝ
Υπάρχουν δύο μέθοδοι διοχέτευσης ορισμάτων Με μετάδοση τιμής – απόθεση (By value) Με αναφορά - απ’ ευθείας χρήση (By reference) Η μέθοδος με μετάδοση τιμής (By value) βασίζεται στην ιδέα ότι ό κώδικας (σώμα) της συνάρτησης λαμβάνει και χρησιμοποιεί ένα αντίγραφο του ορίσματος το οποίο ισχύει όσο η συνάρτηση εκτελείται. Αυτό σημαίνει ότι η τιμή της μεταβλητής του ορίσματος στο σημείο κλήσης είναι η ίδια και μετά την επιστροφή της συνάρτησης μιας και μόνο ένα αντίγραφο της χρησιμοποιείται Η μέθοδος με απ’ ευθείας χρήση (By reference) βασίζεται στην ιδέα ότι ό κώδικας (σώμα) της συνάρτησης λειτουργεί σαν να λαμβάνει και χρησιμοποιεί την ίδια την μεταβλητή του ορίσματος. Αυτό σημαίνει ότι η τιμή της μεταβλητής του ορίσματος στο σημείο κλήσης μπορεί να είναι διαφορετική μετά την επιστροφή της συνάρτησης εάν η συνάρτηση στο σώμα της μεταβάλει την τιμή της παραμέτρου που αντιστοιχεί στο όρισμα που διοχετεύεται με τη μέθοδο της απ’ ευθείας χρήσης
7
ΜΕΘΟΔΟΣ ΜΕ ΜΕΤΑΔΟΣΗ ΤΙΜΗΣ (By Value)
Όταν μία συνάρτηση καλείται οι παράμετροι λαμβάνουν την τιμή των ορισμάτων στο σημείο κλήσης Η κάθε παράμετρος στο σημείο ορισμού δηλώνει τον τύπο του κάθε ορίσματος που περιμένει να συνδυαστεί μαζί του Εάν ο τύπος του ορίσματος και της αντίστοιχης παραμέτρου είναι συμβατοί (π.χ.float και double) τότε ο μεταφραστής (compiler) παράγει ένα αντίγραφο της τιμής του ορίσματος και το αποθέτει στη μεταβλητή της παραμέτρου ώστε να εκτελεσθεί η συνάρτηση. Εάν οι τύποι του ορίσματος και την αντίστοιχης παραμέτρου δεν είναι συμβατοί τότε ο μεταφραστής παράγει ένα μήνυμα λάθους κατά τη διάρκεια της μετάφρασης (compilation) Από τη στιγμή που η τιμή έχει του ορίσματος έχει διοχετευτεί τότε το όρισμα και η αντίστοιχη παράμετρος δεν έχουν καμία σχέση, οπότε ακόμη και εάν η μεταβλητή της παραμέτρου τροποποιηθεί δεν έχει κανένα αποτέλεσμα στη τιμή του ορίσματος Όταν η συνάρτηση «επιστρέψει» με τη χρήση της εντολής return, η τον τερματισμό των εντολών του σώματος της, τα αντίγραφα των ορισμάτων παύουν να ισχύουν
8
ΠΑΡΑΔΕΙΓΜΑ ΜΕ ΑΠΟΘΕΣΗ ΤΙΜΗΣ (BY VALUE) - 1
main() let a, b; BEGIN a 10.0; while (a < 12.0) b plus(a); print(“a, b”); a = a+1; END return 1; plus(VAL x) BEGIN x = x + x; return (x); END
9
ΠΑΡΑΔΕΙΓΜΑ (ΜΕ ΑΠΟΘΕΣΗ ΤΙΜΗΣ) - 2
ΠΑΡΑΔΕΙΓΜΑ (ΜΕ ΑΠΟΘΕΣΗ ΤΙΜΗΣ) - 2 main plus a = 10.0 Διοχέτευση ορίσματος. Η τιμή της χ είναι αντίγραφο της τιμής της a plus(a) x = 10.0 x = x + x x = 20.0 b = 20.0 return (x); Η μεταβλητή χ,παύει να ισχύει */ a = 11.0 plus(a) Διοχέτευση ορίσματος. Η τιμή της χ είναι αντίγραφο της τιμής της a x = 11.0 x = x + x x = 22.0 return (x); Η μεταβλητή χ,παύει να ισχύει */
10
ΜΕΘΟΔΟΣ ΜΕ ΑΝΑΦΟΡΑ - ΑΠ’ΕΥΘΕΙΑΣ ΧΡΗΣΗ (By Reference)
Από τη στιγμή που η τιμή έχει του ορίσματος έχει διοχετευτεί τότε το όρισμα και η αντίστοιχη παράμετρος αναφέρονται στην ίδια θέση μνήμης (aliasing), οπότε εάν η μεταβλητή της παραμέτρου τροποποιηθεί τότε μεταβάλλεται και η τιμή του ορίσματος. Αυτή η μεταβολή παραμένει και μετά την έξοδο της συνάρτησης (θυμηθείτε ότι η παράμετρος και το όρισμα είναι η ίδια θέση μνήμης. Ουσιαστικά δεν υπάρχει διαφορά μεταξύ ορίσματος και παραμέτρου. Απλά χρησιμοποιούμε την ίδια θέση μνήμης με διαφορετικό μνημονικό όνομα (αυτά του ορίσματος και της αντίστοιχης παραμέτρου)
11
ΠΑΡΑΔΕΙΓΜΑ ΜΕ ΑΠ’ΕΥΘΕΙΑΣ ΧΡΗΣΗ (BY REFERENCE) - 1
Αυτή ΔΕΝ είναι η σύνταξη της C – Απλό παράδειγμα main() BEGIN let a; a = 10.0; plus(a); return 1; END double plus(REF x) BEGIN x = x + x; END
12
ΠΑΡΑΔΕΙΓΜΑ (ΜΕ ΑΠ’ΕΥΘΕΙΑΣ ΧΡΗΣΗ) - 2
ΠΑΡΑΔΕΙΓΜΑ (ΜΕ ΑΠ’ΕΥΘΕΙΑΣ ΧΡΗΣΗ) - 2 main plus a = 10.0 plus(a) Διοχέτευση ορίσματος. x = 10.0 x= x + x x = 20.0 a = 20.0 Διοχέτευση ορίσματος plus(a) x = 20.0 x= x + x x = 40.0 a = 40.0 Οι μεταβλητές a, x, αναφέρονται στην ίδια θέση μνήμης οπότε κάθε μεταβολή της τιμής της μίας συνεπάγεται αλλαγή της τιμής της άλλης.
13
ΑΝΑΔΡΟΜΗ Μία συνάρτηση μπορεί να καλεί τον εαυτό της! Αυτή η τεχνική λέγεται αναδρομή. Η αναδρομή είναι σημαντική τεχνική για τον υπολογισμό αναδρομικών συναρτήσεων Ένα τυπικό παράδειγμα αναδρομικής συνάρτησης είναι αυτής που χρησιμοποιείται για τον υπολογισμό του παραγοντικου γινομένου Σημαντικό: Μια αναδρομικη συναρτηση θα πρεπει: Να έχει μια συνθήκη τερματισμού της αναδρομής Να καλέι αναδρομικά τη συνάρτηση με τέτοιο τρόπο ώστε η συνθήκη τερματισμού καποια στιγμή (μετά απο n αναδρομές) θα γίνει αληθής
14
ΠΩΣ ΔΟΥΛΕΥΕΙ Η ΑΝΑΔΡΟΜΗ - ΠΕΡΙΒΑΛΟΝ ΔΙΑΔΙΚΑΣΙΑΣ
Πριν μιλήσουμε για το πώς δουλεύει η αναδρομη ας πούμε δυό λόγια για το περιβάλλον μιας συνάρτησης (γενικότερα μιάς διαδικασίας) Stack Text Heap Aux. Memory 2 N Το περιβάλον μίας διαδικασίας (process);όπως λιγο-πολύ και το περιβαλον μιάς συνάρτησης αποτελείται απο το (text) κείμενο του κώδικα σε γλώσσα μηχανής), (heap) θέσεις μνήμης για τις μεταβλητές της συνάρτησης, (stack) μία δομή για την χρήση των παραμέτρων και των μεταβλητών, και (aux. memory) βοηθητική μνήμη.
15
ΠΩΣ ΔΟΥΛΕΥΕΙ Η ΑΝΑΔΡΟΜΗ
Η αναδρομή δουλεύει ώς εξής: Η συνάρτηση όταν καλεί τον εαυτό της: Σταματά τη λειτουργία της στο σημείο κλήσης (όπως άλλωστε θα περιμέναμε) Το περιβάλλον της συνάρτησης (όλες οι μεταβλητές και οι τιμές τους, καθώς και το σημέιο κλήσης (δηλαδή ποιά θα είναι η επόμενη εντολή όταν η συνάρτηση θα συνεχίσει τη λειτουργία της Καλέι τον εαυτό της (την ίδια συνάρτηση) σαν να ήταν μια οποιαδήποτε άλλη συνάρτηση. Η κλήση γίνεται σ’ένα νέο περιβάλλον κλήσης. Μη ξεχνάμε όμώς ότι κάποιο ορίσματα μπορεί να διοχετεύονται με απ’ευθείας χρήση
16
ΠΑΡΑΔΕΙΓΜΑ ΑΝΑΔΡΟΜΗΣ - 1
Το παραγοντικό γινόμενο ενός αριθμού x υπολογίζεται σαν το γινόμενο x! = x * (x-1) * (x-2) * (x-3) …. *1 = x * (x-1)! όπου 0! = 1 Δηλαδή χ! = Εάν x = 0 x * (x-1)! Εάν x > 0 {
17
ΠΑΡΑΔΕΙΓΜΑ ΑΝΑΔΡΟΜΗΣ - 2
#include <stdio.h> 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
ΠΑΡΑΔΕΙΓΜΑ ΑΝΑΔΡΟΜΗΣ - 3
factorial(5) factorial(5) Επιστρέφεται 5 * 24 = 120 = 5! 5* factorial(4) 5* factorial(4) Επιστρέφεται 4 * 6 = 24 = 4! 4* factorial(3) 4* factorial(3) Επιστρέφεται 3 * 2 = 6 = 3! 3* factorial(2) 3* factorial(2) Επιστρέφεται 2 * 1 = 2 = 2! 2* factorial(1) 2* factorial(1) Επιστρέφεται 1 1 Αναδρομικές Κλήσεις
19
ΓΕΝΙΚΑ ΠΕΡΙ ΜΕΤΑΒΛΗΤΩΝ
Όπως έχουμε αναφέρει, ένα πρόγραμμα είναι μία ακολουθία εντολών, που χρησιμοποιούν μεταβλητές για την παροδική αποθήκευση των δεδομένων που χρειάζεται το πρόγραμμα (οι εντολές του προγράμματος για την ακρίβεια) για τη λειτουργία του Η κάθε μεταβλητή ουσιαστικά είναι ένα συμβολικό όνομα το οποίο αναφέρεται σε κάποιο δεδομένο (data) Τα χαρακτηριστικά μιάς μεταβλητής θα μπορούσαμε να πούμε οτι είναι: Το συμβολικό όνομά της Ο τύπος της, που επίσης ορίζει το μέγεθος της μνήμης που αναφέρεται («καταλαμβάνει») η μεταβλητή Η διεύθυνση της μνήμης από την οποία αρχίζουν τα δεδομένα στα οποία αναφέρεται η μεταβλητή. Για παράδειγμα εάν υποθέσουμε ότι η μεταβλητή x αρχίζει από τη θέση μνήμης 1000 και είναι τύπου unsigned int ο οποίος στην αρχιτεκτονική Pentium 3 καταλαμβάνει 4 bytes, τότε τα δεδομένα στα οποία αναφέρεται η μεταβλητή x βρίσκονται από την θέση μνήμης 1000 μέχρι και τη θέση μνήμης 1003 (θεωρώντας ότι μία θέση μνήμης είναι ένα byte).
20
ΕΙΣΑΓΩΓΗ ΣΤΟΥΣ ΔΕΙΚΤΕΣ (pointers)
Οι μεταβλητές που έχουμε δει μέχρι τώρα: Είναι ή απλού τύπου (int, float, double, char), ή πιο σύνθετου τύπου όπως διανύσματα τα οποία όμως και πάλι τα είδαμε στην απλή τους μορφή δηλαδή διανύσματα και πίνακες από στοιχεία απλών τύπων Η διεύθυνση της μνήμης στην οποία βρίσκονται τα δεδομένα στα οποία αναφέρεται η μεταβλητή δεν μας ενδιέφεραν Το ερώτημα είναι τι πρέπει να χρησιμοποιήσουμε εάν θελήσουμε να αναφερθούμε σε θέσεις μνήμης αντί να αναφερθούμε απλά σε δεδομένα Είναι προφανές ότι πρέπει να χρησιμοποιήσουμε πάλι κάποιο είδος μεταβλητής (άλλωστε όπως έχουμε δει το πρόγραμμα δουλεύει με μεταβλητές). Όμως αυτές οι μεταβλητές δεν παίρνουν οποιαδήποτε τιμή και ανάλογα με το τύπο τους όπώς οι άλλες «κοινές» μεταβλητές. Οι τιμές αυτών των «ειδικών» μεταβλητών είναι πάντα θέσεις μνήμης. Θα μπορούσαμε να πούμε ότι οι τιμές αυτές είναι σαν κάποιοι ειδικοί ακέραιοι αριθμοί που αναφέρονται σε θέσεις δυναμικής μνήμης του Η/Υ μας. Αυτές οι μεταβλητές που οι τιμές τους είναι θέσεις μνήμης του Η/Υ ονομάζονται δείκτες (pointers) Είναι επίσης προφανές ότι χρειαζόμαστε κάποιο τρόπο να ξεχωρίσουμε τις δηλώσεις των «απλών» μεταβλητών, από τις δηλώσεις αυτών των «ειδικών» μεταβλητών που ονομάζονται δείκτες
21
ΕΙΣΑΓΩΓΗ ΣΤΟΥΣ ΔΕΙΚΤΕΣ (pointers)
Πριν όμως προχωρήσουμε άς δούμε τί αντιπροσωπεύουν οι δείκτες Στους περισσότερους μοντέρνους υπολογιστές η δυναμική μνήμη (RAM) είναι κατανεμημένη σε «κελιά» μήκους ενός byte κάθε ένα από τα οποία συνήθως μπορεί να αποθηκεύσει 8 bits Δηλαδή ένας Η/Υ με 16ΜΒ RAM, έχει περίπου 16 εκατομμύρια bytes Κάθε byte έχει μία μοναδική διεύθυνση ώστε να ξεχωρίζει από κάθε άλλο byte στη δυναμική μνήμη Εάν υποθέσουμε ότι η μνήμη έχει μέγεθος κ bytes τότε οι διευθύνσεις είναι αριθμοί από το 0 μέχρι το (κ-1)
22
ΠΑΡΑΔΕΙΓΜΑ Διεύθυνση Περιεχόμενο 1 2 3 4 5 κ-1
23
ΠΑΡΑΔΕΙΓΜΑ Εάν υποθέσουμε ότι η μεταβλητή x είναι ενός τύπου που χρειάζεται 4 bytes τότε η διεύθυνσή της είναι η διεύθυνση του πρώτου byte της. Η τιμή της x είναι 2 (ουσιαστικά είναι η τιμή 2000 2001 2002 2003
24
ΕΙΣΑΓΩΓΗ ΣΤΟΥΣ ΔΕΙΚΤΕΣ (pointers)
Είπαμε προηγουμένως ότι χρειαζόμαστε κάποιο τρόπο να ξεχωρίσουμε τις δηλώσεις των «απλών» μεταβλητών, από τις δηλώσεις αυτών των «ειδικών» μεταβλητών που ονομάζονται δείκτες Η εντολή που δηλώνει μια μεταβλητή σαν δείκτη είναι <τύπος> * <όνομα μεταβλητής>
25
ΠΑΡΑΔΕΙΓΜΑ int a_var; int * a_ptr_var;
Δηλώνει μία «ειδική» μεταβλητή δείκτη με όνομα a_ptr_var Τι όμως σημαίνει η a_ptr_var? Σημαίνει μία μεταβλητή : στην οποία μπορεί να αναφερθούμε στο προγραμμά μας με το συμβολικό όνομα a_ptr_var, η μεταβλητή αυτή παίρνει τιμές που είναι διευθύνσεις μνήμης, και αυτές οι διευθύνσεις μνήμης πρέπει να αποθηκεύουν δεδομένα που είναι τύπου int
26
ΔΕΙΚΤΕΣ - ΣΧΗΜΑΤΙΚΑ Διεύθυνση : 2000 p i Εδώ η p είναι μεταβλητή δείκτη και η τιμή της είναι μια διεύθυνση (στη προκειμένη περίπτωση η διεύθυνση αυτή περιέχει τα δεδομένα της μεταβλητής i). Εδώ η τιμή της p είναι η διεύθυνση 2000. Εάν η p είχε δηλωθεί σαν int * p; τότε η i θα έχει δηλωθεί σαν int i;
27
ΔΕΙΚΤΕΣ - ΣΧΗΜΑΤΙΚΑ Διεύθυνση : 4000 Διεύθυνση : 2000 p i Προφανώς και η μεταβλητή δείκτη p έχει και αυτή μία διεύθυνση!! Στις Περισσότερες περιπτώσεις όμως δεν μας ενδιαφέρει αυτή η διεύθυνση της μεταβλητής δείκτη p. Απλά ας σκεφτούμε ότι μπορούμε να έχουμε μεταβλητές δείκτη σε δείκτες !!!!! Ευτυχώς σπάνια θα χρειαστούμε κάτι τέτοιο.
28
ΠΑΡΑΔΕΙΓΜΑ int *p; /* Δείκτης μόνο σε int */
float *q /* Δείκτης μόνο σε float */ char * r /* Δείκτης μόνο σε char */ Δεν υπάρχουν περιορισμοί σε τι είδους τύπο ένας δείκτης «δείχνει»
29
ΤΕΛΕΣΤΕΣ ΣΧΕΤΙΚΟΙ ΜΕ ΔΕΙΚΤΕΣ
Έχουμε δύο τελεστές σχετκούς με δείκτες: Τελεστής διεύθυνσης (address operator) & Τελεστής έμμεσης διεύθυνσιοδότησης (indirection) *
30
ΤΕΛΕΣΤΗΣ ΔΙΕΥΘΥΝΣΗΣ & Όταν δηλώνουμε μία μεταβλητή δείκτη το μόνο που κάνουμε είναι να κρατάμε μνήμη αρκετά μεγάλη για την τιμή της μεταβλητής δείκτη (όχι την τιμή της μεταβλητής που «δείχνει» η μεταβλητή δείκτη) Στην αρχή η μεταβλητή δείκτη δεν έχει καμία τιμή (όπως άλλωστε θα περιμέναμε και από κάθε άλλη δήλωση μεταβλητής χωρίς αρχική τιμή) Ένας τρόπος να θέσουμε μία αρχική τιμή σε μία μεταβλητή δείκτη είναι να δώσουμε τη διεύθυνση μιας μεταβλητής του προγράμματος Η διεύθυνση μιας μεταβλητής ενός προγράμματος υπολογίζεται από το τελεστή διεύθυνσης & Δηλαδή εάν: int i; Τότε &i επιστρέφει τη διεύθυνση της μεταβλητής i Οπότε εάν έχουμε τις δηλώσεις: int *p; Μπορούμε να αποθέσουμε τη διεύθυνση της μεταβλητής i, στη μεταβλητή p p = &i;
31
ΤΕΛΕΣΤΗΣ ΕΜΜΕΣΗΣ ΔΙΕΥΘΥΝΣΙΟΔΟΤΗΣΗΣ *
Αντίστροφα με τον τελεστή διεύθυνσης &, ο τελεστής έμμεσης διευθυνσιοδότησης *, επιστρέφει την τιμή των δεδομένων που βρίσκονται σε μια θέση μνήμης Οπότε εάν: int *p; int i = 10; p = &i; Τότε η έκφραση *p επιστρέφει τη τιμή που βρίσκεται στη θέση μνήμης που η μεταβλητή δείκτη p «δείχνει» (δηλαδή τη διεύθυνση της μεταβλητής i). Δηλαδή η έκφραση *p επιστρέφει τη τιμή 10.
32
ΔΕΙΚΤΕΣ ΣΑΝ ΠΑΡΑΜΕΤΡΟΙ
Μία από τις σημαντικές χρήσεις των δεικτών είναι ότι επιτρέπουν την διοχέτευση ορισμάτων σε συναρτήσεις με απ’ ευθείας χρήση (by reference). Αυτό σημαίνει ότι αντί να περνάμε ένα αντίγραφο του ορίσματος στην αντίστοιχη παράμετρο, περνάμε τη διεύθυνση του ορίσματος. Οπότε, κάθε αλλαγή στην παράμετρο (στο σώμα της συναρτήσεις) ουσιαστικά μεταβάλει τα περιεχόμενα στη θέση μνήμης του ορίσματος!
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; Η παράμετρος χ λαμβάνει σαν τιμή τη διεύθυνση a
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
ΔΕΙΚΤΕΣ ΚΑΙ scanf Εάν ανατρέξουμε στη χρήση της scanf θα προσέξουμε ότι χρησιμοποιούσαμε το τελεστή διεύθυνσης! π.χ. scanf(“%d”, &i); Σ’ αυτή τη περίπτωση, η συνάρτηση scanf λαμβάνει σαν δεύτερο όρισμα ένα δείκτη (ουσιαστικά τη διεύθυνση της μεταβλητής που θέλουμε να της αποδώσουμε τιμή – συγκεκριμένα εδώ τη μεταβλητή i) Τι γίνεται όμως εάν η μεταβλητή π.χ. (i) είναι δείκτης? H scanf θα διαβάσει το δεδομένο και (δυστυχώς) χωρίς προειδοποίηση θα το αποθέσει σαν θέση μνήμης σαν τιμή του δείκτη Γενικά όταν χρησιμοποιούμε δείκτες σαν παραμέτρους θα πρέπει να είμαστε σίγουροι ότι τα ορίσματα είναι διευθύνσεις και όχι οποιοσδήποτε ακέραια τιμή Η χρήση των πρωτότυπων μας επιτρέπει να είμαστε σίγουροι ότι ο μεταφραστής θα καταλάβει τέτοιου είδους λάθη στις περισσότερες περιπτώσεις
36
ΠΑΡΑΔΕΙΓΜΑ int i, *p; p = &i;
scanf(“%d”, p); /* διαβάζει ακέραιο και τόν */ /* αποθέτει στη μεταβλητή i !! */ scanf(“%d”, &p); /* Λάθος – Διαβάζει ακέραιο */ /* και τον αποθέτει στη μεταβλητή */ /* p !!! Οπότε η τιμή που διαβάζεται γίνεται */ /* η τιμή του p, δηλαδή η p «δείχνει» σε κάποια */ /* υποτιθέμενη θέση μνήμης */
37
ΠΑΡΑΔΕΙΓΜΑ #include <stdio.h> #define N 100
void max_min(int a[], int N, *max, *min) void main() { int b[N], i, high, low; printf(“Enter %d numbers: “, N); for (i=0; i<N, i++) scanf(“%d”, &b[i]); max_min(b, N, &high, &low); printf(“The largest number is: %d”, high) printf(“The smallest numebt is: %d”, low); return 0; } void min_max(int a[], int size, int *max, int *min) { int i; *max = *min = a[0]; for(i = 1, i < size; i++) { if (a[i] > *max) *max = a[i]; else if (a[i] < *min) *min = a[i]; } /* Αυτή η συνάρτηση επιστρέφει το μέγιστο */ /* και το ελάχιστο στοιχείο ενός διανύσματος */
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
ΣΗΜΕΙΩΣΗ Μην επιστρέφετε δείκτη σαν αποτέλεσμα συνάρτησης σε τοπική μεταβλητή π.χ. int *fcn(void) { int i; …….. return &i; } Η μεταβλητή i δεν θα υπάρχει μετά το τέλος της συνάρτησης οπότε ο δείκτης (η διεύθυνση του i) θα είναι άκυρος
40
ΔΕΙΚΤΕΣ ΚΑΙ ΔΙΑΝΥΣΜΑΤΑ
Η σχέση των διανυσμάτων και των δεικτών είναι πολύ σημαντική Εάν θυμηθούμε τη δήλωση ενός διανύσματος float a[10] /* Μονοδιάστατο διάνυσμα 10 στοιχείων τύπου float */ Ουσιαστικά υποδηλώνει τη διεύθυνση του πρώτου του στοιχείου a[0], και μετά τόσες θέσεις μνήμης όσες να χωρέσουν 10 float αριθμοί. Αυτό είναι πως η C καταχωρεί διανύσματα Οπότε *a = 8; /* Θέτει την τιμή 8 στο a[0] */ a0 a1 a9
41
ΔΕΙΚΤΕΣ ΚΑΙ ΔΙΑΝΥΣΜΑΤΑ
int a[10], *p; p = a; *p = 5 /* To a[0] έχει τιμή 5 */ p a[0] a[1]
42
ΑΡΙΘΜΗΤΙΚΗ ΔΕΙΚΤΩΝ Μπορούμε να προσθέσουμε ένα ακέραιο αριθμό από ένα δείκτη, να αφαιρέσουμε ένα ακέραιο αριθμό από ένα δείκτη, ή να αφαιρέσουμε ένα δείκτη από έναν άλλο ! Όταν έχουμε να κάνουμε με διανύσματα, αυτές οι πράξεις γίνονται πολύ ενδιαφέρουσες
43
ΠΑΡΑΔΕΙΓΜΑ int a[10], *p, *q, *w; p = &a[0];
*p = 5 /* To a[0] έχει τιμή 5 */ q = p + 3; w = q-1; p a[0] a[1] p w q a[0] a[1] a[2] a[3] a[4]
44
ΑΦΑΙΡΕΣΗ ΔΕΙΚΤΩΝ Ας θεωρήσουμε int a[10], *p, *q, i; p = &a[5];
q = &a[3]; i = p - q; /* Η τιμή της μεταβλητής i είναι 2 */ i = q - p; /* Η τιμή της μεταβλητής i είναι -2 */ Σημείωση: Η αριθμητική σε δείκτες έχει νόημα συνήθως όταν χρησιμοποιείται σε διανύσματα όπως στα παραδείγματα παραπάνω
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
ΠΑΡΑΔΕΙΓΜΑ include <stdio.h> #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
ΔΙΑΝΥΣΜΑΤΑ ΣΑΝ ΟΡΙΣΜΑΤΑ
Όταν το όνομα ενός διανύσματος χρησιμοποιείται σαν ορίσμα σε μία συνάρτηση διοχετεύεται με απ΄ευθείας χρήση (by reference), σαν να ήταν δείκτης Δηλαδή οποιαδήποτε αλλαγή στην αντίστοιχη παράμετρο στο σώμα της συνάρτησης επιφέρει αλλαγή και στο όρισμα
48
ΠΑΡΑΔΕΙΓΜΑ #include <stdio.h> #define N 100
void min_max(int a[], int N, *max, *min); int main() { int b[N], i, high, low; printf(“Enter %d numbers: “, N); for (i=0; i<N, i++) scanf(“%d”, &b[i]); min_max(b, N, &high, &low); printf(“The largest number is: %d”, high) printf(“The smallest numebt is: %d”, low); return 0; } Διοχετεύεται η διεύθυνση του b[0] void min_max(int a[ ], int size, int *max, int *min) { int i; *max = *min = a[0]; for(i = 1, I < size; i++) { if (a[i] > *max) *max = a[i]; else if (a[i] < *min) *min = a[i]; } /* Αυτή η συνάρτηση επιστρέφει το μέγιστο */ /* και το ελάχιστο στοιχείο ενός διανύσματος */
49
const ΠΑΡΑΜΕΤΡΟΙ Όπως είπαμε, όταν ένα όρισμα διοχετεύεται σε μία συνάρτηση (και δεν είναι δείκτης) τότε η τιμή του αντιγράφεται σε μία προσωρινή μεταβλητή, και χρησιμοποιείται στο σώμα της συνάρτησης Όμως, όταν διοχετεύουμε ένα διάνυσμα (διάταξη) σαν όρισμα τότε ουσιαστικά περνάμε ένα δείκτη Σε μερικές όμως περιπτώσεις, θέλουμε να είμαστε σίγουροι ότι το διάνυσμα δεν θα αλλαχθεί. Τότε χρησιμοποιούμε τη λέξη κλειδί const στις παραμέτρους της συνάρτησης
50
ΠΑΡΑΔΕΙΓΜΑ 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 */
51
ΔΕΙΚΤΕΣ ΚΑΙ ΠΙΝΑΚΕΣ Απλά ας θυμηθούμε ότι τα πολυδιάστατα διανύσματα στη C καταχωρούνται στη μνήμη σαν μία γραμμή μετά την άλλη Σημαντικό: Για πίνακες, σαν όρισμα περνάει το πρώτο στοιχείο a[0]. Δηλαδή, find_largest(a, NUM_COLS * NUM_ROWS); find_largest(a[0], NUM_COLS * NUM_ROWS);
52
ΠΑΡΑΔΕΙΓΜΑ #define NUM_ROWS 10 #define NUM_COLS 10
int a[NUM_ROWS][NUM_COLS]; int row, col; for(row=0; row<10; row++) for(col=0; col<10; col++) a[row][col] = 0; Εναλλακτικά int *p; for(p = &a[0][0]; p<= &a[NUM_ROWS-1][NUM_COLS-1]; p++ *p = 0;
53
ΠΑΡΑΔΕΙΓΜΑ void main() { int x = 10, y = 20, number = 10;
int a[] = {0, 1, 4, 9, 16}; int sum = 0, i, *p; int data[7] = {3, -4, 35, 2, 8, -5, 20}; 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 largest value of array data is: %d\n", maximum(data, 7)); } 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
54
ΔΕΙΚΤΕΣ ΣΕ ΣΥΝΑΡΤΗΣΕΙΣ
Όπως έχουμε μέχρι τώρα δει, οι συναρτήσεις καλούνται με το όνομά τους. Όμως υπάρχει και ένας άλλος τρόπος κλήσης μιας συνάρτησης στη C!! Αυτός ο δεύτερος τρόπος χρησιμοποιεί τη διεύθυνση της συνάρτησης (invocation by function address). Καλώντας μια συνάρτηση με τη διεύθυνσή της και όχι με το όνομά της δίνει στον προγραμματιστή αρκετή ευελιξία. Ας υποθέσουμε το σενάριο όπου ο προγραμματιστής γνωρίζει τι θα πρέπει να κάνει η συνάρτηση που θέλει να καλέσει, αλλά δεν γνωρίζει το όνομά της! Η τεχνική αυτή χρησιμοποιείται επίσης όταν έχουμε μια συνάρτηση που κάνει κάτι γενικό αλλά όταν «παραμετροποιηθεί» με μία άλλη συνάρτηση θα κάνει κάτι πιο εξειδικευμένο. Η πιο εξειδικευμένη λειτουργία έρχεται από την συνάρτηση που «παραμετροποιεί» την πιο γενική συνάρτηση.
55
ΠΑΡΑΔΕΙΓΜΑ Ας υποθέσουμε ότι έχουμε μια συνάρτηση που τυπώνει τα στοιχεία ενός διανύσματος. Υποθέτουμε ότι το διάνυσμα τελειώνει με το στοιχείο null. Όμως δεν γνωρίζουμε τον τύπο των στοιχείων του διανύσματος. Μια τέτοια συνάρτηση θα φαινόταν: void printArray(void *thisArray[]) { int n; void *nextElement; for (n=0; thisArray[n]; n++) nextElement = thisArray[n]; /* Εδώ θα θέλουμε να τυπώσουμε το στοιχείο */ /* ‘Ομως επειδή δεν γνωρίζουμε τον τύπο των στοιχείων */ /* δεν είμαστε σίγουροι τι κώδικα θα πρεπει να γράψουμε εδώ */ }
56
(* <Όνομα_δείκτη> ) (‘ορισμα1, όρισμα2, ...)
ΠΑΡΑΔΕΙΓΜΑ Ας υποθέσουμε τώρα ότι περνάμε μια παράμετρο στη συνάρτηση printArray που είναι δείκτης σε συνάρτηση! Η γενική σύνταξη δεικτών σε συνάρτηση όταν περνάνε σαν παράμετροι είναι <Τύπος_επιστροφής> (* <Όνομα_δείκτη> ) (Τύπος_παραμ1, παραμ1, Τύπος_παραμ2, ...) Η κλήση της συνάρτησης δείκτη γίνεται με τη σύνταξη (* <Όνομα_δείκτη> ) (‘ορισμα1, όρισμα2, ...) Οπότε το παράδειγμά μας γίνεται:
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
ΠΑΡΑΔΕΙΓΜΑ - ΜΑΙΝ #include <stdio.h>
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
ΠΑΡΑΔΕΙΓΜΑ Flavor: Choco Flavor: Strawb Flavor: Vanilla --------------
Το αποτέλεσμα του προγράμματος είναι να τυπωθεί στο stdout το μήνυμα Flavor: Choco Flavor: Strawb Flavor: Vanilla Person: John Person: Kostas Person: Jasmine Person: Mary
Παρόμοιες παρουσιάσεις
© 2024 SlidePlayer.gr Inc.
All rights reserved.