|
c_list_of_double - John COLIBRI.
|
- résumé : encapsulation de liste de valeurs réelles, en utilisant des
ARRAY OF, des tList de ^Double et des tList de c_double
- mots clé : liste de doubles - encapsulation - techniques objet - tList -
série numérique - Vecteur - calcul matriciel - New - pointeurs
- logiciel utilisé : Windows XP personnel, Delphi 6.0
- matériel utilisé : Pentium 2.800 Mhz, 512 Meg de mémoire, 250 Giga disque
dur
- champ d'application : Delphi 5, Delphi 6, Delphi 6, Delphi 6, Delphi 2006,
Turbo Delphi, Delphi 2007 sur Windows
- niveau : développeur Delphi
- plan :
1 - Liste de Réels
Pour réaliser des traitements scientifiques, il est fréquent d'avoir à utiliser
des tableaux ou des liste de réels, en général de type Double.
Lorsque la taille de la série est inconnue à la compilation, il faut armer un
série pouvant être dimensionnée, ou redimensionnée par l'utilisateur. Nous
allons présenter ici une CLASS Delphi et d'autres techniques de
programmation Objet Delphi permettant cette encapsulation de listes de valeurs
numériques.
2 - Encapsulation de liste de Double
2.1 - Les techniques Standard
Si nous connaissons la taille de la série à utiliser, par exemple 1000 ou 1024
points, nous pouvons utiliser:
|
VAR tableau: ARRAY[0..999] OF Double
|
Ici, c'est le programmeur Delphi qui a figé la taille du tableau. Cela
fonctionne parfaitement si l'utilisateur a exactement besoin de 1000 Doubles.
En revanche, si l'utilisateur a besoin de 10 Doubles, il en a 990 de trop, et
s'il en a besoin de 1200, il lui en manque 200.
Depuis Delphi 2, nous pouvons utiliser des tableaux dynamiques, ou c'est
l'utilisateur qui peut indiquer quelle taille de tableau il souhaite utiliser:
- nous déclarons une ARRAY OF (sans donner les bornes initiales et finales
du tableau):
|
VAR tableau_dynamique: ARRAY OF Double
|
Ceci peut être représenté par le schéma suivant:
- nous demandons à l'utilisateur de fournir la taille. Il tape par exemple
cette taille dans un tEdit, et nous dimensionnons le tableau dans un
tButton.OnClick:
l_taille:= IntToStr(Edit1.Text);
SetLength(tableau_dynamique, l_taille);
|
Supposons que l'utilisateur demande 3 cellules. Delphi alloue les 3
cellules:
- l'utilisateur ajoute ses valeurs dans le tableau, exactement comme pour un
tableau statique:
tableau_dynamique[0]:= 3.14;
tableau_dynamique[1]:= -45.67;
|
soit:
- s'il change d'avis, et modifie la taille du tableau, Delphi redimensionne
le tableau. Supposons que l'utilisateur décide d'agrandir son tableau à 7
cellules. Pour cela, il exécute simplement la même instruction SetLength
avec la même valeur:
SetLength(tableau_dynamique, 7);
|
et l'allocateur mémoire de Delphi va:
- chercher en mémoire un espace assez grand pour contenir nos 7 cellules,
et va le réserver en le remplissant de 0
- il va copier les valeurs du tableau actuel au début de la nouvelle zone:
- il va faire pointer notre variable tableau_dynamique vers la nouvelle
zone et rendre l'ancienne zone (de 3 cellules) au système
- la fonction Length permet à chaque instant de connaître la taille. Par
exemple, un développeur prudent vérifiera que l'utilisateur n'essaye pas de
lire ou écrire un zone en dehors du tableau:
IF indice< Length(tableau_dynamique)
THEN tableau_dynamique[indice]:= valeur
ELSE ShowMessage('débordement d''indice');
|
- et finalement, lorsque nous souhaitons libérer la mémoire contenant les
cellules, nous affectons simplement NIL au tableau:
Notez que:
- un ARRAY OF correspond à un pointeur, et SetLength alloue les données
contenues dans le tableau. Mais le compilateur gère cette allocation, et
lorsque nous souhaitons accéder aux données, nous n'avons pas besoin
d'utiliser le symbole ^
- de plus l'allocation / libération est réalisée en utilisant SetLength et
l'affectation de NIL (et non pas via NEW et DISPOSE, comme pour les
pointeurs ordinaires)
Nous constatons alors que nous disposons bien de toutes les fonctionnalités
pour gérer des tableaux de n'importe quelle taille. Toutefois, cela nous oblige
à truffer notre code utilisateur de nombreux tests et appels à des routines de
gestion des tableaux dynamiques. En fait, vu du point de vue de 'utilisateur,
tout ce qu'il souhaite est
- créer son tableau
- ajouter quelques valeur, et ceci sans provoquer d'erreur de débordement
- pouvoir accéder aux valeurs de son tableau pour modifier des valeurs, ou les
lire, avec la syntaxe la plus légère possible.
Compte tenu de cet énoncé du problème, la solution pour un développeur Delphi
est instantannée: il faut créer une CLASS qui encapsule nos instructions
Delphi et représente un liste de Double.
2.2 - La première CLASSe encapsulant un ARRAY OF Double
Voici la définition de notre CLASS c_double_list:
c_array_of_double= Class
m_array_of_double: ARRAY of Double;
m_count: Integer;
Constructor create;
procedure add_double(p_double: Double);
function f_double(p_index: Integer): Double;
Destructor Destroy; Override;
end; // c_array_of_double
|
et:
- m_array_of_double contiendra, comme ci-dessus, les Doubles
- le CONSTRUCTOR va initialiser la CLASS
- add_double permet d'ajouter des valeurs au tableau
- f_double récupère une valeur
Et voici les procédures correspondantes:
Constructor c_array_of_double.create;
begin
InHerited Create;
end; // create
procedure c_array_of_double.add_double(p_double: Double);
begin
if m_count>= Length(m_array_of_double)
then
if Length(m_array_of_double)= 0
then SetLength(m_array_of_double, 1)
else SetLength(m_array_of_double, 2* Length(m_array_of_double));
m_array_of_double[m_count]:= p_double;
Inc(m_count);
end; // add_double
function c_array_of_double.f_double(p_index: Integer): Double;
begin
if p_index>= Length(m_array_of_double)
then Raise Exception.Create('p_index> m_count');
Result:= m_array_of_double[p_index];
end; // f_double
Destructor c_array_of_double.destroy;
begin
m_array_of_double:= Nil;
Inherited;
end; // destroy
|
Et voici un exemple de programme utilisant notre CLASSe:
var g_c_array_of_double: c_array_of_double= Nil;
procedure TForm1.create_arrayClick(Sender: TObject);
begin
g_c_array_of_double:= c_array_of_double.Create;
end; // create_arrayClick
procedure TForm1.add_doublesClick(Sender: TObject);
begin
if g_c_array_of_double= Nil
then g_c_array_of_double.Create;
g_c_array_of_double.add_double(1.1);
g_c_array_of_double.add_double(2.2);
g_c_array_of_double.add_double(3.3);
g_c_array_of_double.add_double(4.4);
end; // add_doubles_Click
procedure TForm1.display_doublesClick(Sender: TObject);
var l_index: Integer;
begin
for l_index:= 0 to g_c_array_of_double.m_count- 1 do
Memo1.Lines.Add(FloatToStr(g_c_array_of_double.f_double(l_index)));
end; // display_doubles_Click
procedure TForm1.free_doubleClick(Sender: TObject);
begin
g_c_array_of_double.Free;
g_c_array_of_double:= Nil;
// or : FreeAndNil(g_c_array_of_double);
end; // free_double_Click
|
Notez que:
- nous n'avons pas donné de dimension initiale à notre tableau. La première
addition se chargera de donner la taille qu'il faut à notre tableau
- notre encapsulation est loin d'être parfaite: l'utilisateur doit encore
accéder à g_c_array_of_double.m_array_of_double, et utiliser
g_c_array_of_double.m_count
Pour mieux rendre notre représentation opaque à l'utilisateur, nous allons
- créer une PROPERTY Doubles et les deux méthodes set_double et
get_double correspondantes
- créer une PROPERTY Count
Voici alors la nouvelle CLASSe:
- sa définition:
const k_granularity= 4;
type c_array_of_double_2=
Class
private
m_array_of_double: ARRAY of Double;
m_count: Integer;
function get_double(p_index: Integer): Double;
procedure set_double(p_index: Integer; p_double: Double);
public
Constructor create;
procedure add_double(p_double: Double);
procedure set_length(p_length: Integer);
Destructor Destroy; Override;
Property Doubles[p_index: Integer] : Double
read get_double write set_double; Default;
Property Count: Integer read m_count write m_count;
end; // c_array_of_double
|
- son implémentation
Constructor c_array_of_double_2.create;
begin
InHerited Create;
SetLength(m_array_of_double, k_granularity);
end; // create
procedure c_array_of_double_2.add_double(p_double: Double);
begin
if m_count>= Length(m_array_of_double)
then SetLength(m_array_of_double, 2* Length(m_array_of_double));
m_array_of_double[m_count]:= p_double;
Inc(m_count);
end; // add_double
function c_array_of_double_2.get_double(p_index: Integer): Double;
begin
if p_index>= Length(m_array_of_double)
then Raise Exception.Create('p_index> m_count');
Result:= m_array_of_double[p_index];
end; // get_double
procedure c_array_of_double_2.set_double(p_index: Integer; p_double: Double);
begin
if p_index>= Length(m_array_of_double)
then begin
m_count:= p_index+ 1;
SetLength(m_array_of_double, m_count);
end;
m_array_of_double[p_index]:= p_double;
end; // set_double
procedure c_array_of_double_2.set_length(p_length: Integer);
begin
m_array_of_double:= Nil;
m_count:= p_length;
SetLength(m_array_of_double, m_count);
end; // set_length
Destructor c_array_of_double_2.destroy;
begin
m_array_of_double:= Nil;
Inherited;
end; // destroy
|
- et un exemple d'utilisation:
var g_c_array_of_double_2: c_array_of_double_2= Nil;
procedure TForm1.create_array_2Click(Sender: TObject);
begin
g_c_array_of_double_2:= c_array_of_double_2.Create;
end; // create_array_2Click
procedure TForm1.add_doubles_2Click(Sender: TObject);
begin
g_c_array_of_double_2.set_length(4);
g_c_array_of_double_2[0]:= 10.0;
g_c_array_of_double_2[1]:= 20.0;
g_c_array_of_double_2[2]:= 40.0;
g_c_array_of_double_2[3]:= 40.0;
g_c_array_of_double_2[4]:= 50.0;
g_c_array_of_double_2[5]:= 60.0;
end; // add_doubles_2Click
procedure TForm1.display_double_2Click(Sender: TObject);
var l_index: Integer;
begin
Memo1.Lines.Add('nombre '+ IntToStr(g_c_array_of_double_2.Count));
for l_index:= 0 to g_c_array_of_double_2.Count- 1 do
Memo1.Lines.Add(FloatToStr(g_c_array_of_double_2[l_index]));
end; // display_double_2Click
procedure TForm1.free_double_2Click(Sender: TObject);
begin
FreeAndNil(g_c_array_of_double_2);
end; // free_double_2Click
|
Et voici l'image de notre application complète:
Notez que:
- dans cet exemple nous avons commencé par donner une taille initiale dans le
CONSTRUCTOR (4 dans notre cas). Une autre possibilité encore aurait été de
doter le CONSTRUCTOR d'un paramètre p_taille_initiale
- pour redimensionner le tableau
- nous vérifions à chaque utilisation d'un indice, que celui-ci est bien
dans la plage des valeurs actuellement permises. Si ce n'est pas le cas,
nous redimensionnons automatiquement le tableau
- pour redimensionner le tableau, plusieurs stratégies sont possibles:
- soit augmenter le tableau d'une cellule à la fois. Il a 3 cellules,
l'utilisateur en veut une de plus, eh bien nous en allouons une de
plus et il a a présent un tableau de 4 cellules, puis 5 cellules
- soit augmenter d'une quantité fixe. Par exemple 10 ou 16. Dans notre
exemple, si nous augmentons de 2, il a 3 au départ, puis 5, 7, 9 etc:
- soit utiliser le système "Buddy-Buddy" qui consiste à doubler la
taille à chaque réallocation. Nous commençons par exemple à 3, puis
augmentons à 6, 12, 24 etc.
Dans les deux derniers cas, il faut gérer deux variables
- la taille allouée (fournie par Length())
- le nombre de cellules actuellement utilisées, contenu dans m_count
- nous avons aussi décidé de laisser l'utilisateur taper n'importe que indice,
en allouant au besoin ce qu'il faut entre la taille actuelle du tableau et
la cellule demandée par l'utilisateur. Par exemple:
- la taille actuelle est de 23
- l'utilisateur veut modifier la cellule 34
- nous allons les cellules entre 24 et 32 en les remplissant de 0
Cette stratégie est astucieuse, mais, en fonction du problème à traiter peut
être risquée. Par exemple, si l'utilisateur se trompe et tape 110 au lieu de
10, notre CLASS lui fournira, sans rien lui dire, un tableau plus grand
qu'il ne le souhaitait. Plusieurs solutions si vous souhaitez éviter ces
redimensionnements automatiques:
- tester l'indice demandé, et provoquer une exception si l'utilisateur
dépasse la limite qu'il a initialement demandé
- forcer le dimensionnement lors de l'appel du CONSTRUCTOR, et ne pas
redimensionner par la suite
2.3 - Utilisation d'une tList de ^Double
Au lieu d'utiliser un ARRAY OF Double, nous pouvons utiliser une tList, qui
a l'avantage essentiel que c'est Delphi qui se charge de l'allocation /
réallocation des cellules. Nous appelons simplement tList.Add(xxx) et cette
instruction ajoute une nouvelle cellule. En fait, la tList utilise un
mécanisme similaire au nôtre:
- lors de la création, une table de 16 cellules est allouée (c'est la
Granularity de la tList), et Count est égal à 0
- lorsque nous appelons Add :
- si Count est inférieur à la taille allouée, la valeur est placée après
la dernière valeur utilisée
- dans le cas contraire, la taille allouée est doublée (système
Buddy-Buddy) et la valeur est placée après la dernière valeur utilisée
Le seul problème est que la table de données utilisée, appelée tList.Items,
n'est pas prévue pour contenir des Doubles, mais de Pointer: un pointeur
générique, de taille 4 octets.
Il nous faut donc placer chaque Double dans une autre cellule, associée à la
cellule courante. Nous pouvons utiliser deux techniques:
- utiliser des pointeurs de Double, et stocker nos doubles dans chaque
cellule pointée
- utiliser une CLASS c_double qui contiendra nos doubles
Commençons par la première technique
Pour cela:
Notez:
A présent, voici donc une CLASS qui a exactement la même fonctionnalité que
la CLASS précédente, mais qui utilise des pointeurs de Double:
- voici la définition de la CLASSe:
type t_pt_double= ^Double;
c_tlist_of_pt_double=
Class(tList)
Private
function get_double(p_index: Integer): Double;
procedure set_double(p_index: Integer; p_double: Double);
Public
Constructor create;
procedure add_double(p_double: Double);
Destructor Destroy; Override;
Property Doubles[p_index: Integer] : Double
Read get_double Write set_double; Default;
end; // c_tlist_of_pt_double
|
- et son implémentation:
Constructor c_tlist_of_pt_double.create;
begin
Inherited Create;
end; // create
procedure c_tlist_of_pt_double.add_double(p_double: Double);
var l_pt_double: t_pt_double;
begin
New(l_pt_double);
l_pt_double^:= p_double;
Inherited Add(l_pt_double);
end; // add_double
function c_tlist_of_pt_double.get_double(p_index: Integer): Double;
var l_pt_double: t_pt_double;
begin
if p_index>= Count
then Raise Exception.Create('p_index> m_count');
Result:= t_pt_double(Items[p_index])^;
end; // get_double
procedure c_tlist_of_pt_double.set_double(p_index: Integer; p_double: Double);
var l_index: Integer;
begin
// -- if index beyond Count, create with 0 filled cells
if p_index>= Count
then
For l_index:= Count to p_index do
add_double(0);
t_pt_double(Items[p_index])^:= p_double;
end; // set_double
Destructor c_tlist_of_pt_double.destroy;
var l_index: Integer;
begin
for l_index:= 0 To Count- 1 do
Dispose(Items[l_index]);
Inherited;
end; // destroy
|
- en prime, un exemple d'utilisation:
var g_c_tlist_of_pt_double: c_tlist_of_pt_double= Nil;
procedure TForm1.create_listClick(Sender: TObject);
begin
g_c_tlist_of_pt_double:= c_tlist_of_pt_double.Create;
end; // create_arrayClick
procedure TForm1.add_doublesClick(Sender: TObject);
begin
if g_c_tlist_of_pt_double= Nil
then g_c_tlist_of_pt_double.Create;
g_c_tlist_of_pt_double.add_double(1.1);
g_c_tlist_of_pt_double.add_double(2.2);
g_c_tlist_of_pt_double.add_double(3.3);
g_c_tlist_of_pt_double.add_double(4.4);
end; // add_doubles_Click
procedure TForm1.display_doublesClick(Sender: TObject);
var l_index: Integer;
begin
for l_index:= 0 to g_c_tlist_of_pt_double.Count- 1 do
Memo1.Lines.Add(FloatToStr(g_c_tlist_of_pt_double.Doubles[l_index]));
end; // display_doubles_Click
procedure TForm1.free_listClick(Sender: TObject);
begin
FreeAndNil(g_c_tlist_of_pt_double);
end; // free_double_Click
procedure TForm1.set_4_directClick(Sender: TObject);
begin
if g_c_tlist_of_pt_double= Nil
then g_c_tlist_of_pt_double.Create;
g_c_tlist_of_pt_double.Doubles[3]:= 4.4;
end; // set_4_directClick
|
qui se présente ainsi:

