IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Guide Pascal et Delphi


précédentsommairesuivant

XI. Pointeurs

Nous passons aux choses sérieuses avec ce chapitre dédié à un sujet souvent décrit comme complexe mais indispensable pour tout programmeur digne de ce nom. Les pointeurs sont en effet constamment utilisés dans les projets les plus importants car ils apportent des possibilités énormes et très diverses. Sans ce concept de pointeurs, de nombreux problèmes ne pourraient pas être transcrits et résolus sur ordinateur.

Ce chapitre commencera par vous expliquer ce que sont les pointeurs, puis diverses utilisations des pointeurs seront passées en revue. Nous parlerons entre autres des pointeurs de record et de transtypage dans ce chapitre.

Si vous êtes habitué au rythme de progression du guide, vous vous sentirez probablement un peu secoué(e) dans ce chapitre, car la notion de pointeur est assez délicate et demande de gros efforts d'apprentissage et d'assimilation au début. Dés que vous aurez "compris le truc", ce que je m'engage à faciliter, tout ira mieux.

XI-A. Présentation de la notion de pointeur

La notion de base de pointeur est assez simple : il s'agit d'une variable qui stocke l'emplacement en mémoire d'un autre élément (qui est le plus souvent une variable). Cette première variable est dite de type pointeur, et est tout simplement nommée "pointeur". Le type Pascal Objet utilisé pour le type pointeur est tout simplement : pointer. Lorsqu'on voit cela pour la première fois, on peut douter de l'intérêt d'un tel assemblage, mais rassurez-vous si c'est votre cas, vous changerez très vite d'avis. L'autre élément dont nous parlions est de type indéterminé, ce qui nous apportera quelques petits soucis, vite résolus au §XI-A-2.
Le contenu de cette variable "pointeur" importe en fait bien peu. Ce qui importe, c'est l'élément "pointé" par la variable "pointeur". Une variable de type pointeur, ou plus simplement un pointeur, contient une adresse. Une adresse est tout simplement un nombre qui repère un emplacement en mémoire (la mémoire des ordinateurs est découpée en octets. Ces octets sont numérotés, et une adresse est tout simplement un numéro d'octet). Mais ne vous souciez pas trop de ces détails techniques, sauf si vous comptez approfondir le sujet (ce qui ne se fera pas ici). Voici un schémas illustrant la notion de pointeur et expliquant deux ou trois autres choses sur la mémoire.

Image non disponible

Si vous êtes curieux de savoir ce qu'est exactement une adresse, lisez le contenu de l'encadré ci-dessous, sinon, passez directement à la suite.

Tout sur les adresses mémoire :

Une adresse mémoire consiste en fait en un nombre. La convention adoptée partout est de noter ce nombre en hexadécimal. Etant donné que la mémoire des ordinateurs est aujourd'hui limitée à 4 gigaoctets, on a besoin d'un nombre entre 0 et 2^32-1, soit entre 0 et FFFFFFFF en hexadécimal.

Si vous ne connaissez pas l'hexadécimal, sachez que c'est un système de numérotation à 16 chiffres au lieu de 10 : les chiffres sont 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F au lieu des 10 chiffres traditionnels de 0 à 9 ; un nombre hexadécimal tel que 1F vaut 1 x 16 + 15 (valeur de F) = 31 en notation décimale classique.

En Pascal Objet, les nombres hexadécimaux sont notés avec le préfixe $. Le nombre 1F sera écrit : $1F.

Un tel nombre est stockable sur 4 octets (256 x 4 = 2^32), ce qui explique la taille d'une variable pointeur, qui contient ce nombre. Ce nombre hexadécimal est nommé adresse dans le cadre de l'utilisation avec les pointeurs. Une adresse permet donc de repèrer un octet quelconque dans la mémoire par son numéro.

Petite précision enfin : lorsque l'élément pointé fait plus d'un seul octet, ce qui est évidemment très souvent le cas, c'est l'octet de début (le premier de la suite d'octets qui stockent l'élément) qui est pointé.

XI-A-1. nil

Le titre de ce paragraphe vous a certainement un peu étonné... non, il ne s'agit pas du fleuve, mais bien encore des pointeurs !

Vous savez maintenant ce qu'est un pointeur : un élément qui stocke l'emplacement d'un autre élément. Parfois, pourtant, un pointeur ne pointera rien du tout (aucun emplacement en mémoire, donc rien). Ce cas particulier de pointeur, dont vous découvrirez rapidement l'extrème utilité, est appelé pointeur nul, ou plus simplement nil. nil est en fait la valeur d'un pointeur qui ne pointe rien. Voici un extrait de code (il est inscrit, comme d'habitude, dans une procédure répondant à l'événement OnClick d'un bouton) :

 
Sélectionnez
procedure TfmPrinc.Button1Click(Sender: TObject);
var
  Machin: pointer;
