User Tools

Site Tools


cpp:pointers

Δείκτες

Οι δείκτες είναι μεταβλητές που προσδιορίζουν τιμές διευθύνσεων στη μνήμη αντί για τιμές μεταβλητών. Η δήλωση ενός δείκτη γίνεται ως εξής:

<type> *var_name;

Για παράδειγμα,

char *c_ptr;    // δείκτης που δείχνει σε μεταβλητές τύπου char
int *i_ptr;     // δείκτης που δείχνει σε μεταβλητές τύπου int
double *d_ptr;  // δείκτης που δείχνει σε μεταβλητές τύπου double
float *f_ptr;   // δείκτης που δείχνει σε μεταβλητές τύπου float

Αρχικοποίηση δεικτών

Η διεύθυνση μνήμης μίας μεταβλητής μπορεί να δοθεί με χρήση του τελεστή & (reference operator). Οποιοσδήποτε δείκτης μπορεί να αρχικοποιηθεί λαμβάνοντας την διεύθυνση μνήμης μίας μεταβλητής ως εξής:

char c = 'a'; char *c_ptr = &a;
int i = 5; int *i_ptr = &i;
double d = 3.14159; double *d_ptr = &d;
float f = 3.14159; float *f_ptr = &f;

Εναλλακτικά, μπορείτε να αρχικοποιήσετε οποιονδήποτε δείκτη στην τιμή 0 δείχνοντας ότι στον συγκεκριμένο δείκτη δεν έχει ανατεθεί καμία τιμή.

char *c_ptr=0; ή char *c_ptr=nullptr;
int *i_ptr; ή char *i_ptr=nullptr;

Παράδειγμα αρχικοποίησης δεικτών

char foo = 'a';
char *foo_ptr = &foo;

Ο παραπάνω κώδικας αρχικοποίησης ενός δείκτη απεικονίζεται γραφικά για ένα σύστημα 32bit στο σχήμα που ακολουθεί. Η μεταβλητή foo_ptr αποθηκεύει τη διεύθυνση της μεταβλητής foo (0x55884421).

Λήψη του περιεχομένου της τιμής ενός δείκτη (pointer dereference)

Μπορείτε να χρησιμοποιήσετε την τιμή ενός δείκτη για να προσπελάσετε τη μεταβλητή στην οποία αυτός δείχνει με χρήση του τελεστή * (dereference operator) πριν από το όνομα του δείκτη ως εξής:

char foo = 'a';
char *foo_ptr = &foo;
char bar = *foo_ptr;

Η μεταβλητή bar αποθηκεύει το περιεχόμενο της διεύθυνσης που δείχνει η μεταβλητή foo_ptr, δηλ το περιεχόμενο της μεταβλητής foo που είναι ο χαρακτήρας 'a'.

  • Ο τελεστής * (dereference operator) όταν τίθεται μπροστά από μία μεταβλητή δείκτη επιστρέφει το περιεχόμενο της διεύθυνσης στην οποία δείχνει ο δείκτης.
  • Ο τελεστής & (address-of operator) επιστρέφει τη διεύθυνση στην οποία δείχνει μία μεταβλητή.

Αριθμητική δεικτών

Η αριθμητική δεικτών περιορίζεται μόνο στις πράξεις της πρόσθεσης και της αφαίρεσης. Μπορούμε να αυξήσουμε ή να μειώσουμε την τιμή ενός δείκτη κατά μία ή περισσότερες θέσεις. Η μεταβολή της τελικής τιμής του δείκτη συνδέεται με τον μέγεθος του τύπου δεδομένων στον οποίο δείχνει ο δείκτης. Για παράδειγμα, ας υποθέσουμε ότι έχουμε τρεις δείκτες που δείχνουν σε διαφορετικούς τύπους μεταβλητών, όπως παρακάτω:

char c='a'; short s=10; int i=100;
char *mychar = &c;
short *myshort = &s;
long *myint = &i;

Στο παραπάνω παράδειγμα η μεταβλητή mychar δείχνει σε δεδομένα μήκους ενός byte. Ας υποθέσουμε επίσης ότι η μεταβλητή myshort δείχνει σε δεδομένα μήκους 2 bytes (εξαρτάται από το hw) και η μεταβλητή myint δείχνει σε δεδομένα μήκους 4 bytes (εξαρτάται και πάλι από το hw).

Ας επιχειρήσουμε να μεταβάλλουμε τις τιμές των παραπάνω δεικτών κατά μία θέση ως εξής:

mychar++;
myshort++;
myint++;

