Μάθατε από το προηγούμενο κεφάλαιο, ότι μπορούμε να πάρουμε τη διεύθυνση μνήμης μιας μεταβλητής με τον τελεστή αναφοράς &:
Παράδειγμα
#include <stdio.h> int main() { int num = 10; printf("The memory address of variable num is: %p", &num); return 0; }
Στο παραπάνω παράδειγμα εκτυπώνουμε τη διεύθυνση μνήμης της μεταβλητής num
. Αυτή η διεύθυνση μνήμης μπορεί να αποθηκευτεί σε μια μεταβλητή τύπου δείκτης.
Για να δηλώσουμε μια μεταβλητή τύπου δείκτης, χρησιμοποιούμε τη σύνταξη type *variable_name;
:
int *ptr;
Αυτός ο κώδικας δηλώνει μια μεταβλητή τύπου int
με όνομα ptr
, η οποία είναι δείκτης σε ένα ακέραιο. Η μεταβλητή αυτή δεν έχει αρχικοποιηθεί ακόμα, οπότε η τιμή της θα είναι μη ορισμένη.
Για να αποθηκεύσουμε τη διεύθυνση μνήμης της μεταβλητής num
στον δείκτη ptr
, χρησιμοποιούμε τον τελεστή αναθέσεως (=):
ptr = #
Αυτός ο κώδικας αντιστοιχεί τη διεύθυνση μνήμης της μεταβλητής num
στον δείκτη ptr
.
Σημειώστε ότι πρέπει να δηλώσετε τον δείκτη στο ίδιο τύπο με τη μεταβλητή στην οποία θα δείχνει. Δηλαδή, αν η μεταβλητή είναι τύπου int
, ο δείκτης πρέπει επίσης να είναι τύπου int *
.
[adinserter block=”2″]
Ας δούμε ένα παράδειγμα που συνδυάζει όλα αυτά:
Παράδειγμα
#include <stdio.h> int main() { int num = 10; int *ptr; ptr = # printf("The value of variable num is: %d\n", num); printf("The memory address of variable num is: %p\n", &num); printf("The value of the pointer ptr is: %p\n", ptr); printf("The value pointed to by ptr is: %d\n", *ptr); return 0; }
Στο παραπάνω παράδειγμα, δηλώνουμε μια μεταβλητή num
τύπου int
, ένα δείκτη ptr
τύπου int *
, και αποθηκεύουμε τη διεύθυνση μνήμης της μεταβλητής num
στον δείκτη ptr
.
Έπειτα, εκτυπώνουμε την τιμή της μεταβλητής num
, τη διεύθυνση μνήμης της μεταβλητής num
, την τιμή του δείκτη ptr
και την τιμή στην οποία δείχνει ο δείκτης ptr
, χρησιμοποιώντας τον τελεστή αναφοράς *
για να αναφερθούμε στην τιμή στην οποία δείχνει ο δείκτης ptr
. Το αποτέλεσμα θα πρέπει να είναι κάτι παρόμοιο με:
The value of variable num is: 10 The memory address of variable num is: 0x7fff564c934c The value of the pointer ptr is: 0x7fff564c934c The value pointed to by ptr is: 10
Αξίζει να σημειωθεί ότι ο δείκτης ptr
δείχνει στην ίδια διεύθυνση μνήμης με την num
. Επίσης, η τιμή του δείκτη ptr
(0x7fff564c934c) είναι ίδια με αυτή της μεταβλητής num
στη διεύθυνση μνήμης αυτής. Τέλος, η τιμή που δείχνει ο δείκτης ptr
(10) είναι η ίδια με αυτή της μεταβλητής num
.
Οι δείκτες είναι ιδιαίτερα χρήσιμοι στην C, καθώς σας επιτρέπουν να αναφέρεστε σε δεδομένα στη μνήμη, αντί για τις τιμές των μεταβλητών. Μπορείτε να χρησιμοποιήσετε τους δείκτες για να δημιουργήσετε πολύπλοκες δομές δεδομένων, όπως τα δυναμικά πίνακες και οι δεσμευμένοι κόμβοι. Οι δείκτες είναι επίσης σημαντικοί στην αποτελεσματική χρήση της μνήμης στην C.
Στο επόμενο κεφάλαιο, θα δούμε περισσότερα για την εργασία με δείκτες και πώς μπορούμε να τους χρησιμοποιήσουμε για να δημιουργήσουμε δομές δεδομένων και να βελτιώσουμε την αποτελεσματικότητα του προγράμματός μας.
Στο παραπάνω παράδειγμα, χρησιμοποιούμε τον τελεστή αναφοράς *
για να πάρουμε την τιμή που βρίσκεται στην διεύθυνση μνήμης που δείχνει ο δείκτης ptr
. Αυτή η διαδικασία ονομάζεται αποαναφορά (dereferencing) του δείκτη, καθώς μας επιτρέπει να αποκτήσουμε πρόσβαση στην τιμή που αντιστοιχεί στη διεύθυνση αυτή.
Ο τελεστής αναφοράς *
είναι ο αντίστροφος του τελεστή αναφοράς &
. Όταν χρησιμοποιούμε τον τελεστή αναφοράς &
, παίρνουμε τη διεύθυνση μνήμης μιας μεταβλητής, ενώ όταν χρησιμοποιούμε τον τελεστή αναφοράς *
, παίρνουμε την τιμή που αντιστοιχεί σε αυτή τη διεύθυνση μνήμης.
Αξίζει να σημειωθεί ότι, αν και στο παραπάνω παράδειγμα χρησιμοποιούμε τον τελεστή αναφοράς *
για να αποαναφέρουμε τον δείκτη, όπως είδαμε προηγουμένως, ο δείκτης μπορεί επίσης να χρησιμοποιηθεί για να αναφερθεί σε δεδομένα στη μνήμη, χωρίς να χρειάζεται αποαναφορά.
Στο επόμενο κεφάλαιο, θα δούμε πώς μπορούμε να χρησιμοποιήσουμε τους δείκτες για να δημιουργήσουμε δομές δεδομένων, όπως οι πίνακες και οι δεσμευμένοι κόμβοι, και πώς μπορούμε να τους χρησιμοποιήσουμε για να διαχειριστούμε τη μνήμη στην C.
Σε γενικές γραμμές, οι δείκτες είναι ένα πολύ δυνατό εργαλείο στη γλώσσα προγραμματισμού C, καθώς μας επιτρέπουν να εργαστούμε με μεταβλητές που απαιτούν μνήμη δυναμικής δέσμευσης και να επιτυγχάνουμε πιο αποδοτική χρήση της μνήμης.
Επιπλέον, οι δείκτες είναι ένα σημαντικό εργαλείο για την ανταλλαγή δεδομένων μεταξύ συναρτήσεων στην C. Μπορούμε να περάσουμε δείκτες σε συναρτήσεις και να χρησιμοποιήσουμε τους δείκτες για να τροποποιήσουμε τις τιμές των μεταβλητών που βρίσκονται εκτός της συνάρτησης.
Μην ξεχνάτε ότι, αν και οι δείκτες είναι χρήσιμοι, είναι επίσης ευάλωτοι σε σφάλματα. Οι δείκτες μπορούν να παρουσιάζουν μη έγκυρες τιμές, όπως NULL, και μπορούν να προκαλέσουν προβλήματα ασφαλείας στο πρόγραμμά μας αν δεν χρησιμοποιηθούν σωστά. Για να αποφύγουμε αυτά τα προβλήματα, πρέπει να είμαστε προσεκτικοί και να χρησιμοποιούμε τους δείκτες με προσοχή.
Τέλος, είναι σημαντικό να σημειώσουμε ότι οι δείκτες είναι ένα θεμελιώδες κομμάτι της C, και ότι η κατανόηση τους είναι ζωτικής σημασίας για την ανάπτυξη προηγμένων προγραμμάτων σε αυτήν τη γλώσσα προγραμματισμού. Επίσης, η κατανόηση των δεικτών στη C μπορεί να σας βοηθήσει να κατανοήσετε καλύτερα και άλλες γλώσσες προγραμματισμού, όπως η C++, η Java και η Python.
Στη γλώσσα C, τα arrays και οι pointers σχετίζονται στενά μεταξύ τους, καθώς τα στοιχεία ενός πίνακα αποθηκεύονται συνεχόμενα στη μνήμη και ένας δείκτης μπορεί να δείξει στη διεύθυνση μνήμης του πρώτου στοιχείου του πίνακα.
[adinserter block=”3″]
Η σύνταξη για την αναφορά σε ένα στοιχείο πίνακα χρησιμοποιώντας έναν δείκτη είναι παρόμοια με τη σύνταξη που χρησιμοποιούμε για την αναφορά σε μια μεταβλητή χρησιμοποιώντας τον τελεστή αναφοράς &
. Για παράδειγμα, αν έχουμε τον ακόλουθο πίνακα:
int numbers[5] = {1, 2, 3, 4, 5};
Μπορούμε να δημιουργήσουμε ένα δείκτη που δείχνει στη διεύθυνση μνήμης του πρώτου στοιχείου του πίνακα:
int *ptr = &numbers[0];
Ή, ακόμη πιο απλά, μπορούμε να αφήσουμε τον μεταγλωττιστή να υπολογίσει τη διεύθυνση μνήμης του πρώτου στοιχείου του πίνακα αυτόματα:
int *ptr = numbers;
Για να αναφερθούμε σε ένα στοιχείο πίνακα χρησιμοποιώντας έναν δείκτη, μπορούμε να χρησιμοποιήσουμε τον τελεστή αναφοράς *
. Για παράδειγμα, αν έχουμε τον ακόλουθο πίνακα:
int numbers[5] = {1, 2, 3, 4, 5};
Μπορούμε να αναφερθούμε στο τρίτο στοιχείο του πίνακα χρησιμοποιώντας έναν δείκτη ως εξής:
int *ptr = &numbers[2]; // ή int *ptr = numbers + 2; printf("%d", *ptr); // θα εκτυπωθεί: 3
Επίσης, η σύνταξη ptr[i]
μπορεί να χρησιμοποιηθεί για την αναφορά στο i-οστό στοιχείο του πίνακα που δείχνει ο δείκτης ptr
. Για παράδειγμα:
int numbers[5] = {1, 2, 3, 4, 5}; int *ptr = &numbers[0]; printf("%d", ptr[2]); // θα εκτυπωθεί: 3
Σημειώνουμε ότι η σύνταξη *(ptr+i)
μπορεί να χρησιμοποιηθεί αντί για τη σύνταξη ptr[i]
για να αναφερθούμε στο i-οστό στοιχείο του πίνακα.
Για παράδειγμα, αν έχουμε τον ακόλουθο πίνακα:
int numbers[5] = {1, 2, 3, 4, 5};
Μπορούμε να εκτυπώσουμε κάθε στοιχείο του πίνακα με τον ακόλουθο τρόπο:
for(int i = 0; i < 5; i++) { printf("%d ", numbers[i]); }
Αυτό θα εκτυπώσει:
1 2 3 4 5
Σημειώνουμε ότι μπορούμε να χρησιμοποιήσουμε τον δείκτη στην παραπάνω επανάληψη ως εξής:
for(int i = 0; i < 5; i++) { printf("%d ", *(numbers + i)); }
Αυτό θα εκτυπώσει το ίδιο αποτέλεσμα.
[adinserter block=”4″]
Μπορούμε να εκτυπώσουμε τη διεύθυνση μνήμης κάθε στοιχείου του πίνακα με τον ακόλουθο τρόπο:
for(int i = 0; i < 5; i++) { printf("The memory address of numbers[%d] is %p\n", i, &numbers[i]); }
Αυτό θα εκτυπώσει κάτι σαν το παρακάτω:
The memory address of numbers[0] is 0x7ffeee42b870 The memory address of numbers[1] is 0x7ffeee42b874 The memory address of numbers[2] is 0x7ffeee42b878 The memory address of numbers[3] is 0x7ffeee42b87c The memory address of numbers[4] is 0x7ffeee42b880
Σημειώνουμε ότι η συνάρτηση printf() χρησιμοποιεί το format specifier %p
για να εκτυπώσει διευθύνσεις μνήμης.
Στο παραπάνω παράδειγμα, η μεταβλητή numbers
είναι ένας πίνακας τύπου int και κάθε στοιχείο του πίνακα έχει μέγεθος 4 bytes (στα περισσότερα συστήματα).
Συνεπώς, η διεύθυνση μνήμης για το πρώτο στοιχείο numbers[0]
είναι 0x7ffeee42b870
, για το δεύτερο στοιχείο numbers[1]
είναι 0x7ffeee42b874
(το οποίο διαφέρει από το 0x7ffeee42b870
κατά 4), κ.λπ.
Επίσης, σημειώνουμε ότι η διεύθυνση μνήμης ενός πίνακα αναφέρεται στη διεύθυνση του πρώτου στοιχείου του πίνακα. Στο παραπάνω παράδειγμα, η διεύθυνση μνήμης του πίνακα numbers
είναι 0x7ffeee42b870
, η οποία αντιστοιχεί στη διεύθυνση μνήμης του πρώτου στοιχείου numbers[0]
.
Στη C, το όνομα ενός πίνακα αντιστοιχεί στη διεύθυνση μνήμης του πρώτου στοιχείου του πίνακα.
Για παράδειγμα, στον παρακάτω κώδικα, το όνομα του πίνακα numbers
αντιστοιχεί στη διεύθυνση μνήμης του πρώτου στοιχείου του πίνακα (&numbers[0]
):
int numbers[5] = {1, 2, 3, 4, 5}; printf("The memory address of the first element is %p\n", numbers); printf("The memory address of the first element is %p\n", &numbers[0]);
Αυτό θα εκτυπώσει κάτι σαν το παρακάτω:
The memory address of the first element is 0x7fff5586f4c0 The memory address of the first element is 0x7fff5586f4c0
Σημειώνουμε ότι και τα δύο printf() statements εκτυπώνουν τη διεύθυνση μνήμης του πρώτου στοιχείου του πίνακα, το οποίο αντιστοιχεί στη διεύθυνση που αντιστοιχεί στο όνομα του πίνακα numbers
.
Αν έχουμε έναν πίνακα myNumbers
και ένα δείκτη p
που δείχνει στον πρώτο αριθμό του πίνακα, μπορούμε να αναφερόμαστε στα στοιχεία του πίνακα χρησιμοποιώντας τον δείκτη.
Στο παρακάτω παράδειγμα, έχουμε έναν πίνακα myNumbers
και ένα δείκτη p
που δείχνει στον πρώτο αριθμό του πίνακα. Έπειτα, εκτυπώνουμε τις τιμές του πίνακα χρησιμοποιώντας το δείκτη p
και τον τελεστή αναφοράς *
:
int myNumbers[5] = {10, 20, 30, 40, 50}; int *p; p = myNumbers; for(int i = 0; i < 5; i++) { printf("%d ", *(p+i)); }
Αυτό θα εκτυπώσει:
10 20 30 40 50
Σημειώνουμε ότι στον παραπάνω κώδικα, η έκφραση *(p+i)
επιστρέφει την τιμή του στοιχείου στην i-η θέση του πίνακα.
Αν έχουμε ένα δείκτη p
που δείχνει στον πρώτο αριθμό ενός πίνακα και θέλουμε να αναφερθούμε σε άλλα στοιχεία του πίνακα, μπορούμε να αυξήσουμε την τιμή του δείκτη για να δείξει σε διαφορετικά στοιχεία του πίνακα.
[adinserter block=”5″]
Στο παρακάτω παράδειγμα, ο δείκτης p
δείχνει στο πρώτο στοιχείο του πίνακα myNumbers
. Χρησιμοποιούμε έναν for loop για να εκτυπώσουμε την τιμή του κάθε στοιχείου του πίνακα. Κάθε φορά που εκτελείται ο βρόχος, αυξάνουμε την τιμή του δείκτη p
κατά 1, ώστε να δείχνει στο επόμενο στοιχείο του πίνακα.
int myNumbers[5] = {10, 20, 30, 40, 50}; int *p; p = myNumbers; for(int i = 0; i < 5; i++) { printf("%d ", *p); p++; }
Αυτό θα εκτυπώσει:
10 20 30 40 50
Σημειώνουμε ότι ο τελεστής ++
αυξάνει την τιμή του δείκτη κατά 1. Επίσης, στο παραπάνω παράδειγμα χρησιμοποιούμε τον τελεστή αναφοράς *
για να πάρουμε την τιμή του στοιχείου στο οποίο δείχνει ο δείκτης p
.
Όπως αναφέρθηκε και προηγουμένως, ο δείκτης p
δείχνει στο πρώτο στοιχείο του πίνακα myNumbers
. Μπορούμε να χρησιμοποιήσουμε τον δείκτη p
σε έναν for loop για να εκτυπώσουμε τα στοιχεία του πίνακα.
Το παρακάτω παράδειγμα δείχνει πώς να εκτυπώσετε τα στοιχεία του πίνακα myNumbers
χρησιμοποιώντας τον δείκτη p
σε έναν for loop:
int myNumbers[5] = {10, 20, 30, 40, 50}; int *p; p = myNumbers; for(int i = 0; i < 5; i++) { printf("%d ", *(p+i)); }
Αυτό θα εκτυπώσει:
10 20 30 40 50
Σημειώνουμε ότι η έκφραση *(p+i)
επιστρέφει την τιμή του στοιχείου στην i-η θέση του πίνακα.
Χρησιμοποιώντας δείκτες, μπορούμε να αλλάξουμε τις τιμές των στοιχείων ενός πίνακα. Για παράδειγμα, μπορούμε να χρησιμοποιήσουμε ένα δείκτη για να αλλάξουμε την τιμή ενός στοιχείου στον πίνακα.
Στο παρακάτω παράδειγμα, χρησιμοποιούμε τον δείκτη p
για να αλλάξουμε την τιμή του τρίτου στοιχείου του πίνακα myNumbers
σε 100:
int myNumbers[5] = {10, 20, 30, 40, 50}; int *p; p = &myNumbers[2]; // δείκτης p δείχνει στο τρίτο στοιχείο του πίνακα *p = 100; // αλλάζουμε την τιμή του τρίτου στοιχείου στο 100 for(int i = 0; i < 5; i++) { printf("%d ", myNumbers[i]); }
Αυτό θα εκτυπώσει:
10 20 100 40 50
Σημειώνουμε ότι ο δείκτης p
δείχνει στο τρίτο στοιχείο του πίνακα myNumbers
, και χρησιμοποιούμε τον τελεστή αναφοράς *
για να αλλάξουμε την τιμή του τρίτου στοιχείου σε 100.