Programmazione D - Puntatori

I puntatori alla programmazione D sono facili e divertenti da imparare. Alcune attività di programmazione D vengono eseguite più facilmente con i puntatori e altre attività di programmazione D, come l'allocazione dinamica della memoria, non possono essere eseguite senza di esse. Di seguito viene mostrato un semplice puntatore.

Invece di puntare direttamente alla variabile, il puntatore punta all'indirizzo della variabile. Come sai ogni variabile è una posizione di memoria e ogni posizione di memoria ha il suo indirizzo definito a cui è possibile accedere utilizzando l'operatore e commerciale (&) che denota un indirizzo in memoria. Considera quanto segue che stampa l'indirizzo delle variabili definite -

import std.stdio;
 
void main () { 
   int var1; 
   writeln("Address of var1 variable: ",&var1);  
   
   char var2[10]; 
   writeln("Address of var2 variable: ",&var2); 
}

Quando il codice precedente viene compilato ed eseguito, produce il seguente risultato:

Address of var1 variable: 7FFF52691928 
Address of var2 variable: 7FFF52691930

Cosa sono i puntatori?

UN pointerè una variabile il cui valore è l'indirizzo di un'altra variabile. Come ogni variabile o costante, è necessario dichiarare un puntatore prima di poter lavorare con esso. La forma generale di una dichiarazione di variabile puntatore è:

type *var-name;

Qui, typeè il tipo base del puntatore; deve essere un tipo di programmazione valido evar-nameè il nome della variabile del puntatore. L'asterisco che hai usato per dichiarare un puntatore è lo stesso asterisco che usi per la moltiplicazione. Però; in questa istruzione l'asterisco viene utilizzato per designare una variabile come puntatore. Di seguito sono riportate le dichiarazioni di puntatore valide:

int    *ip;    // pointer to an integer 
double *dp;    // pointer to a double 
float  *fp;    // pointer to a float 
char   *ch     // pointer to character

Il tipo di dati effettivo del valore di tutti i puntatori, se intero, float, carattere o altro, è lo stesso, un numero esadecimale lungo che rappresenta un indirizzo di memoria. L'unica differenza tra i puntatori di diversi tipi di dati è il tipo di dati della variabile o della costante a cui punta il puntatore.

Utilizzo dei puntatori nella programmazione D.

Ci sono poche operazioni importanti, quando usiamo i puntatori molto frequentemente.

  • definiamo un puntatore variabili

  • assegna l'indirizzo di una variabile a un puntatore

  • accedere infine al valore all'indirizzo disponibile nella variabile pointer.

Questo viene fatto utilizzando l'operatore unario *che restituisce il valore della variabile situata all'indirizzo specificato dal suo operando. Il seguente esempio fa uso di queste operazioni:

import std.stdio; 

void main () { 
   int var = 20;   // actual variable declaration. 
   int *ip;        // pointer variable
   ip = &var;   // store address of var in pointer variable  
   
   writeln("Value of var variable: ",var); 
   
   writeln("Address stored in ip variable: ",ip); 
   
   writeln("Value of *ip variable: ",*ip); 
}

Quando il codice precedente viene compilato ed eseguito, produce il seguente risultato:

Value of var variable: 20 
Address stored in ip variable: 7FFF5FB7E930 
Value of *ip variable: 20

Puntatori nulli

È sempre buona norma assegnare il puntatore NULL a una variabile del puntatore nel caso non si abbia l'indirizzo esatto da assegnare. Questo viene fatto al momento della dichiarazione della variabile. Un puntatore a cui è assegnato un valore null è chiamato anull puntatore.

Il puntatore nullo è una costante con un valore zero definito in diverse librerie standard, incluso iostream. Considera il seguente programma:

import std.stdio;

void main () { 
   int  *ptr = null; 
   writeln("The value of ptr is " , ptr) ;  
}

Quando il codice precedente viene compilato ed eseguito, produce il seguente risultato:

The value of ptr is null

Nella maggior parte dei sistemi operativi, ai programmi non è consentito accedere alla memoria all'indirizzo 0 perché tale memoria è riservata dal sistema operativo. Però; l'indirizzo di memoria 0 ha un significato speciale; segnala che il puntatore non è destinato a puntare a una posizione di memoria accessibile.

