Programmation D - Pointeurs

Les pointeurs de programmation D sont faciles et amusants à apprendre. Certaines tâches de programmation D sont effectuées plus facilement avec des pointeurs, et d'autres tâches de programmation D, telles que l'allocation de mémoire dynamique, ne peuvent pas être effectuées sans eux. Un simple pointeur est illustré ci-dessous.

Au lieu de pointer directement vers la variable, le pointeur pointe vers l'adresse de la variable. Comme vous le savez, chaque variable est un emplacement de mémoire et chaque emplacement de mémoire a son adresse définie, accessible à l'aide de l'opérateur perluète (&) qui désigne une adresse en mémoire. Considérez ce qui suit qui imprime l'adresse des variables définies -

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

Lorsque le code ci-dessus est compilé et exécuté, il produit le résultat suivant -

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

Que sont les pointeurs?

UNE pointerest une variable dont la valeur est l'adresse d'une autre variable. Comme toute variable ou constante, vous devez déclarer un pointeur avant de pouvoir l'utiliser. La forme générale d'une déclaration de variable de pointeur est -

type *var-name;

Ici, typeest le type de base du pointeur; il doit s'agir d'un type de programmation valide etvar-nameest le nom de la variable du pointeur. L'astérisque que vous avez utilisé pour déclarer un pointeur est le même astérisque que vous utilisez pour la multiplication. Pourtant; dans cette instruction, l'astérisque est utilisé pour désigner une variable comme pointeur. Voici la déclaration de pointeur valide -

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

Le type de données réel de la valeur de tous les pointeurs, qu'il soit entier, flottant, caractère ou autre, est le même, un long nombre hexadécimal qui représente une adresse mémoire. La seule différence entre les pointeurs de différents types de données est le type de données de la variable ou de la constante vers laquelle pointe le pointeur.

Utilisation des pointeurs dans la programmation D

Il y a peu d'opérations importantes, lorsque nous utilisons les pointeurs très fréquemment.

  • nous définissons une variable de pointeur

  • affecter l'adresse d'une variable à un pointeur

  • enfin accéder à la valeur à l'adresse disponible dans la variable pointeur.

Ceci est fait en utilisant un opérateur unaire *qui renvoie la valeur de la variable située à l'adresse spécifiée par son opérande. L'exemple suivant utilise ces opérations -

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); 
}

Lorsque le code ci-dessus est compilé et exécuté, il produit le résultat suivant -

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

Pointeurs nuls

Il est toujours recommandé d'affecter le pointeur NULL à une variable de pointeur au cas où vous n'auriez pas d'adresse exacte à attribuer. Cela se fait au moment de la déclaration des variables. Un pointeur auquel est assigné null est appelé unnull aiguille.

Le pointeur nul est une constante avec une valeur de zéro définie dans plusieurs bibliothèques standard, y compris iostream. Considérez le programme suivant -

import std.stdio;

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

Lorsque le code ci-dessus est compilé et exécuté, il produit le résultat suivant -

The value of ptr is null

Sur la plupart des systèmes d'exploitation, les programmes ne sont pas autorisés à accéder à la mémoire à l'adresse 0 car cette mémoire est réservée par le système d'exploitation. Pourtant; l'adresse mémoire 0 a une signification particulière; il signale que le pointeur n'est pas destiné à pointer vers un emplacement mémoire accessible.

Par convention, si un pointeur contient la valeur nulle (zéro), il est supposé ne pointer vers rien. Pour rechercher un pointeur nul, vous pouvez utiliser une instruction if comme suit -

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

Ainsi, si tous les pointeurs inutilisés reçoivent la valeur NULL et que vous évitez l'utilisation d'un pointeur NULL, vous pouvez éviter l'utilisation abusive accidentelle d'un pointeur non initialisé. Plusieurs fois, les variables non initialisées contiennent des valeurs indésirables et il devient difficile de déboguer le programme.

Arithmétique du pointeur

Il existe quatre opérateurs arithmétiques qui peuvent être utilisés sur les pointeurs: ++, -, + et -

Pour comprendre l'arithmétique des pointeurs, considérons un pointeur entier nommé ptr, qui pointe vers l'adresse 1000. En supposant des entiers de 32 bits, effectuons l'opération arithmatique suivante sur le pointeur -

