Déclarer et utiliser les types

Transcription

Déclarer et utiliser les types
[DELPHI] Bien déclarer ses types
Pourquoi ? Tout simplement parce que Delphi hérite du Pascal qui est un langage
extrêmement typé, c'est-à-dire qu'il faut déclarer scrupuleusement tout ce qu'on utilise. Non
seulement ça structure sans faute les programmes, mais surtout cela apporte une logique et
une rigueur qu'il faut comprendre.
Contenu
I) Utiliser un type existant
II) Connaître les types de base
III) Les types énumérés
IV) Les types intervalle
V) Déclarer succintement des variables typées pas comme les autres
VI) Les ensembles
VII) Les enregistrements
VIII) Les tableaux simples
IX) Les tableaux multidimensions
X) Les tableaux à taille variable
XI) Les classes
XII) Transcriptions des types
XIII) Conclusion
I) Utiliser un type existant
C'est tout bête : on l'utilise dans la déclaration des variables. Exemple :
procedure MaProcedure;
var MaVariable : boolean;
MaVariable2, MaVariable3 : integer;
MaVariable4 : integer;
begin
end;
On vient de déclarer une variable booléenne (faux ou vrai) ainsi que trois entiers 32 bits. On
remarque qu'on sépare les variables de même type par des virgules, et rien n'empêche
d'avoir des lignes différentes déclarant des variables de même type.
II) Connaître les types de base
Plus les types sont étendus et plus ils consomment de la mémoire (tout est relatif). De ce fait,
on choisira les types les moins étendus que possible : c'est le fameux «immédiatement
supérieur».
Les entiers
byte
shortint
non signé
signé
0 .. 255
-128 .. 127
word
shortint
non signé
signé
0 .. 65535
-32768 .. 32767
cardinal
integer
longint
non signé
signé
signé
0 .. 2147483647
-2147483648 .. 2147483647
-2147483648 .. 2147483647
int64
signé
integer version 64 bits
On remarque qu'il existe des couples de 8 bits, 16 bits et 32 bits avec l'un étant le demidécalage de l'autre. Même si l'aide de Delphi énonce integer = -32768 .. 32767,
sachez qu'il est toujours considéré comme un 32 bits. Au passage, cardinal doit être
considéré comme un integer positif.
En Delphi, il n'y a pas de soucis pour indiquer si un entier est oui ou non signé (en résumé s'il
peut être négatif), car on passe par des types différents. En C++, par exemple, il y a nuance
entre signed int et unsigned int.
Note: les étendues de tous les types que je vous donne sont à connaître par coeur !
Les réels
Delphi est à la limite de l'arnaque. Voici les types, avec des arrondis assez ronds :
real
single
double
extended
±10^38
±10^38
±10^308
±10^4932
Généralement, pour les petits calculs de rien du tout, real suffit bien. C'est d'ailleurs le type
le plus déclaré dans la VCL de Delphi. Sauf que, pour des calculs plus sympathiques, je
préfère utiliser largement le type extended.
Vous allez me dire qu'il y a un problème d'étendue... Tout à fait, sauf que real est tellement
étendu lui-aussi que les transcriptions de types se font sans dommage (dans la plupart des
cas seulement, car il faut rester conscient du danger). Vous inquiétez pas, on va voir ça
après.
Les booléens
Alors là, je ne comprend pas pourquoi ces zouaves apparaissent que maintenant, car c'est le
type le plus simple qu'il soit. Enfin bref...
boolean = false .. true
III) Les types énumérés
C'est simple : on énumère un à un tout ce qu'il contient. Les éléments qui composent le type
prennent le nom qu'ils veulent, car de toute façon, au sein de l'ordinateur, c'est leur position
dans l'énumération qui les caractérisera. Exemple :
type TCodesSources = (csDelphi, csPhp, csCpp, csAsm, csAsp) ;
Ainsi, que csDelphi s'appelle Delphi, LangageDelphi ou ModeDelphi, ça ne change
rien.
Prenons nos habitudes. C'est Borland qui fixe les règles et c'est bien normal. L'idée est de
précéder tous les éléments d'un couple de deux lettres en minuscules rappelant le nom du
type dans lequel ils sont déclarés. Dans notre cas, le type TCodesSources est dominé par
les lettres "C" et "S". De ce fait, ses éléments seront construits en csMachin, csBidule ou
même csTruc. Si on avait eu TCodes, alors on aurait sûrement retenu coTartanpion. C'est
uniquement un moyen mnémotechnique pour bien programmer.
Pour utiliser ces types, on fait ceci :
type TCodesSources = (csDelphi, csPhp, csCpp) ;
var Langage : TCodesSources;
begin
Langage:=csPhp;
end;
C'est tout... L'intérêt est d'éviter de déclarer des entiers pour désigner tel ou tel langage. Là,
tout est combiné : nom et valeur numérique. Ca permet également de simplifier les tests de
conditions par le passage à une structure en CASE. Comme tout est question de facilité, on
aurait alors :
begin
case Language of
csDelphi: ShowMessage('Delphi');
csPhp: ShowMessage('PHP');
else ShowMessage('C++');
//si on met un ELSE c'est qu'on est sûr qu'il n'y a plus rien après
csCpp
end;
end;
Une fois le type énuméré, ses éléments doivent être considérés dans tout l'EDI de Delphi
comme des constantes qui auraient été déclarées via le mot clé CONST. On peut donc faire
des tests :
begin
if csDelphi < csPhp then
ShowMessage('Vous avez raison !');
end;
Je dois vous faire pointer le doigt sur un détail ultra important qui nous poursuivera jusqu'au
bout de nos rêves, enfin... Le code suivant ne marche pas :
begin
if csPhp=1 then
ShowMessage('Vous avez structurellement tord !');
end;
... mais ce qui suit est parfait :
begin
if Ord(csPhp)=1 then
ShowMessage('Vous avez raison !');
end;
Tiens ? csDelphi est classé rang zéro ? Tout à fait et il ne faut pas l'oublier...
On vient d'introduire la fonction ORD de l'unité System (déclarée automatiquement par le
compilateur, essayez de mettre son nom dans les USES et vous verrez). L'opération inverse
n'existant pas, on doit procéder comme il suit. On va aller chercher explicitement la valeur, là
où elle se trouve :
begin
Langage:=TCodesSources(2);
if Langage=csCpp then
ShowMessage('Vous avez raison !');
end;
J'ai dit «l'opération inverse n'existant pas». En ce qui concerne les char (un caractère ASCII
en bref), la fonction CHR permet de renvoyer le n_nième caractère de la table ASCII, sachant
que l'on a :
type char = #0 .. #255;
IV) Les types intervalle
Disons simplement que le type est défini par une étendue: ça va de tant à tant, les bornes
étant des valeurs de base finies. On note les types énumérés par un double point "..".
Par exemple, nous avons :
type TShortIntPositif = 0..127 ;
L'habitude (et les bonnes manières !) veulent qu'on précède les noms de types par la lettre
"T" en majuscule. Vous avez compris que cela désigne le mot Type. Je le dis que
maintenant, mais c'était valable depuis le début.
Les types de base (cités dans le premier paragraphe) sont fixés et servent à batir d'autres
types. Ainsi, integer ne pourra jamais être substitué à un type que vous aurez vous même
construit par dérivation.
Je vous donne un petit morceau de code qui est incorrect en raison de l'existence d'une
infinité de nombres réels entre -Pi et 2*Pi. On aurait alors déclaré une étendue infinie, ce qui
est absurde... Au passage, il plante bien la version 3 de Delphi.
procedure TForm1.FormCreate(Sender: TObject);
type TFlt = -3.14 .. 6.28 ;
var Vra : TFlt;
begin
Vra:=-3;
end;
Donc ceci ne marche pas...
Vous vous souvenez que boolean = false..true ? Enfin, ça c'est la ruse du
compilateur, car il faut déterminer false et true. C'est l'objet du point suivant.
type TCodesSources = (csDelphi, csPhp, csCpp) ;
TMesLangagesFetiches = csDelphi .. csPhp ;
var LangagesAMoi : TMesLangagesFetiches;
begin
LangagesAMoi:=csDelphi;
LangagesAMoi:=csPhp;
LangagesAMoi:=csCpp;
end;
On compile un beau message d'erreur : csCpp dépasse les limites de sous-entendues.
Ben, vous savez tout...
V) Déclarer succintement des variables typées pas comme les autres
Un type sert pour déclarer des variables. Reprenons notre exemple du début :
type TCodesSources = (csDelphi, csPhp, csCpp) ;
var Langage, MonPoteFaitAussiDeLaProg : TCodesSources;
begin
Langage:=csPhp;
end;
Vous ne voyez bien sûr aucune objection si j'écris ceci :
var Langage, MonPoteFaitAussiDeLaProg : (csDelphi, csPhp, csCpp);
begin
Langage:=csPhp;
end;
Donc, vous avez tout compris...
Cette technique n'est intéressante que pour supprimer des lignes superflues concernant des
types très peu utilisés.
VI) Les ensembles
Je vous présente la propriété Style du persistent TFont :
type TFontStyle = (fsBold, fsItalic, fsUnderline, fsStrikeOut);
TFontStyles = set of TFontStyle;
Qu'est-ce qu'un persistent ? C'est une classe TPersistent qui sert à faire des propriétés
classées dans l'inspecteur d'objets de Delphi. Regardez, il y a un "+" devant la propriété
Font. C'est lié au fait que :
type TFont = class(TPersistent)
...
end;
Ca sert pour le développement de composants, qui ne nous intéressent pas ici.
Ainsi TFontStyles est un set of quelque chose, une combinaison d'éléments en fait. Vous
remarquez que les habitudes préconisent de rajouter un "S" à TFontStyle pour créer un set,
sachant que TFontStyle énumère toutes les entités pour l'ensemble (nécessairement fini).
On a alors les bouts de code suivants :
var FS : TFontStyles;
begin
FS := [fsBold, fsStrikeOut];
if fsBold in FS then
ShowMessage('Vous avez raison');
FS := FS - [fsBold];
if FS = [fsStrikeOut] then
ShowMessage('Vous avez encore raison');
FS := FS + [fsItalic];
end;
On teste la présence d'un élément par le mot magique in. On ajoute et supprime avec les
opérateurs + et -. Et si j'ai envie de trouver les éléments communs, que dois-je faire ? Eh
bien, faites ceci :
var F1, F2, Intersec : TFontStyles;
begin
F1 := [fsBold, fsStrikeOut];
F2 := [fsItalic, fsUnderLine, fsStrikeOut];
Intersec := F1 * F2;
if Intersec = [fsStrikeOut] then
ShowMessage('Vous avez raison');
end;
C'est tout...
VII) Les enregistrements
Ca aussi, c'est un point super passionnant... une fois de plus, il n'y a pas à discutailler 40
ans, mais ça permet de regrouper des variables désignant les propriétés d'un même objet.
Voici un exemple :
type
TVoiture = {packed} record
Couleur : integer;
Matiere : (maAcier, maPapier, maFerraille);
Marque : string;
end;
var MaVoiture : TVoiture;
Ravi de voir que vous vous intéressez à ma voiture... mais en papier, ça ne le fait pas trop !
Je passe chez mon garagiste et il me construit la voiture suivante :
begin
MaVoiture.Couleur := $000000; //toute noire
MaVoiture.Matiere := maAcier;
MaVoiture.Marque := 'Lada';
end;
Il est bien gentil ce garagiste, mais si je lui donne TaVoiture, il va devoir modifier les 3 lignes.
Sachez qu'il peut n'en modifier qu'une s'il procède ainsi :
begin
with MaVoiture do
begin
Couleur := $000000;
Matiere := maAcier;
Marque := 'Lada';
end;
end;
Vous voyez bien sûr le truc... mais le with réserve des surprises. Voici un code :
begin
with MaVoiture, TaVoiture do
Marque := 'Lada';
end;
L'expérience montre que seule la dernière variable accolée dans le with est initialisée.
Alors, où est l'intérêt ? Eh bien, comme MaVoiture et TaVoiture sont de même type, Delphi
ne sait pas à laquelle des deux variables Marque se réfère. Si maintenant on mélange deux
enregistrements n'ayant pas de "sous-clé" commune, par recherche inverse (droite vers la
gauche dans le with), Delphi sait quoi associer à qui. Personnellement, je n'ai jamais utilisé
une telle méthode... il y a trop de risques pour bugger un logiciel (ça suffit comme ça !).
Pour en revenir au début du paragraphe, pourquoi avoir mis en commentaire le mot magique
packed ? Ben, si vous utilisez les fonctions BlockRead et BlockWrite, ça vous sera utile.
Sinon, regardez le paragraphe sur les tableaux simples pour un autre exemple d'application.
En fait, ça fait comme si le record était une seule variable, laquelle en se logeant en
mémoire se partitionne automatiquement pour reformer les différentes parties de
l'enregistrement.
VIII) Les tableaux simples
Vous aimez voyager à travers les dimensions parrallèles ? Eh bien, contentez vous de savoir
compter jusqu'à 10, parce que pour une fois que Delphi nous laisse de la marge, ce n'est pas
le moment de déraper.
Nous avons vu que les énumérations et compagnie commencent à partir de 0. Avec les
tableaux, il est possible de les faire démarrer où on veut. Voici la syntaxe conseillée :
type TTableau = array [0..9] of byte;
var MonTableau : TTableau;
... et même (vous voyez que ça sert) :
var MonTableau : array [0..9] of byte;
Vous notez peut-être pas immédiatement que ce tableau comporte 10 cases et non 9 ! De
même, le tableau suivant a aussi 10 cases (quoi que là c'est plus évident) :
var MonAutreTableau : array [1..10] of integer;
On vient de montrer au passage comment démarrer un tableau ailleurs. Pour illustrer le
fonctionnement, voici des bouts de code :
var i : byte;
begin
MonTableau[7]:=5;
for i:=0 to 9 do
MonTableau[i]:=i;
i:=MonTableau[4];
end;
Ca me permet au passage de montrer que ma variable de boucle "i" est déclarée le plus petit
possible. Rappel: byte = 0..255. Et comme j'ai envie de vous montrer autre chose, on va faire
ceci :
var i : byte;
begin
for i:=9 downto 0 do
MonTableau[i]:=i;
end;
C'est le principe de la boucle inverse sans opérations de soustraction. Voilà la boucle for
expliquée... Notons que Delphi ne donne pas la possibilité de faire des boucles à sauts
d'indice. Soit on ruse avec l'ASM (pas très propre), soit on garde le Pascal et son opérateur
modulo mod. Ainsi, le code suivant n'initialise que les indices pairs :
var i : byte;
begin
for i:=9 downto 0 do
if i mod 2 = 0 then
MonTableau[i]:=i;
end;
Ou alors on fait des boucles repeat pour les sauts d'indice... Tout dépend de ce qu'on veut
faire en fait.
Puisqu'on vient d'introduire la boucle for, pour remplir les deux premières cases du tableau,
on peut aussi faire comme cela :
var b : boolean;
begin
for b:=false to true do
MonTableau[Ord(b)]:=Ord(b);
end;
J'avoue que c'était pas une bonne idée d'évoquer cette subtilité, mais cela montre bien les
rages techniques des types intervalle.
Ponpon final sur les tableaux (accrochez vous, ou passez au grand paragraphe suivant si
vous êtes à la limite du décrochage [je suis sérieux]) : le mot packed. En fait, lorsque vous
développer sous l'EDI de Delphi, les variables string sont limitées à 255 caractères. C'est
un soucis technique des linkers de traduire les programmes en instructions interprétables. A
ce moment du logiciel, on a l'équivalence (pas l'égalité !) entre string et packed array
[1..255] of char.
Cette astuce permet d'écrire MaString:='Salut mec' (car c'est un string), et on peut
récupérer des caractères via MaString[IndexCase] car c'est un tableau (de 255 cases
pour l'anecdote). Le comble est que ce tableau commence à 1 et pas à zéro. Ne m'attaquez
pas ; je n'y suis pour rien... Mais attention, une fois que l'application est compilée, les string
deviennent vastes et théoriquement infinis. Le linker a ajouté des modules afin que
l'application sache manipuler seule les grandes données. On pourra noter des pertes de
performances flagrantes quand une chaîne devient trop longue. Dans certains traitements, il
faut programmer de manière à avoir les plus petits éléments possibles. Par exemple, en
Turbo Pascal, les chaînes de caractères sont limitées à 255 caractères et ce, quel que soit
l'état de vos développements.
En résumé, grâce à packed, au lieu de faire :
begin
MaString[1]:='S';
MaString[2]:='a';
MaString[3]:='l';
....
end;
... eh bien vous faîtes ...
begin
MaString:='Salut mec';
end;
... et c'est considéré comme [S][a][l][u][t][ ][m][e][c]. Le comble (oui encore un
!), c'est qu'en faisant caractère par caractère, il peut y avoir une exception en mémoire
(=erreur qui ne devrait jamais apparaître). Ben ouais, car une fois compilé, string est
dynamique, donc il faut allouer, ce qui n'est pas possible via ce tatonement incrémental. Plus
technique encore, quand vous faîtes MaString:='Salut mec', Delphi transforme votre
syntaxe en utilisant des fonctions de l'unité System. C'est pour cela que cette unité est la plus
mystérieuse de toute, car dans le fond, je suis persuadé qu'elle ne peut même pas se
compiler...
Des fois, il vaudrait mieux ne pas se poser trop de questions.
IX) Les tableaux multidimensions
Pour une matrice, il faut des tableaux de dimension 2 remplis de réels. On fait donc :
const MatSize = 5;
type
TMatCol = array [0..MatSize] of real;
TMatrice = array [0..MatSize] of TMatCol;
var Matrix : TMatrice;
On obtient ainsi une matrice 5x5. Ici TMatCol désigne virtuellement les colonnes de la
matrice. Ca pourrait très bien considérer les lignes... En fait, tout dépend de la modélisation
de la matrice dans la mémoire. La case [X,Y] est-elle (X,Y) ou (Y,X) sur le papier du
mathématicien ? Bref, on s'en fiche du moment que les indices désignent ce qu'on veut qu'ils
désignent.
Pour les multidimensions, le passage à une dimension supérieure requiert l'utilisation de
types intermédiaires (pour une dimension 2, j'ai bien pris deux lignes de code), car sinon
Delphi ne sait plus où donner de la tête.
On a alors :
var Reelle : real;
begin
Matrix[4][3]:=3.14;
Reelle:=Matrix[6][7];
end;
Attention: ce genre de tableau multiplie très rapidement de nombre de variables en mémoire
!! Evitez d'être trop gourmand, surtout si vous succombez au charme de extended.
Pour plus d'informations sur les constantes, allez voir le lien suivant :
http://www.delphifr.com/code.aspx?ID=31036
X) Les tableaux à taille variable
Il faut utiliser la fonction SetLength.
Je ne fais aucun commentaire n'étant pas sujet à un tel problème. Allez voir le code spectral
de Kenavo sur les entrées multimédia pour une mise en jambe.
XI) Les classes
Ce paragraphe est la cerise sur le gâteau... sauf que c'est trop compliqué pour être appliqué
à de simples variables de calcul. Les classes sont la version moderne des enregistrements
record. Elles sont un ensemble de variables, de types, de constantes, de procédures, de
fonctions, de propriétés, d'interfaces, de constructeurs, de destructeurs... et vous concevez
facilement que ça sert pour développer des composants dans vos applications.
type
TMaClasse = class
private
public
protected
published
end;
Imaginons que j'ai construit une classe TSupport. Je voudrais faire une classe
TDeveloppement qui regroupe tous les éléments de TSupport mais en rajoutant certaines
caractéristiques. Il est inutile de faire du copier-coller, on préfère dériver les classes. Exemple
:
type
TSupport = class
end;
TDeveloppement = class(TSupport)
private
FVariableNonInitialisee : boolean;
published
property Propriete:boolean read FVariableNonInitialisee write
FVariableNonInitialisee;
end;
Vous voyez l'apparition de class(TSupport) dans l'expression de TDeveloppement.
Certes, TDeveloppement est définie à partir de TSupport, mais TDeveloppement ne peut plus
accéder aux rubriques private et protected de sa classe parente. En effet, on est passé
à une chose toute différente. C'est pour cela que les rubriques public et published
doivent offrir à l'utilisateur le strict nécessaire sans qu'il soit possible de mettre le bazar dans
la classe.
Si vous voyez ce qui suit, c'est pour indiquer à Delphi que la classe sera définie
ultérieurement. L'analyse descendante de Delphi couplée avec les références circulaires
rendent indispensables le code suivant :
type TSupport = class;
Si j'évoque cette dérivation, c'est pour parler de la transcription des types. On l'avait déjà
évoqué plus haut, et c'est maintenant qu'on va expliquer.
XII) Transcriptions des types
Il y a deux types de transcription : celle des types de base et celle des classes.
Pour les types de base, c'est la notion d'étendue qui est importante. Voyez cet exemple sur
les entiers :
var b : byte;
i : integer;
begin
i:=257;
b:=byte(i);
ShowMessage(IntToStr(b));
end;
Il nous affiche "1" via la transcription de type sur byte. Attention: byte(i) n'est pas une
fonction, c'est une transcription, c'est-à-dire qu'on associe d'une certaine manière deux
choses qui ne sont pas de même type. Voyez l'exemple suivant :
var b : byte;
i : integer;
begin
//d'abord...
b:=57;
i:=b;
ShowMessage(IntToStr(i));
//...puis après
i:=257;
b:=i;
ShowMessage(IntToStr(b));
end;
Les deux cas fonctionnent bien. Le premier ne pose pas de problème puisque l'étendue de
byte est incluse dans celle de integer. En revanche, dans le deuxième cas, du travail est
fait... En utilisant byte(i), ça permet de montrer à des collègues qu'il y a une possibilité de bug
si on n'a pas vu le coincement entre byte et integer.
Ca marche pareillement avec les nombres flottants (=à virgule).
Pour convertir des flottants en entiers ou en chaînes de caractères (et vice versa), il faut
utiliser les fonctions proposées par Delphi : int, round, trunc, abs, StrToFloat, FloatToStr,
IntToStr, StrToInt... De l'assembleur est souvent caché dans ces fonctions.
Pour ce qui est des transcriptions de classe, c'est sympathique mais dangeureux. On a vu
que par dérivation, il y a hiérarchisation des classes. On ne peut convertir des sous-classes
en leur classes parentes que si vous n'utilisez pas les caractéristiques ajoutées aux sousclasses. Pour expliciter cela, le code suivant est bon si on n'exploite pas la propriété
"Propriete" publiée par TDeveloppement. Il n'y aura alors pas de conflits possibles, même si
le risque existe.
var Support : TSupport;
Developpement : TDeveloppement;
begin
Support:=TSupport(Developpement);
end;
J'ajouterai que cette traduction par pseudo-fonction est à exclure. Si on se le permet avec
byte(...), c'est parce qu'il y a une notion d'étendue. Avec les classes, ce n'est vraiment pas
ça. Mieux vaut utiliser le mot clé as. On aurait alors :
var Support : TSupport;
Developpement : TDeveloppement;
begin
Support:=(Developpement as TSupport);
end;
Si je garde les parenthèses, c'est qu'avec les items radio-checkés des TPopupMenu, on est
souvent amené à coder des procédures du style :
procedure TForm1.Menu11Click(Sender: TObject);
begin
(Sender as TMenuItem).Checked:=true;
end;
Delphi dit que TMenuItem dérive de TObject (même lointainement). Ici, nous avons eu affaire
à une transcription descendante. En effet, on considère le parent comme un de ses enfant, et
non l'enfant comme son parent. Ainsi, sachant que dans notre coding, Sender est
nécessairement un TMenuItem, il n'y a aucun litige.
Copyright
© http://altert.family.free.fr/
Ce document est aussi hébergé sur :
http://www.codes-sources.com/