Per convenzione, se un puntatore contiene il valore null (zero), si presume che punti a nulla. Per verificare la presenza di un puntatore nullo puoi utilizzare un'istruzione if come segue:

if(ptr)     // succeeds if p is not null 
if(!ptr)    // succeeds if p is null

Pertanto, se a tutti i puntatori inutilizzati viene assegnato il valore nullo e si evita l'uso di un puntatore nullo, è possibile evitare l'abuso accidentale di un puntatore non inizializzato. Molte volte, le variabili non inizializzate contengono alcuni valori spazzatura e diventa difficile eseguire il debug del programma.

Puntatore aritmetico

Ci sono quattro operatori aritmetici che possono essere usati sui puntatori: ++, -, + e -

Per comprendere l'aritmetica dei puntatori, consideriamo un puntatore intero denominato ptr, che punta all'indirizzo 1000. Supponendo numeri interi a 32 bit, eseguiamo la seguente operazione aritmatica sul puntatore:

ptr++

poi il ptrpunterà alla posizione 1004 perché ogni volta che ptr viene incrementato, punta al numero intero successivo. Questa operazione sposterà il puntatore alla posizione di memoria successiva senza influire sul valore effettivo nella posizione di memoria.

Se ptr punta a un carattere il cui indirizzo è 1000, quindi l'operazione precedente punta alla posizione 1001 perché il carattere successivo sarà disponibile a 1001.

Incremento di un puntatore

Preferiamo usare un puntatore nel nostro programma invece di un array perché il puntatore della variabile può essere incrementato, a differenza del nome dell'array che non può essere incrementato perché è un puntatore costante. Il seguente programma incrementa il puntatore della variabile per accedere a ciascun elemento successivo dell'array -

import std.stdio; 
 
const int MAX = 3; 
 
void main () { 
   int var[MAX] = [10, 100, 200]; 
   int *ptr = &var[0];  

   for (int i = 0; i < MAX; i++, ptr++) { 
      writeln("Address of var[" , i , "] = ",ptr); 
      writeln("Value of var[" , i , "] = ",*ptr); 
   } 
}

Quando il codice precedente viene compilato ed eseguito, produce il seguente risultato:

Address of var[0] = 18FDBC 
Value of var[0] = 10 
Address of var[1] = 18FDC0 
Value of var[1] = 100 
Address of var[2] = 18FDC4 
Value of var[2] = 200

Puntatori vs Array

I puntatori e gli array sono fortemente correlati. Tuttavia, i puntatori e le matrici non sono completamente intercambiabili. Ad esempio, considera il seguente programma:

import std.stdio; 
 
const int MAX = 3;
  
void main () { 
   int var[MAX] = [10, 100, 200]; 
   int *ptr = &var[0]; 
   var.ptr[2]  = 290; 
   ptr[0] = 220;  
   
   for (int i = 0; i < MAX; i++, ptr++) { 
      writeln("Address of var[" , i , "] = ",ptr); 
      writeln("Value of var[" , i , "] = ",*ptr); 
   } 
}

Nel programma sopra, puoi vedere var.ptr [2] per impostare il secondo elemento e ptr [0] che viene utilizzato per impostare l'elemento zeroth. L'operatore di incremento può essere utilizzato con ptr ma non con var.

Quando il codice precedente viene compilato ed eseguito, produce il seguente risultato:

Address of var[0] = 18FDBC 
Value of var[0] = 220 
Address of var[1] = 18FDC0 
Value of var[1] = 100 
Address of var[2] = 18FDC4 
Value of var[2] = 290

Pointer to Pointer

Un puntatore a un puntatore è una forma di più riferimenti indiretti o una catena di puntatori. Normalmente, un puntatore contiene l'indirizzo di una variabile. Quando definiamo un puntatore a un puntatore, il primo puntatore contiene l'indirizzo del secondo puntatore, che punta alla posizione che contiene il valore effettivo come mostrato di seguito.

Una variabile che è un puntatore a un puntatore deve essere dichiarata come tale. Questo viene fatto inserendo un asterisco aggiuntivo davanti al suo nome. Ad esempio, la seguente è la sintassi per dichiarare un puntatore a un puntatore di tipo int -

