Vous ne trouvez pas de réponse à votre problème ? Alors posez la question dans le forum. Souvenez-vous qu'il n'y a jamais de question bête, mais rester dans l'ignorance parce que l'on n'ose pas poser une question, ça c'est une erreur !

[DELPHI] DÉCLARER ET UTILISER LES POINTEURS


Information sur le tutorial

Catégorie :Divers Date de création : 25/07/2005 15:54:37 Vu : 12 414 fois

Note :
9,9 / 10 - par 10 personnes
9,90 / 10

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

Commentaire sur cette source (9)
Ajouter un commentaire et/ou une note

Description

Apprendre à utiliser les pointeurs.

Tutorial

Tutorial liées à celui de GrandVizir: http://www.delphifr.com/tutorial.aspx?ID=177

Introduction

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é 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".

Pointeur.jpg

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 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é 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.

listes chainées.jpg


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;


Acces.jpg

La, c'est parfait, on accède au 3eme élément, sans perdre des informations.


Insertion d'élément

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


Ajout.jpg


Suppression d'élement

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


Suppr.jpg


Conclusion

Pour des exemples un peu plus concret, allez voir ici:
http://www.delphifr.com/code.aspx?ID=32957
25 juillet 2005 16:04:57 :
Quelques fautes d'othographes
26 juillet 2005 02:18:07 :
Mise à jour
29 juillet 2005 19:18:19 :
Ajout de sources d'exemple
02 août 2005 16:44:51 :
Mise à jour
signaler à un administrateur
Commentaire de grandvizir le 25/07/2005 17:16:29

C'est très bien fait. Bravo... Je me doutais bien que ça méritait un chapitre dédié et que tu étais la bonne personne pour le rédiger. Tu reprends bien tout ce qu'on s'est dit, et je ne m'attendais pas à une si bonne présentation.

On peut au passage conseiller ton Puissance 4 (qui a eu beaucoup de succès en plus) :
http://www.delphifr.com/code.aspx?ID=32613

Et comme tu sais, j'avais évoqué la gestion des plugins qui utilise l'opérateur @... A rajouter ! Ca repose simplement sur LoadLibrary, GetProcAdress, FreeLibrary et sur un type déclaré comme procédure ou fonction (avec un type d'appel caractéristique). Tu as d'ailleurs un petit exemple en réserve.

Bonne continuation... ;)

signaler à un administrateur
Commentaire de Delphiprog le 06/08/2005 14:34:16 administrateur CS

Toujours aussi passionné par les pointeurs, n'est-ce pas ?
Félicitations, c'est clair et bien présenté.
Juste une petite remarque : ce que tu appelles les "pointeurs neutres" portent le nom de "pointeurs non typés" dans la documentation Borland. Je trouve cette dernière appellation plus appropriée. Mais, bon, à chacun ses petites manies.

Suggestion : pourquoi ne mets-tu pas un lien vers ce tuto dans tes sources traitant de l'utilisation des pointeurs ?

signaler à un administrateur
Commentaire de jlen100 le 26/10/2005 14:13:47

bien ton tuto il n'y manque qu'un paragraphe sur les Tlist qui sont des listes de pointeurs et qui (à mon avis, mais ce n'est que mon avis) remplacent avantageusement les listes chainées tant pour leur facilités d'utilisation que pour les possibilités qu'elles offrent (methodes: add(), delete(), insert(), first, last...etc et les proprietes: count, index).
mis à part ce détail bravo

signaler à un administrateur
Commentaire de Delphiprog le 26/10/2005 21:16:33 administrateur CS

Ce tuto a pour but d'expliquer les bases de la manipulation des pointeurs. C'est sur que dans la pratique on aura plutôt tendance à utiliser un TList ou l'un de ses nombreux descendants.
Mais pour ceux qui voudraient écrire des classes et qui auraient besoin d'utiliser des listes chainées ou d'implémenter des itérateurs dans une liste de pointeurs, alors ce tuto servira de point de départ.
De plus, on restant générique et en en rentrant pas dans les détails de la VCL made in Borland, la mise en oeuvre pourra se faire dans de nombreux langages, du moins ceux qui supportent les pointeurs.

signaler à un administrateur
Commentaire de bobstien le 26/04/2006 10:35:03

Très bon tutorial, j ai tjs eut de la peine avec les pointeur et grâce à toi je commence à bien comprendre le principe de fonctionnement. Un grand merci à tout ceux qui prenne la peine d expliquer au autre avec un bon tutorial comme celui-ci
Bravo et merci

signaler à un administrateur
Commentaire de Idefix57 le 02/05/2006 06:22:23

Merci beaucoup ,
c'est un peu plus clair dans ma tete ,

tres bien expliquer ,
voila un tutoriel qui me serviras bien ,

@+ idefix

signaler à un administrateur
Commentaire de gilch le 17/10/2006 14:37:04

Merci pour ce tutorial, mais malgrés cela, je n'arrive pas à resoudre mon pb avec l'utilisation d'une dll ecrite en C++, je sais pas si on peut poser des questions ici mais le sujet corespond bien.
La fonction d'appel ecrite en C++ ressemble a la suivante:
EXDLL_DECL int EXReadAsw(WORD *objID, BYTE *format, BYTE **aswBuf, WORD *aswBufSize);

Il y a donc l'utilisation d'un pointeur de pointeur....
je n'arrive pas à déclarer cette fonction sous delphi, si qlq a une idée...

Merci

signaler à un administrateur
Commentaire de foufou3000 le 30/05/2007 20:15:54

merveilleux je suis un debutant et ca ma vraiment donner envie d'utilser les pointeurs
que j'avis peur de les utilisés
merci pour ce tutos impecable.

signaler à un administrateur
Commentaire de HichemAliouche le 23/12/2007 11:06:23

merci

Ajouter un commentaire



Nos sponsors

Sondage...

CalendriCode

Octobre 2008
LMMJVSD
  12345
6789101112
13141516171819
20212223242526
2728293031  

Consulter la suite du CalendriCode



Développement réalisé par Nicolas SOREL (Nix) avec l'aide de : Cyril DURAND et Emmanuel BAÏSE, Merci à Vincent pour ses précieux conseils
CodeS-SourceS.com© Toute reproduction même partielle est interdite sauf accord écrit du Webmaster
CodeS-SourceS.com© est une marque déposée tous droits réservés
Temps d'éxécution de la page : 0,031 sec

Google Coop CodeS-SourceS Google Coop CodeS-SourceS


Certaines images présentes sur le site (notament certains avatars) sont issues des collections IconShock, donc si vous souhaitez utiliser ces icons vous devez les acheter, ne les copiez pas et ne utilisez pas dans vos sites et applications sans les avoir commandé.