![]() |
|
Marco Cantù
Traduit de l'anglais par Iannis Papageorgiadis [email protected] |
Chapitre 7La gestion des chaînes |
En Delphi, il est très simple de manipuler des chaînes de caractères, mais dans les coulisses la situation est plus complexe. Pascal utilise une manière traditionnelle de manipuler les chaînes, Windows possède la sienne, empruntée au langage C, et les versions 32 bits de Delphi comportent un puissant type de données chaîne longue qui est le type par défaut en Delphi.
Un type string est semblable à un type tableau. En fait, une chaîne est presque un tableau de caractères. La preuve en est qu'il est possible d'accéder à un caractère spécifique de la chaîne en utilisant la notation [].
Pour dépasser les limites des chaînes Pascal traditionnelles, les versions 32 bits de Delphi supportent les chaînes longues. En réalité, il existe trois types de chaînes :
Les chaînes longues de Delphi sont basées sur un mécanisme de comptage de références, lequel retient le nombre de variables chaîne qui font référence à la même chaîne en mémoire. Ce comptage de références est également utilisé pour libérer la mémoire lorsqu'une chaîne n'est plus utilisée, c'est-à-dire lorsque le compteur de références arrive à zéro.
Si on souhaite augmenter la taille d'une chaîne en mémoire mais qu'il y a quelque chose d'autre dans la mémoire voisine, la chaîne ne peut pas s'étendre dans le même emplacement mémoire et une copie complète de la chaîne doit donc être effectuée dans un autre emplacement. Lorsque cette situation se présente, Delphi, pendant l'exécution, déplace la chaîne de façon complètement transparente. On fixe simplement la taille maximum de la chaîne à l'aide de la procédure SetLength en allouant effectivement la quantité de mémoire demandée :
SetLength (String1, 200);La procédure SetLength effectue une demande de mémoire et non pas une véritable allocation de mémoire. Elle réserve l'espace mémoire demandé pour une utilisation future sans utiliser effectivement la mémoire. Cette technique est fondée sur une fonctionnalité des systèmes d'exploitation Windows et est utilisée par Delphi pour toutes les allocations dynamiques de mémoire. Lorsque, par exemple, on demande un très grand tableau, sa mémoire est réservée mais pas allouée.
Fixer la longueur d'une chaîne est rarement nécessaire. Le seul cas où l'on doit allouer de la mémoire pour une chaîne longue en utilisant SetLength est quand on doit passer la chaîne comme paramètre à une fonction API (après le transtypage adéquat), comme nous le montrerons bientôt.
Str1 := 'Hello'; Str2 := Str1;En plus de travailler sur les chaînes, le programme affiche dans une boîte liste (list box) leur état interne en utilisant la fonction StringStatus ci-dessous :
function StringStatus (const Str: string): string;
begin
Result := 'Address: ' + IntToStr (Integer (Str)) +
', Length: ' + IntToStr (Length (Str)) +
', References: ' + IntToStr (PInteger (Integer (Str) - 8)^) +
', Value: ' + Str;
end;(N.D.T. : PInteger est déclarée dans l'unité Windows.Pas en tant que ^Integer).
Il est indispensable, dans la fonction StringStatus, de passer le paramètre chaîne en tant que paramètre constante. Passer ce paramètre par copie (par valeur) provoquerait un effet de bord, celui d'avoir une référence supplémentaire à la chaîne lors de l'exécution de la fonction. Par contre, en le passant par référence (var) ou par paramètre constante (const), il n'y aura pas de référence supplémentaire à la chaîne. Ici nous avons utilisé un paramètre constante, puisqu'il n'était pas prévu que la fonction modifie la chaîne.
Pour obtenir l'adresse en mémoire de la chaîne (utile pour
déterminer sa véritable identité et pour voir quand
deux chaînes différentes font référence à
la même zone mémoire), nous avons simplement effectué
un transtypage "en dur" au départ d'un type String vers le
type Integer. Pratiquement, les chaînes sont des références;
il s'agit de pointeurs
Pour obtenir le compteur de références, nous avons basé le code sur le fait peu connu que la longueur et le compteur de références sont réellement stockés dans la chaîne, avant le texte réel et avant la position que pointe la variable chaîne. L'offset (négatif) est -4 pour la longueur de la chaîne (valeur que l'on peut obtenir plus facilement en utilisant la fonction Length) et -8 pour le compteur de références.
On retiendra que cette information interne à propos des offsets pourrait changer dans les versions futures de Delphi; il n'est pas garanti non plus que de telles fonctionnalités non documentées seront maintenues à l'avenir.
En exécutant cet exemple, vous devriez obtenir deux chaînes avec le même contenu, le même emplacement mémoire et un compteur de références de 2, comme on le voit dans la partie supérieure de la boîte liste (list box) de la figure 7.1. Si maintenant vous modifiez la valeur d'une des deux chaînes (n'importe laquelle), l'emplacement mémoire de la chaîne mise à jour changera. C'est l'effet de la technique copie-par-écriture (copy-on-write).
Figure 7.1 : L'exemple StrRef montre l'état interne de deux chaînes, y compris le compteur de références courant.
Nous pouvons effectivement produire cet effet, illustré dans la deuxième partie de la boîte liste ( list box) de la figure 7.1., en écrivant le code suivant pour le gestionnaire de l'événement OnClick du deuxième bouton :
procedure TFormStrRef.BtnChangeClick(Sender: TObject); begin Str1 [2] := 'a'; ListBox1.Items.Add ('Str1 [2] := ''a'''); ListBox1.Items.Add ('Str1 - ' + StringStatus (Str1)); ListBox1.Items.Add ('Str2 - ' + StringStatus (Str2)); end;On notera que le code de la méthode BtnChangeClick peut être exécuté uniquement après la méthode BtnAssignClick. Pour forcer cela, le programme commence avec le deuxième bouton désactivé (sa propriété Enabled est mise à False); il active le bouton à la fin de la première méthode. On peut sans limite étendre cet exemple et utiliser la function StringStatus pour explorer le comportement des chaînes longues dans plusieurs autres situations.
Par exemple, pour copier le titre d'une fiche dans une chaîne
PChar (en utilisant la fonction API GetWindowText) et la
copier ensuite dans le Caption d'un bouton, on peut écrire
le code suivant
procedure TForm1.Button1Click (Sender: TObject); var S1: String; begin SetLength (S1, 100); GetWindowText (Handle, PChar (S1), Length (S1)); Button1.Caption := S1; end;Vous pouvez trouver ce code dans l'exemple LongStr. Notez que si vous écrivez ce code mais que vous n'allouez pas la mémoire pour la chaîne avec SetLength, le programme se bloquera probablement. Si vous utilisez un PChar pour passer une valeur (et non pas pour en recevoir une, comme dans le code ci-dessus), le code est même plus simple, parce qu'il ne faut pas définir une chaîne temporaire et l'initialiser. La ligne de code suivante passe la propriété Caption d'un libellé (label) en tant que paramètre à une fonction API en la transtypant simplement en PChar :
SetWindowText (Handle, PChar (Label1.Caption));Lorsqu'on doit transtyper un WideString à un type compatible Windows, on doit utiliser pour la conversion PWideChar plutôt que PChar. Les chaînes étendues (WideString) sont souvent utilisées pour les programmes OLE et COM.
Après avoir présenté le beau côté de la chose, il nous faut maintenant en examiner les pièges. Quelques problèmes pourraient surgir lors de la conversion d'une chaîne longue en un PChar. Le problème fondamental est que, après la conversion, l'utilisateur devient responsable de la chaîne et de ses contenus, et Delphi ne l'aidera plus. Considérons la petite modification suivante du premier fragment de code ci-dessus, Button1Click :
procedure TForm1.Button2Click(Sender: TObject); var S1: String; begin SetLength (S1, 100); GetWindowText (Handle, PChar (S1), Length (S1)); S1 := S1 + ' est le titre'; // ceci ne fonctionnera pas Button1.Caption := S1; end;Ce programme se compile, mais lorsque vous l'exécuterez, vous serez surpris. Le Caption du bouton contiendra le texte d'origine du titre de la fenêtre sans le texte de la constante chaîne que vous lui avez ajoutée. Le problème provient du fait que lorsque Windows écrit dans la chaîne (à l'intérieur de l'appel API GetWindowText), il n'établit pas correctement la longueur de la chaîne longue Pascal. Delphi peut encore très bien utiliser cette chaîne comme sortie et peut arriver à comprendre quand elle se termine en cherchant le zéro terminal, mais si vous ajoutez des caractères supplémentaires après le zéro terminal, ils seront complètement ignorés.
Comment résoudre ce problème? En disant au système de convertir la chaîne retournée par le retour d'appel de l'API GetWindowText en une chaîne Pascal. Toutefois, si vous écrivez le code suivant :
S1 := String (S1);le système l'ignorera parce que convertir un type de données en lui-même est une opération inutile. Pour obtenir la chaîne longue Pascal appropriée, vous devez re-typer la chaîne en un PChar et laisser à Delphi le soin de la convertir correctement de nouveau en une String :
S1 := String (PChar (S1));En fait, vous pouvez éviter la conversion de la chaîne, parce que les conversion de PChar vers string sont automatiques en Delphi. Voici le code final :
procedure TForm1.Button3Click(Sender: TObject); var S1: String; begin SetLength (S1, 100); GetWindowText (Handle, PChar (S1), Length (S1)); S1 := String (PChar (S1)); S1 := S1 + ' est le titre'; Button3.Caption := S1; end;Une alternative est de rétablir la longueur de la chaîne Delphi en utilisant la longueur de la chaîne PChar, en écrivant :
SetLength (S1, StrLen (PChar (S1)));Vous pouvez trouver trois versions de ce code dans l'exemple LongStr qui comporte trois boutons pour les exécuter. Cependant, si vous devez seulement accéder au titre de la fiche, vous pouvez simplement utiliser la propriété Caption de l'objet fiche même. Il n'est pas nécessaire d'écrire tout ce code confus, qui n'avait comme but que d'illustrer les problèmes de la conversion des chaînes. Il existe en pratique des cas dans lesquels on doit appeler des fonctions API Windows et où il faut alors tenir compte de cette situation complexe.
La fonction Format demande comme paramètres une chaîne avec le texte de base, quelques spécificateurs de format (habituellement marqués par le symbole %) et un tableau de valeurs, une par spécificateur de format. Par exemple, pour formater deux nombres en une chaîne, vous pouvez écrire :
Format ('First %d, Second %d', [n1, n2]);où n1 et n2 sont deux valeurs de type Integer. Le premier spécificateur de format est remplacé par la première valeur, le second par la seconde et ainsi de suite. Si le type résultant du spécificateur de format (indiqué par la lettre qui suit le symbole %) ne s'accorde pas avec le type du paramètre correspondant, il se produit une erreur d'exécution. L'absence de vérification à la compilation est en réalité l'inconvénient majeur de l'utilisation de la fonction Format.
La fonction Format utilise un paramètre tableau ouvert (un paramètre qui peut avoir un nombre arbitraire de valeurs); nous en discuterons vers la fin de ce chapitre. Mais, pour le moment, remarquez uniquement la syntaxe type tableau de la liste des valeurs passées en tant que second paramètre.
Outre %d, on peut utiliser un des nombreux autres spécificateurs de format définis par cette fonction et brièvement listés dans la table 7.1. Ces spécificateurs de format fournissent un résultat par défaut pour les types de données donnés. Cependant, on peut utiliser des spécificateurs de format supplémentaires pour modifier le résultat par défaut. Un spécificateur de taille (width), par exemple, détermine un nombre précis de caractères dans la réponse, tandis qu'un spécificateur de précision indique le nombre de chiffres décimaux. Par exemple :
Format ('%8d', [n1]);convertit le nombre n1 en une chaîne de huit caractères alignés à droite (utiliser le signe moins (-) pour spécifier un alignement à gauche), en le complétant par des espaces.
TYPE DU SPECIFICATEUR | DESCRIPTION |
---|---|
d (décimal) | La valeur entière correspondante est convertie en une chaîne de chiffres décimaux. |
x (hexadécimal) | La valeur entière correspondante est convertie en une chaîne de chiffres hexadécimaux. |
p (pointeur) | La valeur entière correspondante est convertie en une chaîne exprimée en chiffres hexadécimaux. |
s (chaîne de caractères) | La valeur chaîne, caractère, ou PChar correspondante est copiée dans la chaîne résultante. |
e (exponentielle) | La valeur à virgule flottante correspondante est convertie en une chaîne indiquée en notation scientifique. |
f (virgule flottante) | La valeur à virgule flottante correspondante est convertie en une chaîne indiquée en notation en virgule flottante. |
g (général) | La valeur à virgule flottante correspondante est convertie en une chaîne la plus courte possible en utilisant le format en virgule flottante ou la notation scientifique. |
n (nombre) | La valeur à virgule flottante correspondante est convertie en une chaîne format virgule flottante mais utilise aussi les séparateurs de milliers. |
m (monétaire) | La valeur à virgule flottante correspondante est convertie en une chaîne représentant un montant monétaire. La conversion est basée sur les paramètres régionaux. Voir l'aide Delphi à la rubrique variables de format date/heure ou variables de format monétaire. |
Chaque partie comporte une première boîte de saisie (edit
box) avec la valeur numérique que l'on souhaite formater. Au-dessous
de la première boîte de saisie se trouve un bouton pour réaliser
l'opération de formatage et pour afficher le résultat dans
une boîte de message (message box). Vient ensuite une autre
boîte de saisie dans laquelle on peut écrire une chaîne
de format. On peut aussi simplement cliquer sur une des lignes du composant
ListBox, au-dessous, pour sélectionner une chaîne de
format prédéfinie. Chaque fois qu'on écrit une nouvelle
chaîne de format, elle est ajoutée dans le ListBox
correspondant (notez que, en quittant le programme, on perd ces nouveaux
éléments).
procedure TFormFmtTest.BtnIntClick(Sender: TObject); begin ShowMessage (Format (EditFmtInt.Text, [StrToInt (EditInt.Text)])); // si l'élément ne s'y trouve pas, l'y ajouter if ListBoxInt.Items.IndexOf (EditFmtInt.Text) < 0 then ListBoxInt.Items.Add (EditFmtInt.Text); end;Le code effectue l'opération de formatage en utilisant le texte de la boîte de saisie EditFmtInt et la valeur du contrôle EditInt. Si la chaîne de format ne se trouve pas déjà dans la boîte liste (List box), elle y est ajoutée. Si par contre l'utilisateur clique sur un élément de la boîte liste, le code place cette valeur dans la boîte de saisie :
procedure TFormFmtTest.ListBoxIntClick(Sender: TObject); begin EditFmtInt.Text := ListBoxInt.Items [ ListBoxInt.ItemIndex]; end;
Les chaînes sont manipulées en mémoire d'une façon dynamique spéciale, comme celle des tableaux dynamiques. Ce sera le sujet du chapitre suivant.