ptr++

puis le ptrpointera vers l'emplacement 1004 car chaque fois que ptr est incrémenté, il pointe vers l'entier suivant. Cette opération déplacera le pointeur vers l'emplacement de mémoire suivant sans affecter la valeur réelle à l'emplacement de mémoire.

Si ptr pointe vers un caractère dont l'adresse est 1000, puis l'opération ci-dessus pointe vers l'emplacement 1001 car le caractère suivant sera disponible à 1001.

Incrémenter un pointeur

Nous préférons utiliser un pointeur dans notre programme au lieu d'un tableau car le pointeur de variable peut être incrémenté, contrairement au nom du tableau qui ne peut pas être incrémenté car il s'agit d'un pointeur constant. Le programme suivant incrémente le pointeur de variable pour accéder à chaque élément suivant du tableau -

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); 
   } 
}

Lorsque le code ci-dessus est compilé et exécuté, il produit le résultat suivant -

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

Pointeurs vs tableau

Les pointeurs et les tableaux sont étroitement liés. Cependant, les pointeurs et les tableaux ne sont pas complètement interchangeables. Par exemple, considérons le programme suivant -

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); 
   } 
}

Dans le programme ci-dessus, vous pouvez voir var.ptr [2] pour définir le deuxième élément et ptr [0] qui est utilisé pour définir l'élément zéro. L'opérateur d'incrémentation peut être utilisé avec ptr mais pas avec var.

Lorsque le code ci-dessus est compilé et exécuté, il produit le résultat suivant -

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

Pointeur à pointeur

Un pointeur vers un pointeur est une forme d'indirection multiple ou une chaîne de pointeurs. Normalement, un pointeur contient l'adresse d'une variable. Lorsque nous définissons un pointeur vers un pointeur, le premier pointeur contient l'adresse du deuxième pointeur, qui pointe vers l'emplacement qui contient la valeur réelle comme indiqué ci-dessous.

Une variable qui est un pointeur vers un pointeur doit être déclarée comme telle. Cela se fait en plaçant un astérisque supplémentaire devant son nom. Par exemple, voici la syntaxe pour déclarer un pointeur vers un pointeur de type int -

int **var;

Lorsqu'une valeur cible est indirectement pointée par un pointeur vers un pointeur, l'accès à cette valeur nécessite que l'opérateur astérisque soit appliqué deux fois, comme indiqué ci-dessous dans l'exemple -

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); 
}

Lorsque le code ci-dessus est compilé et exécuté, il produit le résultat suivant -

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

Passer le pointeur aux fonctions

D vous permet de passer un pointeur vers une fonction. Pour ce faire, il déclare simplement le paramètre de fonction comme un type de pointeur.

L'exemple simple suivant passe un pointeur vers une fonction.

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; 
}

Lorsque le code ci-dessus est compilé et exécuté, il produit le résultat suivant -

Average is :214.4

Pointeur de retour des fonctions

Considérez la fonction suivante, qui renvoie 10 nombres à l'aide d'un pointeur, signifie l'adresse du premier élément du tableau.

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]; 
}

Lorsque le code ci-dessus est compilé et exécuté, il produit le résultat suivant -

*(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

Pointeur vers un tableau

Un nom de tableau est un pointeur constant vers le premier élément du tableau. Par conséquent, dans la déclaration -

double balance[50];

balanceest un pointeur vers & balance [0], qui est l'adresse du premier élément de la balance du tableau. Ainsi, le fragment de programme suivant attribuep l'adresse du premier élément de balance -

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

Il est légal d'utiliser des noms de tableaux comme pointeurs constants, et vice versa. Par conséquent, * (solde + 4) est un moyen légitime d'accéder aux données de l'équilibre [4].

Une fois que vous avez stocké l'adresse du premier élément dans p, vous pouvez accéder aux éléments du tableau en utilisant * p, * (p + 1), * (p + 2) et ainsi de suite. L'exemple suivant montre tous les concepts discutés ci-dessus -

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)); 
   } 
}

Lorsque le code ci-dessus est compilé et exécuté, il produit le résultat suivant -

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