begin
  Machin := nil;
end;

Ce code, qui ne fait pas grand chose, illustre plusieurs choses. En tout premier, on a déclaré une variable de type pointeur nommée "Machin" (vous voudrez bien excuser mon manque d'inspiration). Cette variable est désormais une variable comme les autres. L'unique instruction de la procédure est une affectation. On affecte nil à la variable "Machin" (Vous remarquerez en passant que nil est un mot réservé du langage Pascal Objet). Ceci prouve que nil est une valeur valable pour un pointeur, puisqu'on l'affecte à une variable de type pointeur. Après cette affectation, "Machin" est un pointeur nul, car sa valeur est nil : "Machin" ne pointe nulle part.

Ce sera désormais une préoccupation à avoir en tête lorsque vous manipulerez un pointeur : avant de vous soucier de l'élément pointé par le pointeur, il vous faudra savoir si le pointeur pointe effectivement quelque chose. Le moyen pour savoir cela sera de comparer le pointeur à nil. Si la comparaison donne "True", c'est que le pointeur est nul, donc ne donne rien, et si elle donne "False", c'est que le pointeur pointe quelque chose.

XI-A-2. Eléments pointés par un pointeur

Nous nous sommes surtout attardé pour l'instant sur les pointeurs et leur contenu. Il est temps de nous occuper un peu des éléments pointés par les pointeurs, qui sont tout de même importants.

Le type pointer utilisé jusqu'à présent a un inconvénient majeur : il déclare en fait un pointeur vers n'importe quoi (une donnée de n'importe quel type, ce type étant tout à fait inconnu, et c'est là le problème) : même si on peut accèder à l'information pointée par le pointeur, il est impossible de savoir de quel type est cette information, ce qui la rend inutilisable concrètement. Etant donné que lorsqu'on utilise un pointeur, c'est le plus souvent pour se servir de l'élément pointé et non du pointeur en lui-même, nous sommes en face d'un problème grave : si un élément est effectivement pointé par un pointeur, et qu'on ne sait pas si c'est une chaîne de caractères ou un entier, on va aux devants de gros embêtements. Il va nous falloir un moyen de savoir de quel type de données est l'information pointée.

La solution est trouvée en utilisant des types de pointeurs spécifiques aux informations pointées. On utilisera dans la réalité des "pointeurs vers des entiers" ou des "pointeurs vers des chaînes de caractères" plutôt que des "pointeurs vers n'importe quoi". Le principe pour se débarrasser de l'ambiguité sur l'élément pointé est en effet de dire tout simplement : ce type de pointeur pointera toujours vers le même type de donnée (entier, chaîne de caractères, ...).

Pour utiliser ces différents types de pointeurs spécifiques, nous utiliserons de vrais types de données Pascal Objet. Le type pointeur vers un autre type de donnée pourra facilement être défini et utilisé dans tout programme. Ces types de pointeurs ne souffreront pas de l'ambiguité sur le type des données pointées car le type de la donnée pointée est déterminé à l'avance par le type du pointeur.

Le type "pointeur vers type" s'écrit ^type en Pascal Objet. Il suffit de précèder le nom du type de donnée pointée par un accent circonflexe pour déclarer un pointeur vers ce type de donnée. Attention, le type en question ne peut PAS être un type composé du genre "array[1..100] of byte" : il faut que le type soit un identificateur. Pour résoudre ce problème, il faut passer par des déclarations de types intermédiaires. Cela sera vu au § XI-B-1 Par exemple, pour déclarer une variable de type "pointeur vers un integer", il faudra utiliser le type ^integer (integer est bien un identificateur). Pour vous en convaincre, substituez dans le code précédent le mot "pointer" par "^integer" :

 
Sélectionnez
procedure TfmPrinc.Button1Click(Sender: TObject);
var
  Machin: ^integer;
begin
  Machin := nil;
end;

Dans cet exemple, la variable "Machin" est toujours un pointeur, même si elle n'est pas déclarée avec le type "pointer". Elle est déclarée comme un pointeur vers un entier. Le seul type possible pour l'élément pointé par "Machin" sera donc par conséquent "integer".

Rassurez-vous, dans la pratique, on utilisera en fait assez peu cette forme de déclaration. On déclarera plutôt un nouveau nom de type (section type), et on déclarera ensuite les variables ou les paramètres en utilisant ce nouveau type. Voici ce que cela donnera pour l'exemple ci-dessus :

 
Sélectionnez
type
  PInteger = ^integer;
var
  Machin: PInteger;
begin
  Machin := nil;
end;

Cet exemple déclare un type nommé "PInteger" qui est le type "pointeur vers un entier". Toute variable ou paramètre de ce type sera un pointeur vers un entier. Ce type est utilisé pour déclarer la variable "Machin", ce qui la déclare comme pointeur vers un entier, comme dans l'extrait de code précédent.

Remarque : notez la première lettre (P) du type PInteger : c'est une convention et un bon repère de nommer les types pointeurs en commençant par un P, contrairement aux autres types personnalisés qui sont commencés par la lettre T.

XI-A-3. Gestion de l'élément pointé par un pointeur

Jusqu'ici, nous nous sommes surtout intéressé aux pointeurs, au type de l'élément pointé, mais pas encore à l'élément pointé lui-même. Cet élément, qui sera souvent d'un type courant tel qu'un entier, une chaîne de caractères, un énuméré ou même d'un type plus complexe comme un record (c.f. § XI-A-4 Pointeurs de record), a une particularité par rapport aux données classiquement stockées dans des variables : il est géré dynamiquement et manuellement, c'est-à-dire qu'au fil de l'éxécution du programme, il nous faudra nous occuper de tâches habituellement invisibles. Ces tâches sont assez déroutantes pour les débutants mais vous vous y habituerez vite, c'est juste une habitude à prendre.

L'élément pointé par un pointeur devra donc être géré manuellement. La gestion de l'élément pointé se divise en deux tâches simples : son initialisation et sa destruction.

Avant de pouvoir utiliser l'élément pointé, il faudra le créer. Cette étape d'initialisation est indispensable, car elle réserve en fait de la place en mémoire pour l'élément pointé par le pointeur, et fait pointer le pointeur vers cet espace fraichement réservé. Utiliser un pointeur vers un espace non réservé (non initialisé), c'est-à-dire un pointeur non créé, conduira inévitablement à un plantage de l'application, voire parfois de Windows (sans vouloir vous effrayer bien entendu, mais c'est la triste et dure vérité).

