ΔΟΜΗΜΕΝΟΣ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΣ Μάθημα 7ο Τμήμα Διοίκησης Επιχειρήσεων Παλαιό ΕΠΔΟ α εξάμηνο Β. Φερεντίνος
Δείκτες (Pointers) (1) 142 Κάθε μεταβλητή, εκτός από την τιμή της, έχει και μία συγκεκριμένη διεύθυνση μνήμης, η οποία ορίζει το μέρος της μνήμης του υπολογιστική στο οποίο είναι αποθηκευμένη η τιμή της. &: Ο τελεστής για την αναφορά στη διεύθυνση μιας μεταβλητής. Όταν ο τελεστής αυτός τοποθετηθεί μπροστά από το όνομα μιας μεταβλητής, τότε ο συνδυασμός: &<όνομα_μεταβλητής> αναφέρεται στη διεύθυνση μνήμης της μεταβλητής αυτής. Οι δείκτες (pointers) είναι ειδικές μεταβλητές που περιέχουν τη διεύθυνση μεταβλητών.
Δείκτες (Pointers) (2) 143 Άρα, για να αναφερθεί ένας δείκτης στη θέση μνήμης μιας μεταβλητής, είναι απαραίτητη η ανάθεση: <όνομα_δείκτη> = &<όνομα_μεταβλητής>; Το όνομα δείκτης προκύπτει από το γεγονός ότι πλέον η ειδική αυτή μεταβλητή δείχνει στην κανονική μεταβλητή, αφού περιέχει τη θέση μνήμης της. Για να μπορεί να γίνει η παραπάνω σύνδεση ενός δείκτη με μια μεταβλητή, θα πρέπει πρώτα να δηλωθεί η μεταβλητήδείκτης. Αυτό γίνεται με τη χρήση του τελεστή *, ως εξής: <τύπος_μεταβλητής> *<όνομα_δείκτη>; Ή δήλωση και αρχικοποίηση σε μια γραμμή: <τύπος_μεταβλητής> *<όνομα_δείκτη> =&<όνομα_μεταβλητής>;
Παράδειγμα 1: 144 int x = 1; int y = 2; int *p; // Δήλωση ενός δείκτη ακέραιας μεταβλητής // Αρχικοποίηση: ο δείκτης p δείχνει στη μεταβλητή x p = &x; y = *p; // Το y γίνεται 1 *p = 0; // Το x γίνεται 0 Το όνομα του δείκτη (εδώ το p) αναφέρεται στη διεύθυνση μνήμης Το όνομα του δείκτη με τον τελεστή * (εδώ το *p) αναφέρεται στην τιμή που υπάρχει αποθηκευμένη σε αυτή τη διεύθυνση μνήμης. Δηλαδή στην τιμή της μεταβλητής στην οποία δείχνει ο δείκτης.
Δείκτες (Pointers) (3) 145 Άρα, ο τελεστής * έχει δύο χρήσεις: Δήλωση δείκτη π.χ. double *d; Προσπέλαση της μεταβλητής στην οποία δείχνει ο δείκτης π.χ. *d = 10.5; Αν υποθέσουμε ότι τα δύο αυτά παραδείγματα συνδέονται, τότε πριν από το δεύτερο θα πρέπει να υπάρχει και η εντολή σύνδεσης του δείκτη d με κάποια double μεταβλητή, π.χ.: double x; d = &x;
Δείκτες (Pointers) Συγκεντρωτικά (1) 146 &x Η διεύθυνση μνήμης μιας μεταβλητής x *p Η τιμή της μεταβλητής στην οποία δείχνει ο δείκτης p (αν π.χ. p = &x; Τότε *p = x ) p Η διεύθυνση μνήμης της μεταβλητής στην οποία δείχνει ο δείκτης p &p Η διεύθυνση μνήμης του δείκτη p (όχι της μεταβλητής που δείχνει ο δείκτης) Καμία συσχέτιση με το &x.
Δείκτες (Pointers) Συγκεντρωτικά (2) 147 Αν p = &x (ο δείκτης p δείχνει στη μεταβλητή x), η διαφορά μεταξύ p και &x είναι όποια και η διαφορά μεταξύ του x και της τιμής του. Το x ισούται με την τιμή του. Το p ισούται με το &x, δηλαδή με τη διεύθυνση του x. Αυτό σημαίνει ότι συμπεριφερόμαστε στο &x σαν τιμή και στο p σαν όνομα (ειδικής) μεταβλητής. *p όνομα για την τιμή της μεταβλητής x p όνομα για τη διεύθυνση της μεταβλητής x x όνομα για την τιμή της μεταβλητής x &x η διεύθυνση της μεταβλητής x Άρα, το *p και το x αναφέρονται ακριβώς στο ίδιο: x = <τιμή>; *p = <τιμή>; (όχι ακριβώς, όπως θα δούμε παρακάτω!) ενώ, p = <διεύθυνση>;
Παράδειγμα 2: 148 int *p; int x = 10, y = 20; p = &x; // Ο p δείχνει στη μεταβλητή x y = *p; *p=5; *p=*p+2; *p+=1; y=*p-1; ++*p; (*p)++; // y=10 // x=5 // x=x+2 (x=7) // x=x+1 (x=8) // y=8-1 (y=7) // x=x+1 (x=9) // x=x+1 (x=10) Στην τελευταία έκφραση (*p)++; η παρένθεση είναι απαραίτητη: Η αύξηση γίνεται στην τιμή στην οποία δείχνει ο δείκτης. Διαφορετικά (*p++;) ο τελεστής ++ θα εφαρμοζόταν στο δείκτη p, ο οποίος απλά θα "έδειχνε" στην επόμενη διεύθυνση στη μνήμη και το *p πλέον θα αναφερόταν σε όποια τιμή βρισκόταν στη συγκεκριμένη διεύθυνση μνήμης.
Δείκτες και συναρτήσεις (1) 149 Π.χ. κλήση συνάρτησης που να αρχικοποιεί int μεταβλητές στο 10. Το πέρασμα της προς αρχικοποίηση μεταβλητής στην παρακάτω συνάρτηση "με τιμή" δεν επιτυγχάνει το επιθυμητό αποτέλεσμα: void init10(int x) x = 10; // η x είναι τοπική μεταβλητή άρα δεν μεταβάλει την // μεταβλητή π.χ. της main από την οποία καλέστηκε, με το init10(y); Το πέρασμα της προς αρχικοποίηση μεταβλητής στην συνάρτηση "με αναφορά" επιλύει το πρόβλημα: void init10(int *x) *x = 10; //Τότε, η κλήση της π.χ. από τη main θα ήταν: // int y; init10(&y);
Δείκτες και συναρτήσεις (2) 150 Δηλαδή στη συνάρτηση που δέχεται δείκτη (*x), στέλνουμε τη θέση μνήμης της μεταβλητής (&y). Όπως αναφέρθηκε στην ενότητα των Συναρτήσεων, στη C η μεταβίβαση (το πέρασμα) μεταβλητών σε συναρτήσεις γίνεται με τιμή. Για να γίνει λοιπόν μεταβίβαση με αναφορά, η παράμετρος εισόδου της συνάρτησης πρέπει να είναι δείκτης μεταβλητής και το όρισμα της συνάρτησης κατά την κλήση της πρέπει να είναι διεύθυνση μνήμης μεταβλητής.
Παράδειγμα δεικτών σε συνάρτηση 151 Συνάρτηση η οποία υπολογίζει τους ν πρώτους όρους της ακολουθίας Fibonacci Οι αριθμοί Fibonacci ορίζονται από την εξής ακολουθία: F 0 = 0, F 1 = 1, F n = F n-1 + F n-2 (με n = 2, 3, 4,..., ) δηλαδή, ο καθένας ισούται με το άθροισμα των δύο προηγούμενών του. Η ακολουθία ξεκινάει ως εξής: 0, 1, 1, 2, 3, 5, 8, 13, 21,...
Κώδικας συνάρτησης Fibonacci 152 void fibonacci(int n) int i, n0 = 0, n1 = 1, oros = 0; for(i=1; i<=n; i++) printf("ο %d-ος όρος της ακολουθίας Fibonacci είναι ο: %d\n", n, oros); oros = n0 + n1; n0 = n1; n1 = oros; void main() int n; printf("δώσε ένα θετικό ακέραιο: "); scanf("%d", &n); fibonacci(n); Όμως οι τιμές της ακολουθίας είναι εσωτερικές μεταβλητές της συνάρτησης που τις υπολογίζει και δεν μπορούν να να χρησιμοποιηθούν περεταίρω μέσα στην main.
Κώδικας συνάρτησης Fibonacci με δείκτες 153 void fibonacci2(int *old, int *new) int temp; temp = *old + *new; *old = *new; *new = temp; void main() int n, i, f1 = 0, f2 = 1; printf("δώσε ένα θετικό ακέραιο: "); scanf("%d", &n); for (i=1; i<=n; i++) printf("ο %d-ος όρος της ακολουθίας Fibonacci είναι ο: %d\n", i, f1); fibonacci2(&f1, &f2);
Δείκτες και πίνακες 154 Στη C, οι πίνακες μπορούν να αντικατασταθούν από δείκτες. void main() float A[5]; float *pa; pa = &A[0]; //ο δείκτης pa δείχνει στη διεύθυνση μνήμης του int i; // πρώτου στοιχείου του πίνακα A. Ισοδύναμα pa = Α; for (i=0; i<5; i++) *(pa+i) = 1; // το *(pa+i) είναι ισοδύναμο με το Α[i] // ή με το *(Α+i)
Παράδειγμα1: 155 int A[5] = 8, 7, 4, 3, 1; int x, y; int *pa; pa = &A[0]; x = *p; // x=8 y = *(p+1); // y=7 y = *(p+4); // y=1
Παράδειγμα2: 156 Η συνάρτηση εύρεσης του μέγιστου ενός πίνακα ακεραίων: int megisto(int Arr[], int s) int max, i; max = Arr[0]; for (i=0; i<s; i++) if (Arr[i] > max) max = Arr[i]; return max; Και στις δύο περιπτώσεις, η κλήση της συνάρτησης (π.χ. από τη main) θα ήταν: int m = megisto(α, 5); όπου Α πίνακας (της main) 5 ακέραιων στοιχείων. Στους πίνακες ο τελεστής & δεν χρησιμοποιείται κατά τη μεταβίβαση του ορίσματος Α, παρόλο που η συνάρτηση δέχεται δείκτη, γιατί το όρισμα αφορά πίνακα και όχι μια απλή μεταβλητή. Ισοδύναμα με pointers: int megisto(int *Arr, int s) int max, i; max = *Arr; for (i=0; i<s; i++) if (*(Arr+i) > max) max = *(Arr+i); return max;
Δείκτες και Συμβολοσειρές 157 Στη C, οι συμβολοσειρές (strings) είναι πίνακες χαρακτήρων: char s [] = "hello world\n"; // πίνακας Στο τέλος κάθε συμβολοσειράς υπάρχει ο ειδικός χαρακτήρας '\0' που συμβολίζει τον τερματισμό της. Σύμφωνα με τη χρήση των δεικτών στους πίνακες, μπορούν να χρησιμοποιηθούν δείκτες αντί για πίνακες χαρακτήρων: char *ps; ps = "hello world\n"; Στο δείκτη ps ανατίθεται η αρχική διεύθυνση μνήμης του string και όχι ολόκληρο το string.
Παράδειγμα: 158 Συνάρτηση που επιστρέφει το μήκος μιας συμβολοσειράς: int string_length(char s[]) int i = 0; while (s[i]!= '\0') i++; return i; Και στις δύο περιπτώσεις, η κλήση της συνάρτησης (π.χ. από τη main) θα ήταν: char s[] = "This is a test"; int len = string_length(s); ή και: Ισοδύναμα με pointers: int string_length(char *s) int i; i=0; while (*(s+i)!= '\0') i++; return i; char *ps = "This is a test"; int len = string_length(ps);
Πίνακες Δεικτών 159 Υπάρχει η δυνατότητα δημιουργίας πινάκων δεικτών. Συνήθως χρησιμοποιούνται για δείκτες συμβολοσειρών. void main() char *week[7] = "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"; printf("%s\n", week[2]); printf("%c\n", *week[2]); printf("%c\n", *(week[2]+1)); θα εκτυπώσει: Wednesday W e Το week[2] αναφέρεται στο 3ο στοιχείο του πίνακα week (δηλαδή σε ολόκληρη τη συμβολοσειρά "Wednesday"). Το *week[2] αναφέρεται στο περιεχόμενο της αρχικής διεύθυνσης μνήμης του δείκτη week[2] (δηλαδή στο πρώτο στοιχείο της συμβολοσειράς "Wednesday", που είναι ο χαρακτήρας 'W'). Το *(week[2]+1)αναφέρεται στην ακριβώς επόμενη διεύθυνση μνήμης από αυτή, που περιέχει τον χαρακτήρα 'e'.