int **var;

Quando un valore di destinazione è indirettamente puntato da un puntatore a un puntatore, quindi l'accesso a quel valore richiede che l'operatore asterisco venga applicato due volte, come mostrato di seguito nell'esempio:

import std.stdio;  

const int MAX = 3;
  
void main () { 
   int var = 3000; 
   writeln("Value of var :" , var); 
   
   int *ptr = &var; 
   writeln("Value available at *ptr :" ,*ptr); 
   
   int **pptr = &ptr; 
   writeln("Value available at **pptr :",**pptr); 
}

Quando il codice precedente viene compilato ed eseguito, produce il seguente risultato:

Value of var :3000 
Value available at *ptr :3000 
Value available at **pptr :3000

Passaggio del puntatore alle funzioni

D consente di passare un puntatore a una funzione. Per fare ciò, dichiara semplicemente il parametro della funzione come un tipo di puntatore.

Il semplice esempio seguente passa un puntatore a una funzione.

import std.stdio; 
 
void main () { 
   // an int array with 5 elements. 
   int balance[5] = [1000, 2, 3, 17, 50]; 
   double avg; 
   
   avg = getAverage( &balance[0], 5 ) ; 
   writeln("Average is :" , avg); 
} 
 
double getAverage(int *arr, int size) { 
   int    i; 
   double avg, sum = 0; 
   
   for (i = 0; i < size; ++i) {
      sum += arr[i]; 
   } 
   
   avg = sum/size; 
   return avg; 
}

Quando il codice precedente viene compilato insieme ed eseguito, produce il seguente risultato:

Average is :214.4

Puntatore di ritorno da funzioni

Considera la seguente funzione, che restituisce 10 numeri utilizzando un puntatore, indica l'indirizzo del primo elemento dell'array.

import std.stdio;
  
void main () { 
   int *p = getNumber(); 
   
   for ( int i = 0; i < 10; i++ ) { 
      writeln("*(p + " , i , ") : ",*(p + i)); 
   } 
} 
 
int * getNumber( ) { 
   static int r [10]; 
   
   for (int i = 0; i < 10; ++i) {
      r[i] = i; 
   }
   
   return &r[0]; 
}

Quando il codice precedente viene compilato ed eseguito, produce il seguente risultato:

*(p + 0) : 0 
*(p + 1) : 1 
*(p + 2) : 2 
*(p + 3) : 3 
*(p + 4) : 4 
*(p + 5) : 5 
*(p + 6) : 6 
*(p + 7) : 7 
*(p + 8) : 8 
*(p + 9) : 9

Puntatore a un array

Il nome di un array è un puntatore costante al primo elemento dell'array. Pertanto, nella dichiarazione -

double balance[50];

balanceè un puntatore a & balance [0], che è l'indirizzo del primo elemento dell'array balance. Pertanto, il seguente frammento di programma assegnap l'indirizzo del primo elemento di balance -

double *p; 
double balance[10]; 
 
p = balance;

È legale utilizzare i nomi degli array come puntatori costanti e viceversa. Pertanto, * (saldo + 4) è un modo legittimo di accedere ai dati in sospeso [4].

Una volta memorizzato l'indirizzo del primo elemento in p, è possibile accedere agli elementi dell'array utilizzando * p, * (p + 1), * (p + 2) e così via. L'esempio seguente mostra tutti i concetti discussi sopra:

import std.stdio;
 
void main () { 
   // an array with 5 elements. 
   double balance[5] = [1000.0, 2.0, 3.4, 17.0, 50.0]; 
   double *p;  
   
   p = &balance[0]; 
  
   // output each array element's value  
   writeln("Array values using pointer " ); 
   
   for ( int i = 0; i < 5; i++ ) { 
      writeln( "*(p + ", i, ") : ", *(p + i)); 
   } 
}

Quando il codice precedente viene compilato ed eseguito, produce il seguente risultato:

Array values using pointer  
*(p + 0) : 1000 
*(p + 1) : 2 
*(p + 2) : 3.4 
*(p + 3) : 17
*(p + 4) : 50