Une fois l'utilisation d'un pointeur terminée, il faudra penser à le détruire, c'est-à-dire libèrer l'espace réservé à l'élément pointé par le pointeur. Cette seconde étape, parfois oubliée, est indispensable lorsqu'on a initialisé le pointeur. Oubliez ce genre d'étape et votre application viendra s'ajouter à la liste des pollueurs : après leur passage, de la mémoire a disparu, et cela entraîne une dégradation des performances, voire un plantage de Windows au bout d'un certain temps.

Le schémas ci-dessous tente de résumer cette délicate notion de gestion de l'élément pointé. Prenez le temps de bien le lire :

Image non disponible

Dans ce schémas, les étapes 1, 3 et 5 sont des états du pointeur : les étapes 1 et 5 sont en fait identiques car dés lors que le pointeur est "détruit", il peut à nouveau être initialisé pour être réutilisé. Les étapes 2 et 4 vous sont dévolues. L'étape 2 est l'initialisation, qui précède l'utilisation. L'étape 4 est la destruction, qui suit l'utilisation. Toute utilisation de l'élément pointé est tout simplement impossible avant l'étape 2 et après l'étape 4 car cet élément pointé n'existe pas.

Remarque : ce schéma ne s'applique qu'aux pointeurs vers des types déterminés créés par les types ^type et non pas au type pointeur général créé par le type pointer.

