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*/
}
//---------------------------------------------------------------------------
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.
|