C++ CH5-CH6

Chapitre 5. Comment faire du code illisible ?

Il est facile, très facile, de faire des programmes illisibles en C ou en C++. A ce propos, deux choses peuvent être dites :
  1. Ça n'accroît pas la vitesse du programme. Si l'on veut aller plus vite, il faut revoir l'algorithme ou changer de compilateur (inutile de faire de l'assembleur : les bons compilateurs se débrouillent mieux que les être humains sur ce terrain. L'avantage de l'assembleur est que là, au moins, on est sûr d'avoir un programme illisible.).
  2. Ça augmente les chances d'avoir des bogues.
Cependant, si vous voulez vous amuser, voici quelques conseils :
5.1. De nouveaux opérateurs
Il existe des opérateurs merveilleux.
Le premier est l'opérateur ternaire (?:). C'est le seul opérateur qui attende 3 paramètres (à part l'opérateur fonctionnel () des fonctions, qui admet n paramètres).
Il permet de remplacer le if. Sa syntaxe est la suivante :
test ? expression1 : expression2
Dans la syntaxe donnée ci-dessus, test est évalué en premier. Son résultat doit être booléen ou entier. Si test est vrai (ou si sa valeur est non nulle), expression1 est calculée et sa valeur est renvoyée. Sinon, c'est la valeur de expression2 qui est renvoyée. Par exemple :
min=(i Ça reste encore lisible. C'est pour cela que l'opérateur virgule (,) a été inventé. Il remplace les blocs d'instructions. Sa syntaxe est :
expression1,expression2[,expression3[…]]
Les expressions sont évaluées de gauche à droite (expression1, expression2, etc…), puis le type et la valeur de la dernière expression sont utilisés pour renvoyer le résultat. Par exemple :
double r = 5;
int i = r*3,1; // À la fin de l'instruction, r vaut 5
// et i vaut 1. r*3 est calculé pour rien.
Enfin, il y a les opérateurs d'incrémentation et de décrémentation (++ et —). Ils s'appliquent comme des préfixes ou des suffixes sur les variables. Lorsqu'ils sont en préfixe, la variable est incrémentée ou décrémentée, puis sa valeur est renvoyée. S'ils sont en suffixe, la valeur de la variable est renvoyée, puis la variable est incrémentée ou décrémentée. Par exemple :
int i=2,j,k;
j=++i; // A la fin de cette instruction, i et j valent 3.
k=j++; // A la fin de cette ligne, k vaut 3 et j vaut 4.
5.2. Quelques conseils
Voici les conseils que je donne pour faire du code illisible :
• abusez des opérateurs vus précédemment ; • placez-les dans les structures de contrôles. Notamment, utilisez l'opérateur virgule pour faire des instructions composées dans les tests du while et dans tous les membres du for. Il est souvent possible de mettre le corps du for dans les parenthèses ; • si nécessaire, utiliser les expressions composées ({ et }) dans les structures de contrôles ; • placez toutes les déclarations de variables globales ensemble ; • ne commentez rien, ou mieux, donnez des commentaires sans rapport avec le code ; • choisissez des noms de variables aléatoires (pensez à une phrase, et prenez les premières ou les deuxièmes lettres des mots au hasard) ; • faites des fonctions à rallonge ; • ne soignez pas l'apparence de votre programme (pas d'indentations, plusieurs lignes ensembles) ; • regroupez-les toutes dans un même fichier, par ordre de non-appariement ; • rajoutez des parenthèses là où elles ne sont pas nécessaires ; • rajoutez des transtypages là où ils ne sont pas nécessaires. Exemple 5-1. Programme parfaitement illisible // Que fait ce programme ?
#include
int main(void)
{
int zkmlpf,geikgh,wdxaj;
scanf("%u",&zkmlpf);for (wdxaj=0,
geikgh=0;
((wdxaj+=++geikgh),geikgh) printf("%u",wdxaj); return 0;
}
Vous l'aurez compris : il est plus simple de dire ici ce qu'il ne faut pas faire que de dire comment il faut faire. Je ne prétend pas imposer à quiconque une méthodologie quelconque, car chacun est libre de programmer comme il l'entend. En effet, certaines conventions de codages sont aussi absurdes qu'inutiles et elles ont l'inconvénient de ne plaire qu'à celui qui les a écrites (et encore…). C'est pour cette raison que je me suis contenté de lister les sources potentielles d'illisibilité des programmes. Sachez donc simplement que si vous utilisez une des techniques données dans ce paragraphe, vous devriez vous assurer que c'est réellement justifié et revoir votre code. Pour obtenir des programmes lisibles, il faut simplement que chacun y mettre un peu du sien, c'est aussi une marque de politesse envers les autres programmeurs.
Chapitre 6. Le préprocesseur C

6.1. Définition
Le préprocesseur est un programme qui analyse un fichier texte et qui lui fait subir certaines transformations. Ces transformations peuvent être l'inclusion d'un fichier, la suppression d'une zone de texte ou le remplacement d'une zone de texte.
Le préprocesseur effectue ces opérations en suivant des ordres qu'il lit dans le fichier en cours d'analyse.
Il est appelé automatiquement par le compilateur, avant la compilation, pour traiter les fichiers à compiler.
6.2. Les commandes du préprocesseur
Toute commande du préprocesseur commence :
  • en début de ligne ;
  • par un signe dièse (#)

Les commandes sont les suivantes :
6.2.1. Inclusion de fichier
L'inclusion de fichier permet de factoriser du texte commun à plusieurs autres fichiers (par exemple des déclarations de types, de constantes, de fonctions, etc…). Le texte commun est mis en général dans un fichier portant l'extension .h (pour « header », fichier d'en-tête de programme).
Syntaxe :
#include "fichier"
ou :
#include
fichier est le nom du fichier à inclure. Lorsque son nom est entre guillemets, le fichier spécifié est recherché dans le répertoire courant (normalement le répertoire du programme). S'il est encadré de crochets, il est recherché d'abord dans le répertoire courant, puis dans les répertoires du chemin de recherche s'il n'est pas trouvé (ces règles ne sont pas fixes, elles ne sont pas normalisées).
Le fichier inclus est traité lui aussi par le préprocesseur.
La signification de la ligne #include au début de tous les programmes utilisant les fonctions scanf et printf devient alors claire. Si vous ouvrez le fichier stdio.h, vous y verrez la déclaration de toutes les fonctions et de tous les types de la librairie d'entrée - sortie standard. De même, les fonctions malloc et free sont déclarées dans le fichier d'en-tête stdlib.h et définies dans la librairie standard. L'inclusion de ces fichiers permet donc de déclarer ces fonctions afin de les utiliser.
6.2.2. Remplacement de texte
Syntaxe :
#define texte remplacement
texte est le texte à remplacer. Le texte de remplacement est remplacement. Il est facultatif (dans ce cas, c'est le texte vide). À chaque fois que texte sera rencontré, il sera remplacé par remplacement.
Cette commande servira à définir les constantes.
Exemple 6-1. Définition de constantes
#define VRAI 1
#define FAUX 0
VRAI sera remplacé systématiquement par 1 et FAUX par 0 dans la suite du texte.
Le préprocesseur définit un certain nombre de chaînes de remplacement automatiquement. Ce sont les suivantes :
  • __LINE__ : donne le numéro de la ligne courante ;
  • __FILE__ : donne le nom du fichier courant ;
  • __DATE__ : renvoie la date du traitement du fichier par le préprocesseur ;
  • __TIME__ : renvoie l'heure du trautement du fichier par le préprocesseur ;
  • __cplusplus : définie uniquement dans le cas d'une compilation C++. Sa valeur doit être 199711L pour les compilateurs compatibles avec le projet de norme du 2 décembre 1996. En pratique, sa valeur est dépendante de l'implémentation utilisée, mais on pourra utiliser cette chaîne de remplacement pour distinguer les parties de code écrites en C++ de celles écrites en C.

Note: Si __FILE__, __DATE__, __TIME__ et __cplusplus sont bien des constantes pour un fichier donné, ce n'est pas le cas de __LINE__. En effet, cette dernière « constante » change bien évidemment de valeur à chaque ligne. On peut considérer qu'elle est redéfinie automatiquement par le préprocesseur à chaque début de ligne.
On fera par ailleurs une distinction bien nette entre les constantes littérales définies avec la directive #define du préprocesseur et les constantes définies avec le mot-clé const. En effet, les constantes littérales ne réservent pas de mémoire. Ce sont des valeurs immédiates, définies par le compilateur. En revanche, les variables de classe de stockage const ont malgré tout une place mémoire réservée. Il est possible, par un transtypage de pointeur, de modifier leur valeur (opération très déconseillée et de surcroît certainement inutile), et elles peuvent être modifiées par l'environnement si elles sont de type volatile. Ce sont donc des variables accessibles en lecture seule plutôt que des constantes. On ne pourra jamais supposer qu'une variable ne change pas de valeur sous prétexte qu'elle a la classe de stockage const, alors qu'évidemment, une vraie constante déclarée avec la directive #define du préprocesseur conservera toujours sa valeur (pourvu qu'on ne la redéfinisse pas).
6.2.3. Définition d'un identificateur
Il est possible de déclarer un identificateur. Pour cela, on utilise aussi #define :
#define identificateur
Pour détruire l'identificateur, il faut utiliser #undef :
#undef identificateur
Lorsqu'un identificateur est défini, il est possible de faire des tests dessus (voir section suivante).
6.2.4. Suppression de texte
Syntaxe :
#ifdef identificateur

#endif
Le texte compris entre le #ifdef (c'est à dire « if defined ») et le #endif est laissé tel quel si l'identificateur identificateur est connu du préprocesseur. Sinon, il est supprimé. L'identificateur peut être déclaré en utilisant simplement la commande #define vue ci-dessus.
Il existe d'autres commandes conditionnelles :
#ifndef (if not defined …)
#elif (sinon, si … )
#if (si … )
qui permettent de faire des fichiers dont certaines parties ne seront pas compilées. C'est ce qu'on appelle la compilation conditionnelle. La directive #if attend en paramètre une expression constante. Le texte qui la suit est inclus dans le fichier si et seulement si cette expression est non nulle. Par exemple :
#if (__cplusplus==199711L)

#endif
permet d'inclure un morceau de code C++ strictement à la norme décrite dans le projet de norme du 2 décembre 1996.
Une autre application courante des directives de compilation est la suivante :
#ifndef DejaLa
#define DejaLa
Texte à n'inclure qu'une seule fois au plus.
#endif
qui permet d'éviter que le texte ne soit inclus plusieurs fois, à la suite de plusieurs appels de #include. En effet, au premier appel, DejaLa n'est pas connu du préprocesseur. Il est donc déclaré et le texte est inclus. Lors de tout autre appel ultérieur, DejaLa existe, et le texte est n'est pas inclus. Ce genre d'écriture se rencontre dans les fichiers d'en-tête, pour lesquels en général on ne veut pas qu'une inclusion multiple ait lieu.
6.2.5. Autres commandes
Le préprocesseur est capable d'effectuer d'autres actions que l'inclusion et la suppression de texte. Les directives qui permettent d'effectuer ces actions sont indiquées ci-dessous :
  • # : ne fait rien (directive nulle) ;
  • #error message : permet de stopper la compilation en affichant le message d'erreur donné en paramètre ;
  • #line numéro [fichier] : permet de changer le numéro de ligne courant et le nom du fichier courant lors de la compilation ;
  • #pragma texte : permet de donner des ordres dépendant de l'implémentation au compilateur. Toute implémentation qui ne reconnaît pas un ordre donné dans une directive #pragma doit l'ignorer pour éviter des messages d'erreurs. par un signe dièse (#)

6.3. Les macros
Le préprocesseur peut, lors du mécanisme de remplacement de texte, prendre des paramètres. Ces paramètres sont placés sans modifications dans le texte de remplacement. Le texte de remplacement est alors appelé macro.
Syntaxe :
#define macro(identificateur[,identificateur[…]]) définition
Exemple 6-2. Macros max et min
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
Pour poursuivre une définition sur la ligne suivante, terminez la ligne courante par le signe \.
Le mécanisme des macros permet de faire l'équivalent des fonctions générales, comme max et min, qui fonctionnent pour tous les types ordonnés. Ainsi, la macro max renvoie le maximum de ses deux paramètres, qu'ils soient entiers, longs ou réels.
On mettra toujours des parenthèses autour des paramètres de la macro. En effet, ces paramètres peuvent être des expressions composées, qui doivent être calculées complètement avant d'être utilisée dans la macro. Les parenthèses forcent ce calcul. Si on ne les met pas, les règles de priorités peuvent générer une erreur de logique dans la macro elle-même. De même, on entourera de parenthèses les macros renvoyant une valeur, afin de forcer leur évaluation complète avant toute utilisation dans une autre expression. Par exemple :
#define mul(x,y) x*y
est une macro fausse. La ligne :
mul(2+3,5+9)
sera remplacée par :
2+3*5+9
ce qui vaut 26, et non pas 70 comme on l'aurait attendu. La bonne macro est :
#define mul(x,y) ((x)*(y))
car elle donne le texte suivant :
((2+3)*(5+9))
et le résultat est correct. De même, la macro :
#define add(x,y) (x)+(y)
est fausse, car l'expression suivante :
add(2,3)*5
est remplacée textuellement par :
(2)+(3)*5
dont le résultat est 17 et non 25 comme on l'aurait espéré. Cette macro doit donc se déclarer comme suit :
#define add(x,y) ((x)+(y))
Ainsi, les parenthèses assurent un comportement cohérent de la macro. Comme on le voit, les parenthèses peuvent alourdir les définitions des macros, mais elles sont absolument nécessaires.
Le résultat du remplacement d'une macro par sa définition est, lui aussi, soumis au préprocesseur. Par conséquent, une macro peut utiliser une autre macro ou une constante définie avec #define. Cependant, ce mécanisme est limité aux macros qui n'ont pas encore été remplacées afin d'éviter une récursion infinie du préprocesseur. Par exemple :
#define toto(x) toto((x)+1)
définit la macro toto. Si plus loin on utilise « toto(3) », le texte de remplacement final sera « toto((3)+1) » et non pas l'expression infinie « (…(((3)+1)+1…)+1 ». Le préprocesseur définit automatiquement la macro defined, qui permet de tester si un identificateur est connu du préprocesseur. Sa syntaxe est la suivante :
defined(identificateur)
La valeur de cette macro est 1 si l'identificateur existe, 0 sinon. Elle est utilisée principalement avec la directive #if. Il est donc équivalent d'écrire :
#if defined(identificateur)

#endif
à la place de :
#ifdef identificateur

#endif
Cependant, defined permet l'écriture d'expressions plus complexes que la directive #if.
6.4. Manipulation de chaînes de caractères dans les macros Le préprocesseur permet d'effectuer des opérations sur les chaînes de caractères. Tout argument de macro peut être transformé en chaîne de caractères dans la définition de la macro s'il est précédé du signe #. Par exemple, la macro suivante :
#define string(s) #s
transforme son argument en chaîne de caractères. Par exemple :
string(2+3)
devient :
"2+3"
Lors de la transformation de l'argument, toute occurrence des caractères " et \ est transformée respectivement en \" et \\ pour conserver ces caractères dans la chaîne de caractères de remplacement.
Le préprocesseur permet également la concaténation de texte grâce à l'opérateur ##. Les arguments de la macro qui sont séparés par cet opérateur sont concaténés (sans être transformés en chaînes de caractères cependant). Par exemple, la macro suivante :
#define nombre(chiffre1,chiffre2) chiffre1##chiffre2
permet de construire un nombre à deux chiffres :
nombre(2,3)
est remplacé par le nombre décimal 23. Le résultat de la concaténation est ensuite analysé pour d'éventuels remplacements additionnels par le préprocesseur.
6.5. Les trigraphes
Le jeu de caractère utilisé par le langage C++ comprend toutes les lettres en majuscules et en minuscules, tous les chiffres et les caractères suivants :
. , ; : ! ? " ' + - ^ * % = & | ~ _ # / \ { } [ ] () < >
Malheureusement, certains environnements sont incapables de gérer quelques-uns de ces caractères. C'est pour résoudre ce problème que les trigraphes ont été créés.
Les trigraphes sont des séquences de trois caractères commençant par deux points d'interrogations. Ils permettent de remplacer les caractères qui ne sont pas accessibles sur tous les environnements. Vous n'utiliserez donc sans doute jamais les trigraphes, à moins d'y être forcé. Les trigraphes disponibles sont définis ci-dessous :
Tableau 6-1. Trigraphes
Trigraphe                    Caractère de remplacement
??=                                          #
??/                                          \
??'                                          ^
??(                                          [
??)                                          ]
??!                                          |
??<                                          {
??>                                          }
??-                                          ~
Commentaires (1)

1. mkout14 07/08/2013

cheap nfl jerseys,

Ajouter un commentaire

Vous utilisez un logiciel de type AdBlock, qui bloque le service de captchas publicitaires utilisé sur ce site. Pour pouvoir envoyer votre message, désactivez Adblock.

MOBILISONS NOUS POUR LE DEVELOPPEMENT DE SARGHINE EN AIDANT L'ASSOCIATION AMSIRAR.

Créer un site gratuit avec e-monsite - Signaler un contenu illicite sur ce site