Venons-en maintenant au code Pascal Objet proprement dit. L'initialisation d'un pointeur se fait au moyen de la procédure "New". Cette procédure admet un unique paramètre qui est le pointeur à initialiser. L'appel de cette procédure avec un pointeur en paramètre correspond à l'étape 2 du schémas ci-dessus. Comme initialisation et destruction vont de paire, voyons tout de suite la méthode de destruction : elle s'effectue par l'appel de la procédure "Dispose". Cette procédure, comme "New", prend un unique paramètre qui est le pointeur à "détruire". L'appel de "Dispose" avec un pointeur en paramètre correspond à l'étape 4 du schémas ci-dessus. "New" et "Dispose" ne peuvent initialiser et détruire que des pointeurs vers des types déterminés, et non des pointeurs de type pointer.

L'exemple ci-dessous utilise les nouvelles notions :

 
Sélectionnez
procedure TfmPrinc.Button1Click(Sender: TObject);
type
  PInteger = ^integer;
var
  Machin: PInteger;
begin
  new(Machin);
  { ici, on peut utiliser l'élément pointé par Machin }
  dispose(Machin);
end;

La procédure ci-dessus déclare "Machin" comme pointeur vers un entier. La première instruction initialise "Machin". Avant cette instruction, l'élément pointé par "Machin" n'existe même pas. Après l'appel à New, l'élément pointé par "Machin" est utilisable (nous allons voir comment juste après). La deuxième instruction détruit le pointeur : l'élément pointé est détruit et le pointeur ne pointe donc plus vers cet élément qui a été libéré de la mémoire.

Nous allons maintenant voir comment utiliser l'élément pointé par un pointeur.

XI-A-4. Utilisation de l'élément pointé par un pointeur

Une fois un pointeur initialisé, il est enfin possible d'utiliser l'élément pointé. Ceci se fait en ajoutant le suffixe ^ (accent circonflexe) au pointeur (^ est appelé opérateur d'indirection car il permet de passer du pointeur à l'élément pointé). Supposons que nous ayons un pointeur vers un entier, nommé "Machin". Dans ce cas, "Machin^" désigne l'élément pointé, et est donc dans ce cas précis un entier. Il peut être utilisé comme toute variable de type entier, à savoir dans une affectation par exemple, ou en tant que paramètre. Voici un exemple :

 
Sélectionnez
procedure TfmPrinc.Button1Click(Sender: TObject);
type
  PInteger = ^integer;
var
  Machin: PInteger;
begin
  new(Machin);
  Machin^ := 1; { affectation d'une valeur entière à un entier }
  ShowMessage(IntToStr(Machin^)); { utilisation comme n'importe quel entier }
  dispose(Machin);
end;

Dans cet exemple, le pointeur est tout d'abord initialisé. Vient ensuite une affectation. Comme "Machin" est de type pointeur vers un entier, "Machin^", qui est l'élément pointé par le pointeur, est un entier, à qui on peut affecter une valeur entière. L'instruction suivante transmet la valeur de l'élément pointé par "Machin", soit toujours un entier, à IntToStr. Cette procédure, qui accepte les entiers, ne fait évidemment pas de difficulté devant "Machin^", qui vaut 1 dans notre cas. Le message affiché contient donc la chaîne "1". Enfin, le pointeur est détruit.

Voilà, il n'y a rien de plus à savoir pour utiliser l'élément pointé : utilisez-le lorsqu'il est initialisé, au moyen de l'opérateur d'indirection ^ et le tour est joué. Pour les types d'éléments pointés les plus simples, c'est tout ce qu'il faut savoir. Dans le paragraphe suivant, nous allons nous attarder sur les types plus complexes comme les tableaux et les enregistrements (record) utilisés avec les pointeurs.

XI-B. Etude de quelques types de pointeurs

Ce paragraphe est destiné à vous fournir une aide concernant des types plus complexes que les types simples tels que les entiers ou les chaînes de caractères : aucune règle particulière n'est à appliquer à ces éléments de types plus complexes, mais leur utilisation avec les pointeurs demande un peu plus d'entrainement et de dextérité. Je vous propose donc quelques explications et exercices, le sujet étant intarrissable.

XI-B-1. Pointeurs de tableaux

Intéressons-nous au cas où l'on doit utiliser un pointeur vers un tableau. Nous évoquerons également les tableaux de pointeurs, bien entendu à ne pas confondre avec les pointeurs de tableaux.

Voici un tableau :

array[1..10] of integer

Si vous suivez mal ce que j'ai dit plus tôt, un pointeur vers ce genre de tableau serait du type :

^array[1..10] of integer

Ce qui n'est pas le cas car "array[1..10] of integer" n'est pas un identificateur ! Oubliez vite la ligne ci-dessus, elle ne sera pas acceptée par Delphi et provoquera une erreur. Voici un bloc type correct qui déclare un type pointeur vers un tableau :

 
Sélectionnez
type
  TTabTest = array[1..10] of integer;
  PTabTest = ^TTabTest;

Tout élément déclaré de type PTabTest serait un pointeur vers un élément de type TTabTest. Or TabTest étant un tableau, un élément de type PTabTest est bien un pointeur vers un tableau.

Voici maintenant un exemple démontrant l'utilisation d'un tel pointeur de tableau :

 
Sélectionnez
procedure TfmPrinc.Button1Click(Sender: TObject);
type
  TTabTest = array[1..100] of integer;
  PTabTest = ^TTabTest;
var
  References: PTabTest;
  indx: integer;
begin
  New(References);
  References^[1] := 0;
  for indx := 1 to 100 do
    References^[indx] := Trunc(Random(10000));
  ShowMessage('Référence n°24 : '+IntToStr(References^[24]));
  Dispose(References);
end;

Cet exemple utilise un type tableau de 100 entiers nommé "TTabTest". Un type pointeur vers ce tableau est ensuite déclaré et nommé "PTabTest". Le test utilise deux variables : un entier et un pointeur de tableau de type PTabTest. La première instruction initialise le pointeur. La deuxième instruction permet de voir comment on accède à un élément du tableau pointé par "References" : on commence par accèder à l'élément pointé, soît le tableau, grâce à l'opérateur d'indirection ^. "References^" étant l'élément pointé par "References", c'est un tableau d'entiers (type TTabTest). On peut donc accèder ensuite à sa première case en utilisant la syntaxe habituelle des tableaux, à savoir en ajoutant le numéro de case entre crochets. Ainsi, "References^[1]" est la première case du tableau pointé par "References" et est donc de type entier.

L'instruction en elle-même est une affectation. Le tableau étant un tableau d'entiers, chaque case du tableau est un entier et peut donc recevoir une valeur entière. On affecte donc la valeur 0 à "References^[1]" qui est une case du tableau. Cette instruction est en fait une initialisation de la case du tableau. L'instruction suivante passe à la vitesse supérieure : on utilise une boucle for pour initialiser directement TOUTES les cases du tableau pointé par "References". A cet effet, une variable "indx" parcours les valeurs de 1 à 100 et est utilisé comme numéro de case pour le tableau, ce qui permet donc de balayer une à une toutes les cases du tableau. Une affectation est faite à chaque fois, et ce avec une valeur aléatoire entière entre 0 et 10000.

L'instruction suivante (appel de ShowMessage) est un exemple d'utilisation de tableau de pointeur : on transmet la valeur de la case 24 du tableau pointé, donc un pointeur, à la fonction "IntToStr" qui la convertit en chaîne. Le reste est très conventionnel et permet d'afficher un message convenable. Enfin, la dernière instruction permet de "détruire" le pointeur (drôle de manière de dire les choses puisque ce n'est pas le pointeur mais l'élément pointé qui est détruit, conformément au schémas présenté plus haut).

XI-B-2. Pointeurs d'enregistrements (record)

Les pointeurs d'enregistrements sont tout simplement des types de pointeurs vers des enregistrements (record). L'utilité de ces pointeurs est considérable et nul doute que vous aurez de nombreuses occasions de vous en servir.

La déclaration d'un type pointeur d'enregistrement se fait comme pour tous les types pointeurs vers un type particulier. Voici un exemple :

 
Sélectionnez
type
  TSexePersonne = (spFeminin, spMasculin);
  TPersonne = record
    Nom,
    Prenom,
    CodePostal,
    Adresse,
    Ville: string;
    Sexe: TSexePersonne;
  end;
  { pointeur d'enregistrement }
  PPersonne = ^TPersonne;

De tels pointeurs s'initialisent et se détruisent comme d'habitude avec New et Dispose. Leur intérêt réside dans la gestion de la mémoire, sujet dont je vais parler juste un petit peu. La mémoire que vous utilisez dans vos programmes n'est pas illimitée : il faut éviter au maximum d'en utiliser de grosses quantités sans passer par les pointeurs. Cette mémoire se décompose à peu près en deux parties : la mémoire locale, et la mémoire globale. La première est celle que vous utilisez lorsque vous déclarez une variable locale par exemple. Cette mémoire est de taille très limitée. La mémoire globale, en revanche, est virtuellement limitée à 4 gigaoctets et Windows s'occupe d'en fournir lorsque c'est nécessaire. C'est là que les pointeurs interviennent : lorsque vous déclarez par exemple 100 enregistrements de type TPersonne, ces enregistrements sont stockés en mémoire locale, ce qui occupe pas mal de place. En utilisant des pointeurs, seuls ces derniers sont stockés en mémoire locale, et les éléments pointés le sont dans la mémoire globale.

Il faudra donc éviter cela :

 
Sélectionnez
array[1..1000] of  TPersonne

au profit de cela :

 
Sélectionnez
array[1..1000] of  PPersonne

la différence sst minime dans la déclaration, mais grande dans le fonctionnement et l'utilisation. Cette dernière forme est à préfèrer dans beaucoup de cas, même si l'utilisation de pointeurs rebute pas mal de programmeurs débutants.>

Venons-en à l'utilisation concrète de ces pointeurs d'enregistrements. En partant du pointeur, il faut accèder à l'élément pointé en placant l'opérateur d'indirection ^ . L'élément pointé étant un enregistrement, on peut accèder à un membre en écrivant un point suivi du nom du membre.

Voici un rapide exemple démontrant cela :

 
Sélectionnez
procedure TForm1.Button1Click(Sender: TObject);
var
  PersTest: PPersonne;
begin
  New(PersTest);
  PersTest^.Nom := 'BEAULIEU';
  PersTest^.Prenom := 'Frédéric';
  PersTest^.Sexe := spMasculin;
  ShowMessage(PersTest^.Nom + ' ' + PersTest^.Prenom);
  Dispose(PersTest);
end;

Dans le code ci-dessus, une variable pointeur vers enregistrement est déclarée, initialisée, utilisée puis détruite. L'utilisation consiste à affecter des valeurs aux membres de l'enregistrement pointé par "PersTest". Vous pouvez au passage voir comment cela se fait. Une autre utilisation est faite en affichant un message comportant le nom et le prénom de la personne.

Il est également possible d'utiliser un bloc with avec de tels pointeurs, puisque with a besoin d'un enregistrement pour fonctionner. Voici la procédure réécrite avec un bloc with :

 
Sélectionnez
procedure TForm1.Button1Click(Sender: TObject);
var
  PersTest: PPersonne;
begin
  New(PersTest);
  with PersTest^ do
    begin
      Nom := 'BEAULIEU';
      Prenom := 'Frédéric';
      Sexe := spMasculin;
      ShowMessage(Nom + ' ' + Prenom);
    end;
  Dispose(PersTest);
end;

Ce genre de bloc permet d'utiliser les pointeurs d'enregistrements à peu près comme les enregistrements eux-mêmes, en ajoutant initialisation et destruction. Dans l'exemple ci-dessus, PersTest est de type TPersonne, et convient donc bien là où on doit avoir un enregistrement.

Les pointeurs d'enregistrements ouvrent la porte à des structures très évoluées comme les listes chaînées ou les arbres. Ces deux notions sont des plus utiles pour modèliser certains problèmes. Nous n'étudierons cependant pas ces notions ici, car elles sont rendues non nécessaires par l'utilisation d'autres outils comme les objets TList sur lesquels, par contre, nous reviendrons prochainement.

XI-C. Transtypage

Les pointeurs sont le domaine idéal pour commencer à vous parler de transtypage. Ne vous laissez pas effrayer par ce mot, il signifie tout simplement "transformation de type". Par transformation, on entend non pas une transformation de structure, mais un passage d'un type à un autre, une conversion explicite ou implicite. Commençons par les implicites.

Depuis longtemps, vous utilisez des transtypages implicites sans vous en rendre compte. Le transtypage est une conversion de type : ce genre de conversion est très utilisé dans Delphi de façon invisible, mais vous pourrez aussi vous servir de cette possibilité.

Prenons l'exemple suivant :

 
Sélectionnez
procedure TfmMain.btTestClick(Sender: TObject);
var
  X: Word;
begin
  X := 123;
  ShowMessage(IntToStr(X));
end;

Lorsque vous donnez X comme paramètre à IntToStr, vous donnez un élément de type Word à une procédure qui accepte un paramètre de type Integer. Cette différence de type est dans l'absolu interdite. Cela ne vous a jamais empèché de vivre jusqu'à maintenant, et que cela ne change pas, car Pascal Objet est un langage évolué et Delphi propose un compilateur puissant : lorsque vous donnez un paramètre du mauvais type, le compilateur regarde s'il est possible de convertir ce que vous lui présentez dans le bon type. Si c'est possible, le compilateur le fait parfois tout seul, notamment dans le cas des nombres ou de certaines chaînes de caractères. Aucune erreur n'est signalée, car Delphi fait les ajustements nécessaires dans votre dos.

Prenons un autre exemple :

 
Sélectionnez
procedure TfmMain.btTestClick(Sender: TObject);
var
  S1: string;
  S2: string[30];
begin
  S1 := 'Salut';
  S2 := S1;
  ShowMessage(S2);
end;

Ici, l'exemple est plus frappant : dans l'affectation "S2 := S1;", on affecte à une chaîne de longueur 30 une chaîne de longueur standard, c'est-à-dire illimitée sous Delphi 5 ou plus. Que se passerait-il si la longueur de S1 étant 45 par exemple ? Lorsqu'on voit une simple affectation, on ne se doute pas de ce qui se fera par derrière : une troncature. S1 sera en effet tronquée si elle est trop longue pour tenir dans S2. Lorsqu'on transmet S2 à ShowMessage, il y a encore divergence des types car le type "string" est attendu alors qu'on transmet une variable de type "string[30]". Là encore, une conversion (techniquement, un transtypage) sera effectué.

Tout ce verbiage nous conduit à parler de transtypages, ceux-là explicites, que l'on peut donc écrire dans le code Pascal Objet. La notion de transtypage est liée à la notion de compatibilité de types. Certains types sont compatibles entre eux, c'est-à-dire qu'un transtypage est possible de l'un à l'autre, mais d'autres sont incompatibles et il n'y aurait aucune signification à effectuer de transtypages entre de tels types (par exemple d'une chaîne de caractères à un booléen).

Tous les types pointeurs sont compatibles entre eux, ce qui fait que n'importe quel pointeur peut être transtypé en n'importe quel autre type de pointeur. Ceci vient du fait que seul le type de l'élément pointé différe entre les types de pointeurs, et non la structure de stockages des pointeurs en mémoire.

Pour transtyper un élément, il faut écrire le nom du type à obtenir, suivi de l'élément entre parenthèses (un peu comme si l'on appelait une fonction, ce qui n'est absolument pas le cas). Les transtypages sont surtout utilisés avec les composants qui permettent d'adjoindre aux éléments contenus un ou des pointeurs. Ces pointeurs sont toujours de type "pointer", ce qui les rend impossibles à utiliser directement pour pointer quelque chose. Il faudra transtyper ces pointeurs en un type pointeur déclaré pour pouvoir accèder aux éléments pointés par le pointeur. Pour des exemples de transtypages, referrez-vous au (futur) chapitre sur les types abstraits de données, qui regorgera d'exemples et expliquera concrètement le transtypage des pointeurs. Rassurez-vous entretemps car cette notion ne devrait pas vous manquer jusque là.

XI-D. Conclusion

C'est déjà la fin de ce chapitre consacré aux pointeurs. Nombre de notions importantes liées aux pointeurs n'y ont pas été abordées, pour ne pas tomber dans l'excés ou le bourrage de crâne. Des notions complèmentaires et nettement plus intéressantes sont abordées au chapitre XV sur les types abstraits de données. Parmi ces notions, seront abordés le transtypage de données et le chaînage.


précédentsommairesuivant

Copyright © 2008 Frédéric Beaulieu. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.