(*p)++ - University of Cyprus

Τμήμα Πληροφορικής
Πανεπιστήμιο Κύπρου
ΕΠΛ132 – Αρχές Προγραμματισμού II
Διάλεξη 6: Δείκτες και
Πίνακες
(Κεφάλαιο 12, KNK-2ED)
Δημήτρης Ζεϊναλιπούρ
http://www.cs.ucy.ac.cy/courses/EPL132
EPL132: Programming Principles II - Demetris Zeinalipour © (University of Cyprus)
6-1
Περιεχόμενο Διάλεξης 6
• Δείκτες & Πίνακες
– Αριθμητική Δεικτών (Pointer Arithmetic)
• Χρήση Ονόματος Πίνακα για Δείκτη (Array
Name as Pointer)
– Χρήση Δεικτών για Επεξεργασία Πινάκων
(Pointers for Array Processing)
– Δείκτες και Πολυδιάστατοι Πίνακες
(Pointers and Multidimensional Arrays)
EPL132: Programming Principles II - Demetris Zeinalipour © (University of Cyprus)
6-2
Αριθμητική Δεικτών
(Pointers Arithmetic)
• Στην C υπάρχει ισχυρός δεσμός ανάμεσα στους δείκτες
και στους πίνακες. Οποιαδήποτε πράξη μπορεί να γίνει
με δείκτες πινάκων (π.χ., a[i]) μπορεί να γίνει και με
δείκτες διευθύνσεων *pa.
• Έστω πίνακας int a[10]. H δήλωση αυτή ορίζει ένα μπλοκ
10 διαδοχικών αντικειμένων με ονόματα a[0], a[1], ..., a[9].
– Αν
int *pa = NULL; pa
τότε η ανάθεση pa = &a[0];
κάνει τον pa να δείχνει το μηδενικό στοιχείο του πίνακα.
• Αριθμητική δεικτών : Αν ο pa είναι δείκτης σε κάποια
θέση του πίνακα, τότε οι pa  i, pa++, pa-- είναι επίσης
δείκτες
– pa + 1, pa++
δείκτης στο επόμενο στοιχείο του πίνακα από
αυτό που δείχνει ο pa.
– pa + i , (pa – i) δείκτης στο σημείο του πίνακα που βρίσκεται i
EPL132: Programming Principles
θέσειςIIμετά
- Demetris
(πριν)
Zeinalipour
από αυτό
© (University
που δείχνει
of Cyprus)
ο pa.
6-3
Αριθμητική Δεικτών
(Παράδειγμα 1)
• Παράδειγμα πρόσθεσης δεικτών:
p = &a[2];
q = p + 3;
p += 6;
EPL132: Programming Principles II - Demetris Zeinalipour © (University of Cyprus)
6-4
Αριθμητική Δεικτών
(Pointers Arithmetic)
• Έτσι, αν pa = &a[0], τότε για παράδειγμα,
pa + 2
δείχνει στη τρίτη θέση του πίνακα, δηλ. στο a[2].
*(pa + 2) έχει την τιμή της 3ης θέσης του πίνακα, δηλ. το a[2].
• Έξ’ορισμού, η τιμή μιας μεταβλητής τύπου πίνακα (int a[]) είναι η
διεύθυνση του μηδενικού στοιχείου του πίνακα.
– Επομένως αντί pa = &a[0] μπορούμε να γράψουμε pa = a.
• Tότε οι pa και a έχουν τις ίδιες τιμές και μπορούν να
χρησιμοποιούνται η μια στη θέση της άλλης:
– Το a[i]
– To &a[i]
είναι ταυτόσημο με τα
είναι ταυτόσημο με τα
*(pa +i) ή *(a + i) ή pa[i].
a+i, pa+i.
• Ωστόσο pa και a ΔΕΝ είναι ταυτόσημα (sizeof(pa) <> sizeof(a))
π.χ., int a[10]; int *pa = NULL;
– sizeof(pa) = 4 bytes (ILP32) ή 8 bytes (LP64)
– sizeof(a) = μέγεθος πίνακα * μέγεθος τύπου
• Επίσης, δεν μπορώ να μεταβάλω ένα πίνακα (π.χ., a++, a=pa)
Programming
Principles
Demetris Zeinalipour © (University of Cyprus)
–EPL132:
Δες παράδειγμα
στην
επόμενηII -διαφάνεια.
6-5
Αριθμητική Δεικτών
(Παράδειγμα 2)
• Προσοχή: ο δείκτης είναι μεταβλητή και έτσι pa = a και
pa++ είναι έγκυρες εκφράσεις. Το όνομα ενός πίνακα
όμως δεν είναι μεταβλητή, επομένως κατασκευές
όπως a = pa και a++ δεν είναι έγκυρες!
• Ποιες από τις πιο κάτω εντολές είναι έγκυρες;
int t[10] = {0}, i = 5,
p
= t;
p[2] = 3;
//
++p;
*p
= 14;
*(t+i) = 33;
//
++t;
=> compile
*p = NULL;
ίδιο με *(p+2) = 3
ίδιο με t[i] = 33
error
value
0
14
3
0
0
33
0
0
0
0
index
t[0]
t[1]
t[2]
t[3]
t[4]
t[5]
t[6]
t[7]
t[8]
t[9]
EPL132: Programming Principles II - Demetris Zeinalipour © (University of Cyprus)
6-6
Σύγκριση Δεικτών
(Comparing Pointers)
• Για τη σύγκριση δεικτών μπορούν να χρησιμοποιηθούν
όλοι οι γνωστοί σχεσιακοί τελεστές (<, <=, >, >=) και οι
τελεστές ισότητας (== and !=).
– Ουσιαστικά συγκρίνονται διευθύνσεις μνήμης (θυμηθείτε ότι
κάθε διεύθυνση αναφέρεται σε ένα byte μνήμης)
– Η χρήση των σχεσιακών τελεστών έχει νόημα ΜΟΝΟ για
δείκτες σε αντικείμενα του ίδιου πίνακα
• τα οποία βρίσκονται σε συνεχόμενες διευθύνσεις μνήμης (άλλες
μεταβλητές μπορεί να βρίσκονται σε αυθαίρετες άλλες διευθύνσεις),
• Παράδειγμα: το αποτέλεσμα της σύγκρισης εξαρτάται
από την απόσταση δυο στοιχείων στον πίνακα., π.χ.,
p = &a[5];
q = &a[1];
(p >= q) => TRUE (1)
(p <= q) => FALSE (0)
EPL132: Programming Principles II - Demetris Zeinalipour © (University of Cyprus)
6-7
Αριθμητική Δεικτών & Πίνακες
(Παράδειγμα 3)
• Τι κάνει το ακόλουθο πρόγραμμα;
#define N 10
…
int a[N], sum, *p = NULL;
…
sum = 0;
for (p = &a[0]; p < &a[N]; p++)
sum += *p;
Μετά την πρώτη
επανάληψη
ή (το αντίστοιχο)
for (p = a; p < a + N; p++)
sum += *p;
To p++ προχωρεί 4 bytes (int) σε κάθε κλήση!
EPL132: Programming Principles II - Demetris Zeinalipour © (University of Cyprus)
6-8
Παράδειγμα
(1-d
Πίνακες + Αριθμητική Δεικτών)
/* Πρόγραμμα αντιστροφής αριθμών με αριθμητική δεικτών */
#include <stdio.h>
#define N 10
int main(void)
{
int a[N], *p = NULL;
For loop με αριθμητική δεικτών
printf("Enter %d numbers: ", N);
for (p = a; p < a + N; p++)
scanf("%d", p); // το p είναι δείκτης άρα δεν θέλει &
printf("In reverse order:");
for (p = a + N - 1; p >= a; p--)
printf(" %d", *p);
printf("\n");
}
return 0;
EPL132: Programming Principles II - Demetris Zeinalipour © (University of Cyprus)
6-9
Συνδυασμός Τελεστών
(* και ++)
• Οι προγραμματιστές C συχνά συνδυάζουν τους
τελεστές * (έμμεσης αναφοράς) and ++
(αύξησης).
• Παράδειγμα
– Μια πρόταση η οποία αναθέτει το 5 στη θέση a[i] και στη
συνέχεια αυξάνει το i κατά 1:
a[i++] = 5;
// a[++i] = 5; Πρώτα αύξηση, μετά ανάθεση
– H αντίστοιχη έκδοση με δείκτες:
*p++ = 5; // Να αποφεύγονται εκφράσεις χωρίς παρενθέσεις
– H επιθεματική (postfix) έκδοση της ++ έχει μεγαλύτερη
προτεραιότητα από το *, οπότε ο μεταγλωττιστής το βλέπει ως:
*(p++) = 5;
// Ισοδύναμο με *p = 5; p++;
EPL132: Programming Principles II - Demetris Zeinalipour © (University of Cyprus)
6-10
Συνδυασμός Τελεστών
(* και ++)
• Πιθανοί συνδυασμοί των * και ++
– Χρησιμοποιείται ΠΑΝΤΑ παρενθέσεις για να αποφύγετε
τον πονοκέφαλο των προτεραιοτήτων
Έκφραση
*(p++)
A=
*(++p)
(*p)++
++(*p)
Νόημα
η τιμή της μεταβλητής Α είναι *p πριν την μετακίνηση.
στη συνέχεια προχωρεί το p
προχώρησε το p πρώτα.
η τιμή της μεταβλητής Α είναι *p μετά την μετακίνηση
η τιμή της μεταβλητής Α είναι *p πριν την αύξηση.
στη συνέχεια αυξάνεται το *p
αύξησε το *p πρώτα.
η τιμή της μεταβλητής Α είναι *p μετά την αύξηση
Εκτός
Στα Πλαίσια Έκφρασης (Ανάθεσης)
Έκφρασης
p2 = p++; Διαφορετικό από p2 = ++p;
p++; Ίδιο
με ++p; Principles II - Demetris Zeinalipour © (University of Cyprus)
EPL132:
Programming
6-12
Πίνακες – Συναρτήσεις - Δείκτες
•
•
int find_largest(int a[], int n) {
int i, max;
Παράδειγμα:
max = a[0];
for (i = 1; i < n; i++)
if (a[i] > max)
max = a[i];
return max;
Κλήση Συνάρτησης: }
largest = find_largest(a, N);
• Εναλλακτικό Πρότυπο με Δείκτες:
int find_largest(int *a, int n)
• Προστασία Στοιχείων Πίνακα (το a προστατεύεται έτσι και αλλιώς
εφόσον έχει τοπική ορατότητα - εμβέλεια, δείτε διαφάνεια 5.30)
int find_largest(const int a[], int n)
ή int find_largest(const int *a, int n)
• H find_largest εκτελείται από το a[2] στοιχείo:
find_largest(&a[2], N-2);
EPL132: Programming Principles II - Demetris Zeinalipour © (University of Cyprus)
6-13
Δείκτες και Πολυδιάστατοι Πίνακες
(Διαχείριση Γραμμών)
• Παρόμοιο με τους 1-d πίνακες (arrays), έτσι και στους Md πίνακες (matrices), οι δείκτες μπορούν να
χρησιμοποιηθούν για να δείχνουν σε στοιχεία των
πινάκων.
• Π.χ., μηδενισμός θέσεων πίνακα
int a[ROW][COL], *p = ΝULL, i;
…
for (p = &a[0][0]; p <= &a[ROW-1][COL-1]; p++)
*p = 0; // διαχείριση στοιχείων με δείκτες
EPL132: Programming Principles II - Demetris Zeinalipour © (University of Cyprus)
6-14
Παράδειγμα
(m-D Πίνακες + Συναρτήσεις + Δείκτες)
Aς δούμε 4 διαφορετικές παραλλαγές υλοποίησης
της sum() των στοιχείων ενός 2D πίνακα
#define ROW 5
#define COL 5
int sum_two_dimensional_array(const int [][COL]);
int sum_two_dimensional_array(const int a[][COL]) {
int i, j, sum = 0;
for(i=0; i<ROW; i++)
for (j=0; j<COL; j++)
sum+=a[i][j];
return sum;
}
Λύση 1: Mε defined ROW +
COL (λογική που έχουμε
χρησιμοποιήσει εκτενώς στο
παρελθόν)
int main() {
const int a[][COL] = {
{1,1,1,1,1}, {2,2,2,2,2}, {3,3,3,3,3}, {4,4,4,4,4}, {5,5,5,5,5}
}; // sum = 75 (5x15)
}
printf("sum:%d\n", sum_two_dimensional_array(a));
return 0;
EPL132: Programming Principles II - Demetris Zeinalipour © (University of Cyprus)
6-15
Παράδειγμα
(m-D Πίνακες + Συναρτήσεις + Δείκτες)
#define COL 5
Λύση 2: Με COL, Χωρίς define ROW
int sum_two_dimensional_array(const int [][COL], int);
int sum_two_dimensional_array(const int a[][COL], int ROW) {
int i, j, sum = 0;
for(i=0; i<ROW; i++)
for (j=0; j<COL; j++)
sum+=a[i][j];
return sum;
Αριθμός στοιχείων
γραμμής
}
int main()
{
int sum = 0;
const int a[][COL] = {
{1,1,1,1,1}, {2,2,2,2,2}, {3,3,3,3,3}, {4,4,4,4,4}, {5,5,5,5,5}
}; // sum = 75 (5x15)
}
sum = sum_two_dimensional_array(a, sizeof(a[0])/sizeof(a[0][0]));
return 0;
Μέγεθος (bytes)
Μέγεθος (bytes)
γραμμής
πίνακα © (University
στοιχείων πίνακα
EPL132: Programming Principles II - Demetris
Zeinalipour
of Cyprus)
6-16
Δείκτες και Πολυδιάστατοι Πίνακες
(Διαχείριση Στηλών)
• Επεξεργασία στηλών είναι κάπως πιο περίπλοκη
εφόσον οι πίνακες αποθηκ. ανά γραμμή (όχι ανά στήλη).
• Παράδειγμα: Ένα loop που αναθέτει την τιμή 0 στην
στήλη i του πίνακα a: Δείκτης σε ένα ROW (πλάτους COL)
int a[ROW][COL], (*p)[COL] = NULL, i=2;
…
// Διεύθυνση γραμμής 0
for (p = &a[0]; p < &a[ROW]; p++)
(*p)[i] = 0; // το p++ προχωρεί COL θέσεις!!!!
ή
for (p = a; p < a + ROWS; p++)
(*p)[i] = 0;
• Το (*p)[i] χρειάζεται παρενθέσεις εφόσον η
προτεραιότητα του [] είναι ψηλότερη από αυτή του *.
– Εναλλακτικά θα ήταν πίνακας δεικτών που θα δούμε αργότερα
• Ακολουθεί
παράδειγμα χρήσης του (*p)[i] …
EPL132: Programming Principles II - Demetris Zeinalipour © (University of Cyprus)
6-17
Δείκτες και Πολυδιάστατοι Πίνακες
(Διαχείριση Στηλών)
int main() // Πρόγραμμα Μηδενισμού Στήλης με Δείκτης
INPUT
{
int sum = 0;
OUTPUT
int a[][COL] = {
{1,1,1,1,1}, {2,2,2,2,2},
{3,3,3,3,3}, {4,4,4,4,4}, {5,5,5,5,5}
}; // sum = 75 (5x15)
Αριθμός στοιχείων
γραμμής
int (*p)[COL] = NULL; // δείκτης σε ROW (πλάτους COL)
int ROW = sizeof(a[0])/sizeof(a[0][0]);
Μέγεθος (bytes) γραμμής πίνακα
Μέγεθος (bytes) στοιχείων πίνακα
int i = 2; // column to change
for (p = &a[0]; p < &a[ROW]; p++)
(*p)[i] = 0;
το p++ προχωρεί COL θέσεις
print_two_dimensional_array(a, ROW);
return 0;
}
EPL132: Programming Principles II - Demetris Zeinalipour © (University of Cyprus)
6-18
Παράδειγμα
(m-D Πίνακες + Συναρτήσεις + Δείκτες)
#define COL 5
Ενδιάμεση Λύση: Ίδιο με Προηγούμενο
αλλά με Δείκτη σε Γραμμή στο πρότυπο
void sum_two_dimensional_array(const int (*)[COL], int);
int sum_two_dimensional_array(const int (*a)[COL], int ROW) {
int i, j, sum = 0;
for(i=0; i<ROW; i++)
for (j=0; j<COL; j++)
sum+=a[i][j];
return sum;
a
}
int main()
{
...
sum = sum_two_dimensional_array(a, sizeof(a[0])/sizeof(a[0][0]));
...
Ίδια κλήση με προηγούμενο
}
παράδειγμα
EPL132: Programming Principles II - Demetris Zeinalipour © (University of Cyprus)
6-20
Παράδειγμα
(m-D Πίνακες + Συναρτήσεις + Δείκτες)
Λύση 3: Δείκτη σε Γραμμή + Απαριθμητό βρόχο
#define COL 5
...
int sum_two_dimensional_array(int (*a)[COL], int ROW) {
int i, sum = 0;
int *pa = &a[0][0]; // πρώτο στοιχείο πίνακα
for(i=0; i < ROW*COL; i++) {
sum += *pa;
pa++;
}
return sum;
a
}
int main()
{
...
sum = sum_two_dimensional_array(a, sizeof(a[0])/sizeof(a[0][0]));
...
}
EPL132: Programming Principles II - Demetris Zeinalipour © (University of Cyprus)
6-21
Επισκόπηση: Πίνακες Δεικτών
• Σε όλα τα προηγούμενα
παραδείγματα το COL ήταν
προσδιορισμένο ως σταθερά.
• Τι γίνεται εάν ΔΕΝ γνωρίζουμε το
COL εκ’ των πρότερων;
• Τότε μπορούμε να
χρησιμοποιήσουμε Πίνακες
Δεικτών (που υλοποιούνται με
Δείκτες σε Δείκτες **p) και τους
οποίους θα δούμε στην ερχόμενη
διάλεξη 7.
– Δεξιά: Παράδειγμα Πίνακα Δεικτών σε
Μήνες (κάθε COL έχει διαφορετικό
πλάτος)
EPL132: Programming Principles II - Demetris Zeinalipour © (University of Cyprus)
6-23