Notez que
- nous avons choisi de conserver add_double. Il aurait été possible
d'utiliser Add, qui est plus traditionnel
- si nous utilisons Add, il est impératif d'appeler INHERITED Add pour
ajouter le pointeur dans la liste (INHERITED n'aurait pas été nécessaire
avec add_double, car la tList n'a aucune méthode ayant ce nom)
- nous avons choisi d'hériter de tList. Il aurait été possible de placer dans
une c_tlist_of_pt_double un attribut m_c_tlist de type tList (comme nous
avions inclus un attribut m_array_of_double dans la CLASS précédente).
L'avantage de l'héritage est de bénéficier directement des propriétés et
méthodes de la tList (Count, Find etc). Si la tList est incluse
(composition plutôt qu'héritage), il faut créer des PROPERTYes qui
relaient les mêmes propriétés de la tList. Dans notre cas, l'héritage
semble avantageux. En revanche si nous souhaitons utiliser plusieurs tList
(voir plus loin), nous n'aurons pas d'autre choix que de placer les tList
dans la CLASSe
Cette technique est certes utilisable, mais reste un peu étrange pour les
nouveaux venus dans Delphi qui préféreraient éviter les pointeurs, qui ont, à
cause de leur utilisation un peu délicate, une mauvaise réputation.
Nous pouvons donc effectuer le même traitement en créant un objet dont la seule
vocation sera de contenir notre valeur Double
2.4 - Une tList de c_double
Pour éviter les New et Dispose (qui fonctionnent parfaitement), nous pouvons
utiliser une CLASS spéciale pour stocker chaque Double.
Donc:
- voici la définition des deux CLASSes:
type c_double=
Class
m_double: Double;
Constructor create(p_double: Double);
end; // c_double
c_tlist_of_c_double=
Class(tList)
Private
function get_double(p_index: Integer): Double;
procedure set_double(p_index: Integer; p_double: Double);
Public
Constructor create;
procedure add_double(p_double: Double);
Destructor Destroy; Override;
Property Doubles[p_index: Integer] : Double
Read get_double Write set_double; Default;
end; // c_tlist_of_c_double
|
- et leur implémentation:
// -- c_double
Constructor c_double.create(p_double: Double);
begin
Inherited Create;
m_double:= p_double;
end; // create
// -- c_tlist_of_c_double.
Constructor c_tlist_of_c_double.create;
begin
Inherited Create;
end; // create
procedure c_tlist_of_c_double.add_double(p_double: Double);
begin
Inherited Add(c_double.create(p_double))
end; // add_double
function c_tlist_of_c_double.get_double(p_index: Integer): Double;
begin
if p_index>= Count
then Raise Exception.Create('p_index> m_count');
Result:= c_double(Items[p_index]).m_double;
end; // get_double
procedure c_tlist_of_c_double.set_double(p_index: Integer; p_double: Double);
var l_index: Integer;
begin
// -- if index beyond Count, create with 0 filled cells
if p_index>= Count
then
For l_index:= Count to p_index do
add_double(0);
c_double(Items[p_index]).m_double:= p_double;
end; // set_double
Destructor c_tlist_of_c_double.destroy;
var l_index: Integer;
begin
for l_index:= 0 To Count- 1 do
c_double(Items[l_index]).Free;
Inherited;
end; // destroy
|
- et le projet Delphi de test:
var g_c_tlist_of_c_double: c_tlist_of_c_double= Nil;
procedure TForm1.create_listClick(Sender: TObject);
begin
g_c_tlist_of_c_double:= c_tlist_of_c_double.Create;
end; // create_arrayClick
procedure TForm1.add_doublesClick(Sender: TObject);
begin
if g_c_tlist_of_c_double= Nil
then g_c_tlist_of_c_double.Create;
g_c_tlist_of_c_double.add_double(1.1);
g_c_tlist_of_c_double.add_double(2.2);
g_c_tlist_of_c_double.add_double(3.3);
g_c_tlist_of_c_double.add_double(4.4);
end; // add_doubles_Click
procedure TForm1.display_doublesClick(Sender: TObject);
var l_index: Integer;
begin
for l_index:= 0 to g_c_tlist_of_c_double.Count- 1 do
Memo1.Lines.Add(FloatToStr(g_c_tlist_of_c_double.Doubles[l_index]));
end; // display_doubles_Click
procedure TForm1.free_listClick(Sender: TObject);
begin
FreeAndNil(g_c_tlist_of_c_double);
end; // free_double_Click
|
dont voici l'image:

2.5 - Comparaison des trois techniques
Nous pouvons facilement comparer les trois techniques par le schéma suivant:
Il en ressort que
- l'utilisation d'un ARRAY OF double est certainement la plus économe au
niveau mémoire, et évite en plus tout surtypage
- l'emploi de pointeurs de Double est assez simple, mais nécessite une
allocation séparée pour chaque cellule, ce qui peut se révéler coûteux en
temps. De plus il faut surtyper les éléments de la tList, au départ prévus
pour contenir des Pointer et pas des Doubles^
- la création de CLASSe c_double est relativement classique. C'est la
technique Delphi la plus naturelle, et elle est utilisée pour encapsuler
dans une tList n'importe quel autre type de cellule (une c_string, une
c_ma_classe qui contiendrait de nombreux champs). Mais il faut savoir que
chaque objet contient un certain nombre d'octets pour gérer les CLASSe: la
taille de l'objet, le pointeur vers la VMT (table des méthodes virtuelles)
etc. D'où un consommation nettement supérieure par rapport à une cellule de
Double. En revanche, si nous encapsulons un Double dans une CLASSe, nous
pouvons y associer, si nous le souhaitons des méthodes qui seraient utiles
pour notre application: vérifier que la valeur est comprise entre certaines
bornes, afficher avec certains formats prédéfinis, sauvegarder dans un flux,
ou relire un flux etc
3 - Encapsulation de liste de Boolean
Lorsque la taille de chaque élément est inférieur à la taille de la cellule
d'une tList (un Pointer, soit 4 octets), nous pouvons stocker la valeur dans
chaque cellule en utilisant le surtypage.
Schématiquement nous avons, pour nos trois structures canoniques:
Et, très rapidement, le code avec stockage dans les Pointer de la tList:
- voici la définition de la CLASSe:
c_tlist_of_boolean=
Class(tList)
Private
function get_boolean(p_index: Integer): Boolean;
procedure set_boolean(p_index: Integer; p_boolean: Boolean);
Public
Constructor create;
procedure add_boolean(p_boolean: Boolean);
Destructor Destroy; Override;
Property Booleans[p_index: Integer] : Boolean
Read get_boolean Write set_boolean; Default;
end; // c_tlist_of_boolean
|
- et leur implémentation:
Constructor c_tlist_of_boolean.create;
begin
Inherited;
end; // create
procedure c_tlist_of_boolean.add_boolean(p_boolean: Boolean);
begin
Inherited Add(Nil);
if p_boolean
then Items[Count- 1]:= Pointer(1);
end; // add_boolean
function c_tlist_of_boolean.get_boolean(p_index: Integer): Boolean;
begin
if p_index>= Count
then Raise Exception.Create('p_index> m_count');
Result:= Integer(Items[p_index])> 0;
end; // get_boolean
procedure c_tlist_of_boolean.set_boolean(p_index: Integer; p_boolean: Boolean);
var l_index: Integer;
begin
// -- if index beyond Count, create with 0 filled cells
if p_index>= Count
then
For l_index:= Count to p_index do
add_boolean(False);
if p_boolean
then Items[p_index]:= Pointer(1)
else Items[p_index]:= Nil;
end; // set_boolean
Destructor c_tlist_of_boolean.destroy;
begin
Inherited;
end; // destroy
|
- et le résultat est:

Notez que
- nous avons ici exploité que False a la valeur 0 (et donc NIL pour un
Pointer) et True la valeur 1
- l'exemple présenté peut être aisément adapté à des liste de Byte,
ShortInt, SmallInt, Integer, Word, dWord, Char et WideChar, énuméré.
Bref, tout ce qui a moins de 4 octet
4 - Liste de Plusieurs Doubles
4.1 - Séries de plusieurs réels
Supposons à présent que nous souhaitions gérer des liste de plusieurs doubles:
- les valeurs X, Y, Z d'un point en 3D
- les mesures électriques d'un composant (tension, intensité, fréquence etc)
- les données économiques d'une série (date, prix, volume pour une série
boursière)
Dans ce cas, nous pouvons aussi mettre en place les mêmes alternatives:
- définir un RECORD ou une CLASSe pour chaque groupe de valeurs, et
utiliser un ARRAY OF t_record ou c_class, et gérer nous-mêmes la
réallocation. Cette solution fonctionne, mais nous perdons la seule
motivation que nous avions pour un Double, qui était d'éviter le surtypage
- utiliser une tList et placer les données dans un t_record^. Ici c'est à
nouveau possible, mais en général, si déjà nous avons plusieurs valeurs à
gérer, il est plus que probable que nous aurons des traitements à réaliser
sur chaque groupe de points (
- l'utilisation d'un CLASS c_class s révèle en général la plus adaptée
4.2 - ARRAY OF record_of_double ou record_of_double OF ARRAY OF Double
En utilisant des tableaux dynamiques, ARRAY OF, nous avons deux solutions:
- soit placer nos chaque valeur multidimensionnelle dans un RECORD et créer
un ARRAY OF de ces RECORDs
- soit créer un RECORD d'une c_array_of_double présenté ci-dessus
Schématiquement nous avons:
Pour la première solution:
- voici la définition des deux CLASSes:
type t_double_record= Record
m_x, m_y, m_z: Double;
end; // t_double_record
c_array_of_record_of_doubles=
Class
private
m_array_of_double_record: ARRAY of t_double_record;
m_count: Integer;
function get_doubles(p_index: Integer): t_double_record;
procedure set_doubles(p_index: Integer;
p_double_record: t_double_record);
function get_x(p_index: Integer): Double;
procedure set_x(p_index: Integer; p_x: Double);
function get_y(p_index: Integer): Double;
procedure set_y(p_index: Integer; p_y: Double);
function get_z(p_index: Integer): Double;
procedure set_z(p_index: Integer; p_z: Double);
public
Constructor create;
procedure add_doubles(p_double_record: t_double_record);
procedure set_length(p_length: Integer);
Destructor Destroy; Override;
Property Doubles[p_index: Integer] : t_double_record
Read get_doubles Write set_doubles; Default;
Property X[p_index: Integer] : Double
Read get_x Write set_x;
Property Y[p_index: Integer] : Double
Read get_y Write set_y;
Property Z[p_index: Integer] : Double
Read get_z Write set_z;
Property Count: Integer Read m_count Write m_count;
end; // c_array_of_double
function f_double_record(p_x, p_y, p_z: Double): t_double_record;
|
- et leur implémentation:
function f_double_record(p_x, p_y, p_z: Double): t_double_record;
begin
Result.m_x:= p_x;
Result.m_y:= p_y;
Result.m_z:= p_z;
end; // f_double_record
// -- c_array_of_record_of_doubles
Constructor c_array_of_record_of_doubles.create;
begin
InHerited Create;
SetLength(m_array_of_double_record, k_granularity);
end; // create
procedure c_array_of_record_of_doubles.add_doubles(p_double_record: t_double_record);
begin
if m_count>= Length(m_array_of_double_record)
then SetLength(m_array_of_double_record, 2* Length(m_array_of_double_record));
m_array_of_double_record[m_count]:= p_double_record;
Inc(m_count);
end; // add_double
function c_array_of_record_of_doubles.get_doubles(p_index: Integer): t_double_record;
begin
if p_index>= Length(m_array_of_double_record)
then Raise Exception.Create('p_index> m_count');
Result:= m_array_of_double_record[p_index];
end; // get_doubles
procedure c_array_of_record_of_doubles.set_doubles(p_index: Integer; p_double_record: t_double_record);
begin
if p_index>= Length(m_array_of_double_record)
then begin
m_count:= p_index+ 1;
SetLength(m_array_of_double_record, m_count);
end;
m_array_of_double_record[p_index]:= p_double_record;
end; // set_doubles
function c_array_of_record_of_doubles.get_x(p_index: Integer): Double;
begin
Result:= Doubles[p_index].m_x;
end; // get_x
procedure c_array_of_record_of_doubles.set_x(p_index: Integer; p_x: Double);
begin
if p_index>= Length(m_array_of_double_record)
then begin
m_count:= p_index+ 1;
SetLength(m_array_of_double_record, m_count);
end;
m_array_of_double_record[p_index].m_x:= p_x;
end; // set_x
// -- ...ooo... idem for Y and Z
procedure c_array_of_record_of_doubles.set_length(p_length: Integer);
begin
m_array_of_double_record:= Nil;
m_count:= p_length;
SetLength(m_array_of_double_record, m_count);
end; // set_length
Destructor c_array_of_record_of_doubles.destroy;
begin
m_array_of_double_record:= Nil;
Inherited;
end; // destroy
|
- et le projet Delphi de test:
var g_c_array_of_record_of_doubles: c_array_of_record_of_doubles= Nil;
procedure TForm1.create_arrayClick(Sender: TObject);
begin
g_c_array_of_record_of_doubles:= c_array_of_record_of_doubles.Create;
end; // create_arrayClick
procedure TForm1.add_doublesClick(Sender: TObject);
begin
if g_c_array_of_record_of_doubles= Nil
then g_c_array_of_record_of_doubles.Create;
g_c_array_of_record_of_doubles
.add_doubles(f_double_record(1.1, 2.2, 3.3));
g_c_array_of_record_of_doubles
.add_doubles(f_double_record(10.1, 20.2, 30.3));
g_c_array_of_record_of_doubles
.add_doubles(f_double_record(100.1, 200.2, 300.3));
// -- not allowed
// g_c_array_of_record_of_doubles.Doubles[3].m_x:= 3.14;
g_c_array_of_record_of_doubles.Doubles[3]:= f_double_record(3.14, 0, 0);
g_c_array_of_record_of_doubles.X[4]:= 6.28;
end; // add_doubles_Click
procedure TForm1.display_doublesClick(Sender: TObject);
var l_index: Integer;
begin
for l_index:= 0 to g_c_array_of_record_of_doubles.Count- 1 do
with g_c_array_of_record_of_doubles[l_index] do
display(Format('x = %5.2f, y =%5.1f, z =%5.1f ', [m_x, m_y, m_z]));
end; // display_doubles_Click
procedure TForm1.free_doubleClick(Sender: TObject);
begin
FreeAndNil(g_c_array_of_record_of_doubles);
end; // free_double_Click
|
dont voici l'image:

Notez que
- nous avons ajouté une FUNCTION qui convertit trois Doubles isolés en un
RECORD
- à notre grande surprise
g_c_array_of_record_of_doubles.Doubles[3].m_x:= 3.14;
|
n'est pas autorisé
Et pour la seconde solution
- voici la définition du RECORD:
t_double_record= Record
m_c_x_array, m_c_y_array, m_c_z_array: c_array_of_double_2;
end; // t_double_record
|
- et le projet Delphi de test:
var g_c_record_of_array_of_doubles: t_double_record;
procedure TForm1.create_arraysClick(Sender: TObject);
begin
with g_c_record_of_array_of_doubles do
begin
m_c_x_array:= c_array_of_double_2.Create;
m_c_y_array:= c_array_of_double_2.Create;
m_c_z_array:= c_array_of_double_2.Create;
end; // with g_c_record_of_array_of_doubles
end; // create_array_2Click
procedure TForm1.add_doubles_2Click(Sender: TObject);
begin
with g_c_record_of_array_of_doubles do
begin
m_c_x_array[0]:= 1.1;
m_c_y_array[0]:= 2.2;
m_c_z_array[0]:= 3.3;
m_c_x_array[1]:= 10.1;
m_c_y_array[1]:= 20.2;
m_c_z_array[1]:= 30.3;
end; // with g_c_record_of_array_of_doubles
end; // add_double_property_Click
procedure TForm1.display_doubles_2Click(Sender: TObject);
var l_index: Integer;
begin
with g_c_record_of_array_of_doubles do
for l_index:= 0 to m_c_x_array.Count- 1 do
display(Format('x= %5.1f, y =%5.1f, z =%5.1f ',
[ m_c_x_array[l_index], m_c_y_array[l_index],
m_c_z_array[l_index] ]));
end; // display_double_property_Click
procedure TForm1.free_doubles_2Click(Sender: TObject);
begin
with g_c_record_of_array_of_doubles do
begin
FreeAndNil(m_c_x_array);
FreeAndNil(m_c_y_array);
FreeAndNil(m_c_z_array);
end; // with g_c_record_of_array_of_doubles
end; // free_double_2Click
|
dont voici l'image:

Notez que
- dans la seconde solution, la définition est triviale, mais le code
d'utilisation plus verbeux. Nous devrions en réalité utiliser un CLASS qui
encapsule les trois ARRAY OF Double. Donc une CLASS OF ARRAY OF Double
- la seconde solution permettrait aussi d'accomoder des séries de taille
différentes. Supposons que nous souhaitions modéliser des nombres et de leur
valeur absolue distinctes. Voici le schéma:
4.3 - tList of pt_record_of_double
Voici le traitement du même problème utilisant des pointeurs:
Pour la première solution:
- voici les définitions:
type t_measure= Record
m_intensity, m_voltage, m_frequency: Double;
end; // t_measure
t_pt_measure= ^t_measure;
c_tlist_of_pt_doubles=
Class(tList)
Private
function get_measure(p_index: Integer): t_measure;
procedure set_measure(p_index: Integer; p_measure: t_measure);
function get_intensity(p_index: Integer): Double;
procedure set_intensity(p_index: Integer; p_intensity: Double);
Public
Constructor create;
procedure add_measure(p_measure: t_measure);
Destructor Destroy; Override;
Property Measure[p_index: Integer] : t_measure | |