Utilisation des pointeurs



Les pointeurs, c'est un long sujet tellement omniprésent dans la programmation orientée objet que je ne sais pas par où commencer. On va commencer par les plus simples, ceux qu'on manipule tous les jours sans le savoir: les pointeurs d'objet. En fait, tout ce qui est déclaré dans les sections var et qui "pointe" vers un objet est un pointeur. La simple déclaration :

Exemple de déclaration d'une référence d'objet

var
  Form1: TForm1;



est un pointeur, car Form1 pointe vers l'espace mémoire occupé par les informations de Form1, et ne la contient pas comme on pourrait le croire au début. L'avantage des pointeurs, c'est que ca permet de désigner un objet d'un type donné et d'accéder à ses propriétés, sans se soucier de l'endroit où il est stocké. Par exemple, si on a plusieurs fiches dans ton projet, on peut déclarer le code suivant :

Utilisation des pointeurs d'objets

var
  MaFiche: TForm;
begin
  MaFiche := Form1;
  { Ma Fiche pointera vers Form1 }
  MaFiche.Caption := 'bla';
  { Caption appartient à la classe de TForm, donc on peut y accéder à l'aide
    du pointeur }
  MaFiche := Form2;
  { Form1 n'est pas détruit, on change juste l'adresse où MaFiche pointe,
    ici vers Form2 }
  MaFiche.Caption := 'bla bla';
  { Ici, c'est le caption de Form2 qui sera changé }
end;



En fait les pointeurs permettent d'éxecuter des actions groupées sur des objets de structure commune. Par exemple, si on veut appliquer le même traitement à toutes tes fiches, il suffit de déclarer une fonction qui se chargera de le faire :

Utilité des pointeurs

procedure ChangeFiche(Form: TForm);
begin
  Form.Caption := 'Bla';
  Form.Color := clRed;
end;

ChangeFiche(Form1);
ChangeFiche(Form2);



Maintenant, on peut appliquer le même traitement à toutes tes fiches en les passant en paramètre à cette procédure.

Bon, ca c'était les pointeurs d'objet. Maintenant, les autres pointeurs, un tout petit peu plus compliqués mais ça reste abordable. En fait, un pointeur, comme son nom l'indique "pointe" vers une adresse mémoire. Quand on déclare un pointeur, il pointe vers un endroit vide, il est alors égal à nil.

Type pointeur

var
  P: TPointer;
begin
  P := nil;
end;



Ici, le P := nil est inutile puisque P vient juste d'être déclaré. On peut ensuite diriger le pointeur vers l'emplacement que l'on veut, par exemple :


Affectation de pointeur

P := Form1;



mais comme ici, P est déclaré en tant que TPointer et non comme TForm, on ne pourra pas accéder à ses propriétés telles que Caption, Color et autres. On aura juste une référence vers l'adresse de Form1. Pour accéder à ses propriétés, il faudra effectuer un transtypage, c'est à dire considérer le pointeur comme un pointeur de fiche, et ce grâce à l'opérateur as :

Utilisation des pointeurs

var
  P: TPointer;
begin
  P := Form1;
  (P as TForm).Caption := 'bla';
end



Cette méthode est possible uniquement grâce au transtypage. Les pointeurs servent aussi à garder une référence vers des objets que l'on créé dynamiquement, sans avoir à déclarer la variable qui gradera son adresse. par exemple :

Utilisation des pointeurs pour les objets

uses
  Registry;

var
  P: TPointer;
begin
  P := TRegistry.Create;
end;



permet de créer un objet TRegistry sans avoir à déclarer un pointeur de type TRegistry. Pour une seule référence, c'est inutile, mais c'est très pratique quand on a des tonnes d'objet de même type à référencer, par exemple, dans un jeu, des missiles.

Disons qu'on déclare des munitions de ce type :

Déclaration classe 'Missile'

type
  TMissile = class(TObject);
  private
    FX, FY: integer;
  public
    property X: integer read FX write FX;
    property Y: integer read FY write FY;
    procedure Move;
    constructor CreateMissile(X, Y: integer);
  end;

constructor TMissile.CreateMissile(X, Y: integer);
begin
  inherited Create;
  Self.X := X;
  Self.Y := Y;
end;

procedure TMissile.Move;
begin
  Y := Y + 1;
end;



Maintenant, disons que le vaisseau controllé par le joueur tire. Il faut donc créér un missile. Pour cela, on fait :

Creation d'un objet

  TMissile.CreateMissile(Vaisseau.X, Vaisseau.Y);



Mais le problème, c'est que pour faire bouger le missile par la suite, il faut pouvoir appeler sa méthode Move, et il faut pouvoir accéder au missile. Or, si on ne garde pas de référence sur le missile, c'est impossible.

C'est à ce moment que les pointeurs révèlent leur utilité. Il suffit de créér un objet de type TList, qui est une liste de Pointeurs, et d'y ajouter les références.

Ainsi, au démmarage de l'application on déclare :

Objet TList

var
  List: TList;

procedure TForm1.FormCreate(Sender: Tobject);
begin
  List := TList.Create
  {Au passage, List est un pointeur vers un TList, soit un pointeur vers un
   pointeur de pointeurs !!! }
end;



Et maintenant, quand on voudra ajouter un missile, on fera simplement :

Ajout à la liste

  List.Add(TMissile.CreateMissile(Vaisseau.X, Vaisseau.Y));



et maintenant on pourra accéder aux missiles simplement grâce à la propriété TList.Items :

Récupération élément

var
  M: TMissile;
begin
  M := List.Items[X];
  M.Move;
end;



Il existe encore un troisième type de pointeurs, qui sont des pointeurs de structure, très utilisé en C et en Pascal, et en général, dans tous les langages non-objet, bien qu'ils soient aussi utilisés dans la programmation Objet.

Structure MonRecord

type
  MonRecord = record
    a: integer;
    b: integer;
  end;



Et bien on peut aussi déclarer des pointeurs de structure, de cette facon :

Pointeur sur structure

type
  PMonRecord = ^MonRecord;



Ainsi, on peut déclarer une variable pointeur vers un record sans lui alouer la mémoire (elle est à nil au début) :

Déclaration pointeur

var
  MR: PMonRecord;



On peut ensuite lui allouer de la mémoire, et accéder à ses membres:

Affectation mémoire

var
  MR: PMonRecord;
begin
  New(MR);
  MR^.a := 25;
end;



Vous remarquez que j'ai ajouté un chapeau entre le MR et son membre. Le chapeau est normalement obligatoire pour un pointeur de structure, bien que Delphi soit assez souple à ce sujet et permette de s'en passer dans la plupart des cas. Vu comme ca, les pointeurs de structure peuvent paraître inutiles, mais si on les imbrique, ca peut devenire très utile.

Imaginons un record et son pointeur déclarés ainsi :

Type TMR

type
  PMR = ^TMR;

  TMR = record
    a: integer;
    Next: PMR;
  end;



Il y a 2 choses surprenantes dans cette déclaration. Premièrement, on peut déclarer un pointeur de structure avant la déclaration de cette structure. C'est la seule fantaisie du langage Pascal à ce niveau, et qui est très pratique. Ensuite, je déclare un Pointeur de structure à l'intérieur même de cette structure, ce qui est possible grâce au premier point, et dont nous allons voir l'utilité tout de suite.

Imaginons maintenant, toujours en utilisant les mêmes structures, que je déclare le code suivant :

Déclaration PMR

var
  a: PMR;
begin
  New(a);
end;



Le pointeur a pointera donc vers un élément de type TMR. Jusque là, rien de fantastique. Mais à présent déclarons ces 3 fonctions:

Fonctions relatives au PMR

function Add(Node: PMR): PMR;
begin
  New(PMR^.Next);
  Result := PMR^.Next;
end;

function Count(Node: PMR): integer;
var
  Temp: PMR;
  i: integer;
begin
  i := 1;
  Temp := Node^.Next;
  while Temp <> nil do
    begin
      i := i + 1;
      Temp := Temp^.Next;
    end;
  Result := i;
end;

function GetItem(Node: PMR; Index: integer): PMR;
var
  Temp: PMR;
  i: integer;
begin
  Result := nil;
  if Index > Count(PMR) then exit;
  i := 0;
  Temp := Node;
  while i < index do
    begin
      i := i + 1;
      Temp := Temp^.Next;
    end;
end;



Et nous voici avec la déclaration d'un tableau dynamique de type integer. En ajoutant simplement 2 ou 3 propriétés on a un tableau complet (il manque simplement la suppression d'éléments). En fait, c'est sur ce principe que les tableaux dynamiques de Delphi 4 et Delphi 5 sont basés, même si maintenant leur implémentation les rend beacoup plus simples, mais jusqu'à Delphi 3, On était obligé d'implémenter ce type de chaine, où chaque élément garde un pointeur sur le suivant.

C'est sûr que maintenant ca peut faire rigoler quand on voit que pour déclarer un tableau dynamique avec les dernières versions de Delphi il suffit de faire :

Redimensionnement tableau dynamique

var
  x: array of integer;
begin
  SetLength(X);
end;


3 requête(s) SQL executée(s) en 0.015 Secs - Temps total de génération de la page : 0.022 Secs