Tutoriel lié à celui de GrandVizir : http://codes-sources.commentcamarche.net/faq/177-delphi-declarer-et-utiliser-les-types
Les pointeurs sont très utiles. Ils permettent d'accélerer ou d'alléger certains processus.
Comme vous le savez, à chaque fois que vous déclarez une variable, une taille est automatiquement allouée dans la mémoire. Chaque variable peut être considérée comme une "boîte" qui contient l'information que l'on a stocké. Ces petites "boîtes" sont placées quelque part dans la mémoire, elles ont une adresse.
Le principe du pointeur c'est de gérer soi-même la mémoire, au lieu de laisser le compilateur le faire.Lorsque vous déclariez votre variable, vous mettiez votre information dans une "boîte" sans vous souciez ou elle se trouvait dans la mémoire. Cette fois, vous allez gérer son emplacement et sa taille.
Un pointeur est une variable spéciale qui contient une adresse. On pourrait comparer un pointeur à un raccourci. Imaginez que vous ayez une liste de 40 divx, faisant 1 go chacun, que vous voulez trier. La premiere solution consiste à trier naturellement ces 40 fichiers, ce qui mettrait un temps fou. La deuxième serait de créer un raccourci pour chaque divx, et de trier, non pas les divx directement, mais les raccourcis de ceux-ci. Ainsi en très peu de temps, on a trié notre liste (même si on n'a pas trié directement la liste, le résultat est le même).
On pourrai donc voir les pointeurs comme des "flèches" qui pointent sur des "boîtes".
Pour déclarer un pointeur rien de plus simple. Il suffit de mettre un accent circonflexe devant le type pointé.
pChiffre = ^Integer; pChaine = ^string; etc...
Ce type de pointeur ne peux pointer que devant le type qui lui est associé.
Lorsque vous allez créer votre pointeur, celui-ci ne pointera sur rien. Vous devez donc lui dire sur quoi pointer.
Exemple :
procedure Main(); var Chiffre1: ^integer; I: integer; begin I:=12; Chiffre1:=nil; // ne pointe sur rien Chiffre1:=@I; WriteLn(Chiffre1^); // Affichera 12 WriteLn(I); // Affichera 12 end;
Analysons l'exemple précédent.
On commence par déclarer un pointeur sur entier (Chiffre1) et un entier (I). Puis on attribue la valeur de 12 à I. Chiffre1 ne pointe au début sur rien (nil). Puis on récupère l'adresse de I, grâce à l'opérateur "@". Maintenant notre "flèche" chiffre1 pointe sur la "boîte" I. Enfin, on aimerait se servir du joli pointeur que l'on a créé, alors on va afficher la valeur de I, indirectement. On déréférence le pointeur, c'est à dire que le pointeur va se comporter comme s'il était la "boîte" pointée (avec le signe"^").
A présent, on sait utiliser les pointeurs comme raccourci. Maintenant, on va voir comment allouer de la mémoire, c'est à dire se servir des pointeurs sans utiliser de variables déjà existante. Vous allez me dire, s'il n'y a pas de variable à pointer, comment faire ? Et bien c'est simple, on peut utiliser un pointeur pour créer une "boite" dans la mémoire.
Pour allouer de la mémoire plusieurs solutions.
On utilise la fonction GetMem. Vous donnez le nom du pointeur, et la taille de la "boîte".
Exemple :
GetMem(Chiffre1,50);
Voila.. Vous venez de créer un espace pour mettre une valeur. Maintenant, on sait que toute les variables ne prennent pas toute la même place en mémoire. On va donc allouer de la mémoire en fonction de la taille de la variable.
Exemple :
GetMem(Chiffre1,SizeOf(integer));
Voila, on est sur d'occuper exactement la taille nécessaire.
Toutefois je vous conseille d'utiliser la fonction "New" qui se charge de tout.
Exemple :
New(Chiffre1);
Cela revient au même, mais c'est plus simple à utiliser, non ?
Notre "boîte"est créée. Il ne reste qu'à la remplir. Pour cela, on déréférence le pointeur, et on fait comme si le pointeur était une variable normale.
Exemple :
Chiffre1^:=52;
Il reste une notion importante, si on alloue un espace mémoire, il faut ABSOLUMENT le libérer quand on a finit de s'en servir.
Pour cela, plusieurs méthodes, en fonction de la méthode utilisée.
Exemple :
GetMem(Chiffre1,50); => FreeMem(Chiffre1,50);
GetMem(Chiffre1,SizeOf(integer)); => FreeMem(Chiffre1,SizeOf(integer));
New(Chiffre1); => Dispose(Chiffre1);
Je vous conseille fortement d'utiliser "new" et "dispose".
Exemple récapitulatif :
procedure Main(); var Chiffre1: ^integer; Chiffre2: pChiffre; Chaine1 : pChaine; I: integer; s: string; begin new(Chiffre1);// Allocation mémoire new(Chiffre2); new(Chaine1); s:='bonjour'; I:=7; Chiffre1^:=5; WriteLn(Chiffre1^);// affichera 5 Chiffre1^:=I; WriteLn(Chiffre1^);// affichera 7 Chiffre2^:=I; WriteLn(Chiffre2^);// affichera 7 Chaine1^:='b'; WriteLn(Chaine1^);// affichera 'b' Chaine1^:=s; WriteLn(Chaine1^);// affichera 'bonjour' dispose(Chiffre1);// Désallocation mémoire dispose(Chiffre2); dispose(Chaine1); end;
Ainsi on remarque que le type "^integer" et "pChiffre" pointe sur la même chose.
Pourtant, ceci: "Chiffre2:=Chiffre1" ne fonctionnera pas. En effet, bien que ces deux types de pointeurs pointent sur la même chose, ils ne sont pas identiques pour autant.
Alors, me direz-vous, pourquoi créer par exemple le type pChiffre et ne pas laisser ^integer ?
La réponse est simple, pour les passage en paramètres dans les fonctions et procédures.
Ceci ne fonctionnera pas :
procedure Test(Pointeur:^integer);
Par contre ceci, oui :
procedure Test(Pointeur:pChiffre);
Voila tout l'intérêt de créer ses propres types pointés.
Ensuite on peut remplir les paramètres de la fonction test comme ceci :
Test(Chiffre1);// Ne fonctionnera pas, les types étant différents Test(Chiffre2);// L'adresse de 7 (fonctionne) Test(@I);// L'adresse de 7 (fonctionne) Test(@7);// Ne fonctionnera pas, 7 n'étant pas une variable
(Je rappelle que l'opérateur @ renvoie l'adresse d'une variable).
Enfin, lorsque vous utilisez des pointeurs, essayez de les utiliser comme suit :
procedure Main(); var Chiffre2: pChiffre; begin new(Chiffre2);// Allocation mémoire Try I:=7; WriteLn(I);// affichera 7 WriteLn(Chiffre2^);// affichera 7 Finally dispose(Chiffre2);// Désallocation mémoire end; end;
Avec cette structure vous êtes sur que votre pointeur sera bien détruit, même en cas d'erreur.
Il y a certaines erreurs qui reviennent souvent. Par exemple, n'essayez jamais de désallouer un pointeur nul.
Exemple :
procedure Main(); var Chiffre2: pChiffre; begin new(Chiffre2);// Allocation mémoire Chiffre2:=nil;// ne pointe sur rien Try I:=7; WriteLn(I);// affichera 7 WriteLn(Chiffre2^);// PLANTE Finally dispose(Chiffre2);// PLANTE end; end;
En passant, lorsque l'on a fait un "Chiffre2:=nil;" on a perdu, dans la mémoire, la "boîte" que l'on pointait. Comme on a perdu l'adresse, on ne pourra plus jamais la retrouver. Une partie de la mémoire sera donc occupée pour rien. (Pas de panique, un petit reboot et c'est réglé).
Deuxième erreur fréquente, n'oubliez pas d'allouer avant de manier un pointeur.
Exemple :
procedure Main(); var Chiffre2: pChiffre; begin Try I:=7; WriteLn(I);// affichera 7 WriteLn(Chiffre2^);// PLANTE Finally dispose(Chiffre2);// PLANTE end; end;
Maintenant vous allez me dire, oui mais dans ton premier exemple tu n'alloues pas.
Rappel (1er exemple) :
procedure Main(); var Chiffre1: ^integer; I: integer; begin I:=12; Chiffre1:=nil; // ne pointe sur rien Chiffre1:=@I; WriteLn( Chiffre1^ ); // Affichera 12 WriteLn( I ); // Affichera 12 end;
C'est pas tout à fait pareil, dans mon premier exemple, je n'ai rien à allouer parce que les variables existent déjà. Ici, la variable I existe, je veux stocker dans la "boîte" I, je n'ai donc pas besoin de créer une nouvelle "boîte".
Enfin, dernière erreur (la plus fréquente), n'oubliez pas de désallouer un pointeur.
Exemple :
procedure Main(); var Chiffre2: pChiffre; begin New(Chiffre1); I:=7; WriteLn(I);// affichera 7 WriteLn(Chiffre2^);// Affiche 7 end;
Le pire dans cette erreur, c'est que ça ne fait pas planter l'application. De plus, Delphi désalloue tout seul les pointeurs si vous oubliez de le faire, mais ne prenez pas de mauvaises habitudes...
On peut bien évidemment pointer sur des structures plus évoluées (Tableau,enregistrement, classe, etc..).
pTab = ^TTab; TTab = Array of Array of integer; pRecord = ^TRecord; TRecord = record Truc:string; Machin:integer; end;
Créer un tableau de pointeur :
TTabPointeur = Array of pChiffre;// Tableau de pointeur pointant sur des integer
Ou même pointer sur un pointeur (bien que je n'ai jamais trouvé une utilité à ceci) :
pPointeur = ^pChiffre; pChiffre = ^integer;
ATTENTION : ne pas confondre "pointeur sur tableau" et "tableau de pointeur".
Un pointeur sur tableau est un pointeur unique qui pointe sur un tableau.
Il se référence comme ceci :
Tab^[0]
Un tableau de pointeur est un tableau qui contient des pointeurs.
Il se référence comme ceci :
Tab[0]^
On peut évidemment déclarer un type "Pointeur sur tableau de pointeur".
Il se référence comme ceci :
Tab^[0]^
Dernier type de pointeur : le pointeur neutre. Ce type de pointeur, peut pointer sur tout.
On le déclare comme ceci:
Pointeur = Pointer;
Toutefois, ce type de pointeur ne peut être déréférencé.
ceci est valide :
Pointeur:=Chiffre2; // (avec Chiffre2^:=5)
ceci est invalide :
Pointeur^:=5;
En effet, il faut transtyper ce type.
ceci est valide :
WriteLn(pChiffre(Pointeur)^); // affiche 5
ceci est invalide :
WriteLn(Pointeur^);
Je vous déconseille d'utiliser ce type de pointeur. En effet, une erreur de pointeur est vite arrivée et il sera difficile de la trouver.
On peut pointer aussi sur des fonctions ou des procédures.
<cod pascal>pFunc = function:integer;// Pointe sur des fonctions
//qui ne prennent rien en paramètre
//mais retournent un type integer
pProc = procedure; // Pointe sur des procédures qui ne prennent rien en paramètre
pFunc2 = function(s:string):integer;// Pointe sur des fonctions
//qui prennent une chaine en paramètre
//et retournent un type integer
pProc2 = procedure(I:integer);// Pointe sur des procédures qui prennent
//un entier en paramètre</code>
Sans le savoir lorsque vous remplissez un événement dans l'inspecteur d'objet (par exemple l'événement "OnClick") c'est un pointeur sur procédure que vous utilisez.
OnClick = Procedure(Sender: TObject);
Pour utiliser nos procédures pointées, il faut faire ceci :
type pFunc2 = function(s:string):integer; function MyStrToInt(s:string):integer; begin Result:=StrToInt(s); end; function MyStrToInt2x(s:string):integer; begin Result:=2*StrToInt(s); end; procedure Main(); var Func: pFunc2; Resultat:integer; begin Func:=MyStrToInt; Resultat:=Func('13'); WriteLn(Resultat);// affiche 13 Func:=MyStrToInt2x; Resultat:=Func('13'); WriteLn(Resultat);// Affiche 26 end;
Cela permet aussi d'avoir indirectement un tableau de procédures ou de fonctions.
TTabFunc = Array of pFunc2;
Enfin, je terminerai sur les listes chainées. Une liste chainée est comparable à un tableau.
Elle se construit avec un enregistrement. Le principe est celui ci : chaque "case" de ce "tableau" contient l'adresse de la prochaine "case". D'une certaine manière cela revient à gérer soit même son tableau.
On la déclare comme ceci :
pListe = ^TListe; TListe = record Truc: integer; ... Suivant: pListe; end;
Pour savoir quand on atteint la fin du tableau, on regarde si la variable "Suivant" vaut "nil". Il existe des variantes comme les listes doublement chainées (chaque case possède l'adresse de la case suivante et de la case précédente), les listes circulaires (la dernière case pointe sur la première), et les listes circulaires doublement chainées.
Pour lire un élément, il suffit de le déréférencer. Mais avant il faut déjà placer le pointeur dessus.
1er élément : Pointeur^
2eme élément : Pointeur^.Suivant^
3eme élément : Pointeur^.Suivant^.Suivant^
Vous imaginez bien que si la liste comporte 120 éléments, on ne va pas écrire "Suivant^.Suivant^...".
On va donc utiliser une boucle.
Exemple :
begin For I:=0 to 2 do Pointeur:=Pointeur^.Suivant; // accès 3ème élément WriteLn(Pointeur^); end;
Ainsi on atteint le 3eme élément du tableau. En contrepartie, on perdu toutes les informations précédentes (on ne peut plus retourner en arrière).
ATTENTION : le "danger" avec les listes chainées c'est de perdre la "tête", c'est à dire perdre les précédents éléments de la liste. Cela arrive fréquemment. Pour parcourir votre liste chainée, créez un pointeur temporaire.
Il faut donc toujours l'écrire comme ceci :
Exemple :
var TmpPointeur:pListe; begin TmpPointeur:=Pointeur; For I:=0 to 2 do TmpPointeur:=TmpPointeur^.Suivant; // accès 3ème élément WriteLn(TmpPointeur^); end;
Là, c'est parfait, on accède au 3ème élément, sans perdre des informations.
Pour insérer un élément, le principe est le suivant : On crée une "boîte" pointée par un pointeur temporaire. On fait pointer notre "boîte" sur la "boîte" suivante de la "boîte" précédente. On fait maintenant pointer la "boîte" précédente sur notre "boîte". Et voila, on a ajouté notre élément. Vous comprenez maintenant l'utilité des listes chainées par rapport aux tableaux. Dans un tableau, il aurait fallu décaler certains éléments vers la droite, en liste chainée, c'est immédiat.
Exemple d'ajout en 3ème position :
var TmpPointeur,PointeurCreation:pListe; begin New(PointeurCreation); TmpPointeur:=Pointeur; // acces 2eme élément For I:=0 to 1 do TmpPointeur:=TmpPointeur^.Suivant; // On fait pointer notre "boîte" sur la "boîte" suivante de la "boîte" précédente. PointeurCreation^.Suivant:=TmpPointeur^.Suivant; // On fait maintenant pointer la "boîte" précédente sur notre "boîte". TmpPointeur^.Suivant:=PointeurCreation; end;
Pour supprimer un élément, le principe est proche de l'insertion d'élément. On se place sur l'élément précédant l'élément à supprimer. Puis on pose un pointeur temporaire sur cet élément. On fait pointer l'élément précédent sur l'élément suivant l'élément à supprimer. Enfin, on détruit l'élément à supprimer.
Exemple de suppression de la 3ème position :
var TmpPointeur,SupprPointeur:pListe; begin TmpPointeur:=Pointeur; // On se place sur l'élément précédent l'élément à supprimer. For I:=0 to 1 do TmpPointeur:=TmpPointeur^.Suivant; // Puis on pose un pointeur temporaire sur cet élément. SupprPointeur:=TmpPointeur^.Suivant; // On fait pointer l'élément précédent sur l'élément suivant l'élément à supprimer. TmpPointeur^.Suivant:=SupprPointeur^.Suivant; // Enfin, on détruit l'élément à supprimer. Dispose(SupprPointeur); end;
Pour des exemples un peu plus concret, allez voir ici:
http://codes-sources.commentcamarche.net/source/32957-tutorial-listes-chainees