const char * const Destination |
Destination est un pointeur vers une chaîne de caractères (au sens C : NTCTS : Null Terminated Character Type Sequence) qui n'a aucune raison d'être modifiée par la fonction FileCopy(), puisque c'est le nom du fichier destination. Donc la chaîne est constante : premier const. Mais le pointeur n'a lui-même aucune raison dêtre modifié par la fonction : second const.
Le nombre d'octets (bytes) NbBytes de chaque paquet est déclaré de type size_t plutôt que int, ou même unsigned int, comme on pourrait s'y attendre. La première raison est que ce paramètre est ensuite passé aux fonctions système read() et write(), dont les paramètres sont de type size_t. La seconde raison est que int et unsigned int sont des types des langages C/C++ (pas portables d'une implémentation à l'autre), alors que le type size_t concerne le système d'exploitation.
Le type size_t est défini dans le fichier <sys/types.h> qui regroupe justement tous les types spécifiques du système Unix.
Ne pas oublier la ligne :
#include <sys/types.h> // size_t |
en indiquant bien la raison de cette inclusion (cette indication est indispensable pour toute inclusion de tout fichier généraliste. Ne pas confondre le fichier "système" sys/types.h avec le fichier C <type.h> qui concerne les types et macros concernant les traitements de caractères (standard ISO), et son équivalent C++ <ctype>.
Ne pas oublier que toutes les déclarations de variables, constantes, types, etc., directement incluses à partir de fichiers C (reconnaissables si le fichier inclus possède l'extension .h), sont globales, comme par exemple ::size_t.
Ne pas oublier les clauses d'inclusion conditionnelles :
#if ! defined __NSUTIL_01A_H__
... |
En C/C++, la dernière constante d'un type énumératif peut être suivie d'une virgule : c'est une tolérance facilitant l'écriture de générateurs de code C/C++.
Ainsi, les deux lignes ci-dessous sont équivalentes :
enum {CstNoError = 0, CstErrOpen = 1 };
enum {CstNoError = 0, CstErrOpen = 1, }; |
Le type size_t vu plus haut est un type non signé. La fonction read() par exemple renvoie le nombre d'octets effectivement lus (donc non signé), mais peut aussi renvoyer -1 en cas d'erreur. D'où le type de retour de la fonction : ssize_t, signifiant signed size_t.
Attention : les macros définies dans les fichiers .h (par exemple O_RDONLY) ne sont pas définies dans l'espace global. Il ne faut donc pas écrire ::O_RDONLY. En effet, elles sont directement remplacées par leur valeur par le pré-processeur avant la compilation et n'ont donc rien à voir avec le langage C++ et les espaces de noms.
const int fdSource = ::open (Source, O_RDONLY); |
Cela n'est pas toujours possible, comme le montre ce petit programme :
int fdSource;
for (; -1 == (fdSource = ::open (Source, O_RDONLY)); ) { cout << "fichier inexistant" << endl; cin >> Source; } |
Déclarer
const int fdSource; |
provoquerait une erreur de compilation car toute constante doit être initialisée à la déclaration. Une autre erreur de compilation apparaîtrait lors de la tentative d'affectation.
C++ propose une solution originale grâce à la fonction générique const_cast<T>(v) qui renvoie la référence de la variable v (si le type T est une référence), débarrassée temporairement du qualifieur const.
Ainsi, on peut écrire :
const int fdSource = n_importe_quoi;
for ( ; -1 == (const_cast <int &> (fdSource) = ::open
(Source, O_RDONLY)); )
|
Toute tentative ultérieure de modifier fdSource sans utiliser à nouveau const_cast provoquera une erreur de compilation.
Dans un fichier, il est commode de se créer des écritures abrégées, en particulier pour raccourcir des préfixes d'identificateurs.
Cela se fait habituellement au moyen de macros, destinées au pré-processeur.
De nouveau pour éviter d'éventuels conflits avec la même macro qui pourrait être redéfinie ailleurs (dans un autre fichiers inclus par exemple), il est nécessaire d'en supprimer la définition dès qu'elle n'est plus utile, comme par exemple :
#define SYST nsSysteme::CExcFctSyst
inline SYST::CExcFctSyst (
#undef SYST |
L'opérateur ternaire ?: est bien commode pour alléger une écriture qui serait inutilement lourde en utilisant un schéma alternatif.
Sa syntaxe est :
expr1 ? expr2 : expr3 |
où expr1 désigne une expression booléenne (vraie ou fausse) tandis que expr2 et expr3 doivent être de même type.
Cela exclut par exemple :
int Nombre = ...;
cout << ((Nombre >= 0) ? Nombre : "Nombre négatif") << endl; |
où expr2 est de type int et expr3 est de type char * (ou NTCTS).
En fait, aucune de ces écritures n'est correcte car errno est (ici) une macro (voir remarque précédente), qui ne doit donc pas du tout être préfixée.
N'oubliez pas que, lors d'une comparaison d'égalité entre une constante et une variable, il est toujours préférable d'écrire en premier la constante (car on a vite fait de confondre l'affectation = et la comparaison ==).
Par exemple :
if (-1 == NbLus) |
au lieu de :
if (NbLus == -1) |
qui pourrait être mal tapé en :
if (NbLus = -1) |
aux conséquences désastreuses !!!
Les wrappers Read() et Write() doivent lever une exception en cas d'échec des fonctions système correspondantes. Ils ne peuvent donc renvoyer normalement que des nombres positifs ou nuls. Le type de retour des deux wrappers doit donc être simplement size_t.
La fonction système close() renvoie (voir man 2 close) :
La plupart des constantes symboliques représentant une option sont codées sous forme d'un entier dont un seul bit est positionné à 1.
Ainsi, les constantes O_CREAT et O_EXCL sont définies dans le fichier /usr/include/bits/fcntl.h par :
#define O_CREAT
0100
#define O_EXCL 0200 |
Ce sont des constantes octales dont les représentations en binaire sont 1 000 000 et 10 000 000, ou, codées par exemple sur les 4 octets d'entiers int :
0000 0000 0000 0000 0000 0000 0100 0000
et
0000 0000 0000 0000 0000 0000 1000 0000
Si une variable, que nous appellerons oflag, contient les deux options, sa valeur en binaire est :
0000 0000 0000 0000 0000 0000 1100 0000
Pour savoir si l'option O_CREAT (le flag) est positionnée dans cette variable, il ne suffit donc pas de faire la comparaison :
if (O_CREAT == oflag) |
Il faut isoler le bit correspondant (oflag & O_CREAT)et vérifier s'il est positionné == O_CREAT :
if ((oflag & O_CREAT) == O_CREAT) |
Comme le résultat de la première opération est soit nul (le bit isolé n'est pas positionné) soit non nul (le bit isolé est positionné), le test peut être simplifié en :
if (oflag & O_CREAT) |
Pour savoir si l'option n'est pas positionnée, il suffit d'écrire :
if (!(oflag & O_CREAT)) |
Attention, l'opérateur ! étant plus prioritaire que l'opérateur &, il faut parenthéser.
La fonction FileCopy() est maintenant susceptible de lever deux types d'exceptions : CExcFct ou CExcFctSystFile. Cependant il est inutile de modifier la fonction main(). En effet, grâce au polymorphisme (fonction virtuelle _Edit() et passage de l'exception par référence), toute capture d'une exception de la classe CException permet de capturer une exception d'une classe dérivée : CExcFct ou CExcFctSystFile.
namespace nsUtil
{ void FileCopy (......) throw (CExcFct); } // nsUtil |
La constante CstExcArg doit être redéfinie dans le fichier exo_02.cxx (par exemple directement dans la fonction ppal()).
exo_03 destination source [1 | g | b | B ] |
Rappel : argv est un tableau de chaînes de caractères. Ainsi, argv[0] pointe sur la première chaîne (la commande elle-même), argv[1] pointe sur la deuxième chaîne (le nom du fichier destination), argv[2] pointe sur la troisième chaîne (le nom du fichier source). S'il n'est pas nul, argv[4] pointe sur la quatrième et dernière chaîne (l'option), qui est une suite de caractères terminée par le caractère nul '\0'.
Il est donc impossible de comparer directement la chaîne de caractères avec les caractères 'B', 'b', 'g'.
Une première idée serait de la comparer avec les chaînes de caractères correspondantes : "B", "b", "g", comme par exemple :
if ("B" == argv [3]) |
Malheureusement, cette écriture ne fait que comparer les pointeurs :
argv[3] pointe-t-il bien sur la constante chaîne de caractères "B"
(en fait "B" représente le pointeur vers la chaîne de contenu B'\0'), et non :
argv[3] pointe-t-il bien sur une chaîne dont le contenu est identique à celui de la constante chaîne de caractères "B"
Pour comparer deux chaînes de caractères au sens C (NTCTS), il faut uttiliser la fonction strcmp() (string compare) ce qui est trop lourd ici.
Une autre idée serait d'utiliser l'option dans un schéma de choix, comme par exemple :
switch (argv [3])
{ case "B" : // ... |
Malheureusement, il s'agit cette fois d'une erreur de syntaxe : l'expression qui suit l'instruction switch doit être de type scalaire entier (char, short, int, long, signés ou non). De plus, les constantes des instructions case doivent aussi être des constantes de type scalaire entier.
La solution ici consiste à extraire le premier caractère (de rang 0) de la chaîne de caractères de rang 3, représentant l'option, et à le comparer à un caractère :
if ('B' == argv [3][0]) |
ou
switch (argv [3][0])
{ case 'B' : // ... |
case Cas1 :
Traitement_1(); break; case Cas2 :
case Cas3 :
|
qui peut être simplifié en :
case Cas2 :
Traitement_2(); case Cas1 :
|
On remarque que :
Il n'est malheureusement pas possible d'indiquer un intervalle pour un case. Il faut donc soit énumérer explicitement toutes les valeurs, avec autant d'instructions case, soit utiliser un schéma alternatif.
Dans la fonction ppal(), remarquer la façon dont l'imbrication des deux schémas de choix a été réduite à un seul schéma de choix.
VARINT = suite_de_caractères_représentant_le_contenu_de_la_variable |
Il est possible de compléter localement le contenu de cette variable.
Ainsi, dans le fichiers Makefile_05, au lieu d'écrire :
#
COMPILER = g++ -c -Wall $*.cxx # # .............. # nsUtil_05.o : nsUtil_05.cxx $(NSSYSTEME_H) $(NSUTIL_H) $(CEXCEPTION_H) g++ -c -D$(macro_destroy) nsUtil_05.cxx -Wall # # .............. # |
est-il plus simple d'écrire :
#
COMPILER = g++ -c -Wall $*.cxx # # .............. # nsUtil_05.o : nsUtil_05.cxx $(NSSYSTEME_H) $(NSUTIL_H) $(CEXCEPTION_H) $(COMPILER) -D$(macro_destroy) # # .............. # |
On rappelle en effet que l'ordre des options n'a pas d'importance.
::sleep (atoi (argv [1])); |
Si au contraire, cette valeur est fréquemment utilisée, il faut faire la conversion une seule fois et en conserver le résultat dans une variable intermédiaire, par exemple, si argv[2] représente un intervalle de temps, en secondes, entre deux opérations répétitives :
const unsigned int CstDelai = atoi (argv [2]);
... for (...) { ... ::sleep (CstDelai); // et non ::sleep (atoi (argv [2])); } |
Remarquer le choix de l'identificateur, préfixé par Cst, car cette donnée n'a aucune raison d'être modifiée par le programme.
Enfin, si cette valeur peut être modifiée, la "variable" ne sera plus précédée du qualifiant const.
Par exemple, si argv[3] représente un nombre de répétitions d'une boucle, la conversion en entier ne sera effectuée qu'une seule fois, avant l'entrée dans la boucle :
for (unsigned int CptBoucle = atoi (argv [3]); CptBoucle--;
)
{ ... } |
L'écriture ci-dessous est particulièrement maladroite :
for (unsigned int CptBoucle = 0; CptBoucle < atoi
(argv [3]); ++CptBoucle)
{ ... } |
dans laquelle la conversion en entier risque d'être effectuée à chaque boucle (sauf avec un compilateur particulièrement performant).
open (argv[1], ...); |
En revanche, si la chaîne doit être "travaillée", on aura peut-être intérêt à la stocker sous forme d'un string, afin de bénéficier des opérations standard de cette classe :
string NomFich (Repertoire + '/' + argv [1] + '.'
+ Extension);
open ((NomFic.c_str(), ...); |
Il faut aussi rappeler que la fonction read() (et son wrapper Read()) renvoient le nombre de caractères lus.
Comme l'indexation commence à 0 en C/C++, ce nombre représente aussi la position du prochain caractère (à lire par exemple, ou libre).
C'est cette propriété qui est utilisée dans la séquence suivante :
char Tampon [6]; // pour ranger
<= 5 caractères "efficaces"
// indexés de 0 à 4 Tampon [Read (fd, Tampon, 5)] = '\0'; // place '\0' au premier rang libre
|
char * pChar = "coucou"; |
Elle signifie qu'il y a quelque part la suite de caractères "coucou" (terminée par le septième caractère '\0'), dont l'adresse est transférée dans le pointeur pChar.
De la même façon, dans l'instruction suivante :
Write (fd, "1234567", 7); |
l'adresse de la chaîne de caractères "1234567" est transmise au paramètre formel buffer de la fonction Write(), de type void * (qui accepte donc tout type d'adresse pour le pointeur effectif). Cela n'implique en rien que la totalité de la chaîne soit écrite dans le fichier. C'est au contraire le dernier paramètre qui précise combien de caractères doivent être transmis à partir de cette adresse.
A noter au passage que le caractère '\0' n'est, lui, pas écrit dans le fichier, ce qui est normal puisqu'un fichier texte n'est pas organisé en chaînes, mais en lignes.
© D. Mathieu
mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique
Créé le 07/07/2001 - Dernière mise à jour : 23/08/2001