Tutorial liées à celui de GrandVizir:
http://www.delphifr.com/tutorial.aspx?ID=177IntroductionLes 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é comme une "boîte" qui contient l'information que l'on a stockée. Ces petites "boîtes" sont placé quelquepart dans la mémoire,elles ont une adresses.
Le principe du pointeur c'est de gérer soit 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écial qui contient une adresse. On pourrait comparer un pointeur à un raccourci. Imaginez que vous ayez une listede 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 deuxieme 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 pas trier 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".
Déclaration de type pointeur simple.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écupere 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 à crée, 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éference 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 parametres 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'interêt de créer ses propres types pointés.
Ensuite on peut remplir les parametres de la fonction test comme ceci:
Test(Chiffre1);// Ne fonctionnera pas, les types étant différents
Test(Chiffre2);// L'adresse de 7 (fonctionnne)
Test(@I);// L'adresse de 7 (fonctionnne)
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.
Les erreurs classiques.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é).
Deuxieme 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'alloue 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 parceque 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 ca 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...
Déclaration de type pointeur plus évoluée.On peut bien évidemment pointer sur des structures plus évolués (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 déference comme ceci:
Tab^[0]Un tableau de pointeur est un tableau qui contient des pointeurs.
Il se déference comme ceci:
Tab[0]^On peut evidemmnent déclarer un type "Pointeur sur tableau de pointeur".
Il se déference comme ceci:
Tab^[0]^Le pointeur neutre.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 5ceci est invalide:
WriteLn(Pointeur^);Je vous déconseille d'utiliser ce type de pointeur. En effet, une erreur de pointeur est vite arrivé et il sera difficile de la trouver.
Les pointeurs sur fonctions/procedures.On peut pointer aussi sur des fonctions ou des procedures.
pFunc = function:integer;// Pointe sur des fonctions qui ne prennent rien en parametre mais retourne un type integer
pProc = procedure;// Pointe sur des procedure qui ne prennent rien en parametre
pFunc2 = function(s:string):integer;// Pointe sur des fonctions qui prennent une chaine en parametre et retourne un type integer
pProc2 = procedure(I:integer);// Pointe sur des procedure qui prennent un entier en parametre
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 procedure que vous utilisez.
OnClick = Procedure(Sender: TObject);Pour utiliser nos procedures 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 procedure ou de fonctions.
TTabFunc = Array of pFunc2;Les listes chainées.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'unecertaine maniere 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 (chaques cases possedent l'adresses de la case suivante et de la case précedente), les listes circulaires (la derniere case pointe sur la premiere), et les listes circulaires doublement chainées.
Manipulation:Accéder à l'élement n:Pour lire un élément, il suffit de le déréferencer. Mais avant il faut déja placer le pointeur dessus.
1er element:
Pointeur^2eme element:
Pointeur^.Suivant^3eme element:
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;
// acces 3eme élément
WriteLn(Pointeur^);
end;Ainsionatteint le 3eme élément du tableau, en contrepartie, on perdu toute 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 frequement. 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;
// acces 3eme élément
WriteLn(TmpPointeur^);
end;
La, c'est parfait, on accède au 3eme élément, sans perdre des informations.
Insertion d'élémentPour insérer un élément le principe est le suivant: On créer 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édentes sur notre "boîte". Et voila, on a ajouté notre élément. Vous comprenez maintenant l'utilité des listes chainées par rapport au tableau. Dans un tableau il aurait fallu décaler certains éléments versla droite, en liste chainée, c'est immédiat.
Exemple d'ajout en 3eme positions:
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édentes sur notre "boîte".
TmpPointeur^.Suivant:=PointeurCreation;
end;
Suppression d'élementPour supprimer un élements le principe est proche de l'insertion d'élément.On se place sur l'élément précédent 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 à suppprimer. Enfin, on détruit l'élément à supprimer.
Exemple de suppression de la 3eme positions:
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 à suppprimer.
TmpPointeur^.Suivant:=SupprPointeur^.Suivant;
// Enfin, on détruit l'élément à supprimer.
Dispose(SupprPointeur);
end;
ConclusionPour des exemples un peu plus concret, allez voir ici:
http://www.delphifr.com/code.aspx?ID=32957