Remarques de développement avec C++ Builder 5
Partie I - par Gilles
Louise
Avant-propos : vue d'ensemble de C++ Builder
5 1. À propos de la fonction "Exécuter|jusqu'au
curseur" 2. Points d'arrêt 3.
Point d'arrêt pendant l'exécution 4. Ouverture
d'un projet 5. Conversion AnsiString en Char*
6. Aide en ligne 7. Erreur
de compilation 8. Construire et Make
9. Appel d'une fonction à l'intérieur du code d'un événement
10. Arrêt d'exécution 11.
Extraction à l'intérieur d'un AnsiString d'un ou plusieurs caractères
12. La fonction IntToHex 13.
Longueur de vos buffers 14. Explorateur de
classes 15. Initialisations 16.
Déclaration des variables 17. Fenêtre d'exécution
18. Grand fichier cpp 19.
Gestionnaire de projet 20. Conseil personnel
21. Remarques et exemple autour de malloc (memory
allocation) 22. Autres exemples de malloc
23. Exemple de malloc pointant des structures
24. Malloc dans un cas de tableaux de pointeurs
de séries de structures 25. À propos de la
réallocation mémoire (realloc) 26. À propos
des unsigned 27. À propos du signe *
(étoile) 28. Polices de caractères du source
C++ 29. Recherche dans le source
30. Les pointeurs de pointeurs
Continuer : Partie II
Vue d'ensemble de
C++ Builder 5
Quand vous entrez dans C++Builder, l’écran se divise
en quatre parties :
1. le menu du progiciel en haut
2. l’inspecteur d’objets sur la gauche
3. une première fenêtre grise
4. le source de base correspondant (en dessous de la fenêtre, faites
F12 pour le voir)
Deux touches sont à connaître dès à présent :
1. F12 qui permute la fenêtre grise avec le source. Si la fenêtre
est visible, F12 fera apparaître le source et inversement.
2. F11 qui fait apparaître l’inspecteur d’objets.
La toute première chose à faire quand vous entrez dans C++Builder
est :
1. soit de sauvegarder le projet vide proposé dès l’entrée
2. soit d’ouvrir un projet existant (via "Réouvrir" du
menu principal)
La raison en est que C++Builder, par sécurité,
sauvegarde de lui-même le projet courant même complètement vide
au bout d’une minute ou deux (il prend la main de son propre chef
quelques secondes pour cette sauvegarde automatique), il choisit
le nom général de "unit1" pour le source et "projet1" pour l’application
en général. Si donc vous n’exécutez pas une des deux opérations
données ci-dessus, vous allez vous retrouver avec ce type de fichiers
(unit1 et projet1) dans le répertoire courant utilisé.
Notez que la fenêtre source C++ contient sur sa
gauche une autre fenêtre, l'explorateur de classes. Cet explorateur,
comme son nom l'indique répertorie les classes de votre application
ainsi que le contenu de ces classes, données et fonctions. Comme
il n'est pas toujours nécessaire de disposer de cette fenêtre, je
vous conseille de ne pas toujours l'afficher ce qui donne plus de
place pour le source en lui-même. On se débarrasse bien sûr de cette
fenêtre en cliquant sur le petit x de la fenêtre mais si vous n'en
voulez pas pour une longue durée, demandez à ce quelle ne soit plus
affichée à partir de maintenant en faisant "Outils|Options d'environnement",
là sélectionnez l'onglet "explorateur de classes" et décochez simplement
la case "Montrer l'explorateur", case que vous pouvez recocher à
tout moment. Vous disposez en plus de la fonction du menu "Voir|explorateur
de classes".
Pour bien naviguer durant votre développement de
l'inspecteur d'objets à une fiche (qui sera très souvent la fiche
principale Form1) et inversement, je vous conseille de ne pas faire
se chevaucher ces fenêtres en mettant l'inspecteur d'objets à gauche
et la fiche à droite. Vous passez alors agréablement de l'un à l'autre
d'autant que quand vous modifiez des éléments de la fiche, les transformations
dans l'inspecteur se font en temps réel et inversement allant de
l'inspecteur à la fiche.
Je vous conseille de vous créer un répertoire spécial
de test, lui-même sous-répertoire du répertoire officiel créé à
l’installation "projects", appelez-le par exemple "Essai".
Il vous permettra de tester certaines syntaxes ou composants. Ainsi,
pour tester un composant avec C++Builder, entrez dans le progiciel
(ou ce qui revient au même faites "Nouvelle application"),
sauvegardez tout de suite ce projet vide nouveau dans le répertoire
Essai (choisissez "Enregistrer le projet sous" du menu
principal). Je vous conseille de choisir non pas unit1 proposé par
défaut mais unit01 et projet01. Ainsi, à chaque nouveau test, vous
passez à l’indice suivant, unit02 et projet 02, unit03 et projet03
et ainsi de suite. La raison en est qu’avec deux chiffres, vos tests
seront toujours triés même au-delà de dix alors que si vous choisissez
un indice d’un seul chiffre, vous aurez projet10 au dessous de projet1
et non pas au dessous de projet9 alors que projet10 viendra logiquement
au dessous de projet09. Si vous devez créer une nouvelle fiche,
celle-ci sera nécessairement associée à une nouvelle unité (c'est
la règle avec C++Builder), utilisez alors les lettres dans le nom
de l’unité. Par exemple, vous en êtes à unit05 et projet05, imaginons
que ce projet05 doit avoir un seconde fiche. Vous faites donc logiquement
"nouvelle fiche" du menu principal, sauvegardez immédiatement
cette nouvelle unité (choisissez "enregistrez sous") et
choisissez comme nom unit05b, si vous devez en avoir une autre,
sauvegardez-la sous le nom de unit05c et ainsi de suite. Tous vos
tests personnels seront ainsi triés et structurés.
Je vous conseille aussi de tenir à jour, par exemple
avec Notepad, un petit fichier de syntaxes C++ pour les avoir toujours
à disposition car on ne peut pas se souvenir de tout. Quand, dans
un de vos tests ci-dessous décrits, une syntaxe C++ vous semble
importante et difficile à retenir, faites un copier-coller de l’unité
cpp (c plus plus) vers ce petit fichier Notepad doté éventuellement
d’un petit commentaire. Ces petits tests structurés et ce petit
bloc notes personnel vous permettront d’évaluer votre propre avancée
et de ne pas perdre votre travail.
Quant à l’application particulière que vous allez
développer, créez-la dans un répertoire nouveau et spécifique à
cette application, je vous conseille d'avoir un répertoire par application.
Ce répertoire sera logiquement un sous-répertoire de "projects"
dans lequel vous avez déjà créé "Essai" pour vos tests.
Comme toujours, vous allez sauvegarder tout de suite le projet vide
dès l’entrée dans C++Builder mais choisissez cette fois-ci un nom
signifiant pour votre projet. Pour l’unité (par défaut unit1), donnez
un nom qui représente une notion importante du projet, pour le nom
du projet, donnez le nom le plus global qui soit, ce sera aussi
in fine le nom de l’exécutable. Bien entendu, ces noms peuvent changer
au cours du développement mais une bonne habitude consiste à donner
des noms significatifs dès le début.
Notez que pour l’unité (par défaut unit1 proposé),
C++Builder vous crée et le cpp (le source C++) et le h (header).
Pour faire apparaître le header, faites ctrl F6 (contrôle F6) ou
alors, cliquez à droite pour avoir le menu surgissant et choisissez
la toute première possibilité "ouvrir le fichier source/en-tête".
Ceci est notamment utile si vous devez rajouter des méthodes à la
classe créée d’office par C++Builder.
Il est important de comprendre les fonctions les
plus courantes dans le menu de C++Builder. Par exemple, nous avons
dit que F12 permet de permuter la fiche avec le source, vous avez
la possibilité d'avoir la même fonction via une icône. En laissant
le curseur quelques instants sur une option du menu, une bulle d'aide
(hint en anglais) apparaît, essayez de trouver l'icône correspondant
à F12 (l'icône montre deux petits objets, l'un grisé l'autre blanc,
munis de deux petites flèches pour évoquer la permutation), la bulle
d'aide affichera "Basculer Fiche/Unité [F12]", il sera donc équivalent
de faire F12 ou de cliquer cette option du menu. Dans le menu "Voir"
vous avez également l'option "Basculer Fiche/Unité" qui fait la
même chose.
Notez que le menu de C++Builder se constitue de
toolbars (on reconnaît ce composant par ses deux petits traits verticaux
sur le côté gauche). Vous pouvez disposer ces toolbars comme vous
l'entendez, vous pouvez même les sortir de leur cadre (espace nommé
controlbar), ils deviennent alors de petites fenêtres que vous pouvez
aussi supprimer. Pour faire réapparaître une toolbar en cas de disparition,
faites "Voir|Barre d'outils" et sélectionner la barre qui vous manque.
Vous pouvez aussi cliquer à droite en pointant la zone toolbars
de C++Builder, la liste des toolbars s'active et vous pouvez en
faire réapparaître ou disparaître.
Pour tout enregistrer facilement, repérer l'icône
qui représente plusieurs disquettes en cascade, il suffit de cliquer
dessus, l'option se désactive alors. Dès qu'il y a la moindre modification
dans votre projet, elle se réactive.
Pour réouvrir facilement un projet, vous pouvez
faire "Fichier|Réouvrir" mais vous pouvez aussi repérer une icône
jaune qui représente une sorte de dossier qui s'ouvre, juste à côté
il y a une petite flèche, cliquez-la et choisissez votre projet.
C++Builder a mémorisé pour vous les projets les plus récents. Si
vous ouvrez toujours le dernier projet (ce qui est le cas quand
on travaille toujours sur un même projet), il vous suffit d'appuyer
sur la touche zéro, ce qui revient à sélectionner le premier élément
de la liste, donc le plus récent.
Si vous cliquez une icône du menu dans la zone
toolbar, vous obtenez alors la fenêtre de dialogue correspondant
à cette fonction avec toujours une possibilité d'aide qui vous explique
en français les diverses possibilités. Si vous passer par le menu
déroulant, il vous suffit de faire F1, vous aurez l'explication
de l'option qui était en surbrillance à ce moment-là. De même si
vous sélectionnez un composant de la palette, faites F1 pour avoir
de l'aide sur ce composant. De même quand le curseur se trouve sur
un mot clé de votre source, F1 vous donnera de l'aide sur cette
fonction. Il est impossible de travailler sans ces aides car il
est impossible de se souvenir de tout.
Quand vous utilisez l’aide sur un composant après
avoir sélectionné un composant de la palette et fait F1, vous tombez
sur une fenêtre avec notamment "propriétés" et "méthodes",
il s’agit évidemment des propriétés et des méthodes liées au composant
sélectionné, vous avez aussi "événements". Choisissez
par exemple "propriétés", une fenêtre apparaît mais l’autre
reste visible. Faites en sorte que ces deux fenêtres ne se chevauchent
pas, mettez par exemple la première fenêtre à droite et quand vous
cliquez sur "propriétés", la fenêtre qui s’affiche à gauche
sans chevauchement. Cette petite astuce va accélérer votre recherche
car quand vous allez cliquer maintenant une propriété, l’aide correspondante
va s’afficher sur la fenêtre de droite et vous passez facilement
de l’une à l’autre. En général, on cherche un exemple pour avoir
les syntaxes d’accès.
Pour commencer avec C++Builder, le mieux est un
bon tutoriel c'est-à-dire un exemple complet expliqué au pas à pas,
le fichier d’aide vous donne deux tutoriels. D'une manière générale,
la règle du jeu est très simple. En entrant dans C++Builder, une
fiche principale est déjà créée avec le code associé automatiquement
écrit par le progiciel. Sauvez en premier lieu ce projet dans votre
répertoire "Essai", pour ce faire cliquez l'icône qui représente
des "disquettes en cascade" et choisissez unit01 et projet01. À
ce stade, vous pouvez déjà cliquer la petite flèche verte pour exécuter
(ou faire F9), le code écrit par C++Builder lui-même se compile
puis s'exécute. Vous ne voyez qu'une fenêtre mais vous pouvez la
déplacer, elle "sait" se redessiner, vous pouvez modifier sa taille
et son menu système (i.e. les trois petits boutons en haut à droite
de la fenêtre) est opérationnel. En cliquant le petit x de ce menu
pour sortir, vous revenez à C++Builder.
Le principe de C++Builder consiste simplement à
ajouter des composants de la palette de composants et à écrire le
code relatif à certains événements. Par exemple, sélectionnez le
composant "Bouton" en cliquant dessus, il se trouve dans l'onglet
"Standard" de la palette de composants, c'est un petit rectangle
à l'intérieur duquel on lit "ok". Une fois sélectionné dans la palette,
cliquez n'importe où dans la fenêtre principale que C++Builder a
nommé Form1. Vous venez de déposer ce composant dans la fenêtre
principale et l'inspecteur d'objets confirme la présence de ce bouton.
C++Builder suppose que vous n'allez pas garder ce nom de "Button1",
c'est pourquoi dans l'inspecteur d'objets vous voyez la propriété
Caption (ce qui signifie "nom affiché à l'écran") activée,
mettez le curseur dans cette case et choisissez un autre nom par
exemple "TEST". Vous voyez qu'en temps réel, l'affichage est mis
à jour dans la fenêtre principale. Remarquez le nombre important
de propriétés disponibles par exemple Left et Top qui sont les coordonnées
de ce bouton dans la fiche. Déplacez ce bouton dans la fiche, vous
verrez que C++Builder a mis a jour ces deux propriétés. De même
Width et Height qui sont les dimensions du bouton. Si maintenant
nous faisons F9 pour exécuter, ça marche mais nous n'avons pour
l'instant associer aucune action au bouton.
Revenons sous C++Builder (cliquez le petit x du
menu système pour stopper l'exécution) et sélectionnez l'onglet
"événements" de l'inspecteur d'objets. Vérifiez bien que le bouton
est bien sélectionné car nous avons maintenant deux objets à savoir
Form1 et Button1. Si vous cliquez le bouton, l'inspecteur d'objets
sélectionne le bouton mais si vous cliquez la fiche, il sélectionne
logiquement Form1. D'ailleurs, en haut de l'inspecteur d'objets
vous disposez d'un petit menu déroulant pour choisir un objet. L'objet
Button1 étant visé par l'inspecteur d'objets, sélectionnez l'onglet
"événements", on vous montre tous les événements que vous pouvez
programmer. Double-cliquez sur l'événement OnClick, C++Builder vous
crée ce qu'on appelle le gestionnaire d'événement associé dans le
source, c'est simplement la méthode qui sera appelée quand le bouton
sera cliqué et il positionne le curseur entre les deux parenthèses
de la méthode car C++Builder attend que vous programmiez ce qui
se passera quand le bouton sera cliqué.
void __fastcall TForm1::Button1Click(TObject
*Sender)
{
}
Il s'agit bien d'une méthode de TForm1, laquelle
a été déclarée dans la classe TForm1, faites ctrl-F6 (contrôle F6)
pour faire afficher le source en-tête, vous lisez clairement void
__fastcall Button1Click(TObject *Sender); dans la section "published".
Affichons un message quand le bouton sera cliqué, à la position
du curseur on écrit par exemple :
Application->MessageBox("Bouton
cliqué!", "Ok", MB_OK);
La méthode Button1Click se présente donc sous cette forme :
void __fastcall TForm1::Button1Click(TObject
*Sender)
{
Application->MessageBox("Bouton cliqué!", "Ok", MB_OK);
}
Faites F9 pour compiler-exécuter, cliquez sur le bouton, vous voyez
bien le message apparaître.
La règle du jeu est donc toujours la même :
on dépose des composants dans la fiche, on initialise ses propriétés
dans l'inspecteur de d'objets, en général C++Builder offre de bonnes
initialisations par défaut mais vous pouvez les adapter, on programme
les événements associés à ces composants.
Notez que dès le départ, C++Builder a créé pour vous le constructeur
de la fiche,
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
c'est là que vous écrirez les initialisations générales
de l'application. Quant aux variables générales de l'application,
on les déclare juste en dessous de la déclaration du pointeur Form1.
Voici les deux endroits clés par rapport au source créé par C++Builder
au départ.
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit01.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
/* Toutes les variables et constantes
générales ici */
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
/* profitez de ce constructeur pour
initialiser votre application*/
}
//---------------------------------------------------------------------------
Cela dit, en POO pure (voir mon article sur les
classes
pour une définition de la POO), on évite autant que possible les
variables générales ou globales, on les déclare de préférence dans
la classe principale (située dans unit1.h) dans la zone réservée
à l'utilisateur. Dans ces conditions, le pointeur Form1 sera en
principe la seule variable générale d'un programme C++ Builder.
Voyez mes Réflexes
à ce sujet pour plus d'explications sur les variables globales et
sur les fonctions isolées à éviter par principe.
Un autre principe fondamental est la création logicielle
de composants c'est-à-dire par programme. On crée les composants
par new et on les supprime symétriquement par delete le moment venu.
Par exemple nous allons faire la même chose que précédemment mais
par programme. Repartons d'un projet vide, appelons-le unit02 et
projet02.
Dans les variables générales (juste après TForm1 *Form1;), déclarons
un pointeur sur un bouton.
TButton *B;
Dans le constructeur de TForm1, nous allons créer le bouton par
logiciel.
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
B=new TButton(this);
B->Parent=this;
B->Caption="TEST";
B->Height=20;
B->Width=60;
B->Left=15;
B->Top=15;
B->OnClick=Button1ClickX;
}
On crée le bouton par new, on indique son Parent
(ici "this" c'est-à-dire Form1, on peut d'ailleurs remplacer this
par Form1, ça marchera tout aussi bien), on donne son Caption (le
mot qui sera affiché dessus), ses dimensions et sa position à l'écran
et aussi la méthode qui sera exécutée en cas de click.
Maintenant pour construire cette méthode, on procède
comme suit. Comme on n'est pas censé connaître par avance la syntaxe
d'appel de la méthode OnClick pour un bouton, on pose un bouton
sur Form1 puis on double-clique sur l'événement OnClick dans l’inspecteur
d’objets, et ce, pour que C++Builder donne la syntaxe d'appel. Il
faut donc :
1. sélectionner ce bouton dans l’inspecteur d’objets
2. sélectionner l’onglet "événements"
3. repérer l’événement OnClick et double-cliquer sur la case de
droite en regard.
C++Builder crée pour vous la méthode dans le source :
void __fastcall TForm1::Button1Click(TObject
*Sender)
{
}
On copie-colle cette méthode à la fin du source
et on lui ajoute une lettre pour changer son nom, j'ajoute un ici
X à la fin donc on a :
void __fastcall TForm1::Button1ClickX(TObject
*Sender)
{
}
Maintenant on fait ctrl-F6 (contrôle F6) pour afficher l'en-tête,
dans la section published, on lit :
void __fastcall Button1Click(TObject *Sender);
C'est la déclaration de la méthode pour le bouton
qu'on a déposé sur la fiche. On copie-colle cette ligne en la mettant
dans la section "public" et on lui rajoute le X, donc on a copié :
void __fastcall Button1ClickX(TObject
*Sender);
Maintenant, on supprime le bouton de Form1 dont
on n'a plus besoin, il nous a seulement servi à créer les syntaxes
d'appel, on est sûr de ne pas se tromper puisque c'est C++Builder
lui-même qui a créé les syntaxes. Maintenant qu'on a déclaré notre
propre méthode, on programme comme précédemment la ligne, on obtient :
void __fastcall TForm1::Button1ClickX(TObject
*Sender)
{
Application->MessageBox("Bouton cliqué!", "ok",MB_OK);
}
C'est la même chose que précédemment, on affichera
un message quand le bouton sera cliqué. Faites F9 pour compiler-exécuter,
vous voyez le bouton "TEST" apparaître et le message s'affiche suite
à un click du bouton.
Comme le bouton a été créé par new, il est bon
de le détruire par delete. Sélectionnez Form1 dans l'inspecteur
d'objets et double-cliquez sur l'événement "OnDestroy", C++Builder
vous crée le gestionnaire suivant :
void __fastcall TForm1::FormDestroy(TObject
*Sender)
{
}
Cette méthode est exécutée au moment de la destruction
de la fenêtre principale (donc à la fin de l'exécution de l'application),
il suffit d'écrire à l’emplacement du curseur (suite à la création
d’un gestionnaire d’événement, C++Builder positionne automatiquement
le curseur entre les deux parenthèses car il attend que vous écriviez
le code de la méthode), écrivez simplement :
delete B;
pour détruire le bouton.
Pour que vous ayez une vision globale, voici le
programme complet. Une grande partie a été écrite automatiquement
par C++Builder (en rouge), une partie a été écrite par C++Builder
suite à une demande de création de gestionnaire d’événement (en
vert) et quelques instructions écrites par nous pour compléter (en
bleu).
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit07.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
TButton *B;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
B=new TButton(this);
B->Parent=this;
B->Caption="TEST";
B->Height=20;
B->Width=60;
B->Left=15;
B->Top=15;
B->OnClick=Button1ClickX;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1ClickX(TObject
*Sender)
{
Application->MessageBox("Bouton cliqué!",
"ok",MB_OK);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject
*Sender)
{
delete B;
}
Voici également l'en-tête :
//-----------------------------------------------------------------------
#ifndef Unit07H
#define Unit07H
//-----------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
//------------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // Composants gérés par l'EDI
void __fastcall FormDestroy(TObject *Sender);
private: // Déclarations utilisateur
public: // Déclarations utilisateur
__fastcall TForm1(TComponent* Owner);
void __fastcall Button1ClickX(TObject
*Sender);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif
C'est donc un petit peu plus compliqué par programme
mais guère plus. On crée le composant par new, on accède logiciellement
à ses propriétés, on déclare des méthodes associées aux événements
traités. Pour connaître les événements, utilisez l'aide en ligne
ou alors déposez le composant sur la fiche pour l'étudier grâce
à l'inspecteur d'objets, voyez les événements accessibles, créez-les
pour en avoir la syntaxe en double-cliquant dans la case en regard
de l’événement, copiez-collez la méthode vide en ajoutant par exemple
une lettre, faites de même dans l'en-tête et mettez la déclaration
résultante dans la section "public", puis virez le composant de
la fiche. Vous vous êtes ainsi créé une méthode personnalisée, en
bleu ci-dessus. Une autre méthode consiste à avoir dans un petit
fichier de type Notepad ces syntaxes d’événements (en plus de syntaxes
C++ particulières) que vous aurez collectées pendant vos tests dans
le répertoire Essai prévu à cet effet, ainsi il vous suffira de
faire un copier-coller du Notepad vers l’éditeur de C++Builder.
Notez également que vous pouvez ouvrir n’importe
quel autre fichier C++ et procéder ainsi à des copier-coller d’un
fichier à un autre. Ouvrir un fichier signifie simplement "ouvrir
un fichier" et non l’inclure au projet, ce pourquoi vous pouvez
toujours ouvrir n’importe quel fichier étranger au projet pour procéder
à des copier-coller. En revanche, si vous faites "Nouveau|fichier
cpp", là C++Builder crée un nouveau cpp et l’inclut au projet.
Quand dans un source cpp vous avez à faire des
modifications périlleuses dont vous n’êtes pas sûr, une bonne méthode
consiste à travailler sur une copie. Ce fichier cpp étant sélectionné
dans l’éditeur, faites alors "Fichier|Enregistrer sous"
et donnez-lui un autre nom, par exemple rajoutez "_copie"
à la fin de son nom, C++Builder a automatiquement exclu l'ancien
fichier du projet et l'a remplacé par ce nouveau qui n'est pour
l'instant qu'une copie de l'ancien. Si vos modifications ont fonctionné,
vous pouvez toujours continuer ainsi (ou alors vous pouvez faire
de nouveau "Fichier|Enregistrer sous" pour lui redonner
son nom d’origine) mais si vous voulez revenir en situation antérieure,
il suffit de supprimer du projet le fichier devenu fautif avec _copie
et d’ajouter au projet l’ancien fichier. Dans les icônes du menu
de C++Builder, vous devez apercevoir une icône avec un + et une
icône avec un -, ce sont ces fonctions qui vous permettront de réaliser
cette suppression et cette addition. Sinon, dans le menu "Projet",
vous avez clairement "ajouter au projet" et "retirer
du projet". Donc vous retirez du projet le fichier avec _copie
et vous ajoutez au projet l’ancien fichier correct, par ces deux
opérations vous êtes revenu en situation antérieure.
Quand vous déposez un composant sur une fiche,
vous ne vous occuper pas de son "instanciation" c’est-à-dire
de sa création en mémoire, C++Builder s’en occupe. En revanche,
quand vous instanciez un objet par new, vous le déclarez parent
de la fiche principale ou d’un autre objet de la fiche, et vous
le supprimez par delete au bon moment. De même pour toute allocation
mémoire par malloc, n’oubliez pas de libérer cette zone par free
au bon moment.
Notez qu'au moment de la compilation, C++Builder
se débarrasse des gestionnaires vides. Par exemple double-cliquez
sur un événement quelconque de Form1 pour créer la méthode vide
correspondant à cet événement puis faites tout de suite ctrl-F9
(contrôle F9) pour compiler seulement l'unité en cours, vous observez
que C++Builder a bien vu que la méthode était vide, donc il l'a
enlevée du source. Il en est de même quand vous sauvegardez le projet
(icône "disquettes en cascade"), les méthodes vides sont supprimées
du source cpp et h. La seule méthode que le progiciel garde toujours,
c'est le constructeur de la fiche (TForm1::TForm1) même si la méthode
ne contient rien. Le plus généralement, cette méthode contiendra
quelque chose puisque c’est là qu’on peut initialiser l’application.
On peut l’initialiser aussi à l’événement OnCreate de la fiche principale
mais puisque C++Builder nous offre ce constructeur, on s’en sert
logiquement pour l'initialisation de l'application.
Si vous êtes amené à créer une autre fiche à la
conception, vous aurez fatalement une autre unité .cpp et un autre
en-tête .h. Mais ces deux fichiers ne peuvent pas communiquer pour
l’instant. Si vous avez par exemple Form1 et Form2, vous ne pourrez
pas accéder à Form1 dans unit2.cpp ni à Form2 dans unit1.cpp. Par
exemple dans unit1.cpp vous ne pourrez pas écrire Form2->Visible=false
car Form2, et donc ses propriétés et autres, ne sont pas encore
accessibles à partir d’unit1. Ainsi imaginons que dans unit1.cpp
vous vouliez accéder à Form2, sélectionnez dans l’éditeur unit1.cpp
de manière à ce que ce soit le fichier actif (car l’éditeur n’a
qu’un fichier de code actif à la fois) puis faites "Fichier|inclure
l’en-tête d'unité", là C++Builder vous propose logiquement
d’inclure l’en-tête de Form2 (unit2.h), acceptez, à partir de ce
moment, Form2 devient accessible dans unit1.cpp. Réciproquement,
si unit2.cpp doit accéder à Form1, sélectionnez unit2.cpp dans l’éditeur
et incluez l’en-tête proposé qui est unit1.h, là Form1 sera accessible
dans unit2.cpp car C++Builder a rajouté l’include concerné dans
le source. Vous pouvez aussi faire cette opération à la main, ça
marchera de la même façon mais en règle générale, il est préférable
de passer par les fonctions du progiciel puisqu'il s'occupe de tout.
//-----------------------------------------------------------------------
#include "vcl.h"
#pragma hdrstop
#include "Unit09.h"
#include "Unit09b.h"
//----------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//----------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------
Dans l'exemple précédent, on suppose que
Form1 est accessible par unit09.h et Form2 par unit09b.h. Comme
il y a autant d'unités que de fiches et de cadres réunis (car un
nouveau cadre crée aussi une unité cpp et h), C++Builder connaît
l'ensemble des en-têtes possibles pour le projet en cours et vous
propose logiquement au moment de l'exécution de la fonction "Fichier|inclure
l’en-tête d'unité" d'inclure un des en-têtes manquants au source
cpp actif de l'éditeur. Ce principe est vrai pour un nombre plus
grand de fenêtres et cadres. En revanche, si vous créez une fenêtre
par logiciel via l’opérateur new, il n’y a pas de création d’unité
cpp ni h, vous n’avez alors pas d’en-tête à inclure pour accéder
à la fenêtre, vous y accédez directement au même titre qu’à tout
objet instancié par new.
Notons pour clore ce petit survol qu'un grand nombre
d'exemples d'applications programmées se trouvent dans le répertoire
Program Files\Borland\CBuilder5\Examples. Chaque répertoire
contient un ou plusieurs programmes d'un certain type, il suffit
de faire "Fichier|Ouvrir un projet", de choisir un projet
(repérable par le logo jaune C++) et de faire F9 pour l'exécuter.
Cela vous donnera une idée sur la façon dont sont conçues les applications.
-:-
1. À propos de la fonction
"Exécuter|jusqu'au curseur"
Votre curseur se trouvant sur une ligne quelconque
du source, cliquer Exécuter du menu général de C++ Builder et choisissez
l'option "jusqu'au curseur" du menu déroulant. Le programme
arrêtera son exécution à ce moment là mais il faut préciser que
le curseur doit se trouver sur une ligne du source où il y a effectivement
une instruction, par exemple juste avant cette instruction mais
ça marche aussi si le curseur se trouve au milieu de cette instruction,
l'arrêt se fera précisément à cette instruction avant son exécution.
Mais si le curseur se trouve sur une ligne blanche ou sur une ligne
où il n'y a qu'une accolade, l'arrêt ne se fait pas. La doc ne précise
pas ce point ce qui fait qu'on peut croire à un dysfonctionnement.
Le mieux est de regarder sur la gouttière à gauche de la fenêtre,
il y a un petit point bleu qui s'allume, cela signifie que cette
ligne est un point d'arrêt possible, il faut donc toujours positionner
le curseur sur un ligne doté d'un tel point bleu. Le mieux est d'utiliser
le raccourci F4. Vous positionnez le curseur avant ou sur une instruction
dans votre code, vous faites F4 pour exécuter le programme jusqu'à
cette position. Dès que l'instruction est rencontrée, le logiciel
vous affiche la fenêtre de code et l'instruction d'arrêt est surlignée.
Là vous pouvez interroger certaines variables via Exécuter|Inspecter
ou encore Exécuter|Évaluer/modifier (Ctrl F7). C'est évidemment
très pratique pour interroger des variables parce que vous n'avez
qu'à donner leur nom sans vous occuper du type (alors qu'un MessageBox
ou un ShowMessage dans le programme à titre d'affichage de debug
n'affiche qu'une variable du type Char* pour le premier et AnsiString
pour le second).
Notez au passage que pour visualiser des tableaux
entiers, il vaut mieux passer par "Exécuter|Inspecter"
qui donne plusieurs visualisations différentes pour chaque élément
du tableau (mode caractère, équivalent décimal, équivalent hexadécimal)
alors que "Exécuter|Évaluer/modifier" est plus adéquat
pour des variables à visualiser et éventuellement à modifier avant
de reprendre l'exécution du programme. En utilisant "Exécuter|Inspecter",
vous donnez d'abord le nom du tableau. Vous ne voyez certes que
les premiers éléments mais vous pouvez agrandir cette zone de visibilité.
Appuyez sur le bouton droit de la souris et choisissez "Étendue"
(ou faites Ctrl R, ce qui revient au même), là vous pouvez indiquez
le nombre d'éléments à voir.
Une autre méthode peut-être même plus rapide consiste
à pointer quelques instants le curseur dans le source sur le nom
d'une variable, le progiciel vous indiquera le contenu de la variable
dans une toute petite fenêtre. Vous avez aussi la possibilité de
passer par ce qu'on appelle des "points de suivi", voir alinéa 68
en troisième partie.
2. Points d'arrêt
Même principe pour les points d'arrêt, le curseur
doit se trouver sur une ligne où il y a effectivement une instruction
sinon l'arrêt ne se fait pas. Vous positionnez le curseur sur l'instruction
où se fera le point d'arrêt, vous faites F5, la ligne où se trouve
l'instruction est alors surlignée. On supprime ce point d'arrêt
en faisant F5 de nouveau sur cette même ligne. Vous pouvez aussi
cliquer sur la bande grise à gauche appelée gouttière qui sert de
marge à la fenêtre du source C juste en face d'une ligne de code,
un point d'arrêt sera de la même façon mémorisé à cet endroit, en
cliquant de nouveau à ce niveau, sur le point rouge de repère, le
point d'arrêt est supprimé. Ces points d'arrêt sont très pratiques,
ils vous permettent de contrôler le contenu de certaines variables,
ils se suppriment facilement (on pointe la ligne avec le curseur
et on fait F5 ou l'on clique sur le point rouge) et votre code n'a
pas été altéré par un ordre quelconque d'affichage des variables.
Notez que pour continuer l'exécution du programme à partir du point
d'arrêt il faut faire Exécuter|Exécuter (ce qui ne va pas de soi,
on aurait pu envisager un ordre dans le menu Exécuter du type "continuer
l'exécution", la doc ne parle pas de cette possibilité). Notez
que Exécuter|Exécuter revient à cliquer sur la petite flèche verte
en haut vers la gauche du menu ou encore à appuyer sur la touche
F9.
3. Point d'arrêt pendant l'exécution
Quand vous exécutez le programme pour le tester
en cliquant sur la petite flèche verte ou via F9, vous remarquerez
dans la ou les fenêtres dédiées au source que de petits points bleus
s'affichent sur la colonne grisée de gauche en face de chaque instruction.
En cliquant sur un de ces petits points, vous créez un point d'arrêt
à l'instruction correspondante et vous pouvez ainsi créer facilement
d'autres points d'arrêt en cliquant d'autres petits points bleus.
On les supprime en recliquant dessus. C'est très pratique car cela
se fait pendant l'exécution même du programme, ces points bleus
disparaissent avec leurs points d'arrêt associés quand le programme
a terminé son exécution (auquel cas on revient à C++ Builder pour
continuer le développement) ou si vous réinitialisez de vous même
le programme (Exécuter|Réinitialiser le programme) pendant l'exécution
ou au moment d'un arrêt. Ce sont donc des points d'arrêt temporaires
ou volatiles qui n'existent que le temps d'un test.
4. Ouverture d'un projet.
Si vous avez créé un raccourci de C++ Builder sur
le bureau et que vous y accédiez en cliquant dessus, le logiciel
vous affiche par défaut un projet vierge. Si vous voulez ouvrir
un projet en cours, le mieux est de passer par Fichier|Réouvrir
qui vous propose une liste de projets que vous avez ouverts dans
vos précédentes utilisations de C++ Builder. Les projets étant proposés
dans l'ordre, le premier qui apparaît est celui sur lequel on a
travaillé la dernière fois, on l'ouvre aussi en appuyant sur la
touche 0 (zéro). C'est assez pratique quand on travaille longtemps
sur un même projet, on l'ouvre ainsi de façon automatique sans se
poser de question. Sinon, autre possibilité, vous allez avec l'explorateur
Windows dans le répertoire où se trouve votre projet (on le reconnaît
par le fait qu'il est doté de la même icône que C++ Builder, il
s'agit du .bpr unique pour un projet donné) et vous cliquez dessus,
C++ Builder est alors chargé avec ce projet. Sinon passez par "Fichier|Ouvrir
un projet" mais dans ce cas C++Builder part toujours du répertoire
C:\Program Files\Borland\CBuilder5\Projects\ par défaut.
5. Conversion AnsiString en Char*
Dans un code classique en C++, on a tout intérêt
à utiliser des chaînes de caractères du type AnsiString, vous avez
ainsi accès à des syntaxes beaucoup plus simples qu'en C. Par exemple
on utilise simplement le signe = et on met une chaîne entre
quotes pour initialiser une variable, par exemple Var_AnsiStr="Bonjour"
au lieu de strcpy(VarChar, "Bonjour") du C classique.
Idem pour les concaténations qui se font simplement avec le signe +
pour des AnsiString au lieu d'un strcat en C. Cela dit, si vous
êtes amené à utiliser des fonctions de C pur, comme C ne connaît
pas du tout les chaînes de type AnsiString, vous devez alors convertir
un AnsiString en Char*. Imaginez par exemple qu'un OpenDialog vous
donne le nom d'un fichier à ouvrir et que ce nom soit dans une variable
AnsiString, vous ne pourrez pas ouvrir ce fichier par un fopen du
langage C car la chaîne d'un fopen est du type Char*, donc il faut
convertir l'AnsiString en Char*. Si donc NomFic est déclaré en AnsiString,
il faudra écrire NomFic.c_str() pour ouvrir le fichier via un fopen.
Vous appliquez ainsi la méthode c_str à la variable AnsiString la
convertissant de cette manière en Char*. Il faudra donc écrire
fopen(NomFic.c_str(), etc.
);
pour que ça marche. Pour plus d'information sur
cette question, mettez votre curseur juste avant la chaîne "c_str"
et appuyez sur F1, l'aide associée à cette question apparaît. (il
en va d'ailleurs de même pour tout autre mot clé). Idem si vous
voulez connaître les statistiques concernant ce fichier (accessibilité,
longueur etc.) on écrira une instruction du genre
stat(NonFic.c_str(), &statbuf)
;
à partir de laquelle vous avez accès à toutes les
caractéristiques du fichier Voir "stat" dans le fichier
d'aide et les exemples pour plus d'information. Idem pour les fonctions
C de traitement de chaîne de caractères, strcpy, strcat etc. N'oubliez
pas les parenthèses ouvrante et fermante quand vous ajouter c_str()
sinon vous avez l'erreur E2034 en compilation. Ne pas oublier que
VarAS.c_str() où VarAS est un AnsiString est considéré comme une
constante, vous ne pouvez donc l'utiliser qu'en lecture mais vous
ne pouvez pas y mettre une valeur. Ainsi par exemple un fputs du
C pur avec VarAS.c_str() sera correct car vous écrivez une chaîne
de caractères mais un fgets sera fautif car vous ne pouvez pas écrire
dans VarAS.c_str() qui encore une fois est une constante. Il faudra
donc pour lire un fichier via fgets utiliser un char* puis convertir
en AnsiString par une affectation classique, VarAS = VarC avec par
exemple char VarC[256].
6. Aide en ligne
Pour connaître la signification d'un paramètre
dans l'inspecteur d'objet, il suffit de cliquer sur ce paramètre
et de faire F1, l'aide concernant ce paramètre apparaît dans une
fenêtre. Idem pour se remémorer la signification d'une instruction
C ou C++ dans le source, on positionne le curseur juste avant le
mot clé et on fait F1. Idem pour comprendre une option du menu,
vous déployez le menu déroulant, vous mettez en reverse video l'option
et vous faites F1, l'aide en ligne de cette fonction apparaîtra.
7. Erreur de compilation
Pour avoir une explication de l'erreur de compilation,
appelez l'aide de C++ Builder en cliquant sur le petit livre du
menu en haut de l'écran ou via Aide|C++ Builder puis entrez dans
la zone de saisie Ennnn où nnnn est le numéro d'erreur annoncée
par le compilateur (la compilation est automatique avant une demande
d'exécution ou par la construction de l'exécutable via Projet|Construire_projet
où "projet" est le nom de votre propre projet qui apparaît
maintenant dans le menu déroulant) ou encore Projet|Make. La réponse
du compilateur se trouve dans une petite fenêtre au bas de l'écran
juste en dessous du code source. En entrant Ennnn (où nnnn est le
numéro de l'erreur e.g. E2034 pour l'erreur 2034), l'aide vous donnera
des explications sur cette erreur particulière. Ou encore (et c'est
peut-être mieux), vous double-cliquez sur la ligne signalant l'erreur
au bas de l'écran pour la sélectionner (elle se met donc en reverse
video) et vous faites comme d'habitude F1, une fenêtre apparaît
pour vous donner des explications sur cette erreur. Notez que si
le compilateur vous indique une ligne d'erreur où vous ne trouvez
de toute évidence rien d'anormal, c'est la ligne précédente qui
est souvent en cause notamment parce que votre instruction ne se
termine pas comme ils se doit par un point-virgule. Pour aller à
une ligne précise (par exemple celle annoncée par le compilateur
pour vérifier votre code), faites Alt G et entrez le numéro de la
ligne. Normalement, le progiciel surligne la première ligne de code
trouvée en erreur mais si vous voulez y retourner par la suite,
il suffit de double-cliquer dessus, c'est assez pratique.
8. Construire et Make
Dans le menu déroulant Projet, vous avez deux options
importantes, l'une est Construire et l'autre Make, elles sont presque
identiques. Construire recompile tout et crée l'exécutable. Make
(Ctrl F9) ne recompile que ce qui a été modifié depuis la dernière
fois et construit l'exécutable. Cette seconde option est donc préférable,
vous ne recompilez que les fichiers modifiés, ce qui est plus rapide
que de tout recompiler à chaque fois. Notez qu'il n'y a pas de touche
raccourci pour Construire alors qu'il y en a une pour Make (Construire
ne s'utilise qu'à titre de vérification, cette option vous permet
d'être sûr que tout le projet se compile normalement notamment quand
vous avez modifié des options de compilation ou des directives).
Quant à l'option Compiler l'unité, elle ne compile comme son nom
l'indique que la fenêtre active, elle vous permet de vérifier qu'il
n'y a pas d'erreur de syntaxe dans votre programme.
9. Appel d'une fonction à l'intérieur
du code d'un événement
Quand vous programmez le code correspondant à un
événement, par exemple
void __fastcall TForm1::Ouvrir1Click(TObject *Sender)
;
code qui s'exécute quand vous sélectionnez la fonction
"ouvrir" d'un menu déroulant, vous avez accès directement
aux objets de l'objet principal, en l'occurrence ici TForm1. Si
cette fiche contient par exemple un mémo que vous voulez rendre
visible à ce moment là, vous écrirez par exemple simplement
memo1->show();
dans le code de cet événement, le programme comprendra
qu'il s'agit de la fiche mémo1 de Form1. Mais si cet événement fait
appel à une fonction que vous écrivez, vous ne pourrez plus écrire
memo1->show();
le compilateur dira qu'il ne connaît pas mémo1.
Il faut alors dans ce cas préciser qu'il s'agit de la fiche memo1
de Form1, il faut donc écrire à l'intérieur de la fonction appelée
Form1-> memo1->show();
Et ça marchera.
10. Arrêt d'exécution
Si, suite à une erreur de programmation votre exécutable
ne vous rend pas la main durant son exécution, vous avez néanmoins
toujours accès au menu de C++ Builder qui reste actif malgré ce
problème, il suffit donc de faire Exécuter|Réinitialiser le programme
ou encore Ctrl F2 qui fait la même chose. Vous avez ainsi repris
la main sur votre développement.
11. Extraction à l'intérieur
d'un AnsiString d'un ou plusieurs caractères
Si vous voulez extraire un seul caractère d'un
AnsiString, il suffit d'écrire sa position entre crochets sachant
que le premier caractère à le numéro 1. Par exemple CarUnique =
Chaine[3] fera que la variable CarUnique contiendra le 3ème
caractère de Chaine, CarUnique et Chaine étant tous deux des AnsiString.
Mais si vous voulez extraire une suite de caractères, il faut utiliser
la méthode SubString(P,L) où P est la position de départ et L la
longueur à partir de cette position. Par exemple SousChaine=Chaine.Substring(3,2)
extraira 2 caractères à partir du troisième caractère de la chaîne
de caractères. Notons que SousChaine=Chaine[3]+Chaine[4] qui serait
censé faire la même chose ne marche pas, le compilateur ne signale
pas d'erreur mais le résultat est faux.
Quand vous saisissez le point dans une syntaxe
du type Chaine.SubString (le point annonce la méthode), il suffit
d'attendre une seconde ou deux, le progiciel vous propose d'office
toutes les méthodes possibles dans le contexte. Si cela ne marche
pas, appuyez sur le bouton droit de la souris dans une fenêtre de
code C et choisissez tout en bas du menu déroulant l'option propriétés,
là choisissez l'onglet "audit de code" et cochez la case
"achèvement du code", c'est ce flag qui indique qu'on
propose au développeur la liste des méthodes disponibles dans le
contexte, cliquez sur aide et vous connaîtrez le détail de ces propriétés.
12. La fonction IntToHex
Cette fonction permet de convertir un integer en
hexadécimal (base 16) sur un nombre de digits donné. On écrit par
exemple VarAS = IntToHex(i,2) signifie que la chaîne AnsiString
VarAS contiendra après exécution l'équivalent décimal du nombre
entier i sur deux digits, i pouvant aussi être du type char (un
char peut toujours être considéré comme un entier). Mais il faut
préciser que i est considéré comme étant codifié en complément à
2 (ce qu'oublie de préciser la doc) et donc peut être négatif (bit
le plus significatif au 1 logique). Si donc ce nombre entier est
négatif, IntToHex l'écrit d'office sur huit chiffres hexadécimaux
(i.e. quatre octets). Cela peut provoquer des erreurs. Imaginez
un buffer de caractères par exemple char buffer[100] déclarant ainsi
une chaîne de 100 caractères c'est-à-dire de 100 octets. Vous voulez
afficher en hexadécimal chacune de ces cases mémoire. Chacune à
un contenu allant en décimal de -128 à +127 c'est-à-dire en hexadécimal
allant de 0x00 à 0XFF. Les valeurs positives peuvent être extraites
normalement sur deux chiffres hexadécimaux. Mais, toutes les valeurs
négatives c'est-à-dire celles dont le bit le plus significatif est
au 1 logique seront transcrites sur 4 octets. Donc le mieux sera
de tout extraire sur 4 octets et ensuite d'utiliser un SubString
pour extraire de ce résultat les deux derniers caractères c'est-à-dire
2 caractères à partir de la 7ème position. On écrira
donc pour traiter la ième case du buffer HexaDec = IntToHex(Buffer[i],8).SubString
(7,2) où HexaDec est un AnsiString qui contiendra après exécution
les deux digits hexadécimaux. Vous voyez qu'on applique directement
la méthode SubString qui extraie 2 caractères à partir de la 7ème
position de l'AnsiString donné par IntToHex sur 8 chiffres. La logique
eût voulu qu'on puisse écrire dans ce cas HexaDec = IntToHex(Buffer[i],2)
mais malheureusement ça sera faux pour toutes les valeurs négatives
qui donneront non pas deux chiffres comme demandé mais huit. (Bug?).
13. Longueur de vos buffers
Attention à ne pas dépasser la longueur de vos
buffers. Si vous écrivez au-delà du buffer, vous risquez d'écraser
certaines données qui se trouvent par hasard juste après ce buffer
mais l'exécution du programme peut très bien continuer encore un
peu si ces variables ne sont pas vitales à ce moment-là de l'exécution.
Cela dit, tôt ou tard le programme va planter et c'est un des cas
où vous avez droit au message "l'application va s'arrêter car
elle a exécuté une opération non conforme". Notez que même
dans ce cas, C++ Builder garde toujours la main si vous testez sous
cet environnement, vous appuyez sur OK et vous retournez à votre
source que vous pouvez maintenant vérifier. Si votre programme fonctionnait
au moment du dernier test, mettez un point d'arrêt juste avant les
lignes rajoutées ou modifiées et faites du pas à pas (une fois arrêté,
faites Maj F7 pour exécuter jusqu'à la ligne suivante et continuez
ainsi le debug).
14. Explorateur de classes
À gauche des fenêtres source, vous avez un explorateur
de classes que vous pouvez déployer en cliquant sur le petit +.
Cela vous donne la structure de votre programme, en cliquant sur
une fonction, le curseur se positionne au début de celle-ci dans
le source. C'est donc très pratique pour s'y retrouver. Si vous
supprimez cet explorateur en cliquant le petit x sur le bord haut
de la fenêtre, ce qui vous donne une fenêtre plus grande au moment
d'écrire le source, vous pouvez toujours le récupérer via Voir|Explorateur
de classes. Pour que les fonctions soient accessibles à partir de
cette liste, il faut déclarer leur prototype en début de listing,
ce qui d'ailleurs est obligatoire si l'appel est antérieur dans
le listing à la fonction elle-même (sinon le compilateur ne connaît
pas le prototype de la fonction). Soit par exemple la fonction Essai
qui revoie un AnsiString et qui a pour argument un integer et un
AnsiString, vous déclarerez au début son prototype à savoir dans
ce cas AnsiString Essai(int, AnsiString); le compilateur connaît
alors le type du résultat et le type des arguments. La fonction
étant dûment déclarée, elle sera accessible à partir de l'explorateur
de classes (elle s'écrit d'ailleurs dans l'explorateur en même temps
que vous l'écrivez dans le source). Sinon, si vous ne déclarez pas
ainsi la fonction (ce qui est possible à condition que vous écriviez
la fonction dans le listing avant que vous l'utilisiez par un appel),
elle apparaîtra dans l'explorateur de classes mais en cliquant dessus,
il ne se passera rien. À vous donc de savoir si vous voulez pouvoir
accéder rapidement au code d'une fonction ou non. Pour plus d'informations
sur les classes, voyez mon
Étude autour des classes du C++.
15. Initialisations
Si votre programme doit procéder à des initialisations,
créez-les au moment de la construction de la fiche principale dont
la fonction (automatiquement créée par le logiciel) est facilement
reconnaissable par sa syntaxe
__fastcall TForm1::TForm1(TComponent* Owner): TForm(Owner);
Cette fonction apparaît d'ailleurs dans l'explorateur
de classes. Cliquez dessus et le progiciel positionnera le curseur
au début du code de cette fonction qui n'est simplement que déclarée
mais qui ne contient rien pour l'instant.
16. Déclaration des variables
Vous voulez savoir où une variable ou constante
a été déclarée, pointez le curseur sur son nom et appuyez sur Ctrl
(la touche contrôle). La variable devient une sorte de lien hypertexte,
en cliquant sur elle, le curseur pointe exactement la ligne du source
où elle a été déclarée. En faisant Ctrl Z (contrôle Z), vous revenez
où vous en étiez dans le source. Pratique non?
17. Fenêtre d'exécution
Si, après avoir cliqué dans le source un point
d'arrêt pour débuguer une partie de programme juste après avoir
lancé un test par F9 qui exécute l'application en cours de développement,
vous avez perdu la fenêtre de l'application en train de s'exécuter
(la fenêtre où se trouve le source étant maintenant active puisque
vous l'avez sollicitée pour y positionner un point d'arrêt), vous
la retrouver facilement en appuyant sur Alt Tab (touche Alt puis
Tab). Là, Windows vous propose d'exécuter une autre fenêtre dont
il vous indique le nom. Si ce n'est pas celle-là, appuyez de nouveau
sur la touche tabulation (Alt étant toujours appuyée) jusqu'à ce
que vous tombiez sur le nom de votre application. Cela vous évite
d'avoir à modifier l'emplacement des fenêtres qui cache le bas de
l'écran où toutes les programmes en cours sont affichés dans de
petits rectangles. Si toutefois ces petits rectangles sont visibles,
vous cliquez alors directement votre application qui est venue s'ajouter
au nombre des programmes en cours.
18. Grand fichier cpp
Pour une fiche donnée, le progiciel vous fournit
un premier fichier point cpp (C++). Si ce fichier devient trop grand,
le mieux est de diviser en deux ce grand fichier d'origine et donc
de créer un autre fichier cpp qui contiendra des routines que vous
ne toucherez plus ou pratiquement plus, fichier qu'il faut compiler
à part. La marche à suivre est la suivante.
- faire Fichier|Nouveau et choisissez comme objet un fichier
cpp parmi les choix possibles.
- par défaut, ce nouveau fichier s'appelle file1.cpp, le mieux
est de faire maintenant Fichier|Enregistrer sous et de lui donner
le nom que vous voulez. Comme vous allez essayer de regrouper
des routines ayant une signification globale pertinente, donnez
lui un nom en conséquence. Vous venez donc d'enregistrer un nouveau
fichier vide cpp.
- Observez que dans le cpp portant le nom du projet, C++ a automatiquement
rajouter le directive USEUNIT avec le nom de ce nouveau cpp source.
D'ailleurs si vous faites maintenant Voir|Gestionnaire de projets,
vous voyez que ce nouveau fichier fait maintenant partie du projet.
- Recopier toute l'en-tête du fichier cpp d'origine (il s'agit
du fichier crée par C++ Builder au moment de la création de la
fiche associée). Il faut recopier la première section contenant
tous les fichiers d'include (ou alors si vous voulez peaufiner,
ne sélectionnez que les includes dont vous avez besoin dans ce
nouveau fichier). De toute façon, il y aura des erreurs à la compilations
s'il en manque.
- Couper toutes les routines concernées par ce transfert et collez-les
dans ce nouveau fichier. Normalement, leur prototype a été déclaré
au début du fichier d'origine, donc si ce fichier d'origine fait
appel à ces routines qui sont maintenant dans l'autre fichier,
il n'y aura pas de problème de compilation.
- Faites maintenant Projet|compiler l'unité. Si vous appelez
des routines situées dans l'autre fichier, le compilateur vous
le signalera, il suffira alors de déclarer le prototype de ces
fonctions. Par exemple vous appelez la routine "essai"
de l'autre fichier (celui d'origine à scinder en deux) qui renvoie
un entier et qui a deux arguments, un integer long et un pointeur
de caractères, vous déclarerez ce prototype ainsi int essai(long int, char*);
- Ayant compilé cette unité à part sans erreur de compilation,
le progiciel vous a créé le fichier point obj correspondant dont
a besoin le lieur au moment de la construction de l'exécutable.
- Faites maintenant Projet|construire (c'est-à-dire créez l'exécutable
complet du projet), vous voyez que ça marche parfaitement. Vous
avez tout à y gagner car d'une part cela vous évite d'avoir à
manipuler des fichiers trop grands et d'autre part, la compilation
est plus rapide puisque le nouveau fichier est déjà compilé.
- Remarque. Les fonctions situées dans ce nouveau fichier ne
seront accessibles par l'explorateur de classes qu'après sa première
compilation. Cela signifie que si vous cliquez sur une des fonctions
visibles dans l'explorateur de classes et appartenant à ce nouveau
source C++ pour y positionner le curseur, cela ne fonctionnera
qu'après une compilation de ce fichier (si le fichier n'est pas
compilé, il ne se passe rien). Notez également que quand vous
chargez un développement, C++ Builder ne charge que le fichier
source de base i.e. celui qu'il a lui-même créé au moment de la
création du projet. Mais si vous cliquez dans l'explorateur de
classe sur une fonction qui est dans un autre fichier (celui que
vous avez créé pour diviser le source en deux), C++ Builder chargera
alors automatiquement ce fichier. C'est évidemment très pratique,
vous n'avez pas besoin de savoir dans quel fichier source se trouve
une fonction (si votre projet est complexe, il peut bien sûr y
en avoir plusieurs et ça deviendrait vite compliqué), vous l'appelez
via l'explorateur de classes qui se charge d'ouvrir le bon source
mais encore une fois, cela ne fonctionne qu'après une première
compilation de ce nouveau source. Il faut signaler que dans certains
cas, l'explorateur de classes ne trouve pas la fonction bien qu'il
soit dûment déclarée, il semble qu'il y ait un bug à ce niveau.
- Autre remarque. Pour tester certaines syntaxes C que vous maîtrisez
mal, vous pouvez toujours vous créer une sorte de brouillon.cpp
isolé que vous pouvez compiler à part juste pour vérifier une
syntaxe. De toute façon, il est bon d'avoir un répertoire de travail
qui contient une copie temporaire de votre développement pour
des tests spécifiques sans risque de toucher votre original. Une
fois votre algorithme éprouvé, vous allez dans votre répertoire
officiel que vous mettez à jour de ces nouveautés par un copier-coller
judicieux.
19. Gestionnaire de projet
Après avoir chargé votre projet (si vous travaillez
toujours sur le même, utilisez Fichier|Réouvrir et une fois le menu
déroulant actif, appuyez sur la touche 0), vous pouvez visualiser
tous les sources liés à ce projet en faisant Voir|Gestionnaire de
projet. C'est très pratique notamment quand vous avez différentes
variantes pour savoir où vous en êtes. En effet, quand vous utilisez
la fonction Enregistrer sous, c'est ce nouveau fichier qui devient
actif, l'ancien ne fait plus partie du projet mais le fichier existe
toujours. Si vous voulez revenir à l'ancienne version, il faut alors
utiliser les fonctions Projet|retirer du projet (là vous supprimez
le source dont vous ne voulez plus) puis Projet|ajouter au projet
(là vous remettez en service l'ancien source). Si vous ne savez
plus où vous en êtes suite à plusieurs de ces changements, utilisez
la fonction Voir|Gestionnaire de projet qui vous indiquera clairement
les fichiers impliqués à ce moment-là.
20. Conseil personnel
Pour aller vite, la meilleure façon est d'aller
très lentement, de penser profondément les choses, de les structurer,
de les maîtriser. Cioran disait "toute forme de hâte trahit
quelque dérangement mental". Festina lente, hâte-toi lentement
disaient les anciens. L'extrême lenteur est une sorte de vitesse
absolue, vous vous en rendrez compte en en tentant l'expérience
dans ce monde de suante précipitation. Ayez aussi cet adage en tête
: suus cuique diei sufficit labor (à chaque jour suffit sa peine)
c'est-à-dire n'essayez pas de tout faire en une seule fois. L'important
est simplement d'avancer dans votre projet, non pas avancer vite
mais simplement avancer. À ce sujet, il est très bon d'utiliser
la fonction "ajouter un élément A faire" via Maj Ctrl
T (vous appuyez simultanément sur Majuscule, Contrôle et la lettre
T) ou encore, en cliquant sur le bouton droit de la souris, un menu
déroulant apparaît où cette fonction est disponible. La principe
est simple. Vous positionnez votre curseur à un endroit du listing
où vous avez quelque chose à faire et à ne pas oublier. Vous appelez
cette fonction et vous écrivez un petit mémo en style télégraphique
rappelant brièvement ce qu'il y a à faire. Le texte ainsi saisi
est écrit en commentaire dans votre source mais de plus son endroit
dans le source est mémorisé par le progiciel. Ensuite, revenant
à votre projet le lendemain, vous cliquez Voir|Liste à faire, toutes
les occurrences de ce genre apparaissent, il suffit de cliquer sur
l'une d'entre elles pour y accéder, C++ Builder charge le source
cpp si ce n'est déjà fait et vous présente l'endroit du source où
il y a ce commentaire et où il faut rajouter quelque chose. Vous
pouvez aussi assigner un priorité, une catégorie (c'est une chaîne
de caractères qui contient un code à vous) etc. Donc avant de fermer
votre projet après une journée de travail, notez par un texte du
type "A faire" l'idée que vous aviez, c'est très utile.
Pour supprimer un élément de cette liste, vous avez le choix entre
le supprimer dans le listing ou de visionner cette liste, de sélectionner
l'occurrence à supprimer et d'appuyer sur la touche Suppr du clavier.
21. Remarques et exemple autour de malloc
(memory allocation)
Je fais une légère déviation vers le C pur car
les syntaxes de malloc sont assez difficiles et l'aide en ligne
ne donne qu'un tout petit exemple.
Quand vous devez déclarer un tableau dont vous
ignorez la longueur (la longueur va dépendre d'une donnée que vous
ignorez au moment du développement soit par exemple une saisie de
l'utilisateur au clavier soit suite au chargement d'un fichier etc.),
il est préférable d'utiliser la fonction C malloc (memory allocation).
Mais il faut se souvenir que malloc non seulement réserve l'emplacement
mémoire demandé et d'autre part initialise un pointeur (c'est toujours
un pointeur donc vous devez avoir le signe *) qui pointe le
premier élément de cette structure. Par exemple vous ouvrez le fichier
N (N étant un AnsiString qui contient le chemin d'accès et le nom
du fichier, MES également qui contiendra un message d'erreur le
cas échéant, Fichier est du type FILE*)
if ((Fichier = fopen(N.c_str(), "r+b"))== NULL)
{
MES="Impossible d'ouvrir le fichier "+N+".";
Application->MessageBox(MES.c_str(),NULL,MB_OK);
return;
}
Notez que le fichier a été
ouvert ici en mode r+b, cela signifie d'une part en lecture écriture
(r pour read et + pour possibilité d'ajout) et d'autre part en binaire,
cela signifie qu'on considère que c'est une suite d'octets. Voyez
la documentation pour plus d'information sur la manière d'ouvrir
les fichiers (vous mettez votre curseur devant fopen et vous faites
F1, c'est l'aide en ligne).
Ensuite vous interrogez les stats pour connaître
la longueur de ce fichier N, par exemple :
stat(N.c_str(), &statbuf);
LongFic = statbuf.st_size;
Cette syntaxe suppose d'une
part que vous ayez déclaré au début du source l'include sys\stat.h
ainsi : #include <sys\stat.h> et d'autre part que vous
ayez déclaré statbuf (nom de variable arbitraire que je choisis)
qui résulte de la structure officielle des statistiques de fichiers,
on le déclare ainsi : struct stat statbuf;
Dans ces conditions stat (N.c_str(), &statbuf)
signifie que vous demandez de mettre à jour dans la structure statbuf
les renseignements concernant le fichier N. Ces statistiques étant
maintenant en mémoire, on peut les lire notamment la longueur du
fichier (mais bien d'autres choses, voyez l'aide en ligne). On a
donc écrit LongFic = statbuf.st_size, LongFic est donc
maintenant égal à la longueur du fichier en nombre d'octets que
l'on a ouvert. Si maintenant on veut lire ce fichier en mémoire,
il faut réserver une place de LongFic octets que l'on ne peut pas
connaître à l'avance. Donc on utilise malloc qui va allouer LongFic
octets. Il est bon de tester LongFic qui ne doit pas être nul sinon
cela signifie que le fichier est vide :
if(!LongFic)
{
MES="Le fichier "+N+" est complètement vide, zéro octet.";
Application->MessageBox(MES.c_str(),NULL,MB_OK);
return;
}
Remarquez que if(!a) équivaut
à if(a==0), de même if(a) équivaut à if(a!=0). Un moyen mnémotechnique
consiste à se souvenir que "non nul" c'est "quelque
chose" et que "nul" c'est "rien". Ainsi
while(a) signifiera "tant que a vaut quelque chose" et
while(!a) "tant que a ne vaut pas quelque chose" et donc
"tant que a est nul".
On suppose avoir préalablement déclaré un pointeur
de caractères au début du programme (ou au début de la fonction)
par exemple char* PtrFic; PtrFic est donc un pointeur qui pointe
un élément ou une suite d'éléments du type char.
Il faut donc maintenant réserver une zone mémoire
de LongFic octets à partir de ce pointeur. On utilise malloc et
l'on écrit :
// allocation de cette mémoire à partir de
PtrFic
if ((PtrFic = (char *) malloc(LongFic)) == NULL)
{
MES="Le fichier "+N+" est trop grand. Je n'arrive pas
à "
"allouer la mémoire correspondante. "
"Sa longueur est de "+IntToStr(LongFic)+" octets. "
"Essayez de libérer un peu de mémoire et recommencez";
Application->MessageBox(MES.c_str(),NULL,MB_OK);
return;
}
Notez que si LongFic est
nul, cela revient à demander 0 octet via malloc qui renvoie logiquement
NULL. C'est pourquoi nous avons écarté ce cas précédemment, à ce
stade du programme on sait que LongFic est non nul.
Remarquez aussi la façon de concaténer les chaînes
de caractères. On écrit entre quotes une première chaîne puis une
deuxième à la ligne suivante et ainsi de suite. Cela évite d'avoir
des lignes trop grandes. Le compilateur comprend qu'il s'agit d'une
seule et même chaîne.
La fonction C malloc renvoie NULL si la demande
d'allocation mémoire a échoué. Cela n'arrive pratiquement jamais
avec nos ordinateurs d'aujourd'hui dotés de 64 voire 128 MO mais
c'est toujours possible si l'utilisateur a ouvert trop d'applications
en même temps. De toute façon il faut toujours tester un code retour
pour une bonne qualité de programme, c'est ce qu'on appelle la programmation
paranoïaque (defensive programmation), c'est le seul cas où il est
excellent d'être parano. Faites attention au nombre et à la place
des parenthèses. Si l'allocation a réussi, PtrFic pointe le premier
octet d'une zone mémoire de LongFic octets. Dans ces conditions
on peut lire le fichier en une seule fois ainsi :
if((NbData=fread(PtrFic,1,LongFic,Fichier))!=LongFic)
{
MES="Impossible de lire le fichier "+N+
" dans sa totalité bien que l'allocation "
"mémoire vient d'être acceptée, "
"le fichier a une longueur de "+IntToStr(LongFic)+
" mais je n'ai pu en lire que "+IntToStr(NbData)+".";
Application->MessageBox(MES.c_str(),NULL,MB_OK);
fclose(Fichier);
return;
}
où NbData est un unsigned
long int. Comme la fonction fread renvoie le nombre d'octets effectivement
lus, il suffit de comparer le nombre d'octets à lire (ici LongFic)
au nombre d'octets effectivement lus (ici NbData) pour savoir si
la lecture s'est bien passée. Voyez l'aide en ligne pour le détail
de fread. Vous accédez à chacun de ces octets en mettant entre crochets
son numéro d'ordre qui ira de 0 à LongFic - 1, par exemple
PtrFic[i] est le ième octet pointé par PtrFic, i étant un unsigned
long int (codé sur 4 octets donc sur 32 bits).
Pour libérer cette mémoire allouée, il faudra écrire
free(PtrFic);
22. Autres exemples de malloc
Il faut se souvenir que malloc renvoie toujours
un pointeur qui pointe le premier élément d'un série d'éléments
d'un certain type. Par exemple, vous voulez allouer à partir d'un
pointeur d'unsigned long int Longueur (nom arbitraire de ce pointeur)
une zone mémoire de NbULI unsigned long int, vous écrirez :
if ((Longueur = (unsigned long int*)
malloc((NbULI)*sizeof(long int))) == NULL) return;
Dans cet exemple si la mémoire
n'a pu être allouée, le pointeur est égal à NULL et il y aura ici
retour de la fonction sinon on continue. Notez que cette écriture
suppose que vous ayez déclaré au début (ou quelque part avant l'appel
à malloc) la variable Longueur comme pointeur sur un unsigned long
int ainsi : >
unsigned long int* Longueur;
Notez que malloc peut être utilisé au moment de
la déclaration d'une variable, par exemple :
char* P=(char*)malloc(1000);
Le compilateur C de C++ Builder
exige que l'on reprécise clairement au moment du malloc le type
char* même dans ce cas bien qu'il y ait redondance. La réservation
mémoire est tellement dérisoire que le code retour du malloc n'est
pas testé dans ce cas. Cette instruction réserve une zone de mille
octets à partir de l'adresse P et est équivalente à char P[1000]
si ce n'est que cette dernière notation ne permettra pas par la
suite la possibilité d'une réallocation via realloc.
23. Exemple de malloc pointant des structures
Supposons qu'on déclare la structure suivante :
struct StructureEssai
struct StructureEssai
{
unsigned char CAR;
int N1,N2;
} STE;
Cette structure contient
trois éléments, un caractère CAR et deux entiers N1 et N2. StructureEssai
est le nom de ce type de structure et STE une variable de ce type.
Vous n'êtes pas tenu de déclarer ces deux éléments. Si vous ne voulez
qu'une seule variable de ce genre, la structure n'a alors pas besoin
de nom et il vous suffit d'écrire :
struct
{
unsigned char CAR;
int N1,N2;
} STE;
Si vous n'avez pas besoin
de variable mais seulement de la déclaration de la structure, vous
écrirez :
struct StructureEssai
{
unsigned char CAR;
int N1,N2;
};
auquel cas la structure
n'existe qu'à titre de prototype.
Si vous déclarez la variable STE (nom arbitraire,
structure essai), vous accédez au caractère de cette structure STE
par la syntaxe STE.CAR et aux entiers via STE.N1 et STE.N2. Remarquez
le point qui sert à désigner un des éléments de la structure.
On veut maintenant déclarer via malloc une zone
mémoire d'une longueur de NBS fois cette structure (NBS étant un
nombre arbitraire non connu à l'avance, NBS est le nom arbitraire
de cette variable pour nombre de structures) avec un pointeur qui
de pointera la première structure de la série. Dans ce cas, la variable
STE ne vous sert à rien, vous n'avez besoin que du prototype de
la structure.
On déclare le pointeur ainsi :
StructureEssai*
PointeurStruc;
PointeurStruc est donc un pointeur
qui pointe un élément de type StructureEssai.
On réserve via malloc la mémoire ainsi :
if ((PointeurStruc = (StructureEssai*)
malloc((NBS)*sizeof(StructureEssai))) == NULL) return;
D'une part vous allouez
NBS fois une structure de longueur StructureEssai et d'autre part
PointeurStruc pointe le premier élément de cette structure.
Soit i un entier non signé (unsigned int ou unsigned
long int) compris entre 0 et NBS-1,
PointeurStruc[i].Car sera le caractère de la ième
structure,
PointeurStruc[i].N1 sera le premier int de la ième
structure,
PointeurStruc[i].N2 sera le deuxième int de la
ième structure,
avec toujours ce fameux point pour accéder à un
élément de la ième structure.
Notez que si vous déclarez la variable STE comme
c'est possible (revoir plus haut dans ce même alinéa les différentes
possibilités de déclaration), vous pouvez alors lire en une seule
fois la ième structure ainsi : STE = PointeurStruc[i];
et dans ce cas vous accédez au caractère de la structure STE qui
vient d'être lue par la syntaxe STE.CAR et aux entiers via STE.N1
et STE.N2 comme nous le disions déjà plus haut.
24. Malloc dans un cas de tableaux de pointeurs
de séries de structures
Si vous déclarez un tableau de pointeurs vers ces
structures par exemple StructureEssai* PointeurStruc[50]; qui
déclare un tableau de 50 pointeurs vers une structure de type StructureEssai,
vous déclarerez la kème allocation mémoire via malloc par :
if ((PointeurStruc[k] = (StructureEssai*)
malloc((NBS)*sizeof(StructureEssai))) == NULL) return;
k étant supposé compris
entre 0 et 49 puisque dans notre exemple nous déclarons 50 pointeurs
vers une structure (les autres variables ont la même signification
que précédemment).
Dans ce cas, pour accéder au caractère de la ième
structure de la kème série de structures il faudra écrire : PointeurStruc[k][i].CAR;
où il faut bien sûr remarquer les deux doubles crochets, le premier
invoque le kème pointeur, il pointe donc la kème série de structures
de ce type et le second la ième structure de cette série numéro
k, la bonne structure étant maintenant pointée, on complète par
le point et le nom de l'élément à invoquer ici CAR.
25. À propos de la réallocation mémoire
(realloc)
Quand une zone mémoire allouée par malloc s'avère
insuffisante dans la contexte, on réalloue cette mémoire via l'instruction
standard C realloc mais il ne faut pas oublier que realloc renvoie
la nouvelle position du pointeur qu'il faut assigner au pointeur
courant. Par exemple vous avez besoin d'une zone mémoire de bloc
octets, bloc étant une constante arbitraire du programme initialisée
ainsi : const int bloc = 1000; Vous avez
déclaré un pointeur P sur une chaîne de caractères ainsi char* P;
vous initialisez la longueur de la zone LZ à bloc (LZ = bloc;
LZ étant un unsigned int) et vous déclarez une zone de LZ octets
via malloc pointée par P :
P = (char*) malloc
(LZ);
À ce stade du programme P pointe
le premier octet d'une zone de LZ octets. Un offset o initialisé
à 0 naviguera dans cette mémoire et y écrira via une syntaxe du
type P[o] = aliquid; (le oème octet pointé par P prend la valeur
aliquid, comme c'est un octet, aliquid ira de 0 à 255 ou de -128
à +127 si vous êtes en signé). Mais quand o, suite à une incrémentation
sera égal à LZ, il sera hors zone (la zone ne va que de 0 à LZ-1),
il faudra donc réallouer la mémoire pour l'agrandir et y ajouter
par exemple un bloc à cette zone, on écrira donc :
LZ+=bloc;
P= (char*) realloc(P,LZ);
ou encore en une seule ligne :
P=(char*) realloc(P,LZ+=bloc);
Notez bien cette syntaxe du realloc
qui a deux arguments, pointeur et longueur et qui renvoie la nouvelle
position du pointeur après réallocation. La longueur de la zone
LZ toujours pointée par P est rallongée de bloc octets (première
instruction) et P pointe maintenant cette nouvelle zone mémoire
plus longue de bloc octets. Si P==NULL après ce realloc, la réallocation
a échoué, cela devrait ne jamais se produire mais s'il faut toujours
tester un code retour par sécurité (programmation paranoïaque).
Il se peut que P n'ait pas bougé de position s'il se trouve possible
à ce moment-là du point de vue du système de rallonger la zone sans
ce changement mais il se peut tout aussi bien que P ait complètement
changé de position dans la mémoire. Cela ne vous regarde en rien,
le programme continue comme si de rien n'était, toutes vos données
sont bien sûr conservées mais la nouvelle zone est maintenant plus
grande. On peut faire autant de realloc que l'on veut. Dans tous
les cas, ne pas oublier de libérer la mémoire après utilisation
via free(P);
La nouvelle réservation peut aussi être plus petite
que l'ancienne (utilisation probablement rare mais possible), dans
ce cas, seule la section commençante des données de l'ancienne zone
est disponible dans la nouvelle.
La fonction realloc est très utile quand on ne
sait pas à l'avance combien de mémoire sera nécessaire. On procède
alors par blocs. Un premier malloc crée une première réservation.
On surveille de près l'offset d'accès en lecture-écriture, dès que
cet offset est hors zone, on agrandit alors l'allocation via realloc
en testant le code retour (si le pointeur est égal à NULL, il y
a échec de l'allocation et donc probablement arrêt pur et simple
du programme qui ne peut pas continuer). Si vous y allez par incrémentation,
l'offset est hors zone dès qu'il est exactement égal à la longueur
de la zone, si o est cet offset et LZ la longueur réservée à un
moment donné, o ne pourra aller que de 0 à LZ-1, dès que o = LZ
il est en dehors de la zone, a fortiori si o > LZ, et c'est à
ce moment-là qu'une réallocation peut s'avérer nécessaire dans le
contexte.
26. À propos des unsigned
Quand vous ne voulez pas considérer le complément
à deux d'une valeur binaire, il faut faire la précision unsigned
au moment de sa déclaration. Par exemple un élément du type char
est un octet. Par défaut il sera considéré comme signé (si toutefois
vous considérez ce char comme un entier ce qui est toujours possible)
si vous ne faites aucune précision c'est-à-dire qu'on ira de -128
à +127. Si vous ne voulez pas de ce signe il faut le préciser par
le mot clé unsigned, par exemple :
unsigned char C;
C est donc un unsigned de type char (octet unique),
on ira donc de 0 à 255 puisque le signe est ignoré.
Il en va de même pour les int (sur 2 octets) et
les long int (sur 4 octets). Par exemple, dans un alinéa précédent,
vous écrivions PointeurStruc[i]Car pour accéder au caractère de
la ième structure, il est évident que i est un unsigned, il faut
donc le déclarer ainsi :
unsigned int i;
Ainsi on ira sur 16 bits de 0 à 65535 (sinon, si
cette précision n'avait pas été faite, le nombre aurait été considéré
comme codifié en complément à 2 et l'on irait de -32768 à +32767).
Si votre zone mémoire contient un nombre de structures inférieur
ou égal à 32767, ça marcherait quand même puisque dans ce cas le
nombre serait toujours positif même en considérant le complément
à deux mais à partir de 32768 et en l'absence de la précision unsigned,
le programme considérerait que i est négatif, vous pointeriez donc
n'importe où dans la mémoire. Le mieux est donc de toujours préciser
unsigned et même, pour les zones mémoire dont vous ignorez la longueur
mais qui peuvent être assez grande, d'utiliser un unsigned long
int, le pointeur d'éléments est alors codifié sur 4 octets donc
sur 32 bits, vous êtes ainsi sûr de pointer toujours correctement
la mémoire sans surprise par une possibilité de nombres négatifs.
27. À propos du signe * (étoile)
Ce signe s'utilise pour exprimer un pointeur qui
pointe un élément d'un certain type, par exemple
char * P;
Cette notation signifie que P est un pointeur sur
un élément de type caractère, on peut aussi considérer que P pointe
le premier caractère d'une chaîne de caractères ou encore le premier
octet d'une série d'octets, c'est le contexte du programme qui le
dira. De façon assez curieuse dans cette notation, l'étoile "appartient"
plus au pointeur P qu'au type char, c'est une convention arbitraire
même si cela peut paraître inattendu. Cela se voit quand vous avez
à déclarer deux pointeurs P1 et P2, il faut écrire char *P1, *P2;
où l'étoile a dû être répétée car la notation char *P1, P2;
déclarerait non pas deux pointeurs mais un pointeur P1 et un caractère
P2. Avec la première déclaration, vous pourriez écrire dans le cours
du programme P1 = P2 ce qui signifie que le pointeur P1
prend la position du pointeur P2 mais cette possibilité serait interdite
avec la deuxième déclaration car alors le compilateur vous dira
qu'il ne peut convertir un char* en char.
En revanche, quand vous déclarez le prototype d'une
fonction, le type char* se met à exister. Imaginons une fonction
ESSAI qui renvoie un entier et qui a pour argument un pointeur sur
octets ou sur caractères. Le prototype de cette fonction se déclarera
ainsi int ESSAI(char*); où l'étoile "appartient"
cette fois-ci au type char et non à une variable qui d'ailleurs
n'est pas mentionnée. L'étoile appartient donc à la variable au
moment de la déclaration d'un pointeur mais au type au moment de
la déclaration d'un prototype.
Notez également la quasi-équivalence qu'il y a
entre les notations * et [ ], étoile et crochets. L'exemple
précédent pourrait tout aussi bien être déclaré par int ESSAI(char[ ]);
il en est de même au moment de l'appel de la fonction, vous pouvez
aussi bien écrire nombre_entier = ESSAI(char* P) que nombre_entier = ESSAI(char
P[ ]); ces deux écritures sont équivalentes du point de vue
du compilateur et du fonctionnement du programme. En revanche le
compilateur refusera les crochets au moment de la déclaration de
la variable, la notation par étoile est alors nécessaire par exemple
char *P;
28. Polices de caractères du source C++
Si vous voulez changer de police de caractères
pour l'affichage de votre code, cliquez sur le bouton droit de la
souris dans un fenêtre de code C puis choisissez l'option propriétés
tout en bas du menu déroulant. Là, sélectionnez l'onglet affichage.
C'est dans ce menu que vous décidez de votre police et de sa taille
pour l'affichage du source. Il semble que la police proposée par
défaut (courrier New taille 9) soit excellente pour du code C, les
lignes sont ainsi assez longues, les italiques des commentaires
entre les signes /* et */ (ou après le signe // pour un
commentaire monoligne) sont très lisibles et les caractères gras
des mots clés bien prononcés mais sans excès.
29. Recherche dans le source
La recherche d'une chaîne de caractères dans le
source se fait via Ctrl F (contrôle F, Find). Après avoir saisi
le mot recherché et validé par OK, le curseur se positionne sur
la première occurrence trouvée et la fenêtre appelée par Ctrl F
disparaît. Pour trouver l'occurrence suivante, faites F3 (et F3
à nouveau pour la suivante etc.). Si vous recherchez un mot qui
se trouve à l'écran, positionnez le curseur dessus (ou juste avant)
puis faites Ctrl F, le mot visé est déjà écrit dans la zone de recherche
du formulaire, ce qui vous en épargne la saisie.
30. Les pointeurs de pointeurs
On utilise deux fois le signe * (étoile) pour
déclarer un pointeur de pointeurs. Imaginons que vous vouliez réserver
NZM zones mémoire à chaque fois de longueur variable parce leur
longueur dépend du contexte informatique (e.g. chargement de fichiers)
ou humain (e.g. entrée de données au clavier). NZM n'est pas connu
à l'avance, il faut donc, une fois ce nombre connu réserver une
zone qui contiendra NZM pointeurs. En premier lieu on déclare un
pointeur de pointeurs PP ainsi :
if((PP=(char**)malloc(NZM*sizeof(char*)))==NULL)
{
Application->MessageBox("Erreur d'allocation mémoire",NULL,MB_OK);
return;
}
if((PP[i]=(char*)malloc(bloc))==NULL)
Application->MessageBox("Erreur d'allocation mémoire",NULL,MB_OK)
;
À ce stade, vous disposez
que NZM pointeurs, PP[0], PP[1] etc. jusqu'à PP[NZM-1].
Pour chacun de ces pointeurs PP[i], vous allez
devoir réserver une zone mémoire de bloc octets (bloc étant soit
une constante définie par vous soit un nombre positif quelconque
même assez grand). La fonction test ESSAI ci-dessous procède à ces
déclarations et remplit arbitrairement la mémoire allouée puis fait
une réallocation. Ce type de remplissage n'a d'autre but que de
montrer que la zone mémoire est bien accessible.
void ESSAI(void)
{
const int NZM=500,bloc=200000;
char** PP;
int i,j;
// Petit message avant test
Application->MessageBox("AVANT test","TEST",MB_OK);
// réservation pour NZM pointeurs
if((PP=(char**)malloc(NZM*sizeof(char*)))==NULL)
{
Application->MessageBox("Erreur d'allocation mémoire",NULL,MB_OK);
return;
}
// Réservation pour les NZM pointeurs d'une zone de bloc octets
for(i=0;i!=NZM;i++)
if((PP[i]=(char*)malloc(bloc))==NULL)
{
Application->MessageBox("Erreur d'allocation mémoire",NULL,MB_OK)
;
return;
}
// remplissage arbitraire des NZM zones de bloc octets
for(i=0;i!=NZM;i++)for(j=0;j!=bloc;j++) PP[i][j]='a';
// Réallocation de toutes les zones du double de la première réservation
(bloc*2)
for(i=0;i!=NZM;i++)
if((PP[i]=(char*)realloc(PP[i],bloc*2))==NULL)
{
Application->MessageBox("Erreur d'allocation mémoire",NULL,MB_OK)
;
return;
}
// remplissage arbitraire des NZM zones maintenant toutes doublées
for(i=0;i!=NZM;i++)for(j=0;j!=bloc*2;j++) PP[i][j]='a';
// Libération des NZM zones mémoire
for(i=0;i!=NZM;i++) free(PP[i]);
// et de la zone des NZM pointeurs
free(PP);
/*Petit message une fois le test terminé qui prouve que tout est correct
sinon il y aurait un retour suite à une allocation fautive */
Application->MessageBox("Tout est correct","TEST",MB_OK);
}// Fin de cette petite fonction de TEST MÉMOIRE.
Si laugmentation dune zone
mémoire via la fonction realloc se fait à lintérieur dune
fonction, il faut alors déclarer le pointeur de la zone mémoire
en référence grâce à lopérateur & et ce, parce que
realloc peut modifier la position du pointeur. Il faut donc que
le programme appelant récupère cette nouvelle valeur en cas de changement.
C'est un des avantages du C++ à savoir l'utilisation de ce signe
qui, à lui seul, exprime la référence. En C pur, la référence était
plus difficile à exprimer. Si par exemple on voulait qu'un entier
i soit du type référence dans une fonction C, il fallait prendre
trois précautions :
- Déclarer que l'argument de la fonction est int* (pointeur sur
un entier)
- appeler la fonction en utilisant &i comme argument.
- Affecter (*i) dans la fonction elle-même.
C++ a apporté une grande simplification à cette
problématique puisque vous n'aurez qu'à déclarer non plus i mais
&i, l'opérateur & à lui seul exprime la référence.
Notez qu'il y a donc en C++ un dual relativement à la référence
puisque l'ancienne syntaxe est tout à fait possible (C++ intégrant
la totalité des notations C).
Imaginons un programme qui déclare une zone mémoire
de bloc octets, bloc étant une constante arbitraire, et qui va écrire
très longtemps dans cette zone mémoire et lallonger dès que
nécessaire. On initialise un entier LZ (longueur de la zone) à bloc
qui est bien la première longueur de cette zone. On écrit dans cette
zone mémoire réservée en la pointant par incrémentation dun
offset. Dès que cet offset est égal à LZ, il pointe le premier octet
hors zone, on décide alors daugmenter la zone de bloc octets
par la fonction booléenne ALLOC qui renvoie « true » si lallocation
a réussi, « false » en cas déchec et qui a deux arguments,
le pointeur envoyé par référence avec lopérateur &
et la nouvelle longueur de réallocation.
// prototype de la fonction ALLOC
bool ALLOC(char*&,long int);
void ESSAI(void)
{
const int bloc=1000;
char *Pointeur =(char*)malloc(bloc);
long int LZ=bloc;
int i=0,ok;
Application->MessageBox("AVANT test","TEST",MB_OK);
do
{
/* si i pointe le premier octet hors zone
on augmente cette zone de bloc octets*/
if(i==LZ)
{
// LZ augmente de bloc octets
if(!ALLOC(Pointeur,LZ+=bloc))
{
Application->MessageBox("Erreur d'allocation ","TEST",MB_OK);
// fin de la boucle si erreur
ok=0;
}
}
// i pointe bien dans la zone avant son incrémentation, remplissage
arbitraire de la case pointée
Pointeur[i++]='a';
/*fin de la boucle si i est arbitrairement grand
cette instruction signifie, si i nest pas égal à 500000 alors
ok=1 (non nul = vrai, true) sinon ok=0 (nul = faux,
false). */
ok=(i!=500000);
}
while(ok) ;
// Libération de la mémoire allouée
free(Pointeur);
Application->MessageBox("APRES test","TEST",MB_OK);
}
//--------------------------------------
bool ALLOC(char *&P,long int L)
{
return (P=(char*) realloc(P,L))!=NULL;
}
//---------------------------------------
Remarquez bien sûr la concision
de la syntaxe. P pointe la nouvelle adresse de la réallocation et
est en même temps comparé à !=NULL. Si cest vrai (true), le
pointeur ne pointe pas NULL après le realloc ce qui signifie que
lallocation a réussi, sinon, si P pointe NULL, lallocation
a échoué. On peut donc considérer que la fonction booléenne ALLOC
répond à la question : la réallocation mémoire a-t-elle réussi ?
true oui, false non.
Si cette précaution navait pas été prise
à savoir celle de déclarer le pointeur par référence avec la syntaxe
char *&P, la nouvelle position en cas de changement de
position mémoire du realloc ne serait pas transmise au programme
appelant, par conséquent ça ne pourrait jamais marcher car le pointeur
pointerait nimporte où après un changement de position de
cette zone. Ça ne marcherait qu'aussi longtemps que la réallocation
se situerait par hasard au même endroit. Faites-en lexpérience
en supprimant le signe & aussi bien dans le prototype que
dans la fonction. Durant lexécution, vous aurez très vite
le message « une erreur dapplication sest produite,
violation daccès etc. » et ce, parce que la zone mémoire allouée
a changé dadresse à un moment donné alors que Pointeur pointe
toujours lancienne adresse, laquelle nest plus daucune
utilité. L'instruction d'écriture en mémoire Pointeur[i++]='a' est
alors fautive car on pointe n'importe où.
Dans cet exemple, la variable Pointeur du programme
appelant est transmise à P de la fonction par référence cest-à-dire
par ladresse mémoire de ce pointeur. Comme ce contenu de la
mémoire est modifié par le realloc, ce nouveau contenu est comme
retransmis à la variable Pointeur du programme appelant.
Il est toujours très important de savoir si une
variable, argument dune fonction, doit être retournée ou non
à lappelant. Si non, on nécrit que le nom de la variable,
si oui, on préfixe le nom de cette variable par lopérateur &.
Dans le premier cas, la fonction travaille avec une copie de la
variable, son contenu à lissue de lexécution nest
pas retransmis à lappelant. Dans le second cas, on donne à
la fonction non pas la variable mais son adresse mémoire (cest
la signification de lopérateur &). Si donc la fonction
écrit dans cette mémoire, ce contenu sera logiquement répercuté
par la variable de lappelant qui pointe précisément cette
mémoire.
|