το αποτέλεσμα των παραπάνω πράξεως είναι το εξής:

  1. η μεταβλητή mychar μετακινείται κατά sizeof(char) (1 byte)
  2. η μεταβλητή myshort μετακινείται κατά sizeof(short) (2 bytes)
  3. η μεταβλητή myint μετακινείται κατά sizeof(int) (4 bytes)

Θα πρέπει να έχετε υπόψη ότι όλοι οι δείκτες καταλαμβάνουν τον ίδιο χώρο στη μνήμη (4-byte για συστήματα 32-bit και 8-byte για συστήματα 64-bit) ανεξάρτητα από τον τύπο δεδομένων στον οποίο δείχνουν.

Δείκτες και πίνακες

Η δήλωση ενός πίνακα ισοδυναμεί με ένα δείκτη που δείχνει στην πρώτη θέση του πίνακα. Για το λόγο αυτό, ένας πίνακας μπορεί να αναθέσει την τιμή του σε ένα δείκτη του ιδίου τύπου ως εξής:

int myarray [5];
int *myarray_ptr = myarray;

Αν και ένας πίνακας μπορεί να αντιμετωπιστεί και ως δείκτης η βασική διαφορά πινάκων και δεικτών είναι ότι ο πίνακας δεν μπορεί να μεταβάλλει την διεύθυνση στην οποία δείχνει, δηλαδή πρόκειται για ένα δείκτη σταθερής διεύθυνσης. Για παράδειγμα η παρακάτω ανάθεση δεν είναι δυνατόν να συμβεί:

myarray = myarray_ptr+1;

Δείτε το παρακάτω παράδειγμα όπου χρησιμοποιούνται πίνακας από ακεραίους και ένας δείκτης σε ακέραιο που δείχνει στις επιμέρους τιμές του πίνακα. Τα σχόλια περιέχουν τη θέση του πίνακα που προσπελάζεται κάθε φορά.

pointers-arrays.cpp
#include <iostream>
using namespace std;
 
int main ()
{
  int numbers[5];
  int * p;
  p = numbers;  *p = 10;          // numbers[0]
  p++;  *p = 20;                  // numbers[1]
  p = &numbers[2];  *p = 30;      // numbers[2]
  p = numbers + 3;  *p = 40;      // numbers[3]
  p = numbers;  *(p+4) = 50;      // numbers[4]
 
  for (int n=0; n<5; n++)
    cout << numbers[n] << ", ";
  cout << endl;
  return 0;
}

Οι δείκτες οφείλουν να δείχνουν σε διευθύνσεις μνήμης που ανήκουν στη διεργασία που εκτελείται. Στην πραγματικότητα όμως οι δείκτες μπορούν να πάρουν οποιαδήποτε τιμή ακόμη και τιμές εκτός του εύρους διευθύνσεων που έχουν ανατεθεί από το λειτουργικό σύστημα στην τρέχουσα διεργασία. Η ανάγνωση από διεύθυνση ή η εγγραφή σε διεύθυνση που δεν ανήκει στη διεργασία δημιουργεί σφάλμα που τερματίζει το πρόγραμμα. Δείτε το παρακάτω παράδειγμα όπου ένας δείκτης αρχικοποιείται σε τιμή εκτός των ορίων του πίνακα intArray.

intArrayPointer.cpp
#include <iostream>
using namespace std;
int main() {
  int intArray[5];
  int *ptr = intArray+10000;
  *ptr = 10;
}

Δείκτες και αλφαριθμητικά

Σε προηγούμενη ενότητα είδαμε ότι τα αλφαριθμητικά είναι ακολουθίες από χαρακτήρες που τερματίζουν στον χαρακτήρα '\0'. Οι ακολουθίες χαρακτήρων μπορούν να αντιμετωπισθούν και ως πίνακες από χαρακτήρες. Δείτε το παρακάτω παράδειγμα ανάγνωσης και εγγραφής των περιεχόμενων ενός αλφαριθμητικού. Παρατηρήστε ότι μπορούμε να χρησιμοποιήσουμε τον τελεστή [] για να πάρουμε το περιεχόμενο θέσης πίνακα αν και το αλφαριθμητικό ορίστηκε με την βοήθεια ενός δείκτη σε χαρακτήρα (char).

string_modification.cpp
#include <string.h>
#include <iostream>
using namespace std;
int main() {
  const char *str = "Hello World";
  for(int i=0; i<strlen(str); i++)
    cout << i << ". " << str[i] << endl;
}

Δείκτες σε δείκτες

Μπορείτε να έχετε δείκτες οι οποίοι δείχνουν σε άλλους δείκτες οι οποίοι με την σειρά τους δείχνουν σε δεδομένα. Ενδεικτικά δείτε το παρακάτω παράδειγμα δεικτών. Οι τέσσερις τελευταίες γραμμές του παρακάτω κώδικα είναι ισοδύναμες και θέτουν το περιεχόμενο της μεταβλητής a σε '@'. Οι μεταβλητές a,b,c,d απεικονίζονται στο σχήμα που ακολουθεί.

pointers2pointers.cpp
char a = '$';
char *b = &a;   // δείκτης στη μεταβλητή a
char **c = &b;  // δείκτης στο δείκτη b
char ***d = &c; // δείκτης στο δείκτη c
 
a = '@'         // το περιεχόμενο της μεταβλητής a γίνεται '@'.
*b = '@';       // το περιεχόμενο του δείκτη b είναι η μεταβλητή a.
**c = '@';      // το περιεχόμενο του δείκτη c είναι ο δείκτης b.
***d = '@';     // το περιεχόμενο του δείκτη d είναι ο δείκτης c.

 Οι μεταβλητές a,b,c,d

Δείκτες τύπου void

Ο δείκτης τύπου void είναι ένας ειδικός τύπος δείκτη ο οποίος έχει το πλεονέκτημα ότι μπορεί να μετατραπεί σε οποιονδήποτε τύπο δείκτη. Επίσης οποιοσδήποτε τύπος δείκτη μπορεί να μετατραπεί σε δείκτη τύπου void. Ο περιορισμός των δεικτών τύπου void είναι ότι δεν υπάρχει τρόπος να προσπελάσουμε άμεσα το περιεχόμενο τους. Για την προσπέλαση τους θα πρέπει προηγούμενα να μετατραπούν σε ένα διαφορετικό τύπο δείκτη. Δείτε το παρακάτω παράδειγμα που χρησιμοποιεί ένα δείκτη void* για να προσπελάσει τα δεδομένα του.

void_ptr.cpp
#include <iostream>
using namespace std;
 
void increase (void* data, int psize)
{
  if ( psize == sizeof(char) ) {
    char* pchar; pchar=(char*)data; ++(*pchar); 
  }
  else if (psize == sizeof(int) ) {
    int* pint; pint=(int*)data; ++(*pint);
  }
}
 
int main ()
{
  char a = 'a';
  int b = 2;
  increase(&a,sizeof(a));
  increase(&b,sizeof(b));
  cout << a << ", " << b << endl;
  return 0;
}

Δεν υφίσταται η αριθμητική δεικτών σε δείκτες τύπου void*.

Δείκτες με τιμή NULL

Δείκτες που δεν είναι αρχικοποιημένοι συχνά αρχικοποιούνται στην τιμή 0 ή NULL. Η C++ εκτός από τις τιμές 0 και null παρέχει και την δεσμευμένη λέξη nullptr που αντιπροσωπεύει και αυτή την τιμή 0 για ένα δείκτη. Οι παρακάτω ορισμοί είναι ισοδύναμοι.

nullptr.cpp
#include <iostream>
 
char *p = 0;
char *q = NULL;
char *f = nullptr;

Η τιμή NULL ορίζεται σε διαφορετικά C++ header αρχεία της standard βιβλιοθήκης, ανάμεσα τους και το iostream.

Δείκτες σε συναρτήσεις

Η C++ επιτρέπει την χρήση δεικτών σε συναρτήσεις. Οι δείκτες σε συναρτήσεις ορίζονται όπως οι συναρτήσεις με την διαφορά ότι το όνομα του δείκτη περικλύεται από παρενθέσεις και πριν από το όνομα προηγείται ο τελεστής *. Δείτε το παρακάτω παράδειγμα ορισμού και χρήσης ενός δείκτη σε συνάρτηση.

functionPointer.cpp
#include <iostream>
using namespace std;
 
int addition (int a, int b){ return a+b; }
int subtraction (int a, int b){ return a-b; }
 
int operation (int x, int y, int (*functioncall)(int,int)){
  int r = (*functioncall)(x,y);
  return r;
}
 
int main(){
  int m,n;
  int (*sub_ptr)(int,int) = subtraction;
 
  m = operation (7, 5, addition);
  n = operation (20, m, sub_ptr);
  cout << "Result: " << n << endl;
  return 0;
}

Παρατηρήστε ότι κατά τον ορισμό ενός δείκτη σε συνάρτηση είναι απαραίτητο να ορίσετε τον τύπο και τη σειρά των ορισμάτων που λαμβάνει η συνάρτηση.

cpp/pointers.txt · Last modified: 2023/04/25 13